@camstack/types 1.1.20 → 1.1.22
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/base-addon.d.ts +30 -4
- package/dist/addon/per-node-store.d.ts +68 -0
- package/dist/addon.js +1 -1
- package/dist/addon.mjs +1 -1
- package/dist/capabilities/camera-streams.cap.d.ts +5 -5
- package/dist/capabilities/decoder.cap.d.ts +2 -0
- package/dist/capabilities/device-manager.cap.d.ts +269 -6
- package/dist/capabilities/index.d.ts +3 -1
- package/dist/capabilities/metrics-provider.cap.d.ts +2 -2
- package/dist/capabilities/motion-detection.cap.d.ts +20 -2
- package/dist/capabilities/pet-feeder.cap.d.ts +185 -0
- package/dist/capabilities/pipeline-executor.cap.d.ts +29 -2
- package/dist/capabilities/pipeline-orchestrator.cap.d.ts +7 -3
- package/dist/capabilities/platform-probe.cap.d.ts +1 -1
- package/dist/capabilities/schemas/streaming-shared.d.ts +2 -2
- package/dist/capabilities/stream-broker.cap.d.ts +1 -1
- package/dist/device/device-management.d.ts +61 -2
- package/dist/device/device-type.d.ts +8 -1
- package/dist/device/index.d.ts +1 -1
- package/dist/expression/ast.d.ts +56 -0
- package/dist/expression/builtins.d.ts +19 -0
- package/dist/expression/compile.d.ts +17 -0
- package/dist/expression/errors.d.ts +16 -0
- package/dist/expression/evaluator.d.ts +14 -0
- package/dist/expression/index.d.ts +25 -0
- package/dist/expression/limits.d.ts +30 -0
- package/dist/expression/link-expression.d.ts +44 -0
- package/dist/expression/parser.d.ts +12 -0
- package/dist/expression/tokenizer.d.ts +38 -0
- package/dist/generated/addon-api.d.ts +616 -12
- package/dist/generated/cap-status-types.d.ts +3 -1
- package/dist/generated/capability-router-map.d.ts +5 -2
- package/dist/generated/device-local-state.d.ts +3 -0
- package/dist/generated/device-proxy.d.ts +4 -1
- package/dist/generated/method-access-map.d.ts +1 -1
- package/dist/generated/system-proxy.d.ts +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1555 -37
- package/dist/index.mjs +1523 -38
- package/dist/interfaces/addon.d.ts +15 -2
- package/dist/interfaces/agent.d.ts +14 -0
- package/dist/interfaces/config-ui.d.ts +19 -1
- package/dist/interfaces/pipeline-executor-capability.d.ts +10 -0
- package/dist/{sleep-BabrCASa.js → sleep-B8cp-HUn.js} +193 -7
- package/dist/{sleep-MHm--th-.mjs → sleep-BiDFW0E7.mjs} +193 -7
- package/dist/units/convert.d.ts +49 -0
- package/dist/units/index.d.ts +8 -0
- package/dist/units/unit-table.d.ts +270 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_sleep = require("./sleep-
|
|
2
|
+
const require_sleep = require("./sleep-B8cp-HUn.js");
|
|
3
3
|
const require_err_msg = require("./err-msg-COpsHMw2.js");
|
|
4
4
|
let zod = require("zod");
|
|
5
5
|
//#region src/health/wiring-health.ts
|
|
@@ -2394,6 +2394,787 @@ var DEVICE_TYPE_INFO = { ["camera"]: {
|
|
|
2394
2394
|
icon: "camera"
|
|
2395
2395
|
} };
|
|
2396
2396
|
//#endregion
|
|
2397
|
+
//#region src/expression/errors.ts
|
|
2398
|
+
/**
|
|
2399
|
+
* Error types for the safe expression engine. Two distinct classes so callers
|
|
2400
|
+
* can tell a compile-time (grammar) failure from a runtime (evaluation)
|
|
2401
|
+
* failure — both are non-fatal to the host: read paths degrade to "skip link".
|
|
2402
|
+
*/
|
|
2403
|
+
/** Thrown by the tokenizer / parser. Carries a 0-based source `position` when
|
|
2404
|
+
* the failure is anchored to a character (author-facing inline feedback). */
|
|
2405
|
+
var ExpressionParseError = class extends Error {
|
|
2406
|
+
position;
|
|
2407
|
+
constructor(message, position) {
|
|
2408
|
+
super(message);
|
|
2409
|
+
this.name = "ExpressionParseError";
|
|
2410
|
+
this.position = position;
|
|
2411
|
+
}
|
|
2412
|
+
};
|
|
2413
|
+
/** Thrown by the evaluator (unknown identifier, type mismatch, non-finite
|
|
2414
|
+
* result, unknown builtin, step-budget exceeded). */
|
|
2415
|
+
var ExpressionEvalError = class extends Error {
|
|
2416
|
+
constructor(message) {
|
|
2417
|
+
super(message);
|
|
2418
|
+
this.name = "ExpressionEvalError";
|
|
2419
|
+
}
|
|
2420
|
+
};
|
|
2421
|
+
//#endregion
|
|
2422
|
+
//#region src/expression/limits.ts
|
|
2423
|
+
/**
|
|
2424
|
+
* Resource-bound constants for the safe expression engine.
|
|
2425
|
+
*
|
|
2426
|
+
* Every bound is defense-in-depth: the grammar is non-Turing-complete (no
|
|
2427
|
+
* loops, recursion, lambdas or member access — see `ast.ts`), so evaluation is
|
|
2428
|
+
* O(nodeCount) by construction. These caps merely put a hard ceiling on the
|
|
2429
|
+
* work a single author-supplied expression can request, so a hostile or
|
|
2430
|
+
* accidental pathological string can never spend unbounded CPU/memory.
|
|
2431
|
+
*/
|
|
2432
|
+
/** Max source length (chars) — checked BEFORE tokenizing so a huge string is
|
|
2433
|
+
* rejected without allocation. */
|
|
2434
|
+
var MAX_EXPRESSION_SOURCE_LENGTH = 2048;
|
|
2435
|
+
/** Max AST nodes — checked during parse; a deeply nested grouping that exceeds
|
|
2436
|
+
* this is rejected as "expression too complex". */
|
|
2437
|
+
var MAX_EXPRESSION_AST_NODES = 256;
|
|
2438
|
+
/** Defense-in-depth walker step budget — one increment per node visit during
|
|
2439
|
+
* evaluation. The grammar guarantees O(nodeCount) walks, so this can only trip
|
|
2440
|
+
* on a crafted maximum-size AST. */
|
|
2441
|
+
var MAX_EXPRESSION_EVAL_STEPS = 4096;
|
|
2442
|
+
/** Max named bindings on one `DeviceLinkExpressionSource`. */
|
|
2443
|
+
var MAX_EXPRESSION_BINDINGS = 32;
|
|
2444
|
+
/** Max positional arguments to any builtin call. */
|
|
2445
|
+
var MAX_EXPRESSION_CALL_ARGS = 16;
|
|
2446
|
+
/** LRU compile-cache capacity (parsed ASTs keyed by raw source string). */
|
|
2447
|
+
var EXPRESSION_COMPILE_CACHE_CAPACITY = 256;
|
|
2448
|
+
/** A legal binding / identifier name. */
|
|
2449
|
+
var EXPRESSION_IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2450
|
+
/** Binding names an author may NOT use: `now` is auto-injected; the literal
|
|
2451
|
+
* keywords lex as values, not identifiers, so binding to them is meaningless. */
|
|
2452
|
+
var RESERVED_BINDING_NAMES = new Set([
|
|
2453
|
+
"now",
|
|
2454
|
+
"true",
|
|
2455
|
+
"false",
|
|
2456
|
+
"null"
|
|
2457
|
+
]);
|
|
2458
|
+
//#endregion
|
|
2459
|
+
//#region src/expression/tokenizer.ts
|
|
2460
|
+
/**
|
|
2461
|
+
* Tokenizer for the safe expression mini-language. Hand-rolled, single-pass,
|
|
2462
|
+
* zero-dependency. The grammar is deliberately boring: decimal numbers,
|
|
2463
|
+
* single/double-quoted strings with a tiny escape set, identifiers, the three
|
|
2464
|
+
* value keywords (`true`/`false`/`null`) and a fixed punctuator set. Anything
|
|
2465
|
+
* outside that — a bare `.`, `=`, `[`, `]`, `{`, `}`, `;`, backtick, `&`, `|` —
|
|
2466
|
+
* is a parse error with a source position, so member access / assignment /
|
|
2467
|
+
* template literals are lexically impossible.
|
|
2468
|
+
*/
|
|
2469
|
+
var KEYWORDS = new Set([
|
|
2470
|
+
"true",
|
|
2471
|
+
"false",
|
|
2472
|
+
"null"
|
|
2473
|
+
]);
|
|
2474
|
+
function isDigit(ch) {
|
|
2475
|
+
return ch >= "0" && ch <= "9";
|
|
2476
|
+
}
|
|
2477
|
+
function isIdentStart(ch) {
|
|
2478
|
+
return ch >= "A" && ch <= "Z" || ch >= "a" && ch <= "z" || ch === "_";
|
|
2479
|
+
}
|
|
2480
|
+
function isIdentPart(ch) {
|
|
2481
|
+
return isIdentStart(ch) || isDigit(ch);
|
|
2482
|
+
}
|
|
2483
|
+
function isWhitespace(ch) {
|
|
2484
|
+
return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v";
|
|
2485
|
+
}
|
|
2486
|
+
/** Tokenize `source` into a flat token list ending with a single `eof` token.
|
|
2487
|
+
* Throws `ExpressionParseError` on any illegal character or unterminated
|
|
2488
|
+
* string. */
|
|
2489
|
+
function tokenize(source) {
|
|
2490
|
+
if (source.length > 2048) throw new ExpressionParseError(`expression too long (${source.length} > ${MAX_EXPRESSION_SOURCE_LENGTH} chars)`, 0);
|
|
2491
|
+
const tokens = [];
|
|
2492
|
+
let i = 0;
|
|
2493
|
+
const n = source.length;
|
|
2494
|
+
while (i < n) {
|
|
2495
|
+
const ch = source[i];
|
|
2496
|
+
if (isWhitespace(ch)) {
|
|
2497
|
+
i += 1;
|
|
2498
|
+
continue;
|
|
2499
|
+
}
|
|
2500
|
+
if (isDigit(ch)) {
|
|
2501
|
+
const start = i;
|
|
2502
|
+
while (i < n && isDigit(source[i])) i += 1;
|
|
2503
|
+
if (i < n && source[i] === ".") {
|
|
2504
|
+
if (i + 1 >= n || !isDigit(source[i + 1])) throw new ExpressionParseError("malformed number: decimal point needs a digit", i);
|
|
2505
|
+
i += 1;
|
|
2506
|
+
while (i < n && isDigit(source[i])) i += 1;
|
|
2507
|
+
}
|
|
2508
|
+
const text = source.slice(start, i);
|
|
2509
|
+
const value = Number(text);
|
|
2510
|
+
if (!Number.isFinite(value)) throw new ExpressionParseError(`malformed number: '${text}'`, start);
|
|
2511
|
+
tokens.push({
|
|
2512
|
+
type: "number",
|
|
2513
|
+
value,
|
|
2514
|
+
pos: start
|
|
2515
|
+
});
|
|
2516
|
+
continue;
|
|
2517
|
+
}
|
|
2518
|
+
if (ch === "'" || ch === "\"") {
|
|
2519
|
+
const quote = ch;
|
|
2520
|
+
const start = i;
|
|
2521
|
+
i += 1;
|
|
2522
|
+
let out = "";
|
|
2523
|
+
let closed = false;
|
|
2524
|
+
while (i < n) {
|
|
2525
|
+
const c = source[i];
|
|
2526
|
+
if (c === "\\") {
|
|
2527
|
+
const next = i + 1 < n ? source[i + 1] : "";
|
|
2528
|
+
if (next === "\\" || next === "'" || next === "\"") {
|
|
2529
|
+
out += next;
|
|
2530
|
+
i += 2;
|
|
2531
|
+
continue;
|
|
2532
|
+
}
|
|
2533
|
+
throw new ExpressionParseError(`invalid string escape: '\\${next}'`, i);
|
|
2534
|
+
}
|
|
2535
|
+
if (c === quote) {
|
|
2536
|
+
closed = true;
|
|
2537
|
+
i += 1;
|
|
2538
|
+
break;
|
|
2539
|
+
}
|
|
2540
|
+
out += c;
|
|
2541
|
+
i += 1;
|
|
2542
|
+
}
|
|
2543
|
+
if (!closed) throw new ExpressionParseError("unterminated string literal", start);
|
|
2544
|
+
tokens.push({
|
|
2545
|
+
type: "string",
|
|
2546
|
+
value: out,
|
|
2547
|
+
pos: start
|
|
2548
|
+
});
|
|
2549
|
+
continue;
|
|
2550
|
+
}
|
|
2551
|
+
if (isIdentStart(ch)) {
|
|
2552
|
+
const start = i;
|
|
2553
|
+
while (i < n && isIdentPart(source[i])) i += 1;
|
|
2554
|
+
const text = source.slice(start, i);
|
|
2555
|
+
if (KEYWORDS.has(text)) tokens.push({
|
|
2556
|
+
type: "keyword",
|
|
2557
|
+
keyword: keywordOf(text),
|
|
2558
|
+
pos: start
|
|
2559
|
+
});
|
|
2560
|
+
else tokens.push({
|
|
2561
|
+
type: "identifier",
|
|
2562
|
+
name: text,
|
|
2563
|
+
pos: start
|
|
2564
|
+
});
|
|
2565
|
+
continue;
|
|
2566
|
+
}
|
|
2567
|
+
const two = i + 1 < n ? source.slice(i, i + 2) : "";
|
|
2568
|
+
if (two === "<=" || two === ">=" || two === "==" || two === "!=" || two === "&&" || two === "||") {
|
|
2569
|
+
tokens.push({
|
|
2570
|
+
type: "punct",
|
|
2571
|
+
punct: two,
|
|
2572
|
+
pos: i
|
|
2573
|
+
});
|
|
2574
|
+
i += 2;
|
|
2575
|
+
continue;
|
|
2576
|
+
}
|
|
2577
|
+
if (isSinglePunct(ch)) {
|
|
2578
|
+
tokens.push({
|
|
2579
|
+
type: "punct",
|
|
2580
|
+
punct: ch,
|
|
2581
|
+
pos: i
|
|
2582
|
+
});
|
|
2583
|
+
i += 1;
|
|
2584
|
+
continue;
|
|
2585
|
+
}
|
|
2586
|
+
throw new ExpressionParseError(`unexpected character '${ch}'`, i);
|
|
2587
|
+
}
|
|
2588
|
+
tokens.push({
|
|
2589
|
+
type: "eof",
|
|
2590
|
+
pos: n
|
|
2591
|
+
});
|
|
2592
|
+
return tokens;
|
|
2593
|
+
}
|
|
2594
|
+
function keywordOf(text) {
|
|
2595
|
+
if (text === "true") return "true";
|
|
2596
|
+
if (text === "false") return "false";
|
|
2597
|
+
return "null";
|
|
2598
|
+
}
|
|
2599
|
+
function isSinglePunct(ch) {
|
|
2600
|
+
return ch === "(" || ch === ")" || ch === "," || ch === "?" || ch === ":" || ch === "+" || ch === "-" || ch === "*" || ch === "/" || ch === "%" || ch === "!" || ch === "<" || ch === ">";
|
|
2601
|
+
}
|
|
2602
|
+
//#endregion
|
|
2603
|
+
//#region src/expression/builtins.ts
|
|
2604
|
+
/**
|
|
2605
|
+
* Frozen, null-prototype builtin function table for the expression engine
|
|
2606
|
+
* (spec §4 rule 4). The table is the SOLE surface of callable functions: the
|
|
2607
|
+
* parser rejects any callee not in it, and the evaluator gates each call on an
|
|
2608
|
+
* own-property check against it.
|
|
2609
|
+
*
|
|
2610
|
+
* Because the object has a NULL prototype AND is `Object.freeze`d:
|
|
2611
|
+
* - it cannot be polluted (no `__proto__` / `constructor` write reaches it);
|
|
2612
|
+
* - a lookup for `toString` / `hasOwnProperty` / `constructor` finds NOTHING
|
|
2613
|
+
* (there is no `Object.prototype` in the chain), so those names are not
|
|
2614
|
+
* callable — they are simply "unknown function" at parse time.
|
|
2615
|
+
*
|
|
2616
|
+
* Every numeric argument is validated as a finite number and every numeric
|
|
2617
|
+
* RESULT is re-checked finite, so `/0`, `sqrt(-1)` (→ NaN) and overflow
|
|
2618
|
+
* (`pow(10,400)` → Infinity) all raise `ExpressionEvalError` and fail the link
|
|
2619
|
+
* closed rather than emitting a garbage value.
|
|
2620
|
+
*/
|
|
2621
|
+
function asFiniteNumber(value, name, index) {
|
|
2622
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a finite number`);
|
|
2623
|
+
return value;
|
|
2624
|
+
}
|
|
2625
|
+
function asString$1(value, name, index) {
|
|
2626
|
+
if (typeof value !== "string") throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a string`);
|
|
2627
|
+
return value;
|
|
2628
|
+
}
|
|
2629
|
+
function finiteResult(value, name) {
|
|
2630
|
+
if (!Number.isFinite(value)) throw new ExpressionEvalError(`${name}: produced a non-finite result`);
|
|
2631
|
+
return value;
|
|
2632
|
+
}
|
|
2633
|
+
function allFiniteNumbers(args, name) {
|
|
2634
|
+
return args.map((a, idx) => asFiniteNumber(a, name, idx));
|
|
2635
|
+
}
|
|
2636
|
+
var INF = Number.POSITIVE_INFINITY;
|
|
2637
|
+
var table = {
|
|
2638
|
+
min: {
|
|
2639
|
+
minArgs: 1,
|
|
2640
|
+
maxArgs: INF,
|
|
2641
|
+
apply: (args) => finiteResult(Math.min(...allFiniteNumbers(args, "min")), "min")
|
|
2642
|
+
},
|
|
2643
|
+
max: {
|
|
2644
|
+
minArgs: 1,
|
|
2645
|
+
maxArgs: INF,
|
|
2646
|
+
apply: (args) => finiteResult(Math.max(...allFiniteNumbers(args, "max")), "max")
|
|
2647
|
+
},
|
|
2648
|
+
abs: {
|
|
2649
|
+
minArgs: 1,
|
|
2650
|
+
maxArgs: 1,
|
|
2651
|
+
apply: (args) => finiteResult(Math.abs(asFiniteNumber(args[0], "abs", 0)), "abs")
|
|
2652
|
+
},
|
|
2653
|
+
floor: {
|
|
2654
|
+
minArgs: 1,
|
|
2655
|
+
maxArgs: 1,
|
|
2656
|
+
apply: (args) => finiteResult(Math.floor(asFiniteNumber(args[0], "floor", 0)), "floor")
|
|
2657
|
+
},
|
|
2658
|
+
ceil: {
|
|
2659
|
+
minArgs: 1,
|
|
2660
|
+
maxArgs: 1,
|
|
2661
|
+
apply: (args) => finiteResult(Math.ceil(asFiniteNumber(args[0], "ceil", 0)), "ceil")
|
|
2662
|
+
},
|
|
2663
|
+
sqrt: {
|
|
2664
|
+
minArgs: 1,
|
|
2665
|
+
maxArgs: 1,
|
|
2666
|
+
apply: (args) => finiteResult(Math.sqrt(asFiniteNumber(args[0], "sqrt", 0)), "sqrt")
|
|
2667
|
+
},
|
|
2668
|
+
round: {
|
|
2669
|
+
minArgs: 1,
|
|
2670
|
+
maxArgs: 2,
|
|
2671
|
+
apply: (args) => {
|
|
2672
|
+
const x = asFiniteNumber(args[0], "round", 0);
|
|
2673
|
+
const digits = args.length > 1 ? Math.trunc(asFiniteNumber(args[1], "round", 1)) : 0;
|
|
2674
|
+
if (digits < 0 || digits > 100) throw new ExpressionEvalError("round: digits must be between 0 and 100");
|
|
2675
|
+
const factor = 10 ** digits;
|
|
2676
|
+
return finiteResult(Math.round(x * factor) / factor, "round");
|
|
2677
|
+
}
|
|
2678
|
+
},
|
|
2679
|
+
pow: {
|
|
2680
|
+
minArgs: 2,
|
|
2681
|
+
maxArgs: 2,
|
|
2682
|
+
apply: (args) => finiteResult(asFiniteNumber(args[0], "pow", 0) ** asFiniteNumber(args[1], "pow", 1), "pow")
|
|
2683
|
+
},
|
|
2684
|
+
clamp: {
|
|
2685
|
+
minArgs: 3,
|
|
2686
|
+
maxArgs: 3,
|
|
2687
|
+
apply: (args) => {
|
|
2688
|
+
const x = asFiniteNumber(args[0], "clamp", 0);
|
|
2689
|
+
const lo = asFiniteNumber(args[1], "clamp", 1);
|
|
2690
|
+
const hi = asFiniteNumber(args[2], "clamp", 2);
|
|
2691
|
+
if (lo > hi) throw new ExpressionEvalError("clamp: lower bound is greater than upper bound");
|
|
2692
|
+
return finiteResult(Math.min(hi, Math.max(lo, x)), "clamp");
|
|
2693
|
+
}
|
|
2694
|
+
},
|
|
2695
|
+
avg: {
|
|
2696
|
+
minArgs: 1,
|
|
2697
|
+
maxArgs: INF,
|
|
2698
|
+
apply: (args) => {
|
|
2699
|
+
const nums = allFiniteNumbers(args, "avg");
|
|
2700
|
+
return finiteResult(nums.reduce((acc, v) => acc + v, 0) / nums.length, "avg");
|
|
2701
|
+
}
|
|
2702
|
+
},
|
|
2703
|
+
sum: {
|
|
2704
|
+
minArgs: 1,
|
|
2705
|
+
maxArgs: INF,
|
|
2706
|
+
apply: (args) => finiteResult(allFiniteNumbers(args, "sum").reduce((acc, v) => acc + v, 0), "sum")
|
|
2707
|
+
},
|
|
2708
|
+
coalesce: {
|
|
2709
|
+
minArgs: 1,
|
|
2710
|
+
maxArgs: INF,
|
|
2711
|
+
apply: (args) => {
|
|
2712
|
+
for (const a of args) if (a !== null) return a;
|
|
2713
|
+
return null;
|
|
2714
|
+
}
|
|
2715
|
+
},
|
|
2716
|
+
age: {
|
|
2717
|
+
minArgs: 2,
|
|
2718
|
+
maxArgs: 2,
|
|
2719
|
+
apply: (args) => finiteResult(asFiniteNumber(args[0], "age", 0) - asFiniteNumber(args[1], "age", 1), "age")
|
|
2720
|
+
},
|
|
2721
|
+
convert: {
|
|
2722
|
+
minArgs: 3,
|
|
2723
|
+
maxArgs: 3,
|
|
2724
|
+
apply: (args, hooks) => {
|
|
2725
|
+
const x = asFiniteNumber(args[0], "convert", 0);
|
|
2726
|
+
const from = asString$1(args[1], "convert", 1).trim();
|
|
2727
|
+
const to = asString$1(args[2], "convert", 2).trim();
|
|
2728
|
+
if (hooks.convert) {
|
|
2729
|
+
const out = hooks.convert(x, from, to);
|
|
2730
|
+
if (out === null) throw new ExpressionEvalError(`convert: cannot convert '${from}' to '${to}'`);
|
|
2731
|
+
return finiteResult(out, "convert");
|
|
2732
|
+
}
|
|
2733
|
+
if (from === to) return x;
|
|
2734
|
+
throw new ExpressionEvalError("convert: unit conversion table not installed");
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
};
|
|
2738
|
+
/** Frozen, null-prototype builtin table. */
|
|
2739
|
+
var EXPRESSION_BUILTINS = Object.freeze(Object.assign(Object.create(null), table));
|
|
2740
|
+
/** The set of valid builtin names — used by the parser to reject unknown
|
|
2741
|
+
* callees at parse time (immediate author feedback). */
|
|
2742
|
+
var EXPRESSION_BUILTIN_NAMES = new Set(Object.keys(table));
|
|
2743
|
+
//#endregion
|
|
2744
|
+
//#region src/expression/parser.ts
|
|
2745
|
+
/**
|
|
2746
|
+
* Pratt (precedence-climbing) parser for the safe expression mini-language.
|
|
2747
|
+
*
|
|
2748
|
+
* Precedence (low → high): ternary `?:` (right-assoc) → `||` → `&&` → equality
|
|
2749
|
+
* → relational → additive → multiplicative → unary `! -` → call / primary.
|
|
2750
|
+
* Calls are ONLY `IDENT '(' args? ')'` at primary position — the callee is a
|
|
2751
|
+
* string validated against the builtin table at parse time, so an unknown
|
|
2752
|
+
* function is rejected immediately (author feedback) and a persisted expression
|
|
2753
|
+
* that references a since-removed builtin degrades at read.
|
|
2754
|
+
*
|
|
2755
|
+
* A node counter caps total AST size (`MAX_EXPRESSION_AST_NODES`) and call
|
|
2756
|
+
* arity is capped (`MAX_EXPRESSION_CALL_ARGS`) — both raise `ExpressionParseError`.
|
|
2757
|
+
*/
|
|
2758
|
+
/** Binary/logical operator precedence (higher binds tighter). */
|
|
2759
|
+
var BINARY_PRECEDENCE = {
|
|
2760
|
+
"||": 1,
|
|
2761
|
+
"&&": 2,
|
|
2762
|
+
"==": 3,
|
|
2763
|
+
"!=": 3,
|
|
2764
|
+
"<": 4,
|
|
2765
|
+
"<=": 4,
|
|
2766
|
+
">": 4,
|
|
2767
|
+
">=": 4,
|
|
2768
|
+
"+": 5,
|
|
2769
|
+
"-": 5,
|
|
2770
|
+
"*": 6,
|
|
2771
|
+
"/": 6,
|
|
2772
|
+
"%": 6
|
|
2773
|
+
};
|
|
2774
|
+
function isLogicalOp(op) {
|
|
2775
|
+
return op === "&&" || op === "||";
|
|
2776
|
+
}
|
|
2777
|
+
function isBinaryOp(op) {
|
|
2778
|
+
return op === "+" || op === "-" || op === "*" || op === "/" || op === "%" || op === "==" || op === "!=" || op === "<" || op === "<=" || op === ">" || op === ">=";
|
|
2779
|
+
}
|
|
2780
|
+
var Parser = class {
|
|
2781
|
+
tokens;
|
|
2782
|
+
pos = 0;
|
|
2783
|
+
nodeCount = 0;
|
|
2784
|
+
identifiers = /* @__PURE__ */ new Set();
|
|
2785
|
+
callees = /* @__PURE__ */ new Set();
|
|
2786
|
+
constructor(tokens) {
|
|
2787
|
+
this.tokens = tokens;
|
|
2788
|
+
}
|
|
2789
|
+
parse() {
|
|
2790
|
+
const ast = this.parseTernary();
|
|
2791
|
+
const tok = this.peek();
|
|
2792
|
+
if (tok.type !== "eof") throw new ExpressionParseError("unexpected trailing input", tok.pos);
|
|
2793
|
+
return {
|
|
2794
|
+
ast,
|
|
2795
|
+
identifiers: this.identifiers,
|
|
2796
|
+
callees: this.callees,
|
|
2797
|
+
nodeCount: this.nodeCount
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
peek() {
|
|
2801
|
+
return this.tokens[this.pos];
|
|
2802
|
+
}
|
|
2803
|
+
next() {
|
|
2804
|
+
return this.tokens[this.pos++];
|
|
2805
|
+
}
|
|
2806
|
+
/** Consume a punctuator token, erroring if the next token isn't it. */
|
|
2807
|
+
expectPunct(punct) {
|
|
2808
|
+
const tok = this.peek();
|
|
2809
|
+
if (tok.type !== "punct" || tok.punct !== punct) throw new ExpressionParseError(`expected '${punct}'`, tok.pos);
|
|
2810
|
+
this.pos += 1;
|
|
2811
|
+
}
|
|
2812
|
+
matchPunct(punct) {
|
|
2813
|
+
const tok = this.peek();
|
|
2814
|
+
if (tok.type === "punct" && tok.punct === punct) {
|
|
2815
|
+
this.pos += 1;
|
|
2816
|
+
return true;
|
|
2817
|
+
}
|
|
2818
|
+
return false;
|
|
2819
|
+
}
|
|
2820
|
+
countNode() {
|
|
2821
|
+
this.nodeCount += 1;
|
|
2822
|
+
if (this.nodeCount > 256) throw new ExpressionParseError("expression too complex", this.peek().pos);
|
|
2823
|
+
}
|
|
2824
|
+
parseTernary() {
|
|
2825
|
+
const test = this.parseBinary(1);
|
|
2826
|
+
if (this.matchPunct("?")) {
|
|
2827
|
+
const consequent = this.parseTernary();
|
|
2828
|
+
this.expectPunct(":");
|
|
2829
|
+
const alternate = this.parseTernary();
|
|
2830
|
+
this.countNode();
|
|
2831
|
+
return {
|
|
2832
|
+
kind: "conditional",
|
|
2833
|
+
test,
|
|
2834
|
+
consequent,
|
|
2835
|
+
alternate
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
return test;
|
|
2839
|
+
}
|
|
2840
|
+
parseBinary(minPrec) {
|
|
2841
|
+
let left = this.parseUnary();
|
|
2842
|
+
for (;;) {
|
|
2843
|
+
const tok = this.peek();
|
|
2844
|
+
if (tok.type !== "punct") break;
|
|
2845
|
+
const prec = BINARY_PRECEDENCE[tok.punct];
|
|
2846
|
+
if (prec === void 0 || prec < minPrec) break;
|
|
2847
|
+
const op = tok.punct;
|
|
2848
|
+
this.pos += 1;
|
|
2849
|
+
const right = this.parseBinary(prec + 1);
|
|
2850
|
+
this.countNode();
|
|
2851
|
+
if (isLogicalOp(op)) left = {
|
|
2852
|
+
kind: "logical",
|
|
2853
|
+
op,
|
|
2854
|
+
left,
|
|
2855
|
+
right
|
|
2856
|
+
};
|
|
2857
|
+
else if (isBinaryOp(op)) left = {
|
|
2858
|
+
kind: "binary",
|
|
2859
|
+
op,
|
|
2860
|
+
left,
|
|
2861
|
+
right
|
|
2862
|
+
};
|
|
2863
|
+
else throw new ExpressionParseError(`unexpected operator '${op}'`, tok.pos);
|
|
2864
|
+
}
|
|
2865
|
+
return left;
|
|
2866
|
+
}
|
|
2867
|
+
parseUnary() {
|
|
2868
|
+
const tok = this.peek();
|
|
2869
|
+
if (tok.type === "punct" && (tok.punct === "!" || tok.punct === "-")) {
|
|
2870
|
+
const op = tok.punct;
|
|
2871
|
+
this.pos += 1;
|
|
2872
|
+
const operand = this.parseUnary();
|
|
2873
|
+
this.countNode();
|
|
2874
|
+
return {
|
|
2875
|
+
kind: "unary",
|
|
2876
|
+
op,
|
|
2877
|
+
operand
|
|
2878
|
+
};
|
|
2879
|
+
}
|
|
2880
|
+
return this.parsePrimary();
|
|
2881
|
+
}
|
|
2882
|
+
parsePrimary() {
|
|
2883
|
+
const tok = this.next();
|
|
2884
|
+
switch (tok.type) {
|
|
2885
|
+
case "number":
|
|
2886
|
+
this.countNode();
|
|
2887
|
+
return {
|
|
2888
|
+
kind: "literal",
|
|
2889
|
+
value: tok.value
|
|
2890
|
+
};
|
|
2891
|
+
case "string":
|
|
2892
|
+
this.countNode();
|
|
2893
|
+
return {
|
|
2894
|
+
kind: "literal",
|
|
2895
|
+
value: tok.value
|
|
2896
|
+
};
|
|
2897
|
+
case "keyword":
|
|
2898
|
+
this.countNode();
|
|
2899
|
+
return {
|
|
2900
|
+
kind: "literal",
|
|
2901
|
+
value: tok.keyword === "null" ? null : tok.keyword === "true"
|
|
2902
|
+
};
|
|
2903
|
+
case "identifier": {
|
|
2904
|
+
const nextTok = this.peek();
|
|
2905
|
+
if (nextTok.type === "punct" && nextTok.punct === "(") return this.parseCall(tok.name, tok.pos);
|
|
2906
|
+
this.identifiers.add(tok.name);
|
|
2907
|
+
this.countNode();
|
|
2908
|
+
return {
|
|
2909
|
+
kind: "identifier",
|
|
2910
|
+
name: tok.name
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
case "punct":
|
|
2914
|
+
if (tok.punct === "(") {
|
|
2915
|
+
const inner = this.parseTernary();
|
|
2916
|
+
this.expectPunct(")");
|
|
2917
|
+
return inner;
|
|
2918
|
+
}
|
|
2919
|
+
throw new ExpressionParseError(`unexpected token '${tok.punct}'`, tok.pos);
|
|
2920
|
+
case "eof": throw new ExpressionParseError("unexpected end of expression", tok.pos);
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
parseCall(callee, pos) {
|
|
2924
|
+
if (!EXPRESSION_BUILTIN_NAMES.has(callee)) throw new ExpressionParseError(`unknown function '${callee}'`, pos);
|
|
2925
|
+
this.expectPunct("(");
|
|
2926
|
+
const args = [];
|
|
2927
|
+
if (!this.matchPunct(")")) for (;;) {
|
|
2928
|
+
args.push(this.parseTernary());
|
|
2929
|
+
if (args.length > 16) throw new ExpressionParseError(`too many arguments to '${callee}'`, pos);
|
|
2930
|
+
if (this.matchPunct(",")) continue;
|
|
2931
|
+
this.expectPunct(")");
|
|
2932
|
+
break;
|
|
2933
|
+
}
|
|
2934
|
+
this.callees.add(callee);
|
|
2935
|
+
this.countNode();
|
|
2936
|
+
return {
|
|
2937
|
+
kind: "call",
|
|
2938
|
+
callee,
|
|
2939
|
+
args
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
};
|
|
2943
|
+
/** Tokenize + parse `source` into a validated `ParsedExpression`. Throws
|
|
2944
|
+
* `ExpressionParseError` on any lexical or grammatical failure. */
|
|
2945
|
+
function parseExpression(source) {
|
|
2946
|
+
return new Parser(tokenize(source)).parse();
|
|
2947
|
+
}
|
|
2948
|
+
//#endregion
|
|
2949
|
+
//#region src/expression/evaluator.ts
|
|
2950
|
+
/**
|
|
2951
|
+
* Tree-walking evaluator for the safe expression mini-language.
|
|
2952
|
+
*
|
|
2953
|
+
* SECURITY (spec §4 rule 2/5):
|
|
2954
|
+
* - The scope is an `Object.create(null)` copy of ONLY the caller's own
|
|
2955
|
+
* enumerable binding entries, so `name in scope` is a pure own-key check and
|
|
2956
|
+
* `constructor` / `__proto__` / `toString` are plain unknown identifiers.
|
|
2957
|
+
* - Performs ZERO I/O and never touches `globalThis` / `Date` / `Math`
|
|
2958
|
+
* directly — the only external calls are into the frozen builtin table.
|
|
2959
|
+
* - The grammar has no loops/recursion/lambdas, so a walk is O(nodeCount) by
|
|
2960
|
+
* construction; the step counter is defense-in-depth for a crafted max-size
|
|
2961
|
+
* AST. Nothing blocks: there are no timers, awaits or unbounded loops.
|
|
2962
|
+
*/
|
|
2963
|
+
var EMPTY_HOOKS = Object.freeze({});
|
|
2964
|
+
/** Build a null-prototype scope from own-enumerable binding entries. Inherited
|
|
2965
|
+
* keys of the input (e.g. from a `{__proto__: {...}}` payload) are NOT copied,
|
|
2966
|
+
* so nothing smuggles in via the prototype chain. */
|
|
2967
|
+
function createExpressionScope(bindings) {
|
|
2968
|
+
const scope = Object.create(null);
|
|
2969
|
+
for (const key of Object.keys(bindings)) if (Object.prototype.hasOwnProperty.call(bindings, key)) scope[key] = bindings[key];
|
|
2970
|
+
return scope;
|
|
2971
|
+
}
|
|
2972
|
+
function isFiniteNumber(value) {
|
|
2973
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
2974
|
+
}
|
|
2975
|
+
/** JS truthiness of a primitive value. */
|
|
2976
|
+
function truthy(value) {
|
|
2977
|
+
return Boolean(value);
|
|
2978
|
+
}
|
|
2979
|
+
function requireFinite(value, context) {
|
|
2980
|
+
if (!Number.isFinite(value)) throw new ExpressionEvalError(`${context} produced a non-finite result`);
|
|
2981
|
+
return value;
|
|
2982
|
+
}
|
|
2983
|
+
function step(ctx) {
|
|
2984
|
+
ctx.steps += 1;
|
|
2985
|
+
if (ctx.steps > ctx.maxSteps) throw new ExpressionEvalError("expression evaluation step budget exceeded");
|
|
2986
|
+
}
|
|
2987
|
+
function evalNode(node, ctx) {
|
|
2988
|
+
step(ctx);
|
|
2989
|
+
switch (node.kind) {
|
|
2990
|
+
case "literal": return node.value;
|
|
2991
|
+
case "identifier":
|
|
2992
|
+
if (!(node.name in ctx.scope)) throw new ExpressionEvalError(`unknown identifier: ${node.name}`);
|
|
2993
|
+
return ctx.scope[node.name];
|
|
2994
|
+
case "unary": return evalUnary(node.op, evalNode(node.operand, ctx));
|
|
2995
|
+
case "binary": return evalBinary(node.op, evalNode(node.left, ctx), evalNode(node.right, ctx));
|
|
2996
|
+
case "logical": {
|
|
2997
|
+
const left = evalNode(node.left, ctx);
|
|
2998
|
+
if (node.op === "&&") return truthy(left) ? evalNode(node.right, ctx) : left;
|
|
2999
|
+
return truthy(left) ? left : evalNode(node.right, ctx);
|
|
3000
|
+
}
|
|
3001
|
+
case "conditional": return truthy(evalNode(node.test, ctx)) ? evalNode(node.consequent, ctx) : evalNode(node.alternate, ctx);
|
|
3002
|
+
case "call": return evalCall(node.callee, node.args.map((a) => evalNode(a, ctx)), ctx.hooks);
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
function evalUnary(op, operand) {
|
|
3006
|
+
if (op === "!") return !truthy(operand);
|
|
3007
|
+
if (!isFiniteNumber(operand)) throw new ExpressionEvalError("unary \"-\" requires a finite number");
|
|
3008
|
+
return requireFinite(-operand, "unary \"-\"");
|
|
3009
|
+
}
|
|
3010
|
+
function evalBinary(op, left, right) {
|
|
3011
|
+
switch (op) {
|
|
3012
|
+
case "==": return left === right;
|
|
3013
|
+
case "!=": return left !== right;
|
|
3014
|
+
case "+":
|
|
3015
|
+
if (typeof left === "string" && typeof right === "string") return left + right;
|
|
3016
|
+
if (isFiniteNumber(left) && isFiniteNumber(right)) return requireFinite(left + right, "\"+\"");
|
|
3017
|
+
throw new ExpressionEvalError("\"+\" requires two numbers or two strings");
|
|
3018
|
+
case "-":
|
|
3019
|
+
case "*":
|
|
3020
|
+
case "/":
|
|
3021
|
+
case "%":
|
|
3022
|
+
if (!isFiniteNumber(left) || !isFiniteNumber(right)) throw new ExpressionEvalError(`"${op}" requires two finite numbers`);
|
|
3023
|
+
return requireFinite(op === "-" ? left - right : op === "*" ? left * right : op === "/" ? left / right : left % right, `"${op}"`);
|
|
3024
|
+
case "<":
|
|
3025
|
+
case "<=":
|
|
3026
|
+
case ">":
|
|
3027
|
+
case ">=":
|
|
3028
|
+
if (isFiniteNumber(left) && isFiniteNumber(right)) return op === "<" ? left < right : op === "<=" ? left <= right : op === ">" ? left > right : left >= right;
|
|
3029
|
+
if (typeof left === "string" && typeof right === "string") return op === "<" ? left < right : op === "<=" ? left <= right : op === ">" ? left > right : left >= right;
|
|
3030
|
+
throw new ExpressionEvalError(`"${op}" requires two numbers or two strings`);
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
function evalCall(callee, args, hooks) {
|
|
3034
|
+
if (!Object.prototype.hasOwnProperty.call(EXPRESSION_BUILTINS, callee)) throw new ExpressionEvalError(`unknown function: ${callee}`);
|
|
3035
|
+
const builtin = EXPRESSION_BUILTINS[callee];
|
|
3036
|
+
if (args.length < builtin.minArgs || args.length > builtin.maxArgs) throw new ExpressionEvalError(`${callee}: wrong number of arguments (${args.length})`);
|
|
3037
|
+
return builtin.apply(args, hooks);
|
|
3038
|
+
}
|
|
3039
|
+
/** Evaluate an AST node against a scope. Throws `ExpressionEvalError` on any
|
|
3040
|
+
* runtime failure (unknown identifier, type mismatch, non-finite result,
|
|
3041
|
+
* step-budget exhaustion). */
|
|
3042
|
+
function evaluateAst(node, scope, opts) {
|
|
3043
|
+
return evalNode(node, {
|
|
3044
|
+
scope,
|
|
3045
|
+
hooks: opts?.hooks ?? EMPTY_HOOKS,
|
|
3046
|
+
maxSteps: opts?.maxSteps ?? 4096,
|
|
3047
|
+
steps: 0
|
|
3048
|
+
});
|
|
3049
|
+
}
|
|
3050
|
+
//#endregion
|
|
3051
|
+
//#region src/expression/compile.ts
|
|
3052
|
+
/**
|
|
3053
|
+
* LRU compile cache for parsed expressions (spec §2.4 "parse once … LRU keyed
|
|
3054
|
+
* by expr"). The cache stores BOTH successes and failures (negative caching),
|
|
3055
|
+
* so a corrupt persisted string costs exactly one tokenize+parse total — not
|
|
3056
|
+
* one per read on a hot resolve path.
|
|
3057
|
+
*
|
|
3058
|
+
* The cache is a module-level singleton: entries are pure, content-addressed
|
|
3059
|
+
* ASTs keyed by the raw source string, so sharing one instance across all
|
|
3060
|
+
* callers is safe and maximises hit rate.
|
|
3061
|
+
*/
|
|
3062
|
+
var cache = /* @__PURE__ */ new Map();
|
|
3063
|
+
function getCached(source) {
|
|
3064
|
+
const hit = cache.get(source);
|
|
3065
|
+
if (hit !== void 0) {
|
|
3066
|
+
cache.delete(source);
|
|
3067
|
+
cache.set(source, hit);
|
|
3068
|
+
return hit;
|
|
3069
|
+
}
|
|
3070
|
+
let result;
|
|
3071
|
+
try {
|
|
3072
|
+
result = {
|
|
3073
|
+
ok: true,
|
|
3074
|
+
parsed: parseExpression(source)
|
|
3075
|
+
};
|
|
3076
|
+
} catch (err) {
|
|
3077
|
+
result = {
|
|
3078
|
+
ok: false,
|
|
3079
|
+
error: err instanceof ExpressionParseError ? err.message : String(err)
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
cache.set(source, result);
|
|
3083
|
+
if (cache.size > 256) {
|
|
3084
|
+
const oldest = cache.keys().next().value;
|
|
3085
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
3086
|
+
}
|
|
3087
|
+
return result;
|
|
3088
|
+
}
|
|
3089
|
+
/** Compile `source` to a `ParsedExpression`, throwing `ExpressionParseError`
|
|
3090
|
+
* on failure. LRU/negative-cached. */
|
|
3091
|
+
function compileExpression(source) {
|
|
3092
|
+
const result = getCached(source);
|
|
3093
|
+
if (result.ok) return result.parsed;
|
|
3094
|
+
throw new ExpressionParseError(result.error);
|
|
3095
|
+
}
|
|
3096
|
+
/** Compile `source`, returning a discriminated result instead of throwing.
|
|
3097
|
+
* Used by read paths that must degrade rather than raise. LRU/negative-cached. */
|
|
3098
|
+
function compileExpressionSafe(source) {
|
|
3099
|
+
return getCached(source);
|
|
3100
|
+
}
|
|
3101
|
+
//#endregion
|
|
3102
|
+
//#region src/expression/link-expression.ts
|
|
3103
|
+
/**
|
|
3104
|
+
* DeviceLink-facing helpers for the expression engine — the single seam both
|
|
3105
|
+
* resolver channels (async provider-read + sync mirror) and the wire-schema
|
|
3106
|
+
* `superRefine` share, so validation and evaluation semantics stay identical
|
|
3107
|
+
* everywhere.
|
|
3108
|
+
*/
|
|
3109
|
+
/** The `now` epoch-ms binding is auto-injected into every evaluation and is a
|
|
3110
|
+
* reserved binding name (authors may not rebind it). */
|
|
3111
|
+
var EXPRESSION_INJECTED_NOW = "now";
|
|
3112
|
+
/**
|
|
3113
|
+
* Coerce an untrusted `getByPath` / mirror read to an `ExpressionValue`.
|
|
3114
|
+
* Non-primitive values (objects, arrays, `undefined`, functions, bigint,
|
|
3115
|
+
* symbol) and non-finite numbers become `undefined` so the caller can apply
|
|
3116
|
+
* its binding-miss policy (→ `null`). `null` itself is a valid value.
|
|
3117
|
+
*/
|
|
3118
|
+
function toExpressionValue(raw) {
|
|
3119
|
+
if (raw === null) return null;
|
|
3120
|
+
if (typeof raw === "string") return raw;
|
|
3121
|
+
if (typeof raw === "boolean") return raw;
|
|
3122
|
+
if (typeof raw === "number") return Number.isFinite(raw) ? raw : void 0;
|
|
3123
|
+
}
|
|
3124
|
+
/**
|
|
3125
|
+
* Author-time validation. Returns `null` when the source is valid, else a
|
|
3126
|
+
* human-readable error message. Checks: the expression compiles; binding count
|
|
3127
|
+
* is within `MAX_EXPRESSION_BINDINGS`; every binding name is a legal identifier,
|
|
3128
|
+
* is not reserved (`now`/keywords) and does not shadow a builtin; and every
|
|
3129
|
+
* FREE identifier of the AST is covered by a binding or the injected `now`.
|
|
3130
|
+
*/
|
|
3131
|
+
function validateExpressionSource(src) {
|
|
3132
|
+
const names = Object.keys(src.bindings);
|
|
3133
|
+
if (names.length > 32) return `too many bindings (${names.length} > 32)`;
|
|
3134
|
+
for (const name of names) {
|
|
3135
|
+
if (!EXPRESSION_IDENTIFIER_RE.test(name)) return `invalid binding name '${name}'`;
|
|
3136
|
+
if (RESERVED_BINDING_NAMES.has(name)) return `binding name '${name}' is reserved`;
|
|
3137
|
+
if (EXPRESSION_BUILTIN_NAMES.has(name)) return `binding name '${name}' shadows a builtin function`;
|
|
3138
|
+
}
|
|
3139
|
+
const compiled = compileExpressionSafe(src.expr);
|
|
3140
|
+
if (!compiled.ok) return compiled.error;
|
|
3141
|
+
const bound = new Set(names);
|
|
3142
|
+
for (const id of compiled.parsed.identifiers) {
|
|
3143
|
+
if (id === "now") continue;
|
|
3144
|
+
if (!bound.has(id)) return `expression references unbound identifier '${id}'`;
|
|
3145
|
+
}
|
|
3146
|
+
return null;
|
|
3147
|
+
}
|
|
3148
|
+
/**
|
|
3149
|
+
* Shared read-path evaluation for BOTH resolver channels. Builds a null-proto
|
|
3150
|
+
* scope from `bindingValues` plus the injected `now` (supplied by the caller
|
|
3151
|
+
* for determinism/testability), compiles via the LRU, and evaluates. Any
|
|
3152
|
+
* failure (parse or eval) returns `{ ok: false }` — the caller treats that as
|
|
3153
|
+
* "skip this link".
|
|
3154
|
+
*/
|
|
3155
|
+
function evaluateLinkExpression(expr, bindingValues, now, opts) {
|
|
3156
|
+
const compiled = compileExpressionSafe(expr);
|
|
3157
|
+
if (!compiled.ok) return {
|
|
3158
|
+
ok: false,
|
|
3159
|
+
error: compiled.error
|
|
3160
|
+
};
|
|
3161
|
+
const scope = createExpressionScope({
|
|
3162
|
+
...bindingValues,
|
|
3163
|
+
["now"]: now
|
|
3164
|
+
});
|
|
3165
|
+
try {
|
|
3166
|
+
return {
|
|
3167
|
+
ok: true,
|
|
3168
|
+
value: evaluateAst(compiled.parsed.ast, scope, opts)
|
|
3169
|
+
};
|
|
3170
|
+
} catch (err) {
|
|
3171
|
+
return {
|
|
3172
|
+
ok: false,
|
|
3173
|
+
error: err instanceof ExpressionEvalError ? err.message : String(err)
|
|
3174
|
+
};
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
//#endregion
|
|
2397
3178
|
//#region src/device/accessory.ts
|
|
2398
3179
|
/**
|
|
2399
3180
|
* Accessory device helpers — shared across drivers.
|
|
@@ -6178,7 +6959,8 @@ var motionDetectionCapability = {
|
|
|
6178
6959
|
methods: {
|
|
6179
6960
|
analyze: require_sleep.method(zod.z.object({
|
|
6180
6961
|
deviceId: zod.z.number(),
|
|
6181
|
-
frame: FrameInputSchema
|
|
6962
|
+
frame: FrameInputSchema.optional(),
|
|
6963
|
+
frameHandle: require_sleep.FrameHandleSchema.optional()
|
|
6182
6964
|
}), MotionAnalysisResultSchema, { kind: "mutation" }),
|
|
6183
6965
|
removeCamera: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
|
|
6184
6966
|
reset: require_sleep.method(zod.z.void(), zod.z.void(), { kind: "mutation" })
|
|
@@ -6496,11 +7278,20 @@ var pipelineExecutorCapability = {
|
|
|
6496
7278
|
* legacy call shape used by existing benchmark code; once all
|
|
6497
7279
|
* callers pass it explicitly we make it required.
|
|
6498
7280
|
*
|
|
6499
|
-
* Exactly one of `frame`, `
|
|
6500
|
-
* provided:
|
|
7281
|
+
* Exactly one of `frame`, `frameHandle`, `imageBase64`,
|
|
7282
|
+
* `referenceImage` must be provided:
|
|
6501
7283
|
* - `frame`: runtime dispatch path (runner → decoded broker frame).
|
|
6502
7284
|
* Carries the raw buffer, dimensions, and format; the executor
|
|
6503
7285
|
* uses it directly without base64 round-tripping.
|
|
7286
|
+
* - `frameHandle` (CB5): a zero-pixel shm `FrameHandle` for the SAME
|
|
7287
|
+
* decoded frame. Both runner and executor are hub-local processes
|
|
7288
|
+
* sharing `/dev/shm`, so the executor maps the named segment and
|
|
7289
|
+
* reads the pixels back zero-copy — eliminating the ~1.2MB
|
|
7290
|
+
* re-serialisation over UDS/MsgPack the `frame` path pays per call.
|
|
7291
|
+
* High-risk: the FrameRing is a latest-wins seqlock with no
|
|
7292
|
+
* refcount, so a recycled slot yields a null read; the executor
|
|
7293
|
+
* then degrades to an empty result and the runner ships pixels via
|
|
7294
|
+
* `frame` as the fallback (queue-depth gated on the runner side).
|
|
6504
7295
|
* - `imageBase64`: one-shot test path (benchmark ImageTab).
|
|
6505
7296
|
* - `referenceImage`: named file from the reference-image store.
|
|
6506
7297
|
*/
|
|
@@ -6508,6 +7299,12 @@ var pipelineExecutorCapability = {
|
|
|
6508
7299
|
engine: PipelineEngineChoiceSchema.optional(),
|
|
6509
7300
|
steps: zod.z.array(PipelineStepInputSchema).min(1),
|
|
6510
7301
|
frame: FrameInputSchema.optional(),
|
|
7302
|
+
/**
|
|
7303
|
+
* CB5 shm passthrough — a `FrameHandle` naming the same ring slot
|
|
7304
|
+
* the decoded pixels live in. One more member of the one-of
|
|
7305
|
+
* frame/frameHandle/image/imageBase64/referenceImage group.
|
|
7306
|
+
*/
|
|
7307
|
+
frameHandle: require_sleep.FrameHandleSchema.optional(),
|
|
6511
7308
|
imageBase64: zod.z.string().optional(),
|
|
6512
7309
|
/**
|
|
6513
7310
|
* Binary JPEG bytes — preferred over `imageBase64` on internal
|
|
@@ -7671,6 +8468,191 @@ var numericSensorCapability = {
|
|
|
7671
8468
|
runtimeState: NumericSensorStatusSchema
|
|
7672
8469
|
};
|
|
7673
8470
|
//#endregion
|
|
8471
|
+
//#region src/capabilities/pet-feeder.cap.ts
|
|
8472
|
+
/**
|
|
8473
|
+
* PetKit pet-feeder cap. Models the control + telemetry surface of a
|
|
8474
|
+
* cloud-connected smart feeder (Fresh Element / Mini / D3 / D4 / D4S
|
|
8475
|
+
* Gemini / D4H / D4SH) as a single coherent slice — bowl food level,
|
|
8476
|
+
* battery, desiccant life, feeding state + the four persisted settings
|
|
8477
|
+
* (child-lock / indicator-light / feed-sound / volume).
|
|
8478
|
+
*
|
|
8479
|
+
* Sources: native PetKit (`nodepetkit` `FeederDevice`). Reusable by any
|
|
8480
|
+
* feeder integration that speaks the same food/desiccant/hopper surface.
|
|
8481
|
+
*
|
|
8482
|
+
* `kind: 'poll'` — PetKit exposes only a cloud REST API (no push
|
|
8483
|
+
* channel), so the provider refreshes the slice on a poll interval and
|
|
8484
|
+
* eagerly after every command.
|
|
8485
|
+
*
|
|
8486
|
+
* Dual-hopper feeders (D4S/D4SH) split the bowl into two independent
|
|
8487
|
+
* hoppers. `isDualHopper` gates the two-hopper UI; `food1`/`food2` carry
|
|
8488
|
+
* the per-hopper levels (both `null` on single-hopper models, where
|
|
8489
|
+
* `foodLevel` is the single reading). The manual-feed portion honours the
|
|
8490
|
+
* PetKit hardware range 4–200 g in 1 g steps.
|
|
8491
|
+
*
|
|
8492
|
+
* Device status is exposed both raw and decoded, mirroring PetKit's HA
|
|
8493
|
+
* integration (RobertD502/py-petkit-api). `status` is the connectivity /
|
|
8494
|
+
* power enum (`normal` / `offline` / `on_batteries`); `error` is the
|
|
8495
|
+
* decoded human-readable fault message (null / `no_error` = healthy);
|
|
8496
|
+
* `errorCode` keeps the raw device integer (0 / null = no error) so a
|
|
8497
|
+
* provider that only has the numeric code stays lossless. A provider maps
|
|
8498
|
+
* the library's fields onto these; any it cannot determine stays `null`.
|
|
8499
|
+
*/
|
|
8500
|
+
/** PetKit manual-feed portion bounds (grams). Mirrors the HA `manual_feed`
|
|
8501
|
+
* number entity (min 4, max 200, step 1, device_class weight). */
|
|
8502
|
+
var PET_FEEDER_MANUAL_FEED_MIN = 4;
|
|
8503
|
+
var PET_FEEDER_MANUAL_FEED_MAX = 200;
|
|
8504
|
+
/**
|
|
8505
|
+
* Feeder connectivity / power status — mirrors the HA petkit device-status
|
|
8506
|
+
* enum: `normal` (online, mains), `offline` (not reaching PetKit cloud),
|
|
8507
|
+
* `on_batteries` (running on battery backup). `null` until first reported.
|
|
8508
|
+
*/
|
|
8509
|
+
var PetFeederDeviceStatusSchema = zod.z.enum([
|
|
8510
|
+
"normal",
|
|
8511
|
+
"offline",
|
|
8512
|
+
"on_batteries"
|
|
8513
|
+
]);
|
|
8514
|
+
var gramsPortion = zod.z.number().int().min(4).max(200);
|
|
8515
|
+
var PetFeederStatusSchema = zod.z.object({
|
|
8516
|
+
/** Food currently in the bowl (grams). Null when the device has not
|
|
8517
|
+
* reported a reading yet. On dual-hopper models this is the combined
|
|
8518
|
+
* bowl reading; per-hopper levels live in `food1`/`food2`. */
|
|
8519
|
+
foodLevel: zod.z.number().nullable(),
|
|
8520
|
+
/** Hopper-1 food level (grams) on dual-hopper feeders; null on
|
|
8521
|
+
* single-hopper models. */
|
|
8522
|
+
food1: zod.z.number().nullable(),
|
|
8523
|
+
/** Hopper-2 food level (grams) on dual-hopper feeders; null on
|
|
8524
|
+
* single-hopper models. */
|
|
8525
|
+
food2: zod.z.number().nullable(),
|
|
8526
|
+
/** Derived low-food flag — mirrors the HA `food_level` binary_sensor
|
|
8527
|
+
* (`device_class: problem`, on = low). True when the bowl is empty /
|
|
8528
|
+
* below the feeder's low threshold. */
|
|
8529
|
+
lowFood: zod.z.boolean(),
|
|
8530
|
+
/** Battery charge 0..100 (%). Null on mains-powered models or when the
|
|
8531
|
+
* device has no battery reading. */
|
|
8532
|
+
batteryPower: zod.z.number().min(0).max(100).nullable(),
|
|
8533
|
+
/** Days of desiccant life remaining. Null when the model has no
|
|
8534
|
+
* desiccant sensor. */
|
|
8535
|
+
desiccantLeftDays: zod.z.number().nullable(),
|
|
8536
|
+
/** True while a feed is in progress. */
|
|
8537
|
+
feeding: zod.z.boolean(),
|
|
8538
|
+
/** Decoded connectivity / power status (HA petkit device-status enum).
|
|
8539
|
+
* Null until the device has reported a status. */
|
|
8540
|
+
status: PetFeederDeviceStatusSchema.nullable(),
|
|
8541
|
+
/** Decoded human-readable fault message. Null (or the device's `no_error`
|
|
8542
|
+
* sentinel) means healthy; a non-null string is an active fault. Pairs
|
|
8543
|
+
* with `errorCode` for consumers that want the raw integer. */
|
|
8544
|
+
error: zod.z.string().nullable(),
|
|
8545
|
+
/** Raw device error code (0 / null = no error). */
|
|
8546
|
+
errorCode: zod.z.number().nullable(),
|
|
8547
|
+
/** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
|
|
8548
|
+
isDualHopper: zod.z.boolean(),
|
|
8549
|
+
/** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
|
|
8550
|
+
childLock: zod.z.boolean(),
|
|
8551
|
+
/** Front indicator-light setting. */
|
|
8552
|
+
indicatorLight: zod.z.boolean(),
|
|
8553
|
+
/** Play a chime when dispensing. */
|
|
8554
|
+
feedSound: zod.z.boolean(),
|
|
8555
|
+
/** Speaker / prompt volume level (device-scaled integer). */
|
|
8556
|
+
volume: zod.z.number(),
|
|
8557
|
+
/** Ms epoch when the slice was last refreshed from the cloud. */
|
|
8558
|
+
lastFetchedAt: zod.z.number()
|
|
8559
|
+
});
|
|
8560
|
+
var petFeederCapability = {
|
|
8561
|
+
name: "pet-feeder",
|
|
8562
|
+
scope: "device",
|
|
8563
|
+
deviceNative: true,
|
|
8564
|
+
mode: "singleton",
|
|
8565
|
+
deviceTypes: [require_sleep.DeviceType.PetFeeder],
|
|
8566
|
+
methods: {
|
|
8567
|
+
/**
|
|
8568
|
+
* Dispense food now. Single-hopper feeders take `grams`; dual-hopper
|
|
8569
|
+
* feeders (D4S/D4SH) accept `hopper1`/`hopper2` to target one or both
|
|
8570
|
+
* hoppers. All portions honour the 4–200 g hardware range. At least
|
|
8571
|
+
* one of the three must be present — the provider rejects an empty
|
|
8572
|
+
* request.
|
|
8573
|
+
*/
|
|
8574
|
+
feed: require_sleep.method(zod.z.object({
|
|
8575
|
+
deviceId: zod.z.number().int().nonnegative(),
|
|
8576
|
+
grams: gramsPortion.optional(),
|
|
8577
|
+
hopper1: gramsPortion.optional(),
|
|
8578
|
+
hopper2: gramsPortion.optional()
|
|
8579
|
+
}), zod.z.void(), {
|
|
8580
|
+
kind: "mutation",
|
|
8581
|
+
auth: "admin"
|
|
8582
|
+
}),
|
|
8583
|
+
/** Cancel an in-progress manual feed. */
|
|
8584
|
+
cancelFeed: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.void(), {
|
|
8585
|
+
kind: "mutation",
|
|
8586
|
+
auth: "admin"
|
|
8587
|
+
}),
|
|
8588
|
+
/** Reset the desiccant "days remaining" counter after replacing it. */
|
|
8589
|
+
resetDesiccant: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.void(), {
|
|
8590
|
+
kind: "mutation",
|
|
8591
|
+
auth: "admin"
|
|
8592
|
+
}),
|
|
8593
|
+
/** Mark a hopper as refilled (D4H/D4S/D4SH). */
|
|
8594
|
+
markFoodReplenished: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.void(), {
|
|
8595
|
+
kind: "mutation",
|
|
8596
|
+
auth: "admin"
|
|
8597
|
+
}),
|
|
8598
|
+
/** Call the pet with the recorded prompt (D3). */
|
|
8599
|
+
callPet: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.void(), {
|
|
8600
|
+
kind: "mutation",
|
|
8601
|
+
auth: "admin"
|
|
8602
|
+
}),
|
|
8603
|
+
/** Play a stored sound by id (D3 / D4H / D4SH). */
|
|
8604
|
+
playSound: require_sleep.method(zod.z.object({
|
|
8605
|
+
deviceId: zod.z.number().int().nonnegative(),
|
|
8606
|
+
soundId: zod.z.number().int().nonnegative()
|
|
8607
|
+
}), zod.z.void(), {
|
|
8608
|
+
kind: "mutation",
|
|
8609
|
+
auth: "admin"
|
|
8610
|
+
}),
|
|
8611
|
+
/** Toggle the child-lock (manual-lock) setting. */
|
|
8612
|
+
setChildLock: require_sleep.method(zod.z.object({
|
|
8613
|
+
deviceId: zod.z.number().int().nonnegative(),
|
|
8614
|
+
on: zod.z.boolean()
|
|
8615
|
+
}), zod.z.void(), {
|
|
8616
|
+
kind: "mutation",
|
|
8617
|
+
auth: "admin"
|
|
8618
|
+
}),
|
|
8619
|
+
/** Toggle the front indicator light. */
|
|
8620
|
+
setIndicatorLight: require_sleep.method(zod.z.object({
|
|
8621
|
+
deviceId: zod.z.number().int().nonnegative(),
|
|
8622
|
+
on: zod.z.boolean()
|
|
8623
|
+
}), zod.z.void(), {
|
|
8624
|
+
kind: "mutation",
|
|
8625
|
+
auth: "admin"
|
|
8626
|
+
}),
|
|
8627
|
+
/** Toggle the dispense chime. */
|
|
8628
|
+
setFeedSound: require_sleep.method(zod.z.object({
|
|
8629
|
+
deviceId: zod.z.number().int().nonnegative(),
|
|
8630
|
+
on: zod.z.boolean()
|
|
8631
|
+
}), zod.z.void(), {
|
|
8632
|
+
kind: "mutation",
|
|
8633
|
+
auth: "admin"
|
|
8634
|
+
}),
|
|
8635
|
+
/** Set the speaker / prompt volume level. */
|
|
8636
|
+
setVolume: require_sleep.method(zod.z.object({
|
|
8637
|
+
deviceId: zod.z.number().int().nonnegative(),
|
|
8638
|
+
level: zod.z.number().int().nonnegative()
|
|
8639
|
+
}), zod.z.void(), {
|
|
8640
|
+
kind: "mutation",
|
|
8641
|
+
auth: "admin"
|
|
8642
|
+
})
|
|
8643
|
+
},
|
|
8644
|
+
status: {
|
|
8645
|
+
schema: PetFeederStatusSchema,
|
|
8646
|
+
kind: "poll"
|
|
8647
|
+
},
|
|
8648
|
+
/**
|
|
8649
|
+
* Runtime-state slice — mirrored by the kernel. UI feeder cards read
|
|
8650
|
+
* the full slice via `device.state.petFeeder.value` and refresh on
|
|
8651
|
+
* every poll without re-querying the provider.
|
|
8652
|
+
*/
|
|
8653
|
+
runtimeState: PetFeederStatusSchema
|
|
8654
|
+
};
|
|
8655
|
+
//#endregion
|
|
7674
8656
|
//#region src/capabilities/power-meter.cap.ts
|
|
7675
8657
|
/**
|
|
7676
8658
|
* Multi-metric electrical meter. One slice can carry any combination
|
|
@@ -9127,6 +10109,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
9127
10109
|
nativeObjectDetection: nativeObjectDetectionCapability,
|
|
9128
10110
|
notifier: notifierCapability,
|
|
9129
10111
|
numericSensor: numericSensorCapability,
|
|
10112
|
+
petFeeder: petFeederCapability,
|
|
9130
10113
|
powerMeter: powerMeterCapability,
|
|
9131
10114
|
presence: presenceCapability,
|
|
9132
10115
|
pressureSensor: pressureSensorCapability,
|
|
@@ -9633,6 +10616,334 @@ var BaseDevice = class {
|
|
|
9633
10616
|
}
|
|
9634
10617
|
};
|
|
9635
10618
|
//#endregion
|
|
10619
|
+
//#region src/units/unit-table.ts
|
|
10620
|
+
/**
|
|
10621
|
+
* Frozen unit → spec table. `•` in the doc marks each dimension's canonical
|
|
10622
|
+
* unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
|
|
10623
|
+
* `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
|
|
10624
|
+
* decimals). K derives from `°C = K − 273.15`.
|
|
10625
|
+
*/
|
|
10626
|
+
var UNIT_TABLE = {
|
|
10627
|
+
"°C": {
|
|
10628
|
+
dimension: "temperature",
|
|
10629
|
+
factor: 1,
|
|
10630
|
+
offset: 0
|
|
10631
|
+
},
|
|
10632
|
+
"°F": {
|
|
10633
|
+
dimension: "temperature",
|
|
10634
|
+
factor: 5 / 9,
|
|
10635
|
+
offset: -160 / 9
|
|
10636
|
+
},
|
|
10637
|
+
K: {
|
|
10638
|
+
dimension: "temperature",
|
|
10639
|
+
factor: 1,
|
|
10640
|
+
offset: -273.15
|
|
10641
|
+
},
|
|
10642
|
+
hPa: {
|
|
10643
|
+
dimension: "pressure",
|
|
10644
|
+
factor: 1,
|
|
10645
|
+
offset: 0
|
|
10646
|
+
},
|
|
10647
|
+
kPa: {
|
|
10648
|
+
dimension: "pressure",
|
|
10649
|
+
factor: 10,
|
|
10650
|
+
offset: 0
|
|
10651
|
+
},
|
|
10652
|
+
Pa: {
|
|
10653
|
+
dimension: "pressure",
|
|
10654
|
+
factor: .01,
|
|
10655
|
+
offset: 0
|
|
10656
|
+
},
|
|
10657
|
+
mbar: {
|
|
10658
|
+
dimension: "pressure",
|
|
10659
|
+
factor: 1,
|
|
10660
|
+
offset: 0
|
|
10661
|
+
},
|
|
10662
|
+
bar: {
|
|
10663
|
+
dimension: "pressure",
|
|
10664
|
+
factor: 1e3,
|
|
10665
|
+
offset: 0
|
|
10666
|
+
},
|
|
10667
|
+
inHg: {
|
|
10668
|
+
dimension: "pressure",
|
|
10669
|
+
factor: 33.8639,
|
|
10670
|
+
offset: 0
|
|
10671
|
+
},
|
|
10672
|
+
mmHg: {
|
|
10673
|
+
dimension: "pressure",
|
|
10674
|
+
factor: 1.33322,
|
|
10675
|
+
offset: 0
|
|
10676
|
+
},
|
|
10677
|
+
psi: {
|
|
10678
|
+
dimension: "pressure",
|
|
10679
|
+
factor: 68.9476,
|
|
10680
|
+
offset: 0
|
|
10681
|
+
},
|
|
10682
|
+
"m/s": {
|
|
10683
|
+
dimension: "speed",
|
|
10684
|
+
factor: 1,
|
|
10685
|
+
offset: 0
|
|
10686
|
+
},
|
|
10687
|
+
"km/h": {
|
|
10688
|
+
dimension: "speed",
|
|
10689
|
+
factor: 1 / 3.6,
|
|
10690
|
+
offset: 0
|
|
10691
|
+
},
|
|
10692
|
+
mph: {
|
|
10693
|
+
dimension: "speed",
|
|
10694
|
+
factor: .44704,
|
|
10695
|
+
offset: 0
|
|
10696
|
+
},
|
|
10697
|
+
kn: {
|
|
10698
|
+
dimension: "speed",
|
|
10699
|
+
factor: .514444,
|
|
10700
|
+
offset: 0
|
|
10701
|
+
},
|
|
10702
|
+
"mm/h": {
|
|
10703
|
+
dimension: "precipitation-rate",
|
|
10704
|
+
factor: 1,
|
|
10705
|
+
offset: 0
|
|
10706
|
+
},
|
|
10707
|
+
"in/h": {
|
|
10708
|
+
dimension: "precipitation-rate",
|
|
10709
|
+
factor: 25.4,
|
|
10710
|
+
offset: 0
|
|
10711
|
+
},
|
|
10712
|
+
m: {
|
|
10713
|
+
dimension: "length",
|
|
10714
|
+
factor: 1,
|
|
10715
|
+
offset: 0
|
|
10716
|
+
},
|
|
10717
|
+
mm: {
|
|
10718
|
+
dimension: "length",
|
|
10719
|
+
factor: .001,
|
|
10720
|
+
offset: 0
|
|
10721
|
+
},
|
|
10722
|
+
cm: {
|
|
10723
|
+
dimension: "length",
|
|
10724
|
+
factor: .01,
|
|
10725
|
+
offset: 0
|
|
10726
|
+
},
|
|
10727
|
+
km: {
|
|
10728
|
+
dimension: "length",
|
|
10729
|
+
factor: 1e3,
|
|
10730
|
+
offset: 0
|
|
10731
|
+
},
|
|
10732
|
+
in: {
|
|
10733
|
+
dimension: "length",
|
|
10734
|
+
factor: .0254,
|
|
10735
|
+
offset: 0
|
|
10736
|
+
},
|
|
10737
|
+
ft: {
|
|
10738
|
+
dimension: "length",
|
|
10739
|
+
factor: .3048,
|
|
10740
|
+
offset: 0
|
|
10741
|
+
},
|
|
10742
|
+
mi: {
|
|
10743
|
+
dimension: "length",
|
|
10744
|
+
factor: 1609.344,
|
|
10745
|
+
offset: 0
|
|
10746
|
+
},
|
|
10747
|
+
lx: {
|
|
10748
|
+
dimension: "illuminance",
|
|
10749
|
+
factor: 1,
|
|
10750
|
+
offset: 0
|
|
10751
|
+
},
|
|
10752
|
+
"W/m²": {
|
|
10753
|
+
dimension: "irradiance",
|
|
10754
|
+
factor: 1,
|
|
10755
|
+
offset: 0
|
|
10756
|
+
},
|
|
10757
|
+
W: {
|
|
10758
|
+
dimension: "power",
|
|
10759
|
+
factor: 1,
|
|
10760
|
+
offset: 0
|
|
10761
|
+
},
|
|
10762
|
+
kW: {
|
|
10763
|
+
dimension: "power",
|
|
10764
|
+
factor: 1e3,
|
|
10765
|
+
offset: 0
|
|
10766
|
+
},
|
|
10767
|
+
VA: {
|
|
10768
|
+
dimension: "apparent-power",
|
|
10769
|
+
factor: 1,
|
|
10770
|
+
offset: 0
|
|
10771
|
+
},
|
|
10772
|
+
Wh: {
|
|
10773
|
+
dimension: "energy",
|
|
10774
|
+
factor: 1,
|
|
10775
|
+
offset: 0
|
|
10776
|
+
},
|
|
10777
|
+
kWh: {
|
|
10778
|
+
dimension: "energy",
|
|
10779
|
+
factor: 1e3,
|
|
10780
|
+
offset: 0
|
|
10781
|
+
},
|
|
10782
|
+
MWh: {
|
|
10783
|
+
dimension: "energy",
|
|
10784
|
+
factor: 1e6,
|
|
10785
|
+
offset: 0
|
|
10786
|
+
},
|
|
10787
|
+
V: {
|
|
10788
|
+
dimension: "voltage",
|
|
10789
|
+
factor: 1,
|
|
10790
|
+
offset: 0
|
|
10791
|
+
},
|
|
10792
|
+
mV: {
|
|
10793
|
+
dimension: "voltage",
|
|
10794
|
+
factor: .001,
|
|
10795
|
+
offset: 0
|
|
10796
|
+
},
|
|
10797
|
+
A: {
|
|
10798
|
+
dimension: "current",
|
|
10799
|
+
factor: 1,
|
|
10800
|
+
offset: 0
|
|
10801
|
+
},
|
|
10802
|
+
mA: {
|
|
10803
|
+
dimension: "current",
|
|
10804
|
+
factor: .001,
|
|
10805
|
+
offset: 0
|
|
10806
|
+
},
|
|
10807
|
+
ppm: {
|
|
10808
|
+
dimension: "concentration-volume",
|
|
10809
|
+
factor: 1,
|
|
10810
|
+
offset: 0
|
|
10811
|
+
},
|
|
10812
|
+
ppb: {
|
|
10813
|
+
dimension: "concentration-volume",
|
|
10814
|
+
factor: .001,
|
|
10815
|
+
offset: 0
|
|
10816
|
+
},
|
|
10817
|
+
"µg/m³": {
|
|
10818
|
+
dimension: "concentration-mass",
|
|
10819
|
+
factor: 1,
|
|
10820
|
+
offset: 0
|
|
10821
|
+
},
|
|
10822
|
+
"mg/m³": {
|
|
10823
|
+
dimension: "concentration-mass",
|
|
10824
|
+
factor: 1e3,
|
|
10825
|
+
offset: 0
|
|
10826
|
+
},
|
|
10827
|
+
kg: {
|
|
10828
|
+
dimension: "mass",
|
|
10829
|
+
factor: 1,
|
|
10830
|
+
offset: 0
|
|
10831
|
+
},
|
|
10832
|
+
g: {
|
|
10833
|
+
dimension: "mass",
|
|
10834
|
+
factor: .001,
|
|
10835
|
+
offset: 0
|
|
10836
|
+
},
|
|
10837
|
+
lb: {
|
|
10838
|
+
dimension: "mass",
|
|
10839
|
+
factor: .453592,
|
|
10840
|
+
offset: 0
|
|
10841
|
+
},
|
|
10842
|
+
oz: {
|
|
10843
|
+
dimension: "mass",
|
|
10844
|
+
factor: .0283495,
|
|
10845
|
+
offset: 0
|
|
10846
|
+
},
|
|
10847
|
+
"%": {
|
|
10848
|
+
dimension: "percentage",
|
|
10849
|
+
factor: 1,
|
|
10850
|
+
offset: 0
|
|
10851
|
+
}
|
|
10852
|
+
};
|
|
10853
|
+
//#endregion
|
|
10854
|
+
//#region src/units/convert.ts
|
|
10855
|
+
/**
|
|
10856
|
+
* Pure unit-conversion API over `UNIT_TABLE`. No dependencies, no side effects.
|
|
10857
|
+
*
|
|
10858
|
+
* Conversion is refused (throws `UnitConversionError`, or returns `undefined`
|
|
10859
|
+
* from the `try*` variant) whenever a unit is unknown or the two units belong
|
|
10860
|
+
* to different dimensions — cross-dimension conversion is deliberately
|
|
10861
|
+
* impossible so a render override can never make the value and its unit label
|
|
10862
|
+
* disagree.
|
|
10863
|
+
*
|
|
10864
|
+
* Callers pass units through `normalizeUnit` (`../device/source-info.ts`) BEFORE
|
|
10865
|
+
* calling here — the table keys only canonical spellings.
|
|
10866
|
+
*/
|
|
10867
|
+
/** Thrown by `convertUnit` when a conversion is refused. Carries the offending
|
|
10868
|
+
* units + a machine-readable `reason` for callers that branch on the cause. */
|
|
10869
|
+
var UnitConversionError = class extends Error {
|
|
10870
|
+
from;
|
|
10871
|
+
to;
|
|
10872
|
+
reason;
|
|
10873
|
+
constructor(from, to, reason) {
|
|
10874
|
+
super(`[units] cannot convert '${from}' → '${to}': ${reason}`);
|
|
10875
|
+
this.name = "UnitConversionError";
|
|
10876
|
+
this.from = from;
|
|
10877
|
+
this.to = to;
|
|
10878
|
+
this.reason = reason;
|
|
10879
|
+
}
|
|
10880
|
+
};
|
|
10881
|
+
/** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
|
|
10882
|
+
function specFor(unit) {
|
|
10883
|
+
return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
|
|
10884
|
+
}
|
|
10885
|
+
/** The dimension a unit belongs to, or `undefined` for an unknown unit. */
|
|
10886
|
+
function unitDimension(unit) {
|
|
10887
|
+
return specFor(unit)?.dimension;
|
|
10888
|
+
}
|
|
10889
|
+
/** Value in `spec`'s unit → its dimension's canonical unit. */
|
|
10890
|
+
function toCanonical(value, spec) {
|
|
10891
|
+
return value * spec.factor + spec.offset;
|
|
10892
|
+
}
|
|
10893
|
+
/** Canonical-unit value → the unit described by `spec`. */
|
|
10894
|
+
function fromCanonical(canonical, spec) {
|
|
10895
|
+
return (canonical - spec.offset) / spec.factor;
|
|
10896
|
+
}
|
|
10897
|
+
/**
|
|
10898
|
+
* Convert `value` from unit `from` to unit `to`. Identity when `from === to`.
|
|
10899
|
+
* Throws `UnitConversionError` when either unit is unknown or the units belong
|
|
10900
|
+
* to different dimensions.
|
|
10901
|
+
*/
|
|
10902
|
+
function convertUnit(value, from, to) {
|
|
10903
|
+
if (from === to) return value;
|
|
10904
|
+
const fromSpec = specFor(from);
|
|
10905
|
+
if (!fromSpec) throw new UnitConversionError(from, to, "unknown-from");
|
|
10906
|
+
const toSpec = specFor(to);
|
|
10907
|
+
if (!toSpec) throw new UnitConversionError(from, to, "unknown-to");
|
|
10908
|
+
if (fromSpec.dimension !== toSpec.dimension) throw new UnitConversionError(from, to, "cross-dimension");
|
|
10909
|
+
return fromCanonical(toCanonical(value, fromSpec), toSpec);
|
|
10910
|
+
}
|
|
10911
|
+
/**
|
|
10912
|
+
* Graceful variant for the render path: returns the converted value, or
|
|
10913
|
+
* `undefined` when the conversion is refused (unknown unit / cross-dimension).
|
|
10914
|
+
* Never throws — a render caller falls back to the raw value + source unit.
|
|
10915
|
+
*/
|
|
10916
|
+
function tryConvertUnit(value, from, to) {
|
|
10917
|
+
if (from === to) return value;
|
|
10918
|
+
const fromSpec = specFor(from);
|
|
10919
|
+
if (!fromSpec) return void 0;
|
|
10920
|
+
const toSpec = specFor(to);
|
|
10921
|
+
if (!toSpec) return void 0;
|
|
10922
|
+
if (fromSpec.dimension !== toSpec.dimension) return void 0;
|
|
10923
|
+
return fromCanonical(toCanonical(value, fromSpec), toSpec);
|
|
10924
|
+
}
|
|
10925
|
+
/**
|
|
10926
|
+
* Whether `value` can be converted between `from` and `to` (same dimension,
|
|
10927
|
+
* both known). Cheap boolean probe for the UI's same-dimension unit picker and
|
|
10928
|
+
* the resolver's convertibility branch — compute ONCE so a unit label and its
|
|
10929
|
+
* value math can never disagree.
|
|
10930
|
+
*/
|
|
10931
|
+
function canConvertUnit(from, to) {
|
|
10932
|
+
if (from === to) return true;
|
|
10933
|
+
const fromDim = unitDimension(from);
|
|
10934
|
+
if (fromDim === void 0) return false;
|
|
10935
|
+
return fromDim === unitDimension(to);
|
|
10936
|
+
}
|
|
10937
|
+
/**
|
|
10938
|
+
* Every known unit spelling in a dimension (table insertion order — canonical
|
|
10939
|
+
* unit first). Powers the UI's same-dimension unit picker.
|
|
10940
|
+
*/
|
|
10941
|
+
function unitsForDimension(dimension) {
|
|
10942
|
+
const out = [];
|
|
10943
|
+
for (const [unit, spec] of Object.entries(UNIT_TABLE)) if (spec.dimension === dimension) out.push(unit);
|
|
10944
|
+
return out;
|
|
10945
|
+
}
|
|
10946
|
+
//#endregion
|
|
9636
10947
|
//#region src/capabilities/device-provider.cap.ts
|
|
9637
10948
|
var ProviderStatusSchema = zod.z.object({
|
|
9638
10949
|
connected: zod.z.boolean(),
|
|
@@ -10491,6 +11802,8 @@ function createSystemProxy(api) {
|
|
|
10491
11802
|
registerDevice: (input) => dispatch("deviceManager", "registerDevice", "mutation", input),
|
|
10492
11803
|
removeDevice: (input) => dispatch("deviceManager", "removeDevice", "mutation", input),
|
|
10493
11804
|
persistConfig: (input) => dispatch("deviceManager", "persistConfig", "mutation", input),
|
|
11805
|
+
getRoleDisplayDefaults: (input) => dispatch("deviceManager", "getRoleDisplayDefaults", "query", input),
|
|
11806
|
+
setRoleDisplayDefaults: (input) => dispatch("deviceManager", "setRoleDisplayDefaults", "mutation", input),
|
|
10494
11807
|
listLocations: (input) => dispatch("deviceManager", "listLocations", "query", input),
|
|
10495
11808
|
addLocation: (input) => dispatch("deviceManager", "addLocation", "mutation", input),
|
|
10496
11809
|
removeLocation: (input) => dispatch("deviceManager", "removeLocation", "mutation", input),
|
|
@@ -12966,11 +14279,13 @@ var decoderCapability = {
|
|
|
12966
14279
|
}), zod.z.void()),
|
|
12967
14280
|
pullFrames: require_sleep.method(zod.z.object({
|
|
12968
14281
|
sessionId: zod.z.string(),
|
|
12969
|
-
maxCount: zod.z.number().default(1)
|
|
14282
|
+
maxCount: zod.z.number().default(1),
|
|
14283
|
+
waitMs: zod.z.number().optional()
|
|
12970
14284
|
}), zod.z.array(require_sleep.DecodedFrameSchema)),
|
|
12971
14285
|
pullHandles: require_sleep.method(zod.z.object({
|
|
12972
14286
|
sessionId: zod.z.string(),
|
|
12973
|
-
maxCount: zod.z.number().default(1)
|
|
14287
|
+
maxCount: zod.z.number().default(1),
|
|
14288
|
+
waitMs: zod.z.number().optional()
|
|
12974
14289
|
}), zod.z.array(require_sleep.FrameHandleSchema)),
|
|
12975
14290
|
getFrame: require_sleep.method(zod.z.object({ handle: require_sleep.FrameHandleSchema }), require_sleep.DecodedFrameSchema.nullable()),
|
|
12976
14291
|
getShmStats: require_sleep.method(zod.z.object({ sessionId: zod.z.string() }), ShmRingStatsSchema.nullable()),
|
|
@@ -13363,30 +14678,57 @@ var ChildLayoutEntrySchema = zod.z.object({
|
|
|
13363
14678
|
* LITERAL source carries a per-device constant (no sibling is read); a
|
|
13364
14679
|
* GLOBAL source (P2e) copies ANY device's status field, addressed by the
|
|
13365
14680
|
* source device's full re-sync-stable `stableId`. */
|
|
14681
|
+
var DeviceLinkFieldSourceSchema = zod.z.object({
|
|
14682
|
+
kind: zod.z.literal("field").optional(),
|
|
14683
|
+
sourceKey: zod.z.string(),
|
|
14684
|
+
cap: zod.z.string(),
|
|
14685
|
+
fieldPath: zod.z.string()
|
|
14686
|
+
});
|
|
14687
|
+
var DeviceLinkLiteralSourceSchema = zod.z.object({
|
|
14688
|
+
kind: zod.z.literal("literal"),
|
|
14689
|
+
value: zod.z.union([
|
|
14690
|
+
zod.z.string(),
|
|
14691
|
+
zod.z.number(),
|
|
14692
|
+
zod.z.boolean(),
|
|
14693
|
+
zod.z.null()
|
|
14694
|
+
])
|
|
14695
|
+
});
|
|
14696
|
+
var DeviceLinkGlobalSourceSchema = zod.z.object({
|
|
14697
|
+
kind: zod.z.literal("global"),
|
|
14698
|
+
sourceStableId: zod.z.string(),
|
|
14699
|
+
cap: zod.z.string(),
|
|
14700
|
+
fieldPath: zod.z.string()
|
|
14701
|
+
});
|
|
14702
|
+
/** Expression source (Stage X): compute the target field from N named bindings
|
|
14703
|
+
* via the safe expression engine. Bindings are field | literal | global — never
|
|
14704
|
+
* another expression (no nesting). The `superRefine` runs the SAME author-time
|
|
14705
|
+
* validation as `validateExpressionSource` (compiles the expr, checks binding
|
|
14706
|
+
* names + identifier coverage) so every wire boundary that parses a DeviceLink
|
|
14707
|
+
* (tRPC mount, kernel create pre-seed, projection output) validates-at-write.
|
|
14708
|
+
* Compiles are LRU-cached, so repeated validation of the same expr is a hit. */
|
|
14709
|
+
var DeviceLinkExpressionSourceSchema = zod.z.object({
|
|
14710
|
+
kind: zod.z.literal("expression"),
|
|
14711
|
+
expr: zod.z.string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
|
|
14712
|
+
bindings: zod.z.record(zod.z.string().regex(EXPRESSION_IDENTIFIER_RE), zod.z.union([
|
|
14713
|
+
DeviceLinkFieldSourceSchema,
|
|
14714
|
+
DeviceLinkLiteralSourceSchema,
|
|
14715
|
+
DeviceLinkGlobalSourceSchema
|
|
14716
|
+
]))
|
|
14717
|
+
}).superRefine((src, ctx) => {
|
|
14718
|
+
const err = validateExpressionSource(src);
|
|
14719
|
+
if (err !== null) ctx.addIssue({
|
|
14720
|
+
code: "custom",
|
|
14721
|
+
message: err,
|
|
14722
|
+
path: ["expr"]
|
|
14723
|
+
});
|
|
14724
|
+
});
|
|
13366
14725
|
var DeviceLinkSchema = zod.z.object({
|
|
13367
14726
|
id: zod.z.string(),
|
|
13368
14727
|
source: zod.z.union([
|
|
13369
|
-
|
|
13370
|
-
|
|
13371
|
-
|
|
13372
|
-
|
|
13373
|
-
fieldPath: zod.z.string()
|
|
13374
|
-
}),
|
|
13375
|
-
zod.z.object({
|
|
13376
|
-
kind: zod.z.literal("literal"),
|
|
13377
|
-
value: zod.z.union([
|
|
13378
|
-
zod.z.string(),
|
|
13379
|
-
zod.z.number(),
|
|
13380
|
-
zod.z.boolean(),
|
|
13381
|
-
zod.z.null()
|
|
13382
|
-
])
|
|
13383
|
-
}),
|
|
13384
|
-
zod.z.object({
|
|
13385
|
-
kind: zod.z.literal("global"),
|
|
13386
|
-
sourceStableId: zod.z.string(),
|
|
13387
|
-
cap: zod.z.string(),
|
|
13388
|
-
fieldPath: zod.z.string()
|
|
13389
|
-
})
|
|
14728
|
+
DeviceLinkFieldSourceSchema,
|
|
14729
|
+
DeviceLinkLiteralSourceSchema,
|
|
14730
|
+
DeviceLinkGlobalSourceSchema,
|
|
14731
|
+
DeviceLinkExpressionSourceSchema
|
|
13390
14732
|
]),
|
|
13391
14733
|
target: zod.z.object({
|
|
13392
14734
|
cap: zod.z.string(),
|
|
@@ -13416,6 +14758,31 @@ var DeviceLinkSchema = zod.z.object({
|
|
|
13416
14758
|
})
|
|
13417
14759
|
]).optional()
|
|
13418
14760
|
});
|
|
14761
|
+
/** Cap-wire shape of a per-cap display refinement — mirrors
|
|
14762
|
+
* `DeviceCapDisplayOverride` in `device-management.ts`. */
|
|
14763
|
+
var DeviceCapDisplayOverrideSchema = zod.z.object({
|
|
14764
|
+
unit: zod.z.string().min(1).optional(),
|
|
14765
|
+
precision: zod.z.number().int().min(0).max(10).optional()
|
|
14766
|
+
});
|
|
14767
|
+
/** Cap-wire shape of an operator-authored per-device display override —
|
|
14768
|
+
* mirrors `DeviceDisplayOverride` in `device-management.ts`. `precision`
|
|
14769
|
+
* bounds mirror `numeric-sensor.cap.ts` (`int 0-10`). */
|
|
14770
|
+
var DeviceDisplayOverrideSchema = zod.z.object({
|
|
14771
|
+
icon: zod.z.string().min(1).optional(),
|
|
14772
|
+
label: zod.z.string().min(1).optional(),
|
|
14773
|
+
unit: zod.z.string().min(1).optional(),
|
|
14774
|
+
precision: zod.z.number().int().min(0).max(10).optional(),
|
|
14775
|
+
hidden: zod.z.boolean().optional(),
|
|
14776
|
+
perCap: zod.z.record(zod.z.string(), DeviceCapDisplayOverrideSchema).optional()
|
|
14777
|
+
});
|
|
14778
|
+
/** Cap-wire shape of a per-role display default — mirrors `RoleDisplayDefault`
|
|
14779
|
+
* in `device-management.ts`. Keyed by `DeviceRole` string (role strings cross
|
|
14780
|
+
* the wire as plain strings everywhere else — cf. `DeviceInfoSchema.role`). */
|
|
14781
|
+
var RoleDisplayDefaultSchema = zod.z.object({
|
|
14782
|
+
unit: zod.z.string().min(1).optional(),
|
|
14783
|
+
precision: zod.z.number().int().min(0).max(10).optional(),
|
|
14784
|
+
icon: zod.z.string().min(1).optional()
|
|
14785
|
+
});
|
|
13419
14786
|
/**
|
|
13420
14787
|
* Serializable projection of a live IDevice.
|
|
13421
14788
|
* Returned by listAll, getDevice, getChildren.
|
|
@@ -13471,7 +14838,9 @@ var DeviceInfoSchema = zod.z.object({
|
|
|
13471
14838
|
* `DeviceMeta.childLayout`. Absent ⇒ no layout declared. */
|
|
13472
14839
|
childLayout: zod.z.array(ChildLayoutEntrySchema).readonly().optional(),
|
|
13473
14840
|
/** Operator-authored cross-device field wirings. See `DeviceMeta.deviceLinks`. */
|
|
13474
|
-
deviceLinks: zod.z.array(DeviceLinkSchema).readonly().optional()
|
|
14841
|
+
deviceLinks: zod.z.array(DeviceLinkSchema).readonly().optional(),
|
|
14842
|
+
/** Operator-authored per-device display override. See `DeviceMeta.display`. */
|
|
14843
|
+
display: DeviceDisplayOverrideSchema.optional()
|
|
13475
14844
|
});
|
|
13476
14845
|
var ConfigEntrySchema = zod.z.object({
|
|
13477
14846
|
key: zod.z.string(),
|
|
@@ -13536,7 +14905,9 @@ var DeviceMetaSchema = zod.z.object({
|
|
|
13536
14905
|
deviceLinks: zod.z.array(DeviceLinkSchema).readonly().optional(),
|
|
13537
14906
|
/** Semantic role string (`DeviceRole`) — propagated from the spawn pre-seed.
|
|
13538
14907
|
* Optional: only present for accessory children that carry a known role. */
|
|
13539
|
-
role: zod.z.string().nullable().optional()
|
|
14908
|
+
role: zod.z.string().nullable().optional(),
|
|
14909
|
+
/** Operator-authored per-device display override. See `DeviceMeta.display`. */
|
|
14910
|
+
display: DeviceDisplayOverrideSchema.optional()
|
|
13540
14911
|
});
|
|
13541
14912
|
/** ConfigUISchema passed through as unknown — mirrors device-provider's CreationSchemaOutputSchema */
|
|
13542
14913
|
var ConfigUISchemaOutput = zod.z.unknown().nullable();
|
|
@@ -13692,6 +15063,29 @@ var deviceManagerCapability = {
|
|
|
13692
15063
|
kind: "mutation",
|
|
13693
15064
|
auth: "admin"
|
|
13694
15065
|
}),
|
|
15066
|
+
/** Set (or clear) the per-device display override on the meta row. Mirrors
|
|
15067
|
+
* `setChildLayout` persistence; `null` clears the override entirely. The
|
|
15068
|
+
* override unit(s) are normalized (`normalizeUnit`) at write so the render
|
|
15069
|
+
* path's `UNIT_TABLE` lookups always hit canonical spellings. Persisted,
|
|
15070
|
+
* projected, and preserved across re-register/restore. Idempotent. */
|
|
15071
|
+
setDisplay: require_sleep.method(zod.z.object({
|
|
15072
|
+
deviceId: zod.z.number(),
|
|
15073
|
+
display: DeviceDisplayOverrideSchema.nullable()
|
|
15074
|
+
}), zod.z.void(), {
|
|
15075
|
+
kind: "mutation",
|
|
15076
|
+
auth: "admin"
|
|
15077
|
+
}),
|
|
15078
|
+
/** Read the operator-authored per-role display defaults (unit/precision/
|
|
15079
|
+
* icon), keyed by `DeviceRole` string. Empty record when none set. */
|
|
15080
|
+
getRoleDisplayDefaults: require_sleep.method(zod.z.object({}), zod.z.object({ defaults: zod.z.record(zod.z.string(), RoleDisplayDefaultSchema) }), { kind: "query" }),
|
|
15081
|
+
/** Replace the per-role display defaults whole-record (full replace — the
|
|
15082
|
+
* caller sends the complete map). Override unit(s) are normalized at write.
|
|
15083
|
+
* Not per-device, so nothing is emitted; the UI invalidates its own query
|
|
15084
|
+
* on mutate. */
|
|
15085
|
+
setRoleDisplayDefaults: require_sleep.method(zod.z.object({ defaults: zod.z.record(zod.z.string(), RoleDisplayDefaultSchema) }), zod.z.void(), {
|
|
15086
|
+
kind: "mutation",
|
|
15087
|
+
auth: "admin"
|
|
15088
|
+
}),
|
|
13695
15089
|
/** List the wireable status-schema fields per cap bound to a device.
|
|
13696
15090
|
* Powers the Wiring tab's field pickers. Caps without a status schema are
|
|
13697
15091
|
* omitted. Item-array caps (`status.itemArray`, e.g. consumables) also
|
|
@@ -15785,7 +17179,10 @@ var pipelineOrchestratorCapability = {
|
|
|
15785
17179
|
methods: {
|
|
15786
17180
|
/**
|
|
15787
17181
|
* Pin a camera's pipeline to a specific agent (L1 affinity).
|
|
15788
|
-
* The orchestrator re-evaluates the assignment immediately
|
|
17182
|
+
* The orchestrator re-evaluates the assignment immediately and persists
|
|
17183
|
+
* the pin under the canonical `pipelineNodeId` device-store key (the
|
|
17184
|
+
* legacy `preferredAgent` key is nulled on write and kept only as a
|
|
17185
|
+
* read-only fallback for stores written before the unification).
|
|
15789
17186
|
*/
|
|
15790
17187
|
assignPipeline: require_sleep.method(zod.z.object({
|
|
15791
17188
|
deviceId: zod.z.number(),
|
|
@@ -15796,8 +17193,9 @@ var pipelineOrchestratorCapability = {
|
|
|
15796
17193
|
}),
|
|
15797
17194
|
/**
|
|
15798
17195
|
* Clear a camera's pipeline pin and let the auto-balancer re-pick
|
|
15799
|
-
* the optimal agent. The orchestrator persists
|
|
15800
|
-
*
|
|
17196
|
+
* the optimal agent. The orchestrator persists the canonical
|
|
17197
|
+
* `pipelineNodeId='auto'` (and nulls the legacy `preferredAgent`),
|
|
17198
|
+
* then re-runs the balancer with the
|
|
15801
17199
|
* cached `RunnerCameraConfig` and migrates only when the chosen
|
|
15802
17200
|
* node differs. The camera stays in `getPipelineAssignments()` —
|
|
15803
17201
|
* just with `pinned=false`. If no runner is currently available
|
|
@@ -19415,7 +20813,10 @@ var HwAccelBackendInputSchema = zod.z.enum([
|
|
|
19415
20813
|
"webgpu",
|
|
19416
20814
|
"none"
|
|
19417
20815
|
]).nullable().optional();
|
|
19418
|
-
var HwAccelResolutionSchema = zod.z.object({
|
|
20816
|
+
var HwAccelResolutionSchema = zod.z.object({
|
|
20817
|
+
preferred: zod.z.array(zod.z.string()).readonly(),
|
|
20818
|
+
rationale: zod.z.string()
|
|
20819
|
+
});
|
|
19419
20820
|
var HardwareEncoderIdSchema = zod.z.enum([
|
|
19420
20821
|
"h264_videotoolbox",
|
|
19421
20822
|
"hevc_videotoolbox",
|
|
@@ -19529,10 +20930,7 @@ var platformProbeCapability = {
|
|
|
19529
20930
|
getCapabilities: require_sleep.method(zod.z.void(), PlatformCapabilitiesSchema),
|
|
19530
20931
|
getHardware: require_sleep.method(zod.z.void(), HardwareInfoSchema),
|
|
19531
20932
|
resolveInferenceConfig: require_sleep.method(zod.z.object({ requirements: zod.z.array(ModelRequirementSchema).readonly() }), ResolvedInferenceConfigSchema),
|
|
19532
|
-
resolveHwAccel: require_sleep.method(zod.z.object({
|
|
19533
|
-
prefer: HwAccelBackendInputSchema,
|
|
19534
|
-
nodeId: zod.z.string().optional()
|
|
19535
|
-
}), HwAccelResolutionSchema),
|
|
20933
|
+
resolveHwAccel: require_sleep.method(zod.z.object({ prefer: HwAccelBackendInputSchema }), HwAccelResolutionSchema),
|
|
19536
20934
|
/**
|
|
19537
20935
|
* Hardware-encoder probe — see Task #185. Cached after first call.
|
|
19538
20936
|
*/
|
|
@@ -20634,6 +22032,7 @@ var CAPABILITY_NAMES = {
|
|
|
20634
22032
|
numericSensor: "numeric-sensor",
|
|
20635
22033
|
oauthIntegration: "oauth-integration",
|
|
20636
22034
|
osd: "osd",
|
|
22035
|
+
petFeeder: "pet-feeder",
|
|
20637
22036
|
pipelineAnalytics: "pipeline-analytics",
|
|
20638
22037
|
pipelineExecutor: "pipeline-executor",
|
|
20639
22038
|
pipelineOrchestrator: "pipeline-orchestrator",
|
|
@@ -21034,6 +22433,10 @@ var CAPABILITY_ROUTER_KEYS = [
|
|
|
21034
22433
|
key: "osd",
|
|
21035
22434
|
name: "osd"
|
|
21036
22435
|
},
|
|
22436
|
+
{
|
|
22437
|
+
key: "petFeeder",
|
|
22438
|
+
name: "pet-feeder"
|
|
22439
|
+
},
|
|
21037
22440
|
{
|
|
21038
22441
|
key: "pipelineAnalytics",
|
|
21039
22442
|
name: "pipeline-analytics"
|
|
@@ -21328,6 +22731,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
|
|
|
21328
22731
|
numericSensorCapability,
|
|
21329
22732
|
oauthIntegrationCapability,
|
|
21330
22733
|
osdCapability,
|
|
22734
|
+
petFeederCapability,
|
|
21331
22735
|
pipelineAnalyticsCapability,
|
|
21332
22736
|
pipelineExecutorCapability,
|
|
21333
22737
|
pipelineOrchestratorCapability,
|
|
@@ -21430,6 +22834,7 @@ var CAP_NAMES_WITH_STATUS = [
|
|
|
21430
22834
|
"notifier",
|
|
21431
22835
|
"numeric-sensor",
|
|
21432
22836
|
"osd",
|
|
22837
|
+
"pet-feeder",
|
|
21433
22838
|
"power-meter",
|
|
21434
22839
|
"presence",
|
|
21435
22840
|
"pressure-sensor",
|
|
@@ -22636,6 +24041,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
22636
24041
|
addonId: null,
|
|
22637
24042
|
access: "view"
|
|
22638
24043
|
},
|
|
24044
|
+
"deviceManager.getRoleDisplayDefaults": {
|
|
24045
|
+
capName: "device-manager",
|
|
24046
|
+
capScope: "system",
|
|
24047
|
+
addonId: null,
|
|
24048
|
+
access: "view"
|
|
24049
|
+
},
|
|
22639
24050
|
"deviceManager.getSettingsSchema": {
|
|
22640
24051
|
capName: "device-manager",
|
|
22641
24052
|
capScope: "system",
|
|
@@ -22786,6 +24197,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
22786
24197
|
addonId: null,
|
|
22787
24198
|
access: "create"
|
|
22788
24199
|
},
|
|
24200
|
+
"deviceManager.setDisplay": {
|
|
24201
|
+
capName: "device-manager",
|
|
24202
|
+
capScope: "system",
|
|
24203
|
+
addonId: null,
|
|
24204
|
+
access: "create"
|
|
24205
|
+
},
|
|
22789
24206
|
"deviceManager.setIntegrationId": {
|
|
22790
24207
|
capName: "device-manager",
|
|
22791
24208
|
capScope: "system",
|
|
@@ -22828,6 +24245,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
22828
24245
|
addonId: null,
|
|
22829
24246
|
access: "create"
|
|
22830
24247
|
},
|
|
24248
|
+
"deviceManager.setRoleDisplayDefaults": {
|
|
24249
|
+
capName: "device-manager",
|
|
24250
|
+
capScope: "system",
|
|
24251
|
+
addonId: null,
|
|
24252
|
+
access: "create"
|
|
24253
|
+
},
|
|
22831
24254
|
"deviceManager.setStreamProfileMap": {
|
|
22832
24255
|
capName: "device-manager",
|
|
22833
24256
|
capScope: "system",
|
|
@@ -23878,6 +25301,66 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
23878
25301
|
addonId: null,
|
|
23879
25302
|
access: "create"
|
|
23880
25303
|
},
|
|
25304
|
+
"petFeeder.callPet": {
|
|
25305
|
+
capName: "pet-feeder",
|
|
25306
|
+
capScope: "device",
|
|
25307
|
+
addonId: null,
|
|
25308
|
+
access: "create"
|
|
25309
|
+
},
|
|
25310
|
+
"petFeeder.cancelFeed": {
|
|
25311
|
+
capName: "pet-feeder",
|
|
25312
|
+
capScope: "device",
|
|
25313
|
+
addonId: null,
|
|
25314
|
+
access: "create"
|
|
25315
|
+
},
|
|
25316
|
+
"petFeeder.feed": {
|
|
25317
|
+
capName: "pet-feeder",
|
|
25318
|
+
capScope: "device",
|
|
25319
|
+
addonId: null,
|
|
25320
|
+
access: "create"
|
|
25321
|
+
},
|
|
25322
|
+
"petFeeder.markFoodReplenished": {
|
|
25323
|
+
capName: "pet-feeder",
|
|
25324
|
+
capScope: "device",
|
|
25325
|
+
addonId: null,
|
|
25326
|
+
access: "create"
|
|
25327
|
+
},
|
|
25328
|
+
"petFeeder.playSound": {
|
|
25329
|
+
capName: "pet-feeder",
|
|
25330
|
+
capScope: "device",
|
|
25331
|
+
addonId: null,
|
|
25332
|
+
access: "create"
|
|
25333
|
+
},
|
|
25334
|
+
"petFeeder.resetDesiccant": {
|
|
25335
|
+
capName: "pet-feeder",
|
|
25336
|
+
capScope: "device",
|
|
25337
|
+
addonId: null,
|
|
25338
|
+
access: "delete"
|
|
25339
|
+
},
|
|
25340
|
+
"petFeeder.setChildLock": {
|
|
25341
|
+
capName: "pet-feeder",
|
|
25342
|
+
capScope: "device",
|
|
25343
|
+
addonId: null,
|
|
25344
|
+
access: "create"
|
|
25345
|
+
},
|
|
25346
|
+
"petFeeder.setFeedSound": {
|
|
25347
|
+
capName: "pet-feeder",
|
|
25348
|
+
capScope: "device",
|
|
25349
|
+
addonId: null,
|
|
25350
|
+
access: "create"
|
|
25351
|
+
},
|
|
25352
|
+
"petFeeder.setIndicatorLight": {
|
|
25353
|
+
capName: "pet-feeder",
|
|
25354
|
+
capScope: "device",
|
|
25355
|
+
addonId: null,
|
|
25356
|
+
access: "create"
|
|
25357
|
+
},
|
|
25358
|
+
"petFeeder.setVolume": {
|
|
25359
|
+
capName: "pet-feeder",
|
|
25360
|
+
capScope: "device",
|
|
25361
|
+
addonId: null,
|
|
25362
|
+
access: "create"
|
|
25363
|
+
},
|
|
23881
25364
|
"pipelineAnalytics.clearTracks": {
|
|
23882
25365
|
capName: "pipeline-analytics",
|
|
23883
25366
|
capScope: "device",
|
|
@@ -25870,6 +27353,7 @@ var KNOWN_CAP_NAMES = [
|
|
|
25870
27353
|
"notifier",
|
|
25871
27354
|
"oauth-integration",
|
|
25872
27355
|
"osd",
|
|
27356
|
+
"pet-feeder",
|
|
25873
27357
|
"pipeline-analytics",
|
|
25874
27358
|
"pipeline-executor",
|
|
25875
27359
|
"pipeline-orchestrator",
|
|
@@ -25945,6 +27429,7 @@ var DEVICE_CAP_NAMES = [
|
|
|
25945
27429
|
"native-object-detection",
|
|
25946
27430
|
"notifier",
|
|
25947
27431
|
"osd",
|
|
27432
|
+
"pet-feeder",
|
|
25948
27433
|
"pipeline-analytics",
|
|
25949
27434
|
"privacy-mask",
|
|
25950
27435
|
"ptz",
|
|
@@ -26781,6 +28266,11 @@ exports.DisposerChain = require_sleep.DisposerChain;
|
|
|
26781
28266
|
exports.DoorbellPressEventSchema = DoorbellPressEventSchema;
|
|
26782
28267
|
exports.DoorbellStatusSchema = DoorbellStatusSchema;
|
|
26783
28268
|
exports.EVENT_PAD_MS = EVENT_PAD_MS;
|
|
28269
|
+
exports.EXPRESSION_BUILTINS = EXPRESSION_BUILTINS;
|
|
28270
|
+
exports.EXPRESSION_BUILTIN_NAMES = EXPRESSION_BUILTIN_NAMES;
|
|
28271
|
+
exports.EXPRESSION_COMPILE_CACHE_CAPACITY = EXPRESSION_COMPILE_CACHE_CAPACITY;
|
|
28272
|
+
exports.EXPRESSION_IDENTIFIER_RE = EXPRESSION_IDENTIFIER_RE;
|
|
28273
|
+
exports.EXPRESSION_INJECTED_NOW = EXPRESSION_INJECTED_NOW;
|
|
26784
28274
|
exports.ElementConfigStore = ElementConfigStore;
|
|
26785
28275
|
exports.EmbeddingInfoSchema = EmbeddingInfoSchema;
|
|
26786
28276
|
exports.EmbeddingResultSchema = EmbeddingResultSchema;
|
|
@@ -26799,6 +28289,8 @@ exports.ExportSetupFieldSchema = ExportSetupFieldSchema;
|
|
|
26799
28289
|
exports.ExportSetupSchema = ExportSetupSchema;
|
|
26800
28290
|
exports.ExposedDeviceSchema = ExposedDeviceSchema;
|
|
26801
28291
|
exports.ExposedResourceSchema = ExposedResourceSchema;
|
|
28292
|
+
exports.ExpressionEvalError = ExpressionEvalError;
|
|
28293
|
+
exports.ExpressionParseError = ExpressionParseError;
|
|
26802
28294
|
exports.FanControlStatusSchema = FanControlStatusSchema;
|
|
26803
28295
|
exports.FanDirectionSchema = FanDirectionSchema;
|
|
26804
28296
|
exports.FeatureManifestSchema = FeatureManifestSchema;
|
|
@@ -26837,6 +28329,11 @@ exports.LogEntrySchema = LogEntrySchema;
|
|
|
26837
28329
|
exports.LogLevelSchema = LogLevelSchema;
|
|
26838
28330
|
exports.LogStreamEntrySchema = LogStreamEntrySchema;
|
|
26839
28331
|
exports.MACRO_LABELS = MACRO_LABELS;
|
|
28332
|
+
exports.MAX_EXPRESSION_AST_NODES = MAX_EXPRESSION_AST_NODES;
|
|
28333
|
+
exports.MAX_EXPRESSION_BINDINGS = MAX_EXPRESSION_BINDINGS;
|
|
28334
|
+
exports.MAX_EXPRESSION_CALL_ARGS = MAX_EXPRESSION_CALL_ARGS;
|
|
28335
|
+
exports.MAX_EXPRESSION_EVAL_STEPS = MAX_EXPRESSION_EVAL_STEPS;
|
|
28336
|
+
exports.MAX_EXPRESSION_SOURCE_LENGTH = MAX_EXPRESSION_SOURCE_LENGTH;
|
|
26840
28337
|
exports.METHOD_ACCESS_MAP = METHOD_ACCESS_MAP;
|
|
26841
28338
|
exports.MODEL_FORMATS = MODEL_FORMATS;
|
|
26842
28339
|
exports.MaskGridDimsSchema = MaskGridDimsSchema;
|
|
@@ -26899,6 +28396,8 @@ exports.OsdOverlayPatchSchema = OsdOverlayPatchSchema;
|
|
|
26899
28396
|
exports.OsdOverlaySchema = OsdOverlaySchema;
|
|
26900
28397
|
exports.OsdPositionEnum = OsdPositionEnum;
|
|
26901
28398
|
exports.OsdStatusSchema = OsdStatusSchema;
|
|
28399
|
+
exports.PET_FEEDER_MANUAL_FEED_MAX = PET_FEEDER_MANUAL_FEED_MAX;
|
|
28400
|
+
exports.PET_FEEDER_MANUAL_FEED_MIN = PET_FEEDER_MANUAL_FEED_MIN;
|
|
26902
28401
|
exports.PIPELINE_FLOW_CAPABILITY_NAMES = PIPELINE_FLOW_CAPABILITY_NAMES;
|
|
26903
28402
|
exports.PIPELINE_OWNER_CAPABILITY_NAMES = PIPELINE_OWNER_CAPABILITY_NAMES;
|
|
26904
28403
|
exports.PROVIDER_KIND_CAP_NAMES = PROVIDER_KIND_CAP_NAMES;
|
|
@@ -26908,6 +28407,7 @@ exports.PackageVersionInfoSchema = PackageVersionInfoSchema;
|
|
|
26908
28407
|
exports.PasskeySummarySchema = PasskeySummarySchema;
|
|
26909
28408
|
exports.PcmSampleFormatSchema = PcmSampleFormatSchema;
|
|
26910
28409
|
exports.PerScopeBreakdownSchema = PerScopeBreakdownSchema;
|
|
28410
|
+
exports.PetFeederStatusSchema = PetFeederStatusSchema;
|
|
26911
28411
|
exports.PickStreamPreferencesSchema = PickStreamPreferencesSchema;
|
|
26912
28412
|
exports.PickStreamRequirementsSchema = PickStreamRequirementsSchema;
|
|
26913
28413
|
exports.PickedCamStreamSchema = PickedCamStreamSchema;
|
|
@@ -26940,6 +28440,7 @@ exports.PtzPresetSchema = PtzPresetSchema;
|
|
|
26940
28440
|
exports.PtzStatusSchema = PtzStatusSchema;
|
|
26941
28441
|
exports.QueryFilterSchema = QueryFilterSchema;
|
|
26942
28442
|
exports.RECOGNITION_TYPES = RECOGNITION_TYPES;
|
|
28443
|
+
exports.RESERVED_BINDING_NAMES = RESERVED_BINDING_NAMES;
|
|
26943
28444
|
exports.RUNTIME_DEFAULTS = RUNTIME_DEFAULTS;
|
|
26944
28445
|
exports.RUNTIME_TO_FORMAT = RUNTIME_TO_FORMAT;
|
|
26945
28446
|
exports.RawStateResultSchema = require_sleep.RawStateResultSchema;
|
|
@@ -27055,7 +28556,9 @@ exports.TrackSchema = TrackSchema;
|
|
|
27055
28556
|
exports.TrackStateSchema = TrackStateSchema;
|
|
27056
28557
|
exports.TrackedDetectionSchema = TrackedDetectionSchema;
|
|
27057
28558
|
exports.TurnServerSchema = TurnServerSchema;
|
|
28559
|
+
exports.UNIT_TABLE = UNIT_TABLE;
|
|
27058
28560
|
exports.UnifiedBrokerInfoSchema = BrokerInfoSchema$1;
|
|
28561
|
+
exports.UnitConversionError = UnitConversionError;
|
|
27059
28562
|
exports.UpdateIntegrationInputSchema = UpdateIntegrationInputSchema;
|
|
27060
28563
|
exports.UpdateStatusSchema = UpdateStatusSchema;
|
|
27061
28564
|
exports.UpdateUserInputSchema = UpdateUserInputSchema;
|
|
@@ -27126,21 +28629,26 @@ exports.buttonCapability = buttonCapability;
|
|
|
27126
28629
|
exports.cameraCredentialsCapability = cameraCredentialsCapability;
|
|
27127
28630
|
exports.cameraPipelineConfigCapability = cameraPipelineConfigCapability;
|
|
27128
28631
|
exports.cameraStreamsCapability = cameraStreamsCapability;
|
|
28632
|
+
exports.canConvertUnit = canConvertUnit;
|
|
27129
28633
|
exports.carbonMonoxideCapability = carbonMonoxideCapability;
|
|
27130
28634
|
exports.cellsToRects = cellsToRects;
|
|
27131
28635
|
exports.classifyStream = classifyStream;
|
|
27132
28636
|
exports.classifyStreams = classifyStreams;
|
|
27133
28637
|
exports.climateControlCapability = climateControlCapability;
|
|
27134
28638
|
exports.colorCapability = colorCapability;
|
|
28639
|
+
exports.compileExpression = compileExpression;
|
|
28640
|
+
exports.compileExpressionSafe = compileExpressionSafe;
|
|
27135
28641
|
exports.connectivityCapability = connectivityCapability;
|
|
27136
28642
|
exports.consumablesCapability = consumablesCapability;
|
|
27137
28643
|
exports.contactCapability = contactCapability;
|
|
27138
28644
|
exports.controlCapability = controlCapability;
|
|
28645
|
+
exports.convertUnit = convertUnit;
|
|
27139
28646
|
exports.cosineSimilarity = cosineSimilarity;
|
|
27140
28647
|
exports.coverCapability = coverCapability;
|
|
27141
28648
|
exports.createDeviceProxy = require_sleep.createDeviceProxy;
|
|
27142
28649
|
exports.createDurableState = require_sleep.createDurableState;
|
|
27143
28650
|
exports.createEvent = require_sleep.createEvent;
|
|
28651
|
+
exports.createExpressionScope = createExpressionScope;
|
|
27144
28652
|
exports.createLazyTrpcSource = require_sleep.createLazyTrpcSource;
|
|
27145
28653
|
exports.createMirrorSource = require_sleep.createMirrorSource;
|
|
27146
28654
|
exports.createRuntimeStateBridge = createRuntimeStateBridge;
|
|
@@ -27171,6 +28679,8 @@ exports.enumSensorCapability = enumSensorCapability;
|
|
|
27171
28679
|
exports.enumerateItemArrayFields = enumerateItemArrayFields;
|
|
27172
28680
|
exports.enumerateSchemaFields = enumerateSchemaFields;
|
|
27173
28681
|
exports.errMsg = require_err_msg.errMsg;
|
|
28682
|
+
exports.evaluateAst = evaluateAst;
|
|
28683
|
+
exports.evaluateLinkExpression = evaluateLinkExpression;
|
|
27174
28684
|
exports.evaluateZoneRules = evaluateZoneRules;
|
|
27175
28685
|
exports.event = require_sleep.event;
|
|
27176
28686
|
exports.eventEmitterCapability = eventEmitterCapability;
|
|
@@ -27246,12 +28756,14 @@ exports.numericSensorCapability = numericSensorCapability;
|
|
|
27246
28756
|
exports.oauthIntegrationCapability = oauthIntegrationCapability;
|
|
27247
28757
|
exports.osdCapability = osdCapability;
|
|
27248
28758
|
exports.parseCameraStreamConfig = parseCameraStreamConfig;
|
|
28759
|
+
exports.parseExpression = parseExpression;
|
|
27249
28760
|
exports.parseJsonArray = require_sleep.parseJsonArray;
|
|
27250
28761
|
exports.parseJsonObject = require_sleep.parseJsonObject;
|
|
27251
28762
|
exports.parseJsonUnknown = require_sleep.parseJsonUnknown;
|
|
27252
28763
|
exports.parseProfileBrokerId = require_sleep.parseProfileBrokerId;
|
|
27253
28764
|
exports.parseStreamParamsFormPatch = parseStreamParamsFormPatch;
|
|
27254
28765
|
exports.pendingFrameworkSwapSchema = pendingFrameworkSwapSchema;
|
|
28766
|
+
exports.petFeederCapability = petFeederCapability;
|
|
27255
28767
|
exports.pickPreferredRtspEntry = pickPreferredRtspEntry;
|
|
27256
28768
|
exports.pipelineAnalyticsCapability = pipelineAnalyticsCapability;
|
|
27257
28769
|
exports.pipelineExecutorCapability = pipelineExecutorCapability;
|
|
@@ -27319,14 +28831,20 @@ exports.taskTargetSchema = taskTargetSchema;
|
|
|
27319
28831
|
exports.temperatureSensorCapability = temperatureSensorCapability;
|
|
27320
28832
|
exports.textToHtml = textToHtml;
|
|
27321
28833
|
exports.toDeviceSummary = toDeviceSummary;
|
|
28834
|
+
exports.toExpressionValue = toExpressionValue;
|
|
27322
28835
|
exports.toStreamSourceEntry = toStreamSourceEntry;
|
|
27323
28836
|
exports.toastCapability = toastCapability;
|
|
28837
|
+
exports.tokenize = tokenize;
|
|
27324
28838
|
exports.transcodeBody = transcodeBody;
|
|
28839
|
+
exports.tryConvertUnit = tryConvertUnit;
|
|
27325
28840
|
exports.turnProviderCapability = turnProviderCapability;
|
|
28841
|
+
exports.unitDimension = unitDimension;
|
|
28842
|
+
exports.unitsForDimension = unitsForDimension;
|
|
27326
28843
|
exports.updateCapability = updateCapability;
|
|
27327
28844
|
exports.userManagementCapability = userManagementCapability;
|
|
27328
28845
|
exports.userPasskeysCapability = userPasskeysCapability;
|
|
27329
28846
|
exports.vacuumControlCapability = vacuumControlCapability;
|
|
28847
|
+
exports.validateExpressionSource = validateExpressionSource;
|
|
27330
28848
|
exports.valveCapability = valveCapability;
|
|
27331
28849
|
exports.vibrationCapability = vibrationCapability;
|
|
27332
28850
|
exports.videoclipsCapability = videoclipsCapability;
|