@telorun/templating 0.10.0 → 0.11.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/README.md +2 -141
- package/dist/cel/catalog.d.ts.map +1 -1
- package/dist/cel/catalog.js +20 -0
- package/dist/cel/compile.d.ts.map +1 -1
- package/dist/cel/compile.js +24 -0
- package/dist/cel/environment.d.ts.map +1 -1
- package/dist/cel/environment.js +5 -1
- package/package.json +2 -2
- package/src/cel/catalog.ts +22 -0
- package/src/cel/compile.ts +22 -0
- package/src/cel/environment.ts +5 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Built to be language-agnostic and infinitely extensible.
|
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
25
|
# Reconcile your manifest into a running backend
|
|
26
|
-
$ telo ./examples/hello-api
|
|
26
|
+
$ telo ./examples/hello-api
|
|
27
27
|
|
|
28
28
|
{"level":30,"time":1771610393008,"pid":1310178,"hostname":"dev","msg":"Server listening at http://127.0.0.1:8844"}
|
|
29
29
|
```
|
|
@@ -44,146 +44,7 @@ $ telo ./examples/hello-api.yaml
|
|
|
44
44
|
|
|
45
45
|
## Example manifest
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
```yaml
|
|
50
|
-
kind: Telo.Application
|
|
51
|
-
metadata:
|
|
52
|
-
name: feedback
|
|
53
|
-
version: 1.0.0
|
|
54
|
-
description: |
|
|
55
|
-
A complete feedback collection REST API — no code, pure YAML.
|
|
56
|
-
Persists entries to SQLite and serves them over HTTP.
|
|
57
|
-
imports:
|
|
58
|
-
Http: std/http-server@0.11.0
|
|
59
|
-
Sql: std/sql@0.9.0
|
|
60
|
-
targets:
|
|
61
|
-
- !ref Migrations
|
|
62
|
-
- !ref Server
|
|
63
|
-
---
|
|
64
|
-
# SQLite database — swap driver/host/database for PostgreSQL with zero YAML changes
|
|
65
|
-
kind: Sql.Connection
|
|
66
|
-
metadata:
|
|
67
|
-
name: Db
|
|
68
|
-
driver: sqlite
|
|
69
|
-
file: ./tmp/feedback.db
|
|
70
|
-
---
|
|
71
|
-
# Migrations: applied automatically before the server starts
|
|
72
|
-
kind: Sql.Migrations
|
|
73
|
-
metadata:
|
|
74
|
-
name: Migrations
|
|
75
|
-
connection: !ref Db
|
|
76
|
-
---
|
|
77
|
-
kind: Sql.Migration
|
|
78
|
-
metadata:
|
|
79
|
-
name: Migration_20260413_182154_CreateFeedback
|
|
80
|
-
version: 20260413_182154_CreateFeedback
|
|
81
|
-
sql: |
|
|
82
|
-
CREATE TABLE IF NOT EXISTS feedback (
|
|
83
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
84
|
-
text TEXT NOT NULL,
|
|
85
|
-
source TEXT,
|
|
86
|
-
score INTEGER NOT NULL DEFAULT 0,
|
|
87
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
88
|
-
)
|
|
89
|
-
---
|
|
90
|
-
kind: Http.Server
|
|
91
|
-
metadata:
|
|
92
|
-
name: Server
|
|
93
|
-
baseUrl: http://localhost:8844
|
|
94
|
-
port: 8844
|
|
95
|
-
logger: true
|
|
96
|
-
openapi:
|
|
97
|
-
info:
|
|
98
|
-
title: Feedback API
|
|
99
|
-
version: 1.0.0
|
|
100
|
-
mounts:
|
|
101
|
-
- path: /v1
|
|
102
|
-
mount: !ref FeedbackRoutes
|
|
103
|
-
---
|
|
104
|
-
kind: Http.Api
|
|
105
|
-
metadata:
|
|
106
|
-
name: FeedbackRoutes
|
|
107
|
-
routes:
|
|
108
|
-
# POST /v1/feedback — insert a new entry, score derived from body length heuristic
|
|
109
|
-
- request:
|
|
110
|
-
path: /feedback
|
|
111
|
-
method: POST
|
|
112
|
-
schema:
|
|
113
|
-
body:
|
|
114
|
-
type: object
|
|
115
|
-
properties:
|
|
116
|
-
text:
|
|
117
|
-
type: string
|
|
118
|
-
minLength: 1
|
|
119
|
-
source:
|
|
120
|
-
type: string
|
|
121
|
-
required: [ text ]
|
|
122
|
-
handler:
|
|
123
|
-
kind: Sql.Exec
|
|
124
|
-
connection: !ref Db
|
|
125
|
-
inputs:
|
|
126
|
-
sql: "INSERT INTO feedback (text, source, score) VALUES (?, ?, ?)"
|
|
127
|
-
bindings:
|
|
128
|
-
- "${{ request.body.text }}"
|
|
129
|
-
- "${{ request.body.source }}"
|
|
130
|
-
- "${{ size(request.body.text) }}"
|
|
131
|
-
response:
|
|
132
|
-
- status: 201
|
|
133
|
-
headers:
|
|
134
|
-
Content-Type: application/json
|
|
135
|
-
body:
|
|
136
|
-
ok: true
|
|
137
|
-
message: Feedback received
|
|
138
|
-
|
|
139
|
-
# GET /v1/feedback — list all entries, newest first
|
|
140
|
-
- request:
|
|
141
|
-
path: /feedback
|
|
142
|
-
method: GET
|
|
143
|
-
handler:
|
|
144
|
-
kind: Sql.Select
|
|
145
|
-
connection: !ref Db
|
|
146
|
-
from: feedback
|
|
147
|
-
columns: [ id, text, source, score, created_at ]
|
|
148
|
-
orderBy:
|
|
149
|
-
- { column: created_at, direction: desc }
|
|
150
|
-
response:
|
|
151
|
-
- status: 200
|
|
152
|
-
headers:
|
|
153
|
-
Content-Type: application/json
|
|
154
|
-
body: "${{ result.rows }}"
|
|
155
|
-
|
|
156
|
-
# GET /v1/feedback/{id} — fetch a single entry
|
|
157
|
-
- request:
|
|
158
|
-
path: /feedback/{id}
|
|
159
|
-
method: GET
|
|
160
|
-
schema:
|
|
161
|
-
params:
|
|
162
|
-
type: object
|
|
163
|
-
properties:
|
|
164
|
-
id:
|
|
165
|
-
type: integer
|
|
166
|
-
required: [ id ]
|
|
167
|
-
handler:
|
|
168
|
-
kind: Sql.Select
|
|
169
|
-
connection: !ref Db
|
|
170
|
-
from: feedback
|
|
171
|
-
columns: [ id, text, source, score, created_at ]
|
|
172
|
-
where:
|
|
173
|
-
- { column: id, op: "=", value: "${{ request.params.id }}" }
|
|
174
|
-
response:
|
|
175
|
-
- status: 200
|
|
176
|
-
when: "size(result.rows) > 0"
|
|
177
|
-
headers:
|
|
178
|
-
Content-Type: application/json
|
|
179
|
-
body: "${{ result.rows[0] }}"
|
|
180
|
-
- status: 404
|
|
181
|
-
headers:
|
|
182
|
-
Content-Type: application/json
|
|
183
|
-
body:
|
|
184
|
-
ok: false
|
|
185
|
-
message: Not found
|
|
186
|
-
```
|
|
47
|
+
See [examples/](./examples/) for a list of working applications.
|
|
187
48
|
|
|
188
49
|
## Status
|
|
189
50
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/cel/catalog.ts"],"names":[],"mappings":"AAGA;;;0DAG0D;AAC1D,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9B,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9B,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAClE,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;CAClC;AA2CD,MAAM,MAAM,mBAAmB,GAC3B,YAAY,GACZ,MAAM,GACN,MAAM,GACN,QAAQ,GACR,MAAM,GACN,YAAY,GACZ,MAAM,GACN,UAAU,GACV,SAAS,GACT,MAAM,CAAC;AAEX;;;qBAGqB;AACrB,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;0EACsE;IACtE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;yDAIqD;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;wBACoB;IACpB,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;2EACuE;IACvE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,WAAW,KAAK,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC;CACjE;AAED,wEAAwE;AACxE,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;AAyE5D,eAAO,MAAM,aAAa,EAAE,SAAS,cAAc,
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/cel/catalog.ts"],"names":[],"mappings":"AAGA;;;0DAG0D;AAC1D,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9B,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9B,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAClE,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;CAClC;AA2CD,MAAM,MAAM,mBAAmB,GAC3B,YAAY,GACZ,MAAM,GACN,MAAM,GACN,QAAQ,GACR,MAAM,GACN,YAAY,GACZ,MAAM,GACN,UAAU,GACV,SAAS,GACT,MAAM,CAAC;AAEX;;;qBAGqB;AACrB,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;0EACsE;IACtE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;yDAIqD;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;wBACoB;IACpB,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;2EACuE;IACvE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,WAAW,KAAK,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC;CACjE;AAED,wEAAwE;AACxE,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;AAyE5D,eAAO,MAAM,aAAa,EAAE,SAAS,cAAc,EAogBlD,CAAC;AAEF,oEAAoE;AACpE,wBAAgB,kBAAkB,IAAI,eAAe,EAAE,CAEtD"}
|
package/dist/cel/catalog.js
CHANGED
|
@@ -358,6 +358,26 @@ export const CEL_FUNCTIONS = [
|
|
|
358
358
|
hostBacked: false,
|
|
359
359
|
build: () => (list) => minMax(list, false),
|
|
360
360
|
},
|
|
361
|
+
{
|
|
362
|
+
name: "sum",
|
|
363
|
+
signature: "sum(list): double",
|
|
364
|
+
category: "math",
|
|
365
|
+
summary: "Sum of a list's numeric elements; 0 for an empty list.",
|
|
366
|
+
deterministic: true,
|
|
367
|
+
hostBacked: false,
|
|
368
|
+
build: () => (list) => Array.isArray(list) ? list.reduce((acc, x) => acc + num(x), 0) : 0,
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: "avg",
|
|
372
|
+
signature: "avg(list): dyn",
|
|
373
|
+
category: "math",
|
|
374
|
+
summary: "Arithmetic mean of a list's numeric elements; null for an empty list.",
|
|
375
|
+
deterministic: true,
|
|
376
|
+
hostBacked: false,
|
|
377
|
+
build: () => (list) => Array.isArray(list) && list.length > 0
|
|
378
|
+
? list.reduce((acc, x) => acc + num(x), 0) / list.length
|
|
379
|
+
: null,
|
|
380
|
+
},
|
|
361
381
|
// JSON
|
|
362
382
|
{
|
|
363
383
|
name: "json",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/cel/compile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/cel/compile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGxD,eAAO,MAAM,cAAc,QAA8B,CAAC;AAC1D,eAAO,MAAM,oBAAoB,QAAqC,CAAC;AAgBvE;;2CAE2C;AAC3C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,aAAa,CAQ/E;AAED;;;;+DAI+D;AAC/D,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CA8BlE;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAE,CAsB5C"}
|
package/dist/cel/compile.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { isCompiledValue } from "@telorun/sdk";
|
|
2
|
+
import { extractAccessChains } from "./analyze.js";
|
|
2
3
|
export const TEMPLATE_REGEX = /\$\{\{\s*([^}]+?)\s*\}\}/g;
|
|
3
4
|
export const EXACT_TEMPLATE_REGEX = /^\s*\$\{\{\s*([^}]+?)\s*\}\}\s*$/;
|
|
5
|
+
/** Root variable identifiers an expression reads, from its parsed AST — the
|
|
6
|
+
* first element of every member-access chain (`self.table` → `self`). Returns
|
|
7
|
+
* undefined when the parse result carries no AST so consumers can tell "no
|
|
8
|
+
* refs" from "unknown". */
|
|
9
|
+
function rootRefs(parsed) {
|
|
10
|
+
const ast = parsed.ast;
|
|
11
|
+
if (!ast)
|
|
12
|
+
return undefined;
|
|
13
|
+
const roots = new Set();
|
|
14
|
+
for (const chain of extractAccessChains(ast)) {
|
|
15
|
+
if (chain.length > 0)
|
|
16
|
+
roots.add(chain[0]);
|
|
17
|
+
}
|
|
18
|
+
return [...roots];
|
|
19
|
+
}
|
|
4
20
|
/** Compile a single CEL expression (no `${{ }}` wrapping) into a CompiledValue.
|
|
5
21
|
* Throws on syntax errors. Used by the `!cel` engine where the entire tagged
|
|
6
22
|
* scalar is treated as one expression. */
|
|
@@ -9,6 +25,7 @@ export function compileExpression(expr, env) {
|
|
|
9
25
|
return {
|
|
10
26
|
__compiled: true,
|
|
11
27
|
source: expr,
|
|
28
|
+
refs: rootRefs(fn),
|
|
12
29
|
call: (ctx) => fn(ctx),
|
|
13
30
|
};
|
|
14
31
|
}
|
|
@@ -34,10 +51,17 @@ export function compileString(s, env) {
|
|
|
34
51
|
}
|
|
35
52
|
if (last < s.length)
|
|
36
53
|
parts.push(s.slice(last));
|
|
54
|
+
const refs = new Set();
|
|
55
|
+
for (const p of parts) {
|
|
56
|
+
if (typeof p !== "string" && p.refs)
|
|
57
|
+
for (const r of p.refs)
|
|
58
|
+
refs.add(r);
|
|
59
|
+
}
|
|
37
60
|
return {
|
|
38
61
|
__compiled: true,
|
|
39
62
|
source: s,
|
|
40
63
|
parts,
|
|
64
|
+
refs: [...refs],
|
|
41
65
|
call: (ctx) => parts.map((p) => (typeof p === "string" ? p : String(p.call(ctx) ?? ""))).join(""),
|
|
42
66
|
};
|
|
43
67
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/cel/environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAE/D,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAoBhD;;;;;;;;;;;;;;;yDAeyD;AACzD;;;;;wBAKwB;AACxB,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAyB5D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/cel/environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAE/D,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAoBhD;;;;;;;;;;;;;;;yDAeyD;AACzD;;;;;wBAKwB;AACxB,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAyB5D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAkBpF"}
|
package/dist/cel/environment.js
CHANGED
|
@@ -68,7 +68,11 @@ export function deriveSignatures(signature) {
|
|
|
68
68
|
}
|
|
69
69
|
export function buildCelEnvironment(handlers = {}) {
|
|
70
70
|
const h = { ...STUB_HANDLERS, ...handlers };
|
|
71
|
-
|
|
71
|
+
// cel-go defaults HomogeneousAggregateLiterals OFF: heterogeneous list/map
|
|
72
|
+
// literals unify to `dyn` rather than erroring. cel-js flips that default to
|
|
73
|
+
// strict; we align with cel-go so manifests (dyn-heavy: request, rows, …)
|
|
74
|
+
// don't hit false positives the runtime evaluates fine.
|
|
75
|
+
let env = new Environment({ unlistedVariablesAreDyn: true, enableOptionalTypes: true, homogeneousAggregateLiterals: false });
|
|
72
76
|
for (const fn of CEL_FUNCTIONS) {
|
|
73
77
|
const impl = fn.build(h);
|
|
74
78
|
// `register` lists one cel-js signature per arity (overloaded functions).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/templating",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Telo Templating - Engine registry and shared CEL core for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@types/uuid": "^10.0.0",
|
|
46
46
|
"typescript": "^5.0.0",
|
|
47
47
|
"vitest": "^2.1.8",
|
|
48
|
-
"@telorun/sdk": "0.
|
|
48
|
+
"@telorun/sdk": "0.50.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"@telorun/sdk": "*"
|
package/src/cel/catalog.ts
CHANGED
|
@@ -428,6 +428,28 @@ export const CEL_FUNCTIONS: readonly CelFunctionDoc[] = [
|
|
|
428
428
|
hostBacked: false,
|
|
429
429
|
build: () => (list: unknown[]) => minMax(list, false),
|
|
430
430
|
},
|
|
431
|
+
{
|
|
432
|
+
name: "sum",
|
|
433
|
+
signature: "sum(list): double",
|
|
434
|
+
category: "math",
|
|
435
|
+
summary: "Sum of a list's numeric elements; 0 for an empty list.",
|
|
436
|
+
deterministic: true,
|
|
437
|
+
hostBacked: false,
|
|
438
|
+
build: () => (list: unknown[]) =>
|
|
439
|
+
Array.isArray(list) ? list.reduce((acc: number, x) => acc + num(x), 0) : 0,
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: "avg",
|
|
443
|
+
signature: "avg(list): dyn",
|
|
444
|
+
category: "math",
|
|
445
|
+
summary: "Arithmetic mean of a list's numeric elements; null for an empty list.",
|
|
446
|
+
deterministic: true,
|
|
447
|
+
hostBacked: false,
|
|
448
|
+
build: () => (list: unknown[]) =>
|
|
449
|
+
Array.isArray(list) && list.length > 0
|
|
450
|
+
? list.reduce((acc: number, x) => acc + num(x), 0) / list.length
|
|
451
|
+
: null,
|
|
452
|
+
},
|
|
431
453
|
// JSON
|
|
432
454
|
{
|
|
433
455
|
name: "json",
|
package/src/cel/compile.ts
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import { isCompiledValue, type CompiledValue } from "@telorun/sdk";
|
|
2
2
|
import type { Environment } from "@marcbachmann/cel-js";
|
|
3
|
+
import { extractAccessChains } from "./analyze.js";
|
|
3
4
|
|
|
4
5
|
export const TEMPLATE_REGEX = /\$\{\{\s*([^}]+?)\s*\}\}/g;
|
|
5
6
|
export const EXACT_TEMPLATE_REGEX = /^\s*\$\{\{\s*([^}]+?)\s*\}\}\s*$/;
|
|
6
7
|
|
|
8
|
+
/** Root variable identifiers an expression reads, from its parsed AST — the
|
|
9
|
+
* first element of every member-access chain (`self.table` → `self`). Returns
|
|
10
|
+
* undefined when the parse result carries no AST so consumers can tell "no
|
|
11
|
+
* refs" from "unknown". */
|
|
12
|
+
function rootRefs(parsed: unknown): readonly string[] | undefined {
|
|
13
|
+
const ast = (parsed as { ast?: unknown }).ast;
|
|
14
|
+
if (!ast) return undefined;
|
|
15
|
+
const roots = new Set<string>();
|
|
16
|
+
for (const chain of extractAccessChains(ast as never)) {
|
|
17
|
+
if (chain.length > 0) roots.add(chain[0]!);
|
|
18
|
+
}
|
|
19
|
+
return [...roots];
|
|
20
|
+
}
|
|
21
|
+
|
|
7
22
|
/** Compile a single CEL expression (no `${{ }}` wrapping) into a CompiledValue.
|
|
8
23
|
* Throws on syntax errors. Used by the `!cel` engine where the entire tagged
|
|
9
24
|
* scalar is treated as one expression. */
|
|
@@ -12,6 +27,7 @@ export function compileExpression(expr: string, env: Environment): CompiledValue
|
|
|
12
27
|
return {
|
|
13
28
|
__compiled: true,
|
|
14
29
|
source: expr,
|
|
30
|
+
refs: rootRefs(fn),
|
|
15
31
|
call: (ctx: Record<string, unknown>) => fn(ctx),
|
|
16
32
|
};
|
|
17
33
|
}
|
|
@@ -38,10 +54,16 @@ export function compileString(s: string, env: Environment): unknown {
|
|
|
38
54
|
}
|
|
39
55
|
if (last < s.length) parts.push(s.slice(last));
|
|
40
56
|
|
|
57
|
+
const refs = new Set<string>();
|
|
58
|
+
for (const p of parts) {
|
|
59
|
+
if (typeof p !== "string" && p.refs) for (const r of p.refs) refs.add(r);
|
|
60
|
+
}
|
|
61
|
+
|
|
41
62
|
return {
|
|
42
63
|
__compiled: true,
|
|
43
64
|
source: s,
|
|
44
65
|
parts,
|
|
66
|
+
refs: [...refs],
|
|
45
67
|
call: (ctx: Record<string, unknown>) =>
|
|
46
68
|
parts.map((p) => (typeof p === "string" ? p : String(p.call(ctx) ?? ""))).join(""),
|
|
47
69
|
} satisfies CompiledValue;
|
package/src/cel/environment.ts
CHANGED
|
@@ -73,7 +73,11 @@ export function deriveSignatures(signature: string): string[] {
|
|
|
73
73
|
|
|
74
74
|
export function buildCelEnvironment(handlers: Partial<CelHandlers> = {}): Environment {
|
|
75
75
|
const h: CelHandlers = { ...STUB_HANDLERS, ...handlers };
|
|
76
|
-
|
|
76
|
+
// cel-go defaults HomogeneousAggregateLiterals OFF: heterogeneous list/map
|
|
77
|
+
// literals unify to `dyn` rather than erroring. cel-js flips that default to
|
|
78
|
+
// strict; we align with cel-go so manifests (dyn-heavy: request, rows, …)
|
|
79
|
+
// don't hit false positives the runtime evaluates fine.
|
|
80
|
+
let env = new Environment({ unlistedVariablesAreDyn: true, enableOptionalTypes: true, homogeneousAggregateLiterals: false });
|
|
77
81
|
for (const fn of CEL_FUNCTIONS) {
|
|
78
82
|
const impl = fn.build(h);
|
|
79
83
|
// `register` lists one cel-js signature per arity (overloaded functions).
|