@telorun/templating 0.4.1 → 0.6.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 +7 -14
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +7 -1
- package/dist/cel/catalog.d.ts +45 -0
- package/dist/cel/catalog.d.ts.map +1 -0
- package/dist/cel/catalog.js +475 -0
- package/dist/cel/compile.d.ts +15 -1
- package/dist/cel/compile.d.ts.map +1 -1
- package/dist/cel/compile.js +37 -0
- package/dist/cel/environment.d.ts +10 -9
- package/dist/cel/environment.d.ts.map +1 -1
- package/dist/cel/environment.js +26 -20
- package/dist/engines/cel.d.ts +7 -1
- package/dist/engines/cel.d.ts.map +1 -1
- package/dist/engines/cel.js +38 -30
- package/dist/engines/sql.d.ts +11 -0
- package/dist/engines/sql.d.ts.map +1 -0
- package/dist/engines/sql.js +38 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/package.json +4 -2
- package/src/builtins.ts +7 -1
- package/src/cel/catalog.ts +540 -0
- package/src/cel/compile.ts +39 -1
- package/src/cel/environment.ts +27 -24
- package/src/engines/cel.ts +44 -35
- package/src/engines/sql.ts +44 -0
- package/src/index.ts +9 -0
package/src/engines/cel.ts
CHANGED
|
@@ -4,7 +4,49 @@ import {
|
|
|
4
4
|
validateChainAgainstSchema,
|
|
5
5
|
} from "../cel/analyze.js";
|
|
6
6
|
import { compileExpression } from "../cel/compile.js";
|
|
7
|
-
import type { EngineDiagnostic, TemplatingEngine } from "../engine.js";
|
|
7
|
+
import type { AnalyzeEnv, EngineDiagnostic, TemplatingEngine } from "../engine.js";
|
|
8
|
+
|
|
9
|
+
/** Statically analyze one CEL expression against the effective context schema:
|
|
10
|
+
* parse → extract member-access chains → validate each chain → flag nullable
|
|
11
|
+
* access. Single source of truth shared by the `!cel` engine (one expression)
|
|
12
|
+
* and the `!sql` engine (one per `${{ }}` interpolation), so diagnostic wording
|
|
13
|
+
* can't drift between them. */
|
|
14
|
+
export function analyzeCelExpression(source: string, env: AnalyzeEnv): EngineDiagnostic[] {
|
|
15
|
+
const out: EngineDiagnostic[] = [];
|
|
16
|
+
|
|
17
|
+
let parsed: ReturnType<typeof env.celEnv.parse>;
|
|
18
|
+
try {
|
|
19
|
+
parsed = env.celEnv.parse(source);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
out.push({
|
|
22
|
+
code: "CEL_SYNTAX_ERROR",
|
|
23
|
+
message: e instanceof Error ? e.message : String(e),
|
|
24
|
+
});
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!env.contextSchema) return out;
|
|
29
|
+
|
|
30
|
+
const chains = extractAccessChains(parsed.ast);
|
|
31
|
+
for (const chain of chains) {
|
|
32
|
+
const err = validateChainAgainstSchema(chain, env.contextSchema as Record<string, any>);
|
|
33
|
+
if (err) out.push({ code: "CEL_UNKNOWN_FIELD", message: err });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const issue of findNullableAccessIssues(
|
|
37
|
+
parsed.ast,
|
|
38
|
+
env.contextSchema as Record<string, any>,
|
|
39
|
+
)) {
|
|
40
|
+
// Index access (member "[index]") attaches without a dot; a named field
|
|
41
|
+
// attaches with one — so the suggested CEL stays valid either way.
|
|
42
|
+
const access = issue.member === "[index]" ? issue.member : `.${issue.member}`;
|
|
43
|
+
out.push({
|
|
44
|
+
code: "CEL_NULLABLE_ACCESS",
|
|
45
|
+
message: `'${issue.path}' may be null — guard it (e.g. '${issue.path} != null && …' or '${issue.path} == null ? … : ${issue.path}${access}') before accessing '${access}'`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
8
50
|
|
|
9
51
|
/** The `!cel` engine. Treats the entire tagged scalar as a single CEL
|
|
10
52
|
* expression — no `${{ }}` wrapping. Analysis runs the same chain validator
|
|
@@ -19,39 +61,6 @@ export const celEngine: TemplatingEngine = {
|
|
|
19
61
|
},
|
|
20
62
|
|
|
21
63
|
analyze(source, env) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
let parsed: ReturnType<typeof env.celEnv.parse>;
|
|
25
|
-
try {
|
|
26
|
-
parsed = env.celEnv.parse(source);
|
|
27
|
-
} catch (e) {
|
|
28
|
-
out.push({
|
|
29
|
-
code: "CEL_SYNTAX_ERROR",
|
|
30
|
-
message: e instanceof Error ? e.message : String(e),
|
|
31
|
-
});
|
|
32
|
-
return out;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!env.contextSchema) return out;
|
|
36
|
-
|
|
37
|
-
const chains = extractAccessChains(parsed.ast);
|
|
38
|
-
for (const chain of chains) {
|
|
39
|
-
const err = validateChainAgainstSchema(chain, env.contextSchema as Record<string, any>);
|
|
40
|
-
if (err) out.push({ code: "CEL_UNKNOWN_FIELD", message: err });
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
for (const issue of findNullableAccessIssues(
|
|
44
|
-
parsed.ast,
|
|
45
|
-
env.contextSchema as Record<string, any>,
|
|
46
|
-
)) {
|
|
47
|
-
// Index access (member "[index]") attaches without a dot; a named field
|
|
48
|
-
// attaches with one — so the suggested CEL stays valid either way.
|
|
49
|
-
const access = issue.member === "[index]" ? issue.member : `.${issue.member}`;
|
|
50
|
-
out.push({
|
|
51
|
-
code: "CEL_NULLABLE_ACCESS",
|
|
52
|
-
message: `'${issue.path}' may be null — guard it (e.g. '${issue.path} != null && …' or '${issue.path} == null ? … : ${issue.path}${access}') before accessing '${access}'`,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
return out;
|
|
64
|
+
return analyzeCelExpression(source, env);
|
|
56
65
|
},
|
|
57
66
|
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { isParameterizedSql, type CompiledValue, type ParameterizedSql } from "@telorun/sdk";
|
|
2
|
+
import { analyzeCelExpression } from "./cel.js";
|
|
3
|
+
import { compileString, toParameterized, TEMPLATE_REGEX } from "../cel/compile.js";
|
|
4
|
+
import type { TemplatingEngine } from "../engine.js";
|
|
5
|
+
|
|
6
|
+
export { isParameterizedSql, type ParameterizedSql };
|
|
7
|
+
|
|
8
|
+
/** The `!sql` engine. Treats the tagged scalar as a SQL string with `${{ }}`
|
|
9
|
+
* interpolations whose values are *bound*, not spliced. Unlike `!cel` (one bare
|
|
10
|
+
* expression) it keeps the literal text and each interpolation separate: at
|
|
11
|
+
* runtime `call()` returns a {@link ParameterizedSql} the consumer turns into a
|
|
12
|
+
* parameterized query. Generic expansion passes that object through untouched
|
|
13
|
+
* (it is the `call()` result), so it survives the step-level input expansion. */
|
|
14
|
+
export const sqlEngine: TemplatingEngine = {
|
|
15
|
+
name: "sql",
|
|
16
|
+
language: "sql",
|
|
17
|
+
|
|
18
|
+
compile(source, env) {
|
|
19
|
+
const inner = compileString(source, env.celEnv);
|
|
20
|
+
return {
|
|
21
|
+
__compiled: true,
|
|
22
|
+
source,
|
|
23
|
+
call: (ctx: Record<string, unknown>): ParameterizedSql => {
|
|
24
|
+
const { fragments, values } = toParameterized(inner, ctx);
|
|
25
|
+
return { __teloParameterized: true, fragments, values };
|
|
26
|
+
},
|
|
27
|
+
} satisfies CompiledValue;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
analyze(source, env) {
|
|
31
|
+
// Each `${{ }}` interpolation is its own CEL expression; reuse the shared
|
|
32
|
+
// per-expression analyzer so diagnostics match the `!cel` engine exactly.
|
|
33
|
+
return expressionsOf(source).flatMap((expr) => analyzeCelExpression(expr, env));
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Extract each `${{ expr }}` body from a `!sql` template source. */
|
|
38
|
+
function expressionsOf(source: string): string[] {
|
|
39
|
+
const exprs: string[] = [];
|
|
40
|
+
for (const m of source.matchAll(TEMPLATE_REGEX)) {
|
|
41
|
+
exprs.push(m[1]!.trim());
|
|
42
|
+
}
|
|
43
|
+
return exprs;
|
|
44
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
export { buildCelEnvironment, type CelHandlers } from "./cel/environment.js";
|
|
2
|
+
export {
|
|
3
|
+
celFunctionCatalog,
|
|
4
|
+
CEL_FUNCTIONS,
|
|
5
|
+
type CelFunctionInfo,
|
|
6
|
+
type CelFunctionDoc,
|
|
7
|
+
type CelFunctionCategory,
|
|
8
|
+
} from "./cel/catalog.js";
|
|
2
9
|
export {
|
|
3
10
|
compileExpression,
|
|
4
11
|
compileString,
|
|
12
|
+
toParameterized,
|
|
5
13
|
TEMPLATE_REGEX,
|
|
6
14
|
EXACT_TEMPLATE_REGEX,
|
|
7
15
|
} from "./cel/compile.js";
|
|
@@ -16,6 +24,7 @@ export { walkCelExpressions } from "./cel/walk.js";
|
|
|
16
24
|
export { celEngine } from "./engines/cel.js";
|
|
17
25
|
export { literalEngine } from "./engines/literal.js";
|
|
18
26
|
export { refEngine } from "./engines/ref.js";
|
|
27
|
+
export { sqlEngine, isParameterizedSql, type ParameterizedSql } from "./engines/sql.js";
|
|
19
28
|
|
|
20
29
|
export { TemplatingEngineRegistry } from "./registry.js";
|
|
21
30
|
export { builtinEngines, createDefaultRegistry, defaultRegistry } from "./builtins.js";
|