@telorun/templating 0.12.0 → 0.14.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 -2
- package/dist/builtins.d.ts +11 -0
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +16 -0
- package/dist/cel/analyze.d.ts +2 -1
- package/dist/cel/analyze.d.ts.map +1 -1
- package/dist/cel/analyze.js +4 -5
- package/dist/cel/catalog.d.ts.map +1 -1
- package/dist/cel/catalog.js +27 -0
- package/dist/cel/diagnose.d.ts +50 -0
- package/dist/cel/diagnose.d.ts.map +1 -0
- package/dist/cel/diagnose.js +223 -0
- package/dist/cel/environment.d.ts +7 -0
- package/dist/cel/environment.d.ts.map +1 -1
- package/dist/cel/environment.js +19 -5
- package/dist/cel/walk.d.ts +17 -1
- package/dist/cel/walk.d.ts.map +1 -1
- package/dist/cel/walk.js +21 -3
- package/dist/engine.d.ts +110 -3
- package/dist/engine.d.ts.map +1 -1
- package/dist/engines/cel.d.ts +13 -9
- package/dist/engines/cel.d.ts.map +1 -1
- package/dist/engines/cel.js +86 -28
- package/dist/engines/include.d.ts +28 -0
- package/dist/engines/include.d.ts.map +1 -0
- package/dist/engines/include.js +142 -0
- package/dist/engines/literal.js +1 -1
- package/dist/engines/ref.js +1 -1
- package/dist/engines/sql.d.ts.map +1 -1
- package/dist/engines/sql.js +32 -5
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/manifest-schemas.d.ts +0 -16
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +0 -95
- package/dist/sentinel.d.ts +37 -0
- package/dist/sentinel.d.ts.map +1 -1
- package/dist/sentinel.js +54 -0
- package/package.json +2 -2
- package/src/builtins.ts +17 -0
- package/src/cel/analyze.ts +4 -7
- package/src/cel/catalog.ts +27 -0
- package/src/cel/diagnose.ts +293 -0
- package/src/cel/environment.ts +21 -5
- package/src/cel/walk.ts +37 -4
- package/src/engine.ts +116 -3
- package/src/engines/cel.ts +91 -31
- package/src/engines/include.ts +164 -0
- package/src/engines/literal.ts +1 -1
- package/src/engines/ref.ts +1 -1
- package/src/engines/sql.ts +41 -7
- package/src/index.ts +36 -5
- package/src/manifest-schemas.ts +0 -98
- package/src/sentinel.ts +62 -0
package/src/manifest-schemas.ts
CHANGED
|
@@ -60,104 +60,6 @@ export const ResourceRefSchema = {
|
|
|
60
60
|
],
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const REF_ANNOTATION = "x-telo-ref";
|
|
64
|
-
|
|
65
|
-
// The legacy base types a reference slot used to pin when references were
|
|
66
|
-
// written as plain strings. Post-migration a reference resolves to an object
|
|
67
|
-
// (the `{kind, name, alias?}` shape, or an unresolved `!ref` sentinel), so a
|
|
68
|
-
// scalar `type` on a ref slot is a stale constraint that would reject the
|
|
69
|
-
// resolved value. Object / array `type`s are left alone — they already admit
|
|
70
|
-
// the reference object (and any inline value a slot like `inputType` accepts).
|
|
71
|
-
const LEGACY_REF_SCALAR_TYPES = new Set(["string", "number", "integer", "boolean"]);
|
|
72
|
-
|
|
73
|
-
// JSON Schema keywords whose values are themselves subschemas. Split by shape so
|
|
74
|
-
// the ref-slot normalizer recurses only into schema positions — never into
|
|
75
|
-
// data-bearing keywords (`default`, `const`, `enum`, `examples`), where a stray
|
|
76
|
-
// `x-telo-ref` key would be data, not an annotation.
|
|
77
|
-
const SUBSCHEMA_SINGLE = [
|
|
78
|
-
"additionalProperties",
|
|
79
|
-
"additionalItems",
|
|
80
|
-
"contains",
|
|
81
|
-
"not",
|
|
82
|
-
"if",
|
|
83
|
-
"then",
|
|
84
|
-
"else",
|
|
85
|
-
"propertyNames",
|
|
86
|
-
"unevaluatedItems",
|
|
87
|
-
"unevaluatedProperties",
|
|
88
|
-
] as const;
|
|
89
|
-
const SUBSCHEMA_LIST = ["allOf", "anyOf", "oneOf", "prefixItems"] as const;
|
|
90
|
-
const SUBSCHEMA_MAP = [
|
|
91
|
-
"properties",
|
|
92
|
-
"patternProperties",
|
|
93
|
-
"$defs",
|
|
94
|
-
"definitions",
|
|
95
|
-
"dependentSchemas",
|
|
96
|
-
] as const;
|
|
97
|
-
|
|
98
|
-
/** Deep-clone `schema`, dropping the stale scalar `type` constraint from every
|
|
99
|
-
* reference-slot node — one carrying an `x-telo-ref` string annotation.
|
|
100
|
-
*
|
|
101
|
-
* A reference slot's value is always a `!ref` sentinel or its resolved
|
|
102
|
-
* `{kind, name, alias?}` object (never a bare string, post-migration). Older
|
|
103
|
-
* published modules still pin `type: "string"` on these slots — the encoding
|
|
104
|
-
* references took when they were written as plain strings — which now rejects
|
|
105
|
-
* the resolved object. Removing only the scalar `type` lets the analyzer and
|
|
106
|
-
* kernel accept references uniformly across module versions during the
|
|
107
|
-
* migration away from `{kind, name}` / string references, without disturbing
|
|
108
|
-
* slots that legitimately accept an inline object (e.g. `inputType` /
|
|
109
|
-
* `outputType`, which take a Telo.Type reference *or* an inline JSON schema).
|
|
110
|
-
* The `x-telo-ref` constraint itself (which kind the reference must satisfy) is
|
|
111
|
-
* checked separately by the analyzer's reference walker, which reads the
|
|
112
|
-
* original schema — not this validation-only copy. */
|
|
113
|
-
export function normalizeRefSlots(schema: unknown): unknown {
|
|
114
|
-
if (schema === null || typeof schema !== "object" || Array.isArray(schema)) {
|
|
115
|
-
return schema;
|
|
116
|
-
}
|
|
117
|
-
const node = schema as Record<string, unknown>;
|
|
118
|
-
const out: Record<string, unknown> = { ...node };
|
|
119
|
-
// Reference slot with a stale scalar `type` (legacy string-ref encoding):
|
|
120
|
-
// drop the constraint so the resolved reference object / sentinel validates.
|
|
121
|
-
//
|
|
122
|
-
// A presence test, not a shape test — deliberately, since `templating` sits
|
|
123
|
-
// BELOW the analyzer in the dependency order and cannot reach the shared
|
|
124
|
-
// `readRefSlot` accessor. Presence is the only thing this rule needs, and it
|
|
125
|
-
// is stable across every annotation shape.
|
|
126
|
-
if (
|
|
127
|
-
node[REF_ANNOTATION] !== undefined &&
|
|
128
|
-
typeof node.type === "string" &&
|
|
129
|
-
LEGACY_REF_SCALAR_TYPES.has(node.type)
|
|
130
|
-
) {
|
|
131
|
-
delete out.type;
|
|
132
|
-
}
|
|
133
|
-
for (const key of SUBSCHEMA_SINGLE) {
|
|
134
|
-
const value = node[key];
|
|
135
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
136
|
-
out[key] = normalizeRefSlots(value);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
for (const key of SUBSCHEMA_LIST) {
|
|
140
|
-
const value = node[key];
|
|
141
|
-
if (Array.isArray(value)) out[key] = value.map(normalizeRefSlots);
|
|
142
|
-
}
|
|
143
|
-
// `items` is either a single subschema or a tuple of subschemas.
|
|
144
|
-
if (Array.isArray(node.items)) {
|
|
145
|
-
out.items = node.items.map(normalizeRefSlots);
|
|
146
|
-
} else if (node.items && typeof node.items === "object") {
|
|
147
|
-
out.items = normalizeRefSlots(node.items);
|
|
148
|
-
}
|
|
149
|
-
for (const key of SUBSCHEMA_MAP) {
|
|
150
|
-
const value = node[key];
|
|
151
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
152
|
-
const mapped: Record<string, unknown> = {};
|
|
153
|
-
for (const [name, sub] of Object.entries(value as Record<string, unknown>)) {
|
|
154
|
-
mapped[name] = normalizeRefSlots(sub);
|
|
155
|
-
}
|
|
156
|
-
out[key] = mapped;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return out;
|
|
160
|
-
}
|
|
161
63
|
|
|
162
64
|
/** Stable URI under which the shared manifest root schema is registered
|
|
163
65
|
* with module-side AJV instances. Module YAMLs reach the fragments via
|
package/src/sentinel.ts
CHANGED
|
@@ -32,3 +32,65 @@ export function makeTaggedSentinel(engine: string, source: string): TaggedSentin
|
|
|
32
32
|
export function isRefSentinel(v: unknown): v is TaggedSentinel & { engine: "ref" } {
|
|
33
33
|
return isTaggedSentinel(v) && v.engine === "ref";
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
/** The CEL engine's name. Beside the other sentinel predicates for the same
|
|
37
|
+
* reason they are: a consumer that spells an engine name inline is a second
|
|
38
|
+
* place the registry's key is written down. */
|
|
39
|
+
export const CEL_ENGINE = "cel";
|
|
40
|
+
|
|
41
|
+
/** Engine names of the two file-embedding tags. Named here beside the other
|
|
42
|
+
* sentinel predicates so the kernel's resolution pass and the engines
|
|
43
|
+
* themselves agree on one spelling. */
|
|
44
|
+
export const INCLUDE_TEXT_ENGINE = "include-text";
|
|
45
|
+
export const INCLUDE_BYTES_ENGINE = "include-bytes";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The dotted chain a value names, or undefined.
|
|
49
|
+
*
|
|
50
|
+
* Only a PLAIN CHAIN — `steps.encode.result.output` — in either spelling a
|
|
51
|
+
* manifest may carry it: a `!cel` sentinel or the `${{ }}` string form. An
|
|
52
|
+
* expression that COMPUTES rather than names has no schema to read off a context,
|
|
53
|
+
* so a caller that navigates one gets nothing and reports nothing: silence where
|
|
54
|
+
* the analyzer knows least is the conservative direction.
|
|
55
|
+
*
|
|
56
|
+
* Here rather than in each caller because "is this expression a plain chain" is
|
|
57
|
+
* one question, and two copies of the answer would eventually disagree about a
|
|
58
|
+
* shape like `a.b[0]`.
|
|
59
|
+
*/
|
|
60
|
+
export function plainChainOf(value: unknown): string | undefined {
|
|
61
|
+
const source = isTaggedSentinel(value)
|
|
62
|
+
? value.engine === CEL_ENGINE
|
|
63
|
+
? value.source
|
|
64
|
+
: undefined
|
|
65
|
+
: typeof value === "string"
|
|
66
|
+
? /^\s*\$\{\{(.+)\}\}\s*$/.exec(value)?.[1]
|
|
67
|
+
: undefined;
|
|
68
|
+
if (typeof source !== "string") return undefined;
|
|
69
|
+
const trimmed = source.trim();
|
|
70
|
+
return /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(trimmed)
|
|
71
|
+
? trimmed
|
|
72
|
+
: undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Both file-embedding tag names, for a consumer holding an engine NAME rather
|
|
76
|
+
* than a value (the analyzer's expression walk reports names). */
|
|
77
|
+
export const INCLUDE_ENGINE_NAMES: ReadonlySet<string> = new Set([
|
|
78
|
+
INCLUDE_TEXT_ENGINE,
|
|
79
|
+
INCLUDE_BYTES_ENGINE,
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
/** True when `v` is an `!include-text` / `!include-bytes` sentinel — a file
|
|
83
|
+
* embed marked at parse time and still unresolved.
|
|
84
|
+
*
|
|
85
|
+
* The kernel's creation-time resolution keys off this the way Phase-5
|
|
86
|
+
* injection keys off {@link isRefSentinel}. Both tags survive precompile as
|
|
87
|
+
* markers rather than collapsing to a value, because the read is deferred: a
|
|
88
|
+
* manifest load must not pull payload layers, and the analyzer that types the
|
|
89
|
+
* slot cannot open files at all. */
|
|
90
|
+
export function isIncludeSentinel(
|
|
91
|
+
v: unknown,
|
|
92
|
+
): v is TaggedSentinel & { engine: typeof INCLUDE_TEXT_ENGINE | typeof INCLUDE_BYTES_ENGINE } {
|
|
93
|
+
return (
|
|
94
|
+
isTaggedSentinel(v) && (v.engine === INCLUDE_TEXT_ENGINE || v.engine === INCLUDE_BYTES_ENGINE)
|
|
95
|
+
);
|
|
96
|
+
}
|