@camstack/types 1.1.19 → 1.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon.js +1 -1
- package/dist/addon.mjs +1 -1
- package/dist/capabilities/battery.cap.d.ts +6 -0
- package/dist/capabilities/capability-definition.d.ts +39 -0
- package/dist/capabilities/consumables.cap.d.ts +35 -0
- package/dist/capabilities/device-manager.cap.d.ts +396 -15
- package/dist/capabilities/index.d.ts +1 -1
- package/dist/capabilities/pipeline-orchestrator.cap.d.ts +7 -3
- package/dist/device/device-binding.d.ts +9 -3
- package/dist/device/device-management.d.ts +87 -6
- package/dist/device/index.d.ts +2 -2
- package/dist/device/schema-fields.d.ts +13 -0
- 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 +452 -4
- package/dist/generated/device-proxy.d.ts +1 -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 +5 -2
- package/dist/index.js +1342 -20
- package/dist/index.mjs +1313 -21
- package/dist/interfaces/agent.d.ts +21 -0
- package/dist/{sleep-MHm--th-.mjs → sleep-BO1nweKv.mjs} +1 -0
- package/dist/{sleep-BabrCASa.js → sleep-DaQgDq90.js} +1 -0
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
|
-
import type { DeviceType } from './device-type.js';
|
|
3
2
|
import type { IDevice } from './device.js';
|
|
3
|
+
import type { DeviceType } from './device-type.js';
|
|
4
4
|
import type { SourceInfo } from './source-info.js';
|
|
5
5
|
export interface DeviceManualCreation {
|
|
6
6
|
getChildCreationSchema(type: DeviceType): z.ZodObject<z.core.$ZodLooseShape>;
|
|
@@ -95,17 +95,54 @@ export interface ChildLayoutEntry {
|
|
|
95
95
|
readonly collapsed?: boolean;
|
|
96
96
|
}
|
|
97
97
|
export type ChildLayout = readonly ChildLayoutEntry[];
|
|
98
|
-
/**
|
|
99
|
-
*
|
|
98
|
+
/** Field source: copy one field of a sibling accessory's cap status.
|
|
99
|
+
* Addressed by the source's re-sync-stable accessory `stableIdSuffix`
|
|
100
100
|
* (`sourceKey`) — NOT a raw numeric id — so the link survives a re-sync. The
|
|
101
101
|
* source must be a sibling accessory under the SAME parent container as the
|
|
102
|
-
* target device; resolution is `${parentStableId}-${sourceKey}
|
|
103
|
-
*
|
|
104
|
-
export interface
|
|
102
|
+
* target device; resolution is `${parentStableId}-${sourceKey}`. For a source
|
|
103
|
+
* anywhere else in the cluster use `DeviceLinkGlobalSource`. */
|
|
104
|
+
export interface DeviceLinkFieldSource {
|
|
105
|
+
readonly kind?: 'field';
|
|
105
106
|
readonly sourceKey: string;
|
|
106
107
|
readonly cap: string;
|
|
107
108
|
readonly fieldPath: string;
|
|
108
109
|
}
|
|
110
|
+
/** Literal source: a per-device constant (e.g. `battery.binary = true`,
|
|
111
|
+
* a consumable item's `label`/`resettable`). No sibling is read. */
|
|
112
|
+
export interface DeviceLinkLiteralSource {
|
|
113
|
+
readonly kind: 'literal';
|
|
114
|
+
readonly value: string | number | boolean | null;
|
|
115
|
+
}
|
|
116
|
+
/** Global source (P2e): copy one field of ANY device's cap status, regardless
|
|
117
|
+
* of parent container. Addressed by the source device's FULL `stableId` —
|
|
118
|
+
* chosen over the numeric id because a re-sync (`resetToSource`) REALLOCATES
|
|
119
|
+
* numeric ids while stableIds are deterministic from the provider (the same
|
|
120
|
+
* property the sibling `sourceKey` mechanism relies on). stableId uniqueness
|
|
121
|
+
* is formally per-addon; the resolver matches the first meta row with that
|
|
122
|
+
* stableId (effective global uniqueness — an `addonId` disambiguator can be
|
|
123
|
+
* added later without a wire break). */
|
|
124
|
+
export interface DeviceLinkGlobalSource {
|
|
125
|
+
readonly kind: 'global';
|
|
126
|
+
readonly sourceStableId: string;
|
|
127
|
+
readonly cap: string;
|
|
128
|
+
readonly fieldPath: string;
|
|
129
|
+
}
|
|
130
|
+
/** The source kinds a single expression BINDING may use — a sibling FIELD, a
|
|
131
|
+
* per-device LITERAL, or a GLOBAL device field. Never another expression:
|
|
132
|
+
* bindings do not nest, so an expression cannot reference another expression. */
|
|
133
|
+
export type DeviceLinkExpressionBinding = DeviceLinkFieldSource | DeviceLinkLiteralSource | DeviceLinkGlobalSource;
|
|
134
|
+
/** Expression source (Stage X): compute the target field from N named source
|
|
135
|
+
* bindings via a safe, non-Turing-complete infix expression (see
|
|
136
|
+
* `packages/types/src/expression/`). Each binding resolves to an
|
|
137
|
+
* `ExpressionValue` and is exposed to the expression under its record key.
|
|
138
|
+
* `now` (epoch ms) is auto-injected and is a reserved binding name. The
|
|
139
|
+
* evaluated result flows through `transform` exactly like a scalar source. */
|
|
140
|
+
export interface DeviceLinkExpressionSource {
|
|
141
|
+
readonly kind: 'expression';
|
|
142
|
+
readonly expr: string;
|
|
143
|
+
readonly bindings: Readonly<Record<string, DeviceLinkExpressionBinding>>;
|
|
144
|
+
}
|
|
145
|
+
export type DeviceLinkSource = DeviceLinkFieldSource | DeviceLinkLiteralSource | DeviceLinkGlobalSource | DeviceLinkExpressionSource;
|
|
109
146
|
/** The target field a link writes: a dot-path into the target cap's status. */
|
|
110
147
|
export interface DeviceLinkTarget {
|
|
111
148
|
readonly cap: string;
|
|
@@ -134,6 +171,42 @@ export interface DeviceLink {
|
|
|
134
171
|
readonly transform?: DeviceLinkTransform;
|
|
135
172
|
}
|
|
136
173
|
export type DeviceLinks = readonly DeviceLink[];
|
|
174
|
+
/** Per-cap display refinement inside a `DeviceDisplayOverride` — unit/precision
|
|
175
|
+
* only (icon/label/hidden are device-level). Keyed by cap name for devices
|
|
176
|
+
* carrying several numeric caps (e.g. power-meter). */
|
|
177
|
+
export interface DeviceCapDisplayOverride {
|
|
178
|
+
readonly unit?: string;
|
|
179
|
+
readonly precision?: number;
|
|
180
|
+
}
|
|
181
|
+
/** Operator-authored per-device display override. Rides the `DeviceMeta`
|
|
182
|
+
* lifecycle (persist/preserve/project/pre-seed) — deliberately NOT a new
|
|
183
|
+
* cap/store. All fields optional; an empty object is equivalent to absent.
|
|
184
|
+
* `unit` triggers render-time conversion when same-dimension as the live
|
|
185
|
+
* slice unit (storage stays in source units); `precision` applies AFTER
|
|
186
|
+
* conversion; `hidden` removes the device from list surfaces (distinct from
|
|
187
|
+
* the HA-only accessories-cap `hiddenChildIds` mechanism). */
|
|
188
|
+
export interface DeviceDisplayOverride {
|
|
189
|
+
/** Key into ui-library's display icon registry. */
|
|
190
|
+
readonly icon?: string;
|
|
191
|
+
/** Overrides the `DEVICE_ROLE_META` label (NOT the device name). */
|
|
192
|
+
readonly label?: string;
|
|
193
|
+
/** Canonical spelling (`normalizeUnit` applied at write). */
|
|
194
|
+
readonly unit?: string;
|
|
195
|
+
/** Int 0-10, same contract as the slice `precision` field. */
|
|
196
|
+
readonly precision?: number;
|
|
197
|
+
readonly hidden?: boolean;
|
|
198
|
+
readonly perCap?: Readonly<Record<string, DeviceCapDisplayOverride>>;
|
|
199
|
+
}
|
|
200
|
+
/** Operator-authored per-role display default (unit/precision/icon), keyed by
|
|
201
|
+
* `DeviceRole` string. Resolution merges these UNDER any per-device
|
|
202
|
+
* `DeviceDisplayOverride`. Stored whole-record (full replace) in the
|
|
203
|
+
* device-manager addon store's `roleDisplayDefaults` key — NOT on any device's
|
|
204
|
+
* meta row. */
|
|
205
|
+
export interface RoleDisplayDefault {
|
|
206
|
+
readonly unit?: string;
|
|
207
|
+
readonly precision?: number;
|
|
208
|
+
readonly icon?: string;
|
|
209
|
+
}
|
|
137
210
|
export interface DeviceMeta {
|
|
138
211
|
readonly id: number;
|
|
139
212
|
readonly stableId: string;
|
|
@@ -185,6 +258,12 @@ export interface DeviceMeta {
|
|
|
185
258
|
* cap field). Same create/persist/project/restore lifecycle as `childLayout`.
|
|
186
259
|
* Absent ⇒ no links. Overlaid onto the target cap's `getStatus` at read time. */
|
|
187
260
|
readonly deviceLinks?: DeviceLinks;
|
|
261
|
+
/** Operator-authored per-device display override (icon/label/unit/precision/
|
|
262
|
+
* hidden). Same create/persist/project/restore lifecycle as `deviceLinks`.
|
|
263
|
+
* Absent ⇒ no override; the renderer falls back to role default → live slice
|
|
264
|
+
* → canonical unit. Applied at RENDER time only — storage stays in source
|
|
265
|
+
* units. */
|
|
266
|
+
readonly display?: DeviceDisplayOverride;
|
|
188
267
|
}
|
|
189
268
|
/**
|
|
190
269
|
* Initial meta a provider supplies on `onCreateDevice`. Differs
|
|
@@ -215,6 +294,8 @@ export interface InitialDeviceMeta {
|
|
|
215
294
|
readonly childLayout?: ChildLayout;
|
|
216
295
|
/** Cross-device field wirings set at create. Mirrors `DeviceMeta.deviceLinks`. */
|
|
217
296
|
readonly deviceLinks?: DeviceLinks;
|
|
297
|
+
/** Per-device display override set at create. Mirrors `DeviceMeta.display`. */
|
|
298
|
+
readonly display?: DeviceDisplayOverride;
|
|
218
299
|
}
|
|
219
300
|
/**
|
|
220
301
|
* Single-call payload returned by `BaseDeviceProvider.onCreateDevice`.
|
package/dist/device/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type { IDevice } from './device.js';
|
|
|
11
11
|
export { DEVICE_PROFILES, BATTERY_DEVICE_PROFILE, deviceMatchesProfile, resolveDeviceProfile, } from './device-profile.js';
|
|
12
12
|
export type { DeviceProfile, DeviceProfileMatch, DeviceProfileDefaults, PipelinePhaseMode, } from './device-profile.js';
|
|
13
13
|
export type { ICameraDevice, StreamSourceEntry } from './camera-device.js';
|
|
14
|
-
export type { DeviceManualCreation, DeviceDiscovery, DiscoveredDevice, SavedDevice, DeviceMeta, InitialDeviceMeta, CreateDeviceSpec, ChildLayout, ChildLayoutEntry, DeviceLinkSource, DeviceLinkTarget, DeviceLinkTransform, DeviceLink, DeviceLinks, } from './device-management.js';
|
|
14
|
+
export type { DeviceManualCreation, DeviceDiscovery, DiscoveredDevice, SavedDevice, DeviceMeta, InitialDeviceMeta, CreateDeviceSpec, ChildLayout, ChildLayoutEntry, DeviceLinkFieldSource, DeviceLinkLiteralSource, DeviceLinkGlobalSource, DeviceLinkExpressionBinding, DeviceLinkExpressionSource, DeviceLinkSource, DeviceLinkTarget, DeviceLinkTransform, DeviceLink, DeviceLinks, } from './device-management.js';
|
|
15
15
|
export { zodEntriesToConfigUI } from './zod-to-config-ui.js';
|
|
16
16
|
export type { DeviceConfigEntry } from './zod-to-config-ui.js';
|
|
17
17
|
export { createRuntimeStateBridge } from './runtime-state-helpers.js';
|
|
@@ -19,5 +19,5 @@ export type { RuntimeStateBridge } from './runtime-state-helpers.js';
|
|
|
19
19
|
export type { IDeviceRuntimeState, Snapshot as RuntimeStateSnapshot, } from './device-runtime-state.js';
|
|
20
20
|
export { getByPath, setByPath } from './path-util.js';
|
|
21
21
|
export { applyTransform } from './device-link-transform.js';
|
|
22
|
-
export { enumerateSchemaFields } from './schema-fields.js';
|
|
22
|
+
export { enumerateItemArrayFields, enumerateSchemaFields } from './schema-fields.js';
|
|
23
23
|
export type { WireableField } from './schema-fields.js';
|
|
@@ -3,9 +3,22 @@ export interface WireableField {
|
|
|
3
3
|
readonly path: string;
|
|
4
4
|
readonly kind: 'string' | 'number' | 'boolean' | 'enum';
|
|
5
5
|
readonly enumValues?: readonly string[];
|
|
6
|
+
/** True when `path` is relative to ONE item of an item-array cap (see
|
|
7
|
+
* `CapabilityStatusItemArray`) — a link targeting it must carry a
|
|
8
|
+
* `target.itemKey`. Absent/false for plain status fields. */
|
|
9
|
+
readonly item?: boolean;
|
|
6
10
|
}
|
|
7
11
|
/** Walk a cap status schema into flat wireable leaf fields (dotted paths).
|
|
8
12
|
* Recurses into nested ZodObject (unwrapping nullable/optional/default first),
|
|
9
13
|
* so fields with null live values are still offered. Arrays / unknown shapes
|
|
10
14
|
* are skipped — never throws. */
|
|
11
15
|
export declare function enumerateSchemaFields(schema: z.ZodType, prefix?: string): readonly WireableField[];
|
|
16
|
+
/** Enumerate the per-item wireable fields of an item-array cap (see
|
|
17
|
+
* `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
|
|
18
|
+
* `keyField` (the key comes from the link's `itemKey`, never from a wired
|
|
19
|
+
* source), each tagged `item: true` so the authoring UI collects an
|
|
20
|
+
* `itemKey` alongside the field. Never throws. */
|
|
21
|
+
export declare function enumerateItemArrayFields(itemArray: {
|
|
22
|
+
readonly keyField: string;
|
|
23
|
+
readonly itemSchema: z.ZodType;
|
|
24
|
+
}): readonly WireableField[];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST for the safe expression mini-language.
|
|
3
|
+
*
|
|
4
|
+
* SECURITY BY CONSTRUCTION (spec §4 rule 3): there is deliberately NO member
|
|
5
|
+
* node, NO index node, NO assignment node and NO lambda/function node in this
|
|
6
|
+
* union. `a.b`, `a["b"]`, `a = b` and `() => …` are therefore UNREPRESENTABLE —
|
|
7
|
+
* `__proto__` / `constructor` / `prototype` traversal is impossible at the type
|
|
8
|
+
* level, not by a runtime denylist. `ExpressionCallNode.callee` is a plain
|
|
9
|
+
* string (a builtin name), never a node, so a computed call like `(min)(1)` is
|
|
10
|
+
* unparseable.
|
|
11
|
+
*
|
|
12
|
+
* The value domain is the four JSON primitives — no objects, arrays or
|
|
13
|
+
* functions ever flow through the evaluator.
|
|
14
|
+
*/
|
|
15
|
+
/** The only value types the engine produces or consumes. */
|
|
16
|
+
export type ExpressionValue = number | string | boolean | null;
|
|
17
|
+
export type ExpressionBinaryOperator = '+' | '-' | '*' | '/' | '%' | '==' | '!=' | '<' | '<=' | '>' | '>=';
|
|
18
|
+
export type ExpressionLogicalOperator = '&&' | '||';
|
|
19
|
+
export type ExpressionUnaryOperator = '-' | '!';
|
|
20
|
+
export interface ExpressionLiteralNode {
|
|
21
|
+
readonly kind: 'literal';
|
|
22
|
+
readonly value: ExpressionValue;
|
|
23
|
+
}
|
|
24
|
+
export interface ExpressionIdentifierNode {
|
|
25
|
+
readonly kind: 'identifier';
|
|
26
|
+
readonly name: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ExpressionUnaryNode {
|
|
29
|
+
readonly kind: 'unary';
|
|
30
|
+
readonly op: ExpressionUnaryOperator;
|
|
31
|
+
readonly operand: ExpressionNode;
|
|
32
|
+
}
|
|
33
|
+
export interface ExpressionBinaryNode {
|
|
34
|
+
readonly kind: 'binary';
|
|
35
|
+
readonly op: ExpressionBinaryOperator;
|
|
36
|
+
readonly left: ExpressionNode;
|
|
37
|
+
readonly right: ExpressionNode;
|
|
38
|
+
}
|
|
39
|
+
export interface ExpressionLogicalNode {
|
|
40
|
+
readonly kind: 'logical';
|
|
41
|
+
readonly op: ExpressionLogicalOperator;
|
|
42
|
+
readonly left: ExpressionNode;
|
|
43
|
+
readonly right: ExpressionNode;
|
|
44
|
+
}
|
|
45
|
+
export interface ExpressionConditionalNode {
|
|
46
|
+
readonly kind: 'conditional';
|
|
47
|
+
readonly test: ExpressionNode;
|
|
48
|
+
readonly consequent: ExpressionNode;
|
|
49
|
+
readonly alternate: ExpressionNode;
|
|
50
|
+
}
|
|
51
|
+
export interface ExpressionCallNode {
|
|
52
|
+
readonly kind: 'call';
|
|
53
|
+
readonly callee: string;
|
|
54
|
+
readonly args: readonly ExpressionNode[];
|
|
55
|
+
}
|
|
56
|
+
export type ExpressionNode = ExpressionLiteralNode | ExpressionIdentifierNode | ExpressionUnaryNode | ExpressionBinaryNode | ExpressionLogicalNode | ExpressionConditionalNode | ExpressionCallNode;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ExpressionValue } from './ast.js';
|
|
2
|
+
/** Optional runtime hooks the evaluator threads through to builtins. Stage U
|
|
3
|
+
* installs the real unit-conversion table here; absent → the `convert` stub. */
|
|
4
|
+
export interface ExpressionEvalHooks {
|
|
5
|
+
/** Convert `value` from unit `from` to unit `to`; `null` when the pair is
|
|
6
|
+
* not convertible. Absent hook → `convert` uses its identity-only stub. */
|
|
7
|
+
readonly convert?: (value: number, from: string, to: string) => number | null;
|
|
8
|
+
}
|
|
9
|
+
export interface ExpressionBuiltin {
|
|
10
|
+
readonly minArgs: number;
|
|
11
|
+
/** `Number.POSITIVE_INFINITY` for variadic builtins. */
|
|
12
|
+
readonly maxArgs: number;
|
|
13
|
+
readonly apply: (args: readonly ExpressionValue[], hooks: ExpressionEvalHooks) => ExpressionValue;
|
|
14
|
+
}
|
|
15
|
+
/** Frozen, null-prototype builtin table. */
|
|
16
|
+
export declare const EXPRESSION_BUILTINS: Readonly<Record<string, ExpressionBuiltin>>;
|
|
17
|
+
/** The set of valid builtin names — used by the parser to reject unknown
|
|
18
|
+
* callees at parse time (immediate author feedback). */
|
|
19
|
+
export declare const EXPRESSION_BUILTIN_NAMES: ReadonlySet<string>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ParsedExpression } from './parser.js';
|
|
2
|
+
export type CompileResult = {
|
|
3
|
+
readonly ok: true;
|
|
4
|
+
readonly parsed: ParsedExpression;
|
|
5
|
+
} | {
|
|
6
|
+
readonly ok: false;
|
|
7
|
+
readonly error: string;
|
|
8
|
+
};
|
|
9
|
+
/** Compile `source` to a `ParsedExpression`, throwing `ExpressionParseError`
|
|
10
|
+
* on failure. LRU/negative-cached. */
|
|
11
|
+
export declare function compileExpression(source: string): ParsedExpression;
|
|
12
|
+
/** Compile `source`, returning a discriminated result instead of throwing.
|
|
13
|
+
* Used by read paths that must degrade rather than raise. LRU/negative-cached. */
|
|
14
|
+
export declare function compileExpressionSafe(source: string): CompileResult;
|
|
15
|
+
/** Test-only: clear the module-level cache. Not part of the public engine
|
|
16
|
+
* surface — used to make cache-behaviour assertions deterministic. */
|
|
17
|
+
export declare function __clearExpressionCompileCache(): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error types for the safe expression engine. Two distinct classes so callers
|
|
3
|
+
* can tell a compile-time (grammar) failure from a runtime (evaluation)
|
|
4
|
+
* failure — both are non-fatal to the host: read paths degrade to "skip link".
|
|
5
|
+
*/
|
|
6
|
+
/** Thrown by the tokenizer / parser. Carries a 0-based source `position` when
|
|
7
|
+
* the failure is anchored to a character (author-facing inline feedback). */
|
|
8
|
+
export declare class ExpressionParseError extends Error {
|
|
9
|
+
readonly position?: number;
|
|
10
|
+
constructor(message: string, position?: number);
|
|
11
|
+
}
|
|
12
|
+
/** Thrown by the evaluator (unknown identifier, type mismatch, non-finite
|
|
13
|
+
* result, unknown builtin, step-budget exceeded). */
|
|
14
|
+
export declare class ExpressionEvalError extends Error {
|
|
15
|
+
constructor(message: string);
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ExpressionEvalHooks } from './builtins.js';
|
|
2
|
+
import type { ExpressionNode, ExpressionValue } from './ast.js';
|
|
3
|
+
export interface ExpressionEvalOptions {
|
|
4
|
+
readonly hooks?: ExpressionEvalHooks;
|
|
5
|
+
readonly maxSteps?: number;
|
|
6
|
+
}
|
|
7
|
+
/** Build a null-prototype scope from own-enumerable binding entries. Inherited
|
|
8
|
+
* keys of the input (e.g. from a `{__proto__: {...}}` payload) are NOT copied,
|
|
9
|
+
* so nothing smuggles in via the prototype chain. */
|
|
10
|
+
export declare function createExpressionScope(bindings: Readonly<Record<string, ExpressionValue>>): Record<string, ExpressionValue>;
|
|
11
|
+
/** Evaluate an AST node against a scope. Throws `ExpressionEvalError` on any
|
|
12
|
+
* runtime failure (unknown identifier, type mismatch, non-finite result,
|
|
13
|
+
* step-budget exhaustion). */
|
|
14
|
+
export declare function evaluateAst(node: ExpressionNode, scope: Record<string, ExpressionValue>, opts?: ExpressionEvalOptions): ExpressionValue;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe expression engine — public surface. A non-Turing-complete infix
|
|
3
|
+
* mini-language (tokenizer → Pratt parser → whitelisted-AST interpreter) with
|
|
4
|
+
* NO eval / new Function / node:vm, NO member access, NO loops/lambdas, and
|
|
5
|
+
* hard resource bounds. Powers the `expression` DeviceLinkSource kind.
|
|
6
|
+
*
|
|
7
|
+
* This is the module barrel; leaf files inside @camstack/types MUST import from
|
|
8
|
+
* the deep modules (e.g. `../expression/compile.js`), never through this file
|
|
9
|
+
* OR the root barrel (see biome-plugins/no-types-barrel-leaf-import.grit).
|
|
10
|
+
*/
|
|
11
|
+
export type { ExpressionValue, ExpressionNode, ExpressionLiteralNode, ExpressionIdentifierNode, ExpressionUnaryNode, ExpressionBinaryNode, ExpressionLogicalNode, ExpressionConditionalNode, ExpressionCallNode, ExpressionBinaryOperator, ExpressionLogicalOperator, ExpressionUnaryOperator, } from './ast.js';
|
|
12
|
+
export { ExpressionParseError, ExpressionEvalError } from './errors.js';
|
|
13
|
+
export { MAX_EXPRESSION_SOURCE_LENGTH, MAX_EXPRESSION_AST_NODES, MAX_EXPRESSION_EVAL_STEPS, MAX_EXPRESSION_BINDINGS, MAX_EXPRESSION_CALL_ARGS, EXPRESSION_COMPILE_CACHE_CAPACITY, EXPRESSION_IDENTIFIER_RE, RESERVED_BINDING_NAMES, } from './limits.js';
|
|
14
|
+
export { tokenize } from './tokenizer.js';
|
|
15
|
+
export type { Token, Punctuator, Keyword } from './tokenizer.js';
|
|
16
|
+
export { parseExpression } from './parser.js';
|
|
17
|
+
export type { ParsedExpression } from './parser.js';
|
|
18
|
+
export { EXPRESSION_BUILTINS, EXPRESSION_BUILTIN_NAMES, } from './builtins.js';
|
|
19
|
+
export type { ExpressionBuiltin, ExpressionEvalHooks } from './builtins.js';
|
|
20
|
+
export { createExpressionScope, evaluateAst } from './evaluator.js';
|
|
21
|
+
export type { ExpressionEvalOptions } from './evaluator.js';
|
|
22
|
+
export { compileExpression, compileExpressionSafe } from './compile.js';
|
|
23
|
+
export type { CompileResult } from './compile.js';
|
|
24
|
+
export { EXPRESSION_INJECTED_NOW, toExpressionValue, validateExpressionSource, evaluateLinkExpression, } from './link-expression.js';
|
|
25
|
+
export type { ExpressionSourceInput, EvaluateLinkExpressionResult, } from './link-expression.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource-bound constants for the safe expression engine.
|
|
3
|
+
*
|
|
4
|
+
* Every bound is defense-in-depth: the grammar is non-Turing-complete (no
|
|
5
|
+
* loops, recursion, lambdas or member access — see `ast.ts`), so evaluation is
|
|
6
|
+
* O(nodeCount) by construction. These caps merely put a hard ceiling on the
|
|
7
|
+
* work a single author-supplied expression can request, so a hostile or
|
|
8
|
+
* accidental pathological string can never spend unbounded CPU/memory.
|
|
9
|
+
*/
|
|
10
|
+
/** Max source length (chars) — checked BEFORE tokenizing so a huge string is
|
|
11
|
+
* rejected without allocation. */
|
|
12
|
+
export declare const MAX_EXPRESSION_SOURCE_LENGTH = 2048;
|
|
13
|
+
/** Max AST nodes — checked during parse; a deeply nested grouping that exceeds
|
|
14
|
+
* this is rejected as "expression too complex". */
|
|
15
|
+
export declare const MAX_EXPRESSION_AST_NODES = 256;
|
|
16
|
+
/** Defense-in-depth walker step budget — one increment per node visit during
|
|
17
|
+
* evaluation. The grammar guarantees O(nodeCount) walks, so this can only trip
|
|
18
|
+
* on a crafted maximum-size AST. */
|
|
19
|
+
export declare const MAX_EXPRESSION_EVAL_STEPS = 4096;
|
|
20
|
+
/** Max named bindings on one `DeviceLinkExpressionSource`. */
|
|
21
|
+
export declare const MAX_EXPRESSION_BINDINGS = 32;
|
|
22
|
+
/** Max positional arguments to any builtin call. */
|
|
23
|
+
export declare const MAX_EXPRESSION_CALL_ARGS = 16;
|
|
24
|
+
/** LRU compile-cache capacity (parsed ASTs keyed by raw source string). */
|
|
25
|
+
export declare const EXPRESSION_COMPILE_CACHE_CAPACITY = 256;
|
|
26
|
+
/** A legal binding / identifier name. */
|
|
27
|
+
export declare const EXPRESSION_IDENTIFIER_RE: RegExp;
|
|
28
|
+
/** Binding names an author may NOT use: `now` is auto-injected; the literal
|
|
29
|
+
* keywords lex as values, not identifiers, so binding to them is meaningless. */
|
|
30
|
+
export declare const RESERVED_BINDING_NAMES: ReadonlySet<string>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type ExpressionEvalOptions } from './evaluator.js';
|
|
2
|
+
import type { ExpressionValue } from './ast.js';
|
|
3
|
+
/** The `now` epoch-ms binding is auto-injected into every evaluation and is a
|
|
4
|
+
* reserved binding name (authors may not rebind it). */
|
|
5
|
+
export declare const EXPRESSION_INJECTED_NOW = "now";
|
|
6
|
+
/**
|
|
7
|
+
* Coerce an untrusted `getByPath` / mirror read to an `ExpressionValue`.
|
|
8
|
+
* Non-primitive values (objects, arrays, `undefined`, functions, bigint,
|
|
9
|
+
* symbol) and non-finite numbers become `undefined` so the caller can apply
|
|
10
|
+
* its binding-miss policy (→ `null`). `null` itself is a valid value.
|
|
11
|
+
*/
|
|
12
|
+
export declare function toExpressionValue(raw: unknown): ExpressionValue | undefined;
|
|
13
|
+
/** Shape the author-time validator accepts (structural subset of a
|
|
14
|
+
* `DeviceLinkExpressionSource`). */
|
|
15
|
+
export interface ExpressionSourceInput {
|
|
16
|
+
readonly expr: string;
|
|
17
|
+
readonly bindings: Readonly<Record<string, unknown>>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Author-time validation. Returns `null` when the source is valid, else a
|
|
21
|
+
* human-readable error message. Checks: the expression compiles; binding count
|
|
22
|
+
* is within `MAX_EXPRESSION_BINDINGS`; every binding name is a legal identifier,
|
|
23
|
+
* is not reserved (`now`/keywords) and does not shadow a builtin; and every
|
|
24
|
+
* FREE identifier of the AST is covered by a binding or the injected `now`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function validateExpressionSource(src: ExpressionSourceInput): string | null;
|
|
27
|
+
/** Result of a link-expression evaluation — `ok` carries the value, the error
|
|
28
|
+
* branch carries a reason the async channel can log while the sync channel
|
|
29
|
+
* silently skips. */
|
|
30
|
+
export type EvaluateLinkExpressionResult = {
|
|
31
|
+
readonly ok: true;
|
|
32
|
+
readonly value: ExpressionValue;
|
|
33
|
+
} | {
|
|
34
|
+
readonly ok: false;
|
|
35
|
+
readonly error: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Shared read-path evaluation for BOTH resolver channels. Builds a null-proto
|
|
39
|
+
* scope from `bindingValues` plus the injected `now` (supplied by the caller
|
|
40
|
+
* for determinism/testability), compiles via the LRU, and evaluates. Any
|
|
41
|
+
* failure (parse or eval) returns `{ ok: false }` — the caller treats that as
|
|
42
|
+
* "skip this link".
|
|
43
|
+
*/
|
|
44
|
+
export declare function evaluateLinkExpression(expr: string, bindingValues: Readonly<Record<string, ExpressionValue>>, now: number, opts?: ExpressionEvalOptions): EvaluateLinkExpressionResult;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ExpressionNode } from './ast.js';
|
|
2
|
+
export interface ParsedExpression {
|
|
3
|
+
readonly ast: ExpressionNode;
|
|
4
|
+
/** Free identifiers referenced by the expression (NOT callees). */
|
|
5
|
+
readonly identifiers: ReadonlySet<string>;
|
|
6
|
+
/** Builtin function names called by the expression. */
|
|
7
|
+
readonly callees: ReadonlySet<string>;
|
|
8
|
+
readonly nodeCount: number;
|
|
9
|
+
}
|
|
10
|
+
/** Tokenize + parse `source` into a validated `ParsedExpression`. Throws
|
|
11
|
+
* `ExpressionParseError` on any lexical or grammatical failure. */
|
|
12
|
+
export declare function parseExpression(source: string): ParsedExpression;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** The fixed punctuator set of the language. */
|
|
2
|
+
export type Punctuator = '(' | ')' | ',' | '?' | ':' | '+' | '-' | '*' | '/' | '%' | '!' | '<' | '<=' | '>' | '>=' | '==' | '!=' | '&&' | '||';
|
|
3
|
+
/** The three value keywords. */
|
|
4
|
+
export type Keyword = 'true' | 'false' | 'null';
|
|
5
|
+
export interface NumberToken {
|
|
6
|
+
readonly type: 'number';
|
|
7
|
+
readonly value: number;
|
|
8
|
+
readonly pos: number;
|
|
9
|
+
}
|
|
10
|
+
export interface StringToken {
|
|
11
|
+
readonly type: 'string';
|
|
12
|
+
readonly value: string;
|
|
13
|
+
readonly pos: number;
|
|
14
|
+
}
|
|
15
|
+
export interface IdentifierToken {
|
|
16
|
+
readonly type: 'identifier';
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly pos: number;
|
|
19
|
+
}
|
|
20
|
+
export interface KeywordToken {
|
|
21
|
+
readonly type: 'keyword';
|
|
22
|
+
readonly keyword: Keyword;
|
|
23
|
+
readonly pos: number;
|
|
24
|
+
}
|
|
25
|
+
export interface PunctToken {
|
|
26
|
+
readonly type: 'punct';
|
|
27
|
+
readonly punct: Punctuator;
|
|
28
|
+
readonly pos: number;
|
|
29
|
+
}
|
|
30
|
+
export interface EofToken {
|
|
31
|
+
readonly type: 'eof';
|
|
32
|
+
readonly pos: number;
|
|
33
|
+
}
|
|
34
|
+
export type Token = NumberToken | StringToken | IdentifierToken | KeywordToken | PunctToken | EofToken;
|
|
35
|
+
/** Tokenize `source` into a flat token list ending with a single `eof` token.
|
|
36
|
+
* Throws `ExpressionParseError` on any illegal character or unterminated
|
|
37
|
+
* string. */
|
|
38
|
+
export declare function tokenize(source: string): readonly Token[];
|