@syncropel/projections 0.6.0 → 0.7.0
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/CHANGELOG.md +20 -0
- package/dist/expr.d.ts +53 -0
- package/dist/expr.d.ts.map +1 -0
- package/dist/expr.js +288 -0
- package/dist/expr.js.map +1 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +123 -29
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +6 -3
- package/dist/schema.js.map +1 -1
- package/dist/validators.d.ts +1 -1
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +4 -3
- package/dist/validators.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@syncropel/projections` are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project uses [SemVer](https://semver.org/spec/v2.0.0.html).
|
|
4
4
|
|
|
5
|
+
## [0.7.0] — 2026-05-19
|
|
6
|
+
|
|
7
|
+
SRP v0.7 — the **host action grammar**. v0.7 adds **no node types**: it is pure grammar so a *generic* host can render a workspace record with no workspace-specific code (ADR-109). Additive and backwards-compatible — every v0.1–v0.6 document remains valid; the `srp` field now accepts `"0.1"`–`"0.7"`. See ADR-107/108/109 and `syncropel-research/docs/workspace-as-record/`.
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **The action grammar** — four self-describing action verbs alongside the legacy `ActionDesc` (`{intent}`) form, which stays valid as the non-generic-host escape hatch:
|
|
12
|
+
- **`EmitAction`** (`{ emit: EmitSpec, id?, optimistic?, onSuccess? }`) — emit a record via the SDK. `EmitSpec` is `{ act, thread, body }`; `thread: "$new"` allocates a fresh thread; `body`/`thread` strings support `{…}` interpolation. `id` keys the mutation lifecycle; `optimistic` supplies a body inserted immediately + rolled back on failure; `onSuccess` is a list of actions the host runs (in order) once the emit resolves — e.g. to close + clear a composer after a create (not run on failure).
|
|
13
|
+
- **`TableRowAction.action`** — a `data-table` row action may now carry an `SRPAction` (dispatched with the row as `{row}` runtime). When omitted the legacy `{ intent, payload: { row } }` form is used.
|
|
14
|
+
- **`SetStateAction`** (`{ setState: { key, value } }`) — write the host state bag.
|
|
15
|
+
- **`NavigateAction`** (`{ navigate: "<path>" }`) — route the viewer.
|
|
16
|
+
- **`OpenPaneAction`** (`{ openPane: { region, record } }`) — open a record in a `<WorkspaceShell>` region.
|
|
17
|
+
- **`SRPAction`** — the union of all five; `NodeBase.actions` and every node's `actions` map now accept it.
|
|
18
|
+
- **`QueryBinding`** — a named query binding (`{ name, filter?, fold? }`): `filter` is a raw ADR-037 VQL query, `fold` names a server-side projection/fold endpoint. `bind.query` on `record-line-list` / `data-table` / `board` now accepts `VQLQuery | QueryBinding`. `name` powers query-count reads via the expression path `query.<name>.count`.
|
|
19
|
+
- **The expression sublanguage** (`src/expr.ts`) — a small, sandboxed, pure expression language for `when` guards and `{…}` interpolation: dotted-path reads, comparison + boolean operators, literals, parentheses; no function calls, no I/O. A strict syntactic subset of CEL. New exports: `evaluateExpression`, `evaluateGuard`, `interpolate`, `parseExpression`, type `Scope`.
|
|
20
|
+
|
|
21
|
+
### Compatibility
|
|
22
|
+
|
|
23
|
+
- Additive. Every v0.1–v0.6 document validates unchanged. `ActionDesc` is exported unchanged. `NodeBase.actions` and node `actions` maps widen from `ActionDesc` to `SRPAction` (which includes `ActionDesc`) — constructing the legacy form still type-checks.
|
|
24
|
+
|
|
5
25
|
## [0.6.0] — 2026-05-18
|
|
6
26
|
|
|
7
27
|
SRP v0.6 — two configuration-control node types: `facet-grid` (a faceted filter) and `column-config` (a grid column picker). Additive and backwards-compatible: every v0.1–v0.5 document remains valid; the `srp` field now accepts `"0.1"`–`"0.6"`. Completes the declarative bodies of a library workspace's Filters + Columns popovers. See ADR-106.
|
package/dist/expr.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SRP v0.7 — the expression sublanguage (ADR-109 D2).
|
|
3
|
+
*
|
|
4
|
+
* A small, sandboxed, pure expression language shared by `when` guards
|
|
5
|
+
* and `{…}` interpolation in SRP documents. It is a strict syntactic
|
|
6
|
+
* subset of CEL: dotted-path reads over a host-supplied scope, comparison
|
|
7
|
+
* and boolean operators, literals, and parentheses. There are **no
|
|
8
|
+
* function calls and no I/O** — evaluation is pure and total (it never
|
|
9
|
+
* throws; an unresolvable path is `undefined`). Parsing throws only on a
|
|
10
|
+
* syntax error, which `parseExpression` catches for authoring-time
|
|
11
|
+
* validation.
|
|
12
|
+
*
|
|
13
|
+
* Grammar (precedence low → high):
|
|
14
|
+
* or := and ('||' and)*
|
|
15
|
+
* and := cmp ('&&' cmp)*
|
|
16
|
+
* cmp := unary (('=='|'!='|'<'|'>'|'<='|'>='|'~') unary)?
|
|
17
|
+
* unary := '!' unary | primary
|
|
18
|
+
* primary := number | string | 'true' | 'false' | 'null'
|
|
19
|
+
* | path | '(' or ')'
|
|
20
|
+
* path := ident ('.' ident)*
|
|
21
|
+
*
|
|
22
|
+
* The scope is `{ state, row, form, record, mutation, query, … }` — the
|
|
23
|
+
* evaluator resolves dotted paths against it and is agnostic to which
|
|
24
|
+
* roots a host supplies.
|
|
25
|
+
*/
|
|
26
|
+
export type Scope = Record<string, unknown>;
|
|
27
|
+
/**
|
|
28
|
+
* Evaluate an SRP expression against a scope. Returns the value; never
|
|
29
|
+
* throws. A syntactically invalid expression returns `undefined`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function evaluateExpression(src: string, scope: Scope): unknown;
|
|
32
|
+
/**
|
|
33
|
+
* Evaluate a `when` guard. An empty / whitespace-only guard is satisfied
|
|
34
|
+
* (true). Any other expression is evaluated and coerced to a boolean.
|
|
35
|
+
*/
|
|
36
|
+
export declare function evaluateGuard(src: string | undefined, scope: Scope): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Substitute every `{expression}` occurrence in `template` with the
|
|
39
|
+
* stringified value of that expression. `undefined` / `null` substitute
|
|
40
|
+
* as the empty string. Text outside braces passes through verbatim.
|
|
41
|
+
*/
|
|
42
|
+
export declare function interpolate(template: string, scope: Scope): string;
|
|
43
|
+
/**
|
|
44
|
+
* Parse-check an expression without evaluating it — for authoring-time
|
|
45
|
+
* validation. Returns `{ ok: true }` or `{ ok: false, error }`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseExpression(src: string): {
|
|
48
|
+
ok: true;
|
|
49
|
+
} | {
|
|
50
|
+
ok: false;
|
|
51
|
+
error: string;
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=expr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expr.d.ts","sourceRoot":"","sources":["../src/expr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAyP5C;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAMrE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAG5E;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAKlE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,GACV;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAO7C"}
|
package/dist/expr.js
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SRP v0.7 — the expression sublanguage (ADR-109 D2).
|
|
3
|
+
*
|
|
4
|
+
* A small, sandboxed, pure expression language shared by `when` guards
|
|
5
|
+
* and `{…}` interpolation in SRP documents. It is a strict syntactic
|
|
6
|
+
* subset of CEL: dotted-path reads over a host-supplied scope, comparison
|
|
7
|
+
* and boolean operators, literals, and parentheses. There are **no
|
|
8
|
+
* function calls and no I/O** — evaluation is pure and total (it never
|
|
9
|
+
* throws; an unresolvable path is `undefined`). Parsing throws only on a
|
|
10
|
+
* syntax error, which `parseExpression` catches for authoring-time
|
|
11
|
+
* validation.
|
|
12
|
+
*
|
|
13
|
+
* Grammar (precedence low → high):
|
|
14
|
+
* or := and ('||' and)*
|
|
15
|
+
* and := cmp ('&&' cmp)*
|
|
16
|
+
* cmp := unary (('=='|'!='|'<'|'>'|'<='|'>='|'~') unary)?
|
|
17
|
+
* unary := '!' unary | primary
|
|
18
|
+
* primary := number | string | 'true' | 'false' | 'null'
|
|
19
|
+
* | path | '(' or ')'
|
|
20
|
+
* path := ident ('.' ident)*
|
|
21
|
+
*
|
|
22
|
+
* The scope is `{ state, row, form, record, mutation, query, … }` — the
|
|
23
|
+
* evaluator resolves dotted paths against it and is agnostic to which
|
|
24
|
+
* roots a host supplies.
|
|
25
|
+
*/
|
|
26
|
+
const PATH_RE = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z0-9_]+)*/;
|
|
27
|
+
const NUM_RE = /^-?\d+(?:\.\d+)?/;
|
|
28
|
+
const OPS = ["==", "!=", "<=", ">=", "&&", "||", "<", ">", "!", "~"];
|
|
29
|
+
function tokenize(src) {
|
|
30
|
+
const toks = [];
|
|
31
|
+
let i = 0;
|
|
32
|
+
while (i < src.length) {
|
|
33
|
+
const ch = src[i];
|
|
34
|
+
if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r") {
|
|
35
|
+
i += 1;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (ch === "(") {
|
|
39
|
+
toks.push({ t: "lparen" });
|
|
40
|
+
i += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (ch === ")") {
|
|
44
|
+
toks.push({ t: "rparen" });
|
|
45
|
+
i += 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (ch === "'" || ch === '"') {
|
|
49
|
+
let j = i + 1;
|
|
50
|
+
let s = "";
|
|
51
|
+
while (j < src.length && src[j] !== ch) {
|
|
52
|
+
if (src[j] === "\\" && j + 1 < src.length) {
|
|
53
|
+
s += src[j + 1];
|
|
54
|
+
j += 2;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
s += src[j];
|
|
58
|
+
j += 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (j >= src.length)
|
|
62
|
+
throw new SyntaxError("unterminated string");
|
|
63
|
+
toks.push({ t: "str", v: s });
|
|
64
|
+
i = j + 1;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const rest = src.slice(i);
|
|
68
|
+
const op = OPS.find((o) => rest.startsWith(o));
|
|
69
|
+
if (op) {
|
|
70
|
+
toks.push({ t: "op", v: op });
|
|
71
|
+
i += op.length;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const num = NUM_RE.exec(rest);
|
|
75
|
+
if (num && num[0] !== "-") {
|
|
76
|
+
toks.push({ t: "num", v: Number(num[0]) });
|
|
77
|
+
i += num[0].length;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const path = PATH_RE.exec(rest);
|
|
81
|
+
if (path) {
|
|
82
|
+
toks.push({ t: "path", v: path[0] });
|
|
83
|
+
i += path[0].length;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
throw new SyntaxError(`unexpected character '${ch}' at ${i}`);
|
|
87
|
+
}
|
|
88
|
+
toks.push({ t: "eof" });
|
|
89
|
+
return toks;
|
|
90
|
+
}
|
|
91
|
+
// --- Parser ----------------------------------------------------------------
|
|
92
|
+
class Parser {
|
|
93
|
+
toks;
|
|
94
|
+
pos = 0;
|
|
95
|
+
constructor(toks) {
|
|
96
|
+
this.toks = toks;
|
|
97
|
+
}
|
|
98
|
+
peek() {
|
|
99
|
+
return this.toks[this.pos];
|
|
100
|
+
}
|
|
101
|
+
next() {
|
|
102
|
+
return this.toks[this.pos++];
|
|
103
|
+
}
|
|
104
|
+
parse() {
|
|
105
|
+
const ast = this.parseOr();
|
|
106
|
+
if (this.peek().t !== "eof") {
|
|
107
|
+
throw new SyntaxError("unexpected trailing input");
|
|
108
|
+
}
|
|
109
|
+
return ast;
|
|
110
|
+
}
|
|
111
|
+
parseOr() {
|
|
112
|
+
let left = this.parseAnd();
|
|
113
|
+
while (this.isOp("||")) {
|
|
114
|
+
this.next();
|
|
115
|
+
left = { kind: "binary", op: "||", left, right: this.parseAnd() };
|
|
116
|
+
}
|
|
117
|
+
return left;
|
|
118
|
+
}
|
|
119
|
+
parseAnd() {
|
|
120
|
+
let left = this.parseCmp();
|
|
121
|
+
while (this.isOp("&&")) {
|
|
122
|
+
this.next();
|
|
123
|
+
left = { kind: "binary", op: "&&", left, right: this.parseCmp() };
|
|
124
|
+
}
|
|
125
|
+
return left;
|
|
126
|
+
}
|
|
127
|
+
parseCmp() {
|
|
128
|
+
const left = this.parseUnary();
|
|
129
|
+
const tok = this.peek();
|
|
130
|
+
if (tok.t === "op" &&
|
|
131
|
+
["==", "!=", "<", ">", "<=", ">=", "~"].includes(tok.v)) {
|
|
132
|
+
this.next();
|
|
133
|
+
return {
|
|
134
|
+
kind: "binary",
|
|
135
|
+
op: tok.v,
|
|
136
|
+
left,
|
|
137
|
+
right: this.parseUnary(),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return left;
|
|
141
|
+
}
|
|
142
|
+
parseUnary() {
|
|
143
|
+
if (this.isOp("!")) {
|
|
144
|
+
this.next();
|
|
145
|
+
return { kind: "unary", operand: this.parseUnary() };
|
|
146
|
+
}
|
|
147
|
+
return this.parsePrimary();
|
|
148
|
+
}
|
|
149
|
+
parsePrimary() {
|
|
150
|
+
const tok = this.next();
|
|
151
|
+
if (tok.t === "num")
|
|
152
|
+
return { kind: "lit", value: tok.v };
|
|
153
|
+
if (tok.t === "str")
|
|
154
|
+
return { kind: "lit", value: tok.v };
|
|
155
|
+
if (tok.t === "lparen") {
|
|
156
|
+
const inner = this.parseOr();
|
|
157
|
+
if (this.next().t !== "rparen") {
|
|
158
|
+
throw new SyntaxError("expected ')'");
|
|
159
|
+
}
|
|
160
|
+
return inner;
|
|
161
|
+
}
|
|
162
|
+
if (tok.t === "path") {
|
|
163
|
+
if (tok.v === "true")
|
|
164
|
+
return { kind: "lit", value: true };
|
|
165
|
+
if (tok.v === "false")
|
|
166
|
+
return { kind: "lit", value: false };
|
|
167
|
+
if (tok.v === "null")
|
|
168
|
+
return { kind: "lit", value: null };
|
|
169
|
+
return { kind: "path", segments: tok.v.split(".") };
|
|
170
|
+
}
|
|
171
|
+
throw new SyntaxError(`unexpected token ${JSON.stringify(tok)}`);
|
|
172
|
+
}
|
|
173
|
+
isOp(v) {
|
|
174
|
+
const tok = this.peek();
|
|
175
|
+
return tok.t === "op" && tok.v === v;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function compile(src) {
|
|
179
|
+
return new Parser(tokenize(src)).parse();
|
|
180
|
+
}
|
|
181
|
+
// --- Evaluator (total — never throws) --------------------------------------
|
|
182
|
+
function resolvePath(scope, segments) {
|
|
183
|
+
let cur = scope;
|
|
184
|
+
for (const seg of segments) {
|
|
185
|
+
if (cur === null || typeof cur !== "object")
|
|
186
|
+
return undefined;
|
|
187
|
+
cur = cur[seg];
|
|
188
|
+
}
|
|
189
|
+
return cur;
|
|
190
|
+
}
|
|
191
|
+
function truthy(v) {
|
|
192
|
+
if (v === undefined || v === null || v === false)
|
|
193
|
+
return false;
|
|
194
|
+
if (v === 0 || v === "")
|
|
195
|
+
return false;
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
function compare(op, l, r) {
|
|
199
|
+
switch (op) {
|
|
200
|
+
case "==":
|
|
201
|
+
return l === r;
|
|
202
|
+
case "!=":
|
|
203
|
+
return l !== r;
|
|
204
|
+
case "<":
|
|
205
|
+
return l < r;
|
|
206
|
+
case ">":
|
|
207
|
+
return l > r;
|
|
208
|
+
case "<=":
|
|
209
|
+
return l <= r;
|
|
210
|
+
case ">=":
|
|
211
|
+
return l >= r;
|
|
212
|
+
case "~":
|
|
213
|
+
return String(l ?? "")
|
|
214
|
+
.toLowerCase()
|
|
215
|
+
.includes(String(r ?? "").toLowerCase());
|
|
216
|
+
default:
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function evalAst(ast, scope) {
|
|
221
|
+
switch (ast.kind) {
|
|
222
|
+
case "lit":
|
|
223
|
+
return ast.value;
|
|
224
|
+
case "path":
|
|
225
|
+
return resolvePath(scope, ast.segments);
|
|
226
|
+
case "unary":
|
|
227
|
+
return !truthy(evalAst(ast.operand, scope));
|
|
228
|
+
case "binary": {
|
|
229
|
+
if (ast.op === "&&") {
|
|
230
|
+
return truthy(evalAst(ast.left, scope))
|
|
231
|
+
? evalAst(ast.right, scope)
|
|
232
|
+
: evalAst(ast.left, scope);
|
|
233
|
+
}
|
|
234
|
+
if (ast.op === "||") {
|
|
235
|
+
const lv = evalAst(ast.left, scope);
|
|
236
|
+
return truthy(lv) ? lv : evalAst(ast.right, scope);
|
|
237
|
+
}
|
|
238
|
+
return compare(ast.op, evalAst(ast.left, scope), evalAst(ast.right, scope));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// --- Public API ------------------------------------------------------------
|
|
243
|
+
/**
|
|
244
|
+
* Evaluate an SRP expression against a scope. Returns the value; never
|
|
245
|
+
* throws. A syntactically invalid expression returns `undefined`.
|
|
246
|
+
*/
|
|
247
|
+
export function evaluateExpression(src, scope) {
|
|
248
|
+
try {
|
|
249
|
+
return evalAst(compile(src), scope);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Evaluate a `when` guard. An empty / whitespace-only guard is satisfied
|
|
257
|
+
* (true). Any other expression is evaluated and coerced to a boolean.
|
|
258
|
+
*/
|
|
259
|
+
export function evaluateGuard(src, scope) {
|
|
260
|
+
if (src === undefined || src.trim() === "")
|
|
261
|
+
return true;
|
|
262
|
+
return truthy(evaluateExpression(src, scope));
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Substitute every `{expression}` occurrence in `template` with the
|
|
266
|
+
* stringified value of that expression. `undefined` / `null` substitute
|
|
267
|
+
* as the empty string. Text outside braces passes through verbatim.
|
|
268
|
+
*/
|
|
269
|
+
export function interpolate(template, scope) {
|
|
270
|
+
return template.replace(/\{([^}]*)\}/g, (_m, expr) => {
|
|
271
|
+
const v = evaluateExpression(expr, scope);
|
|
272
|
+
return v === undefined || v === null ? "" : String(v);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Parse-check an expression without evaluating it — for authoring-time
|
|
277
|
+
* validation. Returns `{ ok: true }` or `{ ok: false, error }`.
|
|
278
|
+
*/
|
|
279
|
+
export function parseExpression(src) {
|
|
280
|
+
try {
|
|
281
|
+
compile(src);
|
|
282
|
+
return { ok: true };
|
|
283
|
+
}
|
|
284
|
+
catch (e) {
|
|
285
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=expr.js.map
|
package/dist/expr.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expr.js","sourceRoot":"","sources":["../src/expr.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SRP v0.7 — the expression sublanguage (ADR-109 D2).\n *\n * A small, sandboxed, pure expression language shared by `when` guards\n * and `{…}` interpolation in SRP documents. It is a strict syntactic\n * subset of CEL: dotted-path reads over a host-supplied scope, comparison\n * and boolean operators, literals, and parentheses. There are **no\n * function calls and no I/O** — evaluation is pure and total (it never\n * throws; an unresolvable path is `undefined`). Parsing throws only on a\n * syntax error, which `parseExpression` catches for authoring-time\n * validation.\n *\n * Grammar (precedence low → high):\n * or := and ('||' and)*\n * and := cmp ('&&' cmp)*\n * cmp := unary (('=='|'!='|'<'|'>'|'<='|'>='|'~') unary)?\n * unary := '!' unary | primary\n * primary := number | string | 'true' | 'false' | 'null'\n * | path | '(' or ')'\n * path := ident ('.' ident)*\n *\n * The scope is `{ state, row, form, record, mutation, query, … }` — the\n * evaluator resolves dotted paths against it and is agnostic to which\n * roots a host supplies.\n */\n\nexport type Scope = Record<string, unknown>;\n\n// --- AST -------------------------------------------------------------------\n\n/** `~` is case-insensitive substring containment (`left` contains `right`). */\ntype BinOp = \"==\" | \"!=\" | \"<\" | \">\" | \"<=\" | \">=\" | \"~\" | \"&&\" | \"||\";\n\ntype Ast =\n | { kind: \"lit\"; value: unknown }\n | { kind: \"path\"; segments: string[] }\n | { kind: \"unary\"; operand: Ast }\n | { kind: \"binary\"; op: BinOp; left: Ast; right: Ast };\n\n// --- Tokenizer -------------------------------------------------------------\n\ntype Tok =\n | { t: \"num\"; v: number }\n | { t: \"str\"; v: string }\n | { t: \"path\"; v: string }\n | { t: \"op\"; v: string }\n | { t: \"lparen\" }\n | { t: \"rparen\" }\n | { t: \"eof\" };\n\nconst PATH_RE = /^[A-Za-z_][A-Za-z0-9_]*(?:\\.[A-Za-z0-9_]+)*/;\nconst NUM_RE = /^-?\\d+(?:\\.\\d+)?/;\nconst OPS = [\"==\", \"!=\", \"<=\", \">=\", \"&&\", \"||\", \"<\", \">\", \"!\", \"~\"];\n\nfunction tokenize(src: string): Tok[] {\n const toks: Tok[] = [];\n let i = 0;\n while (i < src.length) {\n const ch = src[i] as string;\n if (ch === \" \" || ch === \"\\t\" || ch === \"\\n\" || ch === \"\\r\") {\n i += 1;\n continue;\n }\n if (ch === \"(\") {\n toks.push({ t: \"lparen\" });\n i += 1;\n continue;\n }\n if (ch === \")\") {\n toks.push({ t: \"rparen\" });\n i += 1;\n continue;\n }\n if (ch === \"'\" || ch === '\"') {\n let j = i + 1;\n let s = \"\";\n while (j < src.length && src[j] !== ch) {\n if (src[j] === \"\\\\\" && j + 1 < src.length) {\n s += src[j + 1];\n j += 2;\n } else {\n s += src[j];\n j += 1;\n }\n }\n if (j >= src.length) throw new SyntaxError(\"unterminated string\");\n toks.push({ t: \"str\", v: s });\n i = j + 1;\n continue;\n }\n const rest = src.slice(i);\n const op = OPS.find((o) => rest.startsWith(o));\n if (op) {\n toks.push({ t: \"op\", v: op });\n i += op.length;\n continue;\n }\n const num = NUM_RE.exec(rest);\n if (num && num[0] !== \"-\") {\n toks.push({ t: \"num\", v: Number(num[0]) });\n i += num[0].length;\n continue;\n }\n const path = PATH_RE.exec(rest);\n if (path) {\n toks.push({ t: \"path\", v: path[0] });\n i += path[0].length;\n continue;\n }\n throw new SyntaxError(`unexpected character '${ch}' at ${i}`);\n }\n toks.push({ t: \"eof\" });\n return toks;\n}\n\n// --- Parser ----------------------------------------------------------------\n\nclass Parser {\n private pos = 0;\n constructor(private readonly toks: Tok[]) {}\n\n private peek(): Tok {\n return this.toks[this.pos] as Tok;\n }\n private next(): Tok {\n return this.toks[this.pos++] as Tok;\n }\n\n parse(): Ast {\n const ast = this.parseOr();\n if (this.peek().t !== \"eof\") {\n throw new SyntaxError(\"unexpected trailing input\");\n }\n return ast;\n }\n\n private parseOr(): Ast {\n let left = this.parseAnd();\n while (this.isOp(\"||\")) {\n this.next();\n left = { kind: \"binary\", op: \"||\", left, right: this.parseAnd() };\n }\n return left;\n }\n\n private parseAnd(): Ast {\n let left = this.parseCmp();\n while (this.isOp(\"&&\")) {\n this.next();\n left = { kind: \"binary\", op: \"&&\", left, right: this.parseCmp() };\n }\n return left;\n }\n\n private parseCmp(): Ast {\n const left = this.parseUnary();\n const tok = this.peek();\n if (\n tok.t === \"op\" &&\n [\"==\", \"!=\", \"<\", \">\", \"<=\", \">=\", \"~\"].includes(tok.v)\n ) {\n this.next();\n return {\n kind: \"binary\",\n op: tok.v as BinOp,\n left,\n right: this.parseUnary(),\n };\n }\n return left;\n }\n\n private parseUnary(): Ast {\n if (this.isOp(\"!\")) {\n this.next();\n return { kind: \"unary\", operand: this.parseUnary() };\n }\n return this.parsePrimary();\n }\n\n private parsePrimary(): Ast {\n const tok = this.next();\n if (tok.t === \"num\") return { kind: \"lit\", value: tok.v };\n if (tok.t === \"str\") return { kind: \"lit\", value: tok.v };\n if (tok.t === \"lparen\") {\n const inner = this.parseOr();\n if (this.next().t !== \"rparen\") {\n throw new SyntaxError(\"expected ')'\");\n }\n return inner;\n }\n if (tok.t === \"path\") {\n if (tok.v === \"true\") return { kind: \"lit\", value: true };\n if (tok.v === \"false\") return { kind: \"lit\", value: false };\n if (tok.v === \"null\") return { kind: \"lit\", value: null };\n return { kind: \"path\", segments: tok.v.split(\".\") };\n }\n throw new SyntaxError(`unexpected token ${JSON.stringify(tok)}`);\n }\n\n private isOp(v: string): boolean {\n const tok = this.peek();\n return tok.t === \"op\" && tok.v === v;\n }\n}\n\nfunction compile(src: string): Ast {\n return new Parser(tokenize(src)).parse();\n}\n\n// --- Evaluator (total — never throws) --------------------------------------\n\nfunction resolvePath(scope: Scope, segments: string[]): unknown {\n let cur: unknown = scope;\n for (const seg of segments) {\n if (cur === null || typeof cur !== \"object\") return undefined;\n cur = (cur as Record<string, unknown>)[seg];\n }\n return cur;\n}\n\nfunction truthy(v: unknown): boolean {\n if (v === undefined || v === null || v === false) return false;\n if (v === 0 || v === \"\") return false;\n return true;\n}\n\nfunction compare(op: BinOp, l: unknown, r: unknown): unknown {\n switch (op) {\n case \"==\":\n return l === r;\n case \"!=\":\n return l !== r;\n case \"<\":\n return (l as number) < (r as number);\n case \">\":\n return (l as number) > (r as number);\n case \"<=\":\n return (l as number) <= (r as number);\n case \">=\":\n return (l as number) >= (r as number);\n case \"~\":\n return String(l ?? \"\")\n .toLowerCase()\n .includes(String(r ?? \"\").toLowerCase());\n default:\n return undefined;\n }\n}\n\nfunction evalAst(ast: Ast, scope: Scope): unknown {\n switch (ast.kind) {\n case \"lit\":\n return ast.value;\n case \"path\":\n return resolvePath(scope, ast.segments);\n case \"unary\":\n return !truthy(evalAst(ast.operand, scope));\n case \"binary\": {\n if (ast.op === \"&&\") {\n return truthy(evalAst(ast.left, scope))\n ? evalAst(ast.right, scope)\n : evalAst(ast.left, scope);\n }\n if (ast.op === \"||\") {\n const lv = evalAst(ast.left, scope);\n return truthy(lv) ? lv : evalAst(ast.right, scope);\n }\n return compare(ast.op, evalAst(ast.left, scope), evalAst(ast.right, scope));\n }\n }\n}\n\n// --- Public API ------------------------------------------------------------\n\n/**\n * Evaluate an SRP expression against a scope. Returns the value; never\n * throws. A syntactically invalid expression returns `undefined`.\n */\nexport function evaluateExpression(src: string, scope: Scope): unknown {\n try {\n return evalAst(compile(src), scope);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Evaluate a `when` guard. An empty / whitespace-only guard is satisfied\n * (true). Any other expression is evaluated and coerced to a boolean.\n */\nexport function evaluateGuard(src: string | undefined, scope: Scope): boolean {\n if (src === undefined || src.trim() === \"\") return true;\n return truthy(evaluateExpression(src, scope));\n}\n\n/**\n * Substitute every `{expression}` occurrence in `template` with the\n * stringified value of that expression. `undefined` / `null` substitute\n * as the empty string. Text outside braces passes through verbatim.\n */\nexport function interpolate(template: string, scope: Scope): string {\n return template.replace(/\\{([^}]*)\\}/g, (_m, expr: string) => {\n const v = evaluateExpression(expr, scope);\n return v === undefined || v === null ? \"\" : String(v);\n });\n}\n\n/**\n * Parse-check an expression without evaluating it — for authoring-time\n * validation. Returns `{ ok: true }` or `{ ok: false, error }`.\n */\nexport function parseExpression(\n src: string,\n): { ok: true } | { ok: false; error: string } {\n try {\n compile(src);\n return { ok: true };\n } catch (e) {\n return { ok: false, error: e instanceof Error ? e.message : String(e) };\n }\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,13 +13,16 @@
|
|
|
13
13
|
* v0.5 adds four library-workspace nodes (popover, slider, rating,
|
|
14
14
|
* thumbnail) plus `data-table` sticky columns and meter / rating /
|
|
15
15
|
* thumbnail cell kinds. v0.6 adds two configuration-control nodes
|
|
16
|
-
* (facet-grid, column-config).
|
|
17
|
-
*
|
|
16
|
+
* (facet-grid, column-config). v0.7 adds no node types — it is pure
|
|
17
|
+
* grammar: the host action grammar, named query bindings, and the
|
|
18
|
+
* expression sublanguage. Every increment is additive + backwards-compatible.
|
|
18
19
|
*
|
|
19
20
|
* Docs: https://syncropel.com
|
|
20
21
|
*/
|
|
21
|
-
export type { SRPDocument, SRPMeta, SRPNode, NodeType, ColumnNode, RowNode, GridNode, CardNode, DividerNode, RecordLineNode, RecordLineListNode, ChipNode, HeadingNode, TextNode, StatNode, KeyValueNode, ButtonNode, IconButtonNode, CopyButtonNode, SelectNode, EmptyStateNode, ErrorStateNode, SkeletonNode, TabsNode, DataTableNode, BoardNode, TextInputNode, FormNode, SegmentedNode, SegmentedOption, DataTableColumnDef, TableSort, TableRowAction, TableRowTone, MeterNode, GaugeNode, AvatarNode, ProgressNode, SparklineNode, ChartNode, TreeNode, TagInputNode, VizThreshold, GaugeZone, ChartPoint, ChartSeries, TreeItem, PopoverNode, SliderNode, RatingNode, ThumbnailNode, FacetGridNode, ColumnConfigNode, FacetOption, FacetCategory, ColumnConfigColumn, ColumnConfigPreset, Gap, Padding, DividerSpacing, Cols, ColumnAlign, RowAlign, Justify, RecordLineVariant, ChipTone, TextSize, TextWeight, TextTone, ButtonVariant, ButtonSize, SkeletonShape, TableColumnAlign, TableCellKind, DataTableVariant, TextInputKind, RowTone, SortDirection, RowActionPosition, SegmentedVariant, VizTone, VizSize, MeterVariant, SparklineVariant, ChartKind, GlyphKind, PopoverAlign, ThumbnailRadius, ThumbnailAspect, ActionDesc, VQLQuery, } from "./schema.js";
|
|
22
|
+
export type { SRPDocument, SRPMeta, SRPNode, NodeType, ColumnNode, RowNode, GridNode, CardNode, DividerNode, RecordLineNode, RecordLineListNode, ChipNode, HeadingNode, TextNode, StatNode, KeyValueNode, ButtonNode, IconButtonNode, CopyButtonNode, SelectNode, EmptyStateNode, ErrorStateNode, SkeletonNode, TabsNode, DataTableNode, BoardNode, TextInputNode, FormNode, SegmentedNode, SegmentedOption, DataTableColumnDef, TableSort, TableRowAction, TableRowTone, MeterNode, GaugeNode, AvatarNode, ProgressNode, SparklineNode, ChartNode, TreeNode, TagInputNode, VizThreshold, GaugeZone, ChartPoint, ChartSeries, TreeItem, PopoverNode, SliderNode, RatingNode, ThumbnailNode, FacetGridNode, ColumnConfigNode, FacetOption, FacetCategory, ColumnConfigColumn, ColumnConfigPreset, Gap, Padding, DividerSpacing, Cols, ColumnAlign, RowAlign, Justify, RecordLineVariant, ChipTone, TextSize, TextWeight, TextTone, ButtonVariant, ButtonSize, SkeletonShape, TableColumnAlign, TableCellKind, DataTableVariant, TextInputKind, RowTone, SortDirection, RowActionPosition, SegmentedVariant, VizTone, VizSize, MeterVariant, SparklineVariant, ChartKind, GlyphKind, PopoverAlign, ThumbnailRadius, ThumbnailAspect, ActionDesc, VQLQuery, SRPAction, EmitAction, EmitSpec, SetStateAction, NavigateAction, OpenPaneAction, QueryBinding, } from "./schema.js";
|
|
22
23
|
export { NODE_TYPES } from "./schema.js";
|
|
24
|
+
export { evaluateExpression, evaluateGuard, interpolate, parseExpression, } from "./expr.js";
|
|
25
|
+
export type { Scope } from "./expr.js";
|
|
23
26
|
export { validateSRP, validateNode, isSRPDocument, isSRPNode, } from "./validators.js";
|
|
24
27
|
export type { ValidationResult, ValidationError, ValidationErrorCode, } from "./validators.js";
|
|
25
28
|
export { parseInline, stripFormatting } from "./markdown.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,YAAY,EACV,WAAW,EACX,OAAO,EACP,OAAO,EACP,QAAQ,EAER,UAAU,EACV,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,WAAW,EAEX,cAAc,EACd,kBAAkB,EAClB,QAAQ,EAER,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,YAAY,EAEZ,UAAU,EACV,cAAc,EACd,cAAc,EACd,UAAU,EAEV,cAAc,EACd,cAAc,EACd,YAAY,EAEZ,QAAQ,EACR,aAAa,EACb,SAAS,EACT,aAAa,EACb,QAAQ,EAER,aAAa,EACb,eAAe,EAEf,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,YAAY,EAEZ,SAAS,EACT,SAAS,EACT,UAAU,EACV,YAAY,EACZ,aAAa,EACb,SAAS,EACT,QAAQ,EACR,YAAY,EAEZ,YAAY,EACZ,SAAS,EACT,UAAU,EACV,WAAW,EACX,QAAQ,EAER,WAAW,EACX,UAAU,EACV,UAAU,EACV,aAAa,EAEb,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAElB,GAAG,EACH,OAAO,EACP,cAAc,EACd,IAAI,EACJ,WAAW,EACX,QAAQ,EACR,OAAO,EACP,iBAAiB,EACjB,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,aAAa,EACb,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,OAAO,EACP,OAAO,EACP,YAAY,EACZ,gBAAgB,EAChB,SAAS,EACT,SAAS,EAET,YAAY,EACZ,eAAe,EACf,eAAe,EAEf,UAAU,EACV,QAAQ,EAER,SAAS,EACT,UAAU,EACV,QAAQ,EACR,cAAc,EACd,cAAc,EACd,cAAc,EACd,YAAY,GACb,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,WAAW,EACX,eAAe,GAChB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAGvC,OAAO,EACL,WAAW,EACX,YAAY,EACZ,aAAa,EACb,SAAS,GACV,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC7D,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -13,12 +13,15 @@
|
|
|
13
13
|
* v0.5 adds four library-workspace nodes (popover, slider, rating,
|
|
14
14
|
* thumbnail) plus `data-table` sticky columns and meter / rating /
|
|
15
15
|
* thumbnail cell kinds. v0.6 adds two configuration-control nodes
|
|
16
|
-
* (facet-grid, column-config).
|
|
17
|
-
*
|
|
16
|
+
* (facet-grid, column-config). v0.7 adds no node types — it is pure
|
|
17
|
+
* grammar: the host action grammar, named query bindings, and the
|
|
18
|
+
* expression sublanguage. Every increment is additive + backwards-compatible.
|
|
18
19
|
*
|
|
19
20
|
* Docs: https://syncropel.com
|
|
20
21
|
*/
|
|
21
22
|
export { NODE_TYPES } from "./schema.js";
|
|
23
|
+
// SRP v0.7 — the expression sublanguage (when guards + {…} interpolation)
|
|
24
|
+
export { evaluateExpression, evaluateGuard, interpolate, parseExpression, } from "./expr.js";
|
|
22
25
|
// Validators
|
|
23
26
|
export { validateSRP, validateNode, isSRPDocument, isSRPNode, } from "./validators.js";
|
|
24
27
|
// Markdown subset parser
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAwHH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,0EAA0E;AAC1E,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,WAAW,EACX,eAAe,GAChB,MAAM,WAAW,CAAC;AAGnB,aAAa;AACb,OAAO,EACL,WAAW,EACX,YAAY,EACZ,aAAa,EACb,SAAS,GACV,MAAM,iBAAiB,CAAC;AAQzB,yBAAyB;AACzB,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC","sourcesContent":["/**\n * @syncropel/projections — Syncropel Rendering Protocol (SRP) v0.6.\n *\n * TypeScript schema + runtime validators for declarative UI documents\n * rendered by @syncropel/react (and other renderers). Zero runtime\n * dependencies; universal runtime (Node, Deno, Bun, Workers, browsers).\n *\n * v0.2 adds five interactive container + input node types (tabs,\n * data-table, board, text-input, form). v0.3 adds the `segmented` node\n * plus a polished-data-grid increment to `data-table`. v0.4 adds eight\n * visualization + hierarchy nodes (meter, gauge, avatar, progress,\n * sparkline, chart, tree, tag-input) plus `data-table` row selection.\n * v0.5 adds four library-workspace nodes (popover, slider, rating,\n * thumbnail) plus `data-table` sticky columns and meter / rating /\n * thumbnail cell kinds. v0.6 adds two configuration-control nodes\n * (facet-grid, column-config). v0.7 adds no node types — it is pure\n * grammar: the host action grammar, named query bindings, and the\n * expression sublanguage. Every increment is additive + backwards-compatible.\n *\n * Docs: https://syncropel.com\n */\n\n// Schema types\nexport type {\n SRPDocument,\n SRPMeta,\n SRPNode,\n NodeType,\n // Container nodes\n ColumnNode,\n RowNode,\n GridNode,\n CardNode,\n DividerNode,\n // Record-rendering nodes\n RecordLineNode,\n RecordLineListNode,\n ChipNode,\n // Data-display nodes\n HeadingNode,\n TextNode,\n StatNode,\n KeyValueNode,\n // Interactive nodes\n ButtonNode,\n IconButtonNode,\n CopyButtonNode,\n SelectNode,\n // Feedback nodes\n EmptyStateNode,\n ErrorStateNode,\n SkeletonNode,\n // Interactive containers + inputs (SRP v0.2)\n TabsNode,\n DataTableNode,\n BoardNode,\n TextInputNode,\n FormNode,\n // Choice control (SRP v0.3)\n SegmentedNode,\n SegmentedOption,\n // Data-grid supporting types (SRP v0.3)\n DataTableColumnDef,\n TableSort,\n TableRowAction,\n TableRowTone,\n // Visualization + hierarchy nodes (SRP v0.4)\n MeterNode,\n GaugeNode,\n AvatarNode,\n ProgressNode,\n SparklineNode,\n ChartNode,\n TreeNode,\n TagInputNode,\n // Visualization + hierarchy supporting types (SRP v0.4)\n VizThreshold,\n GaugeZone,\n ChartPoint,\n ChartSeries,\n TreeItem,\n // Library-workspace nodes (SRP v0.5)\n PopoverNode,\n SliderNode,\n RatingNode,\n ThumbnailNode,\n // Configuration-control nodes (SRP v0.6)\n FacetGridNode,\n ColumnConfigNode,\n FacetOption,\n FacetCategory,\n ColumnConfigColumn,\n ColumnConfigPreset,\n // Token unions\n Gap,\n Padding,\n DividerSpacing,\n Cols,\n ColumnAlign,\n RowAlign,\n Justify,\n RecordLineVariant,\n ChipTone,\n TextSize,\n TextWeight,\n TextTone,\n ButtonVariant,\n ButtonSize,\n SkeletonShape,\n TableColumnAlign,\n TableCellKind,\n DataTableVariant,\n TextInputKind,\n RowTone,\n SortDirection,\n RowActionPosition,\n SegmentedVariant,\n VizTone,\n VizSize,\n MeterVariant,\n SparklineVariant,\n ChartKind,\n GlyphKind,\n // Library-workspace tokens (SRP v0.5)\n PopoverAlign,\n ThumbnailRadius,\n ThumbnailAspect,\n // Actions + queries\n ActionDesc,\n VQLQuery,\n // SRP v0.7 — host action grammar + query bindings\n SRPAction,\n EmitAction,\n EmitSpec,\n SetStateAction,\n NavigateAction,\n OpenPaneAction,\n QueryBinding,\n} from \"./schema.js\";\n\nexport { NODE_TYPES } from \"./schema.js\";\n\n// SRP v0.7 — the expression sublanguage (when guards + {…} interpolation)\nexport {\n evaluateExpression,\n evaluateGuard,\n interpolate,\n parseExpression,\n} from \"./expr.js\";\nexport type { Scope } from \"./expr.js\";\n\n// Validators\nexport {\n validateSRP,\n validateNode,\n isSRPDocument,\n isSRPNode,\n} from \"./validators.js\";\n\nexport type {\n ValidationResult,\n ValidationError,\n ValidationErrorCode,\n} from \"./validators.js\";\n\n// Markdown subset parser\nexport { parseInline, stripFormatting } from \"./markdown.js\";\nexport type { InlineNode } from \"./markdown.js\";\n"]}
|