@telorun/templating 0.13.0 → 0.15.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/dist/builtins.d.ts +11 -0
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +13 -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 +100 -0
- package/dist/engine.d.ts +19 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engines/include.d.ts +5 -2
- package/dist/engines/include.d.ts.map +1 -1
- package/dist/engines/include.js +15 -5
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- 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 +18 -0
- package/dist/sentinel.d.ts.map +1 -1
- package/dist/sentinel.js +32 -0
- package/package.json +2 -2
- package/src/builtins.ts +14 -0
- package/src/cel/analyze.ts +4 -7
- package/src/cel/catalog.ts +102 -0
- package/src/engine.ts +20 -0
- package/src/engines/include.ts +16 -5
- package/src/index.ts +8 -2
- package/src/manifest-schemas.ts +0 -98
- package/src/sentinel.ts +33 -0
package/dist/builtins.d.ts
CHANGED
|
@@ -8,6 +8,17 @@ import type { TemplatingEngine } from "./engine.js";
|
|
|
8
8
|
* another (e.g. `cel + literal`); always ship the same set. */
|
|
9
9
|
export declare const builtinEngines: readonly TemplatingEngine[];
|
|
10
10
|
export declare function createDefaultRegistry(): TemplatingEngineRegistry;
|
|
11
|
+
/**
|
|
12
|
+
* The type a tag always produces, or undefined when its produced type is a
|
|
13
|
+
* function of the slot rather than of the tag (`!cel`, `!ref`).
|
|
14
|
+
*
|
|
15
|
+
* The single reader of `TemplatingEngine.producedType`, so a consumer asks the
|
|
16
|
+
* registry what a tag produces instead of recognising tag names — the same seam
|
|
17
|
+
* `fileClaims` opened for payload membership. Reads the default registry
|
|
18
|
+
* because a produced type is a property of the engine, not of a host's
|
|
19
|
+
* configuration, and every host ships the same built-in set.
|
|
20
|
+
*/
|
|
21
|
+
export declare function producedTypeOf(engineName: string): Record<string, unknown> | undefined;
|
|
11
22
|
/** Memoized singleton: returns the default registry. Hosts that don't need
|
|
12
23
|
* per-instance isolation (precompile, the analyzer's tagged-value walker)
|
|
13
24
|
* should use this so they share the same registry instance the YAML tag
|
package/dist/builtins.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD;;;;;gEAKgE;AAChE,eAAO,MAAM,cAAc,EAAE,SAAS,gBAAgB,EAOrD,CAAC;AAEF,wBAAgB,qBAAqB,IAAI,wBAAwB,CAMhE;AAID;;;oBAGoB;AACpB,wBAAgB,eAAe,IAAI,wBAAwB,CAK1D"}
|
|
1
|
+
{"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD;;;;;gEAKgE;AAChE,eAAO,MAAM,cAAc,EAAE,SAAS,gBAAgB,EAOrD,CAAC;AAEF,wBAAgB,qBAAqB,IAAI,wBAAwB,CAMhE;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAEtF;AAID;;;oBAGoB;AACpB,wBAAgB,eAAe,IAAI,wBAAwB,CAK1D"}
|
package/dist/builtins.js
CHANGED
|
@@ -25,6 +25,19 @@ export function createDefaultRegistry() {
|
|
|
25
25
|
}
|
|
26
26
|
return registry;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* The type a tag always produces, or undefined when its produced type is a
|
|
30
|
+
* function of the slot rather than of the tag (`!cel`, `!ref`).
|
|
31
|
+
*
|
|
32
|
+
* The single reader of `TemplatingEngine.producedType`, so a consumer asks the
|
|
33
|
+
* registry what a tag produces instead of recognising tag names — the same seam
|
|
34
|
+
* `fileClaims` opened for payload membership. Reads the default registry
|
|
35
|
+
* because a produced type is a property of the engine, not of a host's
|
|
36
|
+
* configuration, and every host ships the same built-in set.
|
|
37
|
+
*/
|
|
38
|
+
export function producedTypeOf(engineName) {
|
|
39
|
+
return defaultRegistry().get(engineName)?.producedType?.();
|
|
40
|
+
}
|
|
28
41
|
let defaultRegistryCache;
|
|
29
42
|
/** Memoized singleton: returns the default registry. Hosts that don't need
|
|
30
43
|
* per-instance isolation (precompile, the analyzer's tagged-value walker)
|
package/dist/cel/analyze.d.ts
CHANGED
|
@@ -29,7 +29,8 @@ export declare function findNullableAccessIssues(node: ASTNode, contextSchema: R
|
|
|
29
29
|
* Check whether a member-access chain accesses only fields declared in a JSON Schema.
|
|
30
30
|
* Returns an error string if a field is unknown in a schema that declares explicit
|
|
31
31
|
* properties without `additionalProperties: true`, or if the chain attempts to
|
|
32
|
-
* reach inside
|
|
32
|
+
* reach inside a `live` value type — one whose consumption has effects, so its
|
|
33
|
+
* contents exist only for a consumer that drains it.
|
|
33
34
|
* Returns null when the chain is valid or the schema is too open to judge.
|
|
34
35
|
*/
|
|
35
36
|
export declare function validateChainAgainstSchema(chain: string[], schema: Record<string, any>): string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/cel/analyze.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/cel/analyze.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAGpD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE,EAAE,CAI7D;AA0DD;;+DAE+D;AAC/D,eAAO,MAAM,aAAa,QAAQ,CAAC;AAqBnC,UAAU,aAAa;IACrB,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;CAChB;AAoFD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,OAAO,EACb,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACjC,aAAa,EAAE,CAIjB;AA2FD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,MAAM,GAAG,IAAI,CAsBf"}
|
package/dist/cel/analyze.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isLiveSlot } from "@telorun/sdk";
|
|
1
2
|
/**
|
|
2
3
|
* Extract all member-access chains from a CEL AST.
|
|
3
4
|
* Returns arrays like ["request", "query", "name"] for `request.query.name`.
|
|
@@ -254,7 +255,8 @@ function walkNullable(node, nonNull, boundVars, issues, schema) {
|
|
|
254
255
|
* Check whether a member-access chain accesses only fields declared in a JSON Schema.
|
|
255
256
|
* Returns an error string if a field is unknown in a schema that declares explicit
|
|
256
257
|
* properties without `additionalProperties: true`, or if the chain attempts to
|
|
257
|
-
* reach inside
|
|
258
|
+
* reach inside a `live` value type — one whose consumption has effects, so its
|
|
259
|
+
* contents exist only for a consumer that drains it.
|
|
258
260
|
* Returns null when the chain is valid or the schema is too open to judge.
|
|
259
261
|
*/
|
|
260
262
|
export function validateChainAgainstSchema(chain, schema) {
|
|
@@ -268,10 +270,7 @@ export function validateChainAgainstSchema(chain, schema) {
|
|
|
268
270
|
return null;
|
|
269
271
|
if (key in props) {
|
|
270
272
|
const propSchema = props[key];
|
|
271
|
-
if (propSchema &&
|
|
272
|
-
typeof propSchema === "object" &&
|
|
273
|
-
propSchema["x-telo-stream"] === true &&
|
|
274
|
-
i < chain.length - 1) {
|
|
273
|
+
if (isLiveSlot(propSchema) && i < chain.length - 1) {
|
|
275
274
|
const path = chain.slice(0, i + 1).join(".");
|
|
276
275
|
return `'${path}' yields a stream — pipe it through an Encoder or iterate in a JS.Script step (no member access on stream-typed values)`;
|
|
277
276
|
}
|
|
@@ -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;
|
|
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;AAwH5D,eAAO,MAAM,aAAa,EAAE,SAAS,cAAc,EAslBlD,CAAC;AAEF,oEAAoE;AACpE,wBAAgB,kBAAkB,IAAI,eAAe,EAAE,CAEtD"}
|
package/dist/cel/catalog.js
CHANGED
|
@@ -106,6 +106,51 @@ const dateInZone = (tz) => {
|
|
|
106
106
|
const p = zoneParts(now, tz, { year: "numeric", month: "2-digit", day: "2-digit" });
|
|
107
107
|
return `${p.year}-${p.month}-${p.day}`;
|
|
108
108
|
};
|
|
109
|
+
const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
110
|
+
/** Base64 → bytes, written out rather than delegated: `Buffer` is not browser-
|
|
111
|
+
* safe and `atob` round-trips through a string, which is the corruption this
|
|
112
|
+
* pair exists to avoid. Accepts the URL-safe alphabet and tolerates missing
|
|
113
|
+
* padding, both of which appear in real API payloads; a character outside the
|
|
114
|
+
* alphabet is a hard error rather than a silently dropped byte. */
|
|
115
|
+
function decodeBase64ToBytes(input) {
|
|
116
|
+
const clean = input.replace(/[\r\n\t ]/g, "").replace(/-/g, "+").replace(/_/g, "/").replace(/=+$/, "");
|
|
117
|
+
// A remainder of 1 cannot come from any byte sequence: base64 encodes 3 bytes
|
|
118
|
+
// as 4 characters, so the valid remainders are 0, 2 and 3. Left to the loop it
|
|
119
|
+
// would leave 6 bits unwritten and return a SHORT buffer — the silently dropped
|
|
120
|
+
// byte this function refuses for a bad character.
|
|
121
|
+
if (clean.length % 4 === 1) {
|
|
122
|
+
throw new Error("bytesFromBase64: input length is not valid base64");
|
|
123
|
+
}
|
|
124
|
+
const out = new Uint8Array(Math.floor((clean.length * 3) / 4));
|
|
125
|
+
let bits = 0;
|
|
126
|
+
let acc = 0;
|
|
127
|
+
let written = 0;
|
|
128
|
+
for (const ch of clean) {
|
|
129
|
+
const value = BASE64_ALPHABET.indexOf(ch);
|
|
130
|
+
if (value < 0)
|
|
131
|
+
throw new Error(`bytesFromBase64: '${ch}' is not a base64 character`);
|
|
132
|
+
acc = (acc << 6) | value;
|
|
133
|
+
bits += 6;
|
|
134
|
+
if (bits >= 8) {
|
|
135
|
+
bits -= 8;
|
|
136
|
+
out[written++] = (acc >> bits) & 0xff;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return out.subarray(0, written);
|
|
140
|
+
}
|
|
141
|
+
function encodeBytesToBase64(input) {
|
|
142
|
+
let out = "";
|
|
143
|
+
for (let i = 0; i < input.length; i += 3) {
|
|
144
|
+
const a = input[i];
|
|
145
|
+
const b = i + 1 < input.length ? input[i + 1] : undefined;
|
|
146
|
+
const c = i + 2 < input.length ? input[i + 2] : undefined;
|
|
147
|
+
out += BASE64_ALPHABET[a >> 2];
|
|
148
|
+
out += BASE64_ALPHABET[((a & 0x03) << 4) | ((b ?? 0) >> 4)];
|
|
149
|
+
out += b === undefined ? "=" : BASE64_ALPHABET[((b & 0x0f) << 2) | ((c ?? 0) >> 6)];
|
|
150
|
+
out += c === undefined ? "=" : BASE64_ALPHABET[c & 0x3f];
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
109
154
|
export const CEL_FUNCTIONS = [
|
|
110
155
|
// Collections
|
|
111
156
|
{
|
|
@@ -303,6 +348,37 @@ export const CEL_FUNCTIONS = [
|
|
|
303
348
|
hostBacked: false,
|
|
304
349
|
build: () => (s, suffix) => suffix && s.endsWith(suffix) ? s.slice(0, s.length - suffix.length) : s,
|
|
305
350
|
},
|
|
351
|
+
// One `slice` over the three sequence types rather than three names: CEL
|
|
352
|
+
// already treats `size()` that way, and the alternative teaches an author that
|
|
353
|
+
// slicing bytes is a different operation from slicing a string.
|
|
354
|
+
//
|
|
355
|
+
// ONE `dyn` signature rather than a concrete overload per element type. The
|
|
356
|
+
// concrete set is more precise, but cel-js refuses overloads that overlap, so
|
|
357
|
+
// it cannot also carry the `dyn` case — and a step result whose producer
|
|
358
|
+
// declares no output type IS `dyn`, which is the common receiver. With only the
|
|
359
|
+
// concrete set the checker resolves such a call to whichever overload it tries
|
|
360
|
+
// first and pins the result to that type, so slicing an untyped byte buffer
|
|
361
|
+
// came back typed `string`. Precision here would be lost at the CEL boundary
|
|
362
|
+
// anyway, where type arguments are erased.
|
|
363
|
+
{
|
|
364
|
+
name: "slice",
|
|
365
|
+
signature: "slice(dyn, int, int): dyn",
|
|
366
|
+
// Not "string": it is the one function over strings, bytes AND lists, and the
|
|
367
|
+
// category is what groups it in the generated reference — filing it under
|
|
368
|
+
// strings hides it from the byte and list readers who need it most.
|
|
369
|
+
category: "collection",
|
|
370
|
+
summary: "Take the half-open range [start, end) of a string, bytes or list.",
|
|
371
|
+
deterministic: true,
|
|
372
|
+
hostBacked: false,
|
|
373
|
+
// Indices arrive as BigInt (a CEL int is int64), and both `String.slice` and
|
|
374
|
+
// `TypedArray.subarray` reject one. `subarray` rather than `slice` for bytes:
|
|
375
|
+
// a view costs no copy, and every consumer treats the result as read-only.
|
|
376
|
+
build: () => (value, start, end) => {
|
|
377
|
+
const from = Number(start);
|
|
378
|
+
const to = Number(end);
|
|
379
|
+
return value instanceof Uint8Array ? value.subarray(from, to) : value.slice(from, to);
|
|
380
|
+
},
|
|
381
|
+
},
|
|
306
382
|
// Math
|
|
307
383
|
{
|
|
308
384
|
name: "abs",
|
|
@@ -416,6 +492,30 @@ export const CEL_FUNCTIONS = [
|
|
|
416
492
|
hostBacked: true,
|
|
417
493
|
build: (h) => (s) => h.base64Decode(s),
|
|
418
494
|
},
|
|
495
|
+
// Base64 <-> BYTES, as distinct from the two above, which are UTF-8
|
|
496
|
+
// string<->string and silently corrupt anything that is not text. Those keep
|
|
497
|
+
// their meaning (changing it would change what shipped manifests mean); these
|
|
498
|
+
// are the correct path for binary, and the pair a byte-typed slot needs.
|
|
499
|
+
// Pure-JS on purpose: `Buffer` is what made the string pair host-backed, and
|
|
500
|
+
// this package has to stay browser-safe.
|
|
501
|
+
{
|
|
502
|
+
name: "bytesFromBase64",
|
|
503
|
+
signature: "bytesFromBase64(string): bytes",
|
|
504
|
+
category: "encoding",
|
|
505
|
+
summary: "Decode a base64 string to raw bytes.",
|
|
506
|
+
deterministic: true,
|
|
507
|
+
hostBacked: false,
|
|
508
|
+
build: () => (s) => decodeBase64ToBytes(s),
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
name: "bytesToBase64",
|
|
512
|
+
signature: "bytesToBase64(bytes): string",
|
|
513
|
+
category: "encoding",
|
|
514
|
+
summary: "Encode raw bytes as a base64 string.",
|
|
515
|
+
deterministic: true,
|
|
516
|
+
hostBacked: false,
|
|
517
|
+
build: () => (b) => encodeBytesToBase64(b),
|
|
518
|
+
},
|
|
419
519
|
{
|
|
420
520
|
name: "urlEncode",
|
|
421
521
|
signature: "urlEncode(string): string",
|
package/dist/engine.d.ts
CHANGED
|
@@ -131,5 +131,24 @@ export interface TemplatingEngine {
|
|
|
131
131
|
* A source the engine considers malformed claims nothing; `analyze` is what
|
|
132
132
|
* reports why. */
|
|
133
133
|
fileClaims?(source: string): readonly EngineFileClaim[];
|
|
134
|
+
/** The type this tag ALWAYS produces, as a JSON Schema fragment.
|
|
135
|
+
*
|
|
136
|
+
* Declared by the engine, never recognised by a consumer — the `fileClaims`
|
|
137
|
+
* precedent applied to the one fact it left behind. Before this, the analyzer
|
|
138
|
+
* hardcoded two tag names to hand an `!include-bytes` a byte placeholder and
|
|
139
|
+
* an `!include-text` a string one; the only place a tag's produced type was
|
|
140
|
+
* written down was in its consumer, so a future tag producing bytes had to be
|
|
141
|
+
* added to a set rather than declaring it.
|
|
142
|
+
*
|
|
143
|
+
* Absent for an engine whose produced type is a function of the SLOT rather
|
|
144
|
+
* than of the tag — `!cel`, whose type is only derivable from the expression,
|
|
145
|
+
* and `!ref`, which is an identity marker. Their values keep taking a
|
|
146
|
+
* slot-shaped placeholder.
|
|
147
|
+
*
|
|
148
|
+
* What falls out is the property this preserves exactly: because an embed's
|
|
149
|
+
* type is a constant of the tag, a byte embed at a string slot and text at a
|
|
150
|
+
* byte slot both fail statically, through the ordinary schema check and with
|
|
151
|
+
* no diagnostic code of their own. */
|
|
152
|
+
producedType?(): Record<string, unknown>;
|
|
134
153
|
}
|
|
135
154
|
//# sourceMappingURL=engine.d.ts.map
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD;;wEAEwE;AACxE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED;;;;;;;;;eASe;AACf,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;0EAa0E;AAC1E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;oDAEoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC;CAC9B;AAED;;;;yBAIyB;AACzB,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IACrC,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;oDAEgD;IAChD,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;CAClC;AAED;;;iFAGiF;AACjF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAClD,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;4CAgB4C;AAC5C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;wBAEwB;AACxB,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;;2EAKuE;IACvE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAE3B;;;yDAGqD;IACrD,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,aAAa,GAAG,OAAO,CAAC;IAElE;;iEAE6D;IAC7D,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,aAAa,CAAC;IAExD;;;;;;;;;;;;uBAYmB;IACnB,UAAU,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,eAAe,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD;;wEAEwE;AACxE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED;;;;;;;;;eASe;AACf,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;0EAa0E;AAC1E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;oDAEoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC;CAC9B;AAED;;;;yBAIyB;AACzB,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IACrC,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;oDAEgD;IAChD,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;CAClC;AAED;;;iFAGiF;AACjF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAClD,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;4CAgB4C;AAC5C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;wBAEwB;AACxB,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;;2EAKuE;IACvE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAE3B;;;yDAGqD;IACrD,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,aAAa,GAAG,OAAO,CAAC;IAElE;;iEAE6D;IAC7D,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,aAAa,CAAC;IAExD;;;;;;;;;;;;uBAYmB;IACnB,UAAU,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,eAAe,EAAE,CAAC;IAExD;;;;;;;;;;;;;;;;;2CAiBuC;IACvC,YAAY,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C"}
|
|
@@ -19,7 +19,10 @@ export interface NormalizedIncludePath {
|
|
|
19
19
|
export declare function normalizeIncludePath(source: string): NormalizedIncludePath;
|
|
20
20
|
/** Embeds a file's contents as a UTF-8 string. */
|
|
21
21
|
export declare const includeTextEngine: TemplatingEngine;
|
|
22
|
-
/** Embeds a file's contents as raw bytes —
|
|
23
|
-
*
|
|
22
|
+
/** Embeds a file's contents as raw bytes — the shape every `Telo.Bytes` slot
|
|
23
|
+
* accepts. The name is written as a literal rather than imported from the SDK:
|
|
24
|
+
* templating is the lower package, and a produced type is a schema fragment,
|
|
25
|
+
* which is data. What checks it is the registry the analyzer and the kernel both
|
|
26
|
+
* read. */
|
|
24
27
|
export declare const includeBytesEngine: TemplatingEngine;
|
|
25
28
|
//# sourceMappingURL=include.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"include.d.ts","sourceRoot":"","sources":["../../src/engines/include.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAaxF,MAAM,WAAW,qBAAqB;IACpC;kEAC8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACxC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,qBAAqB,CA0E1E;
|
|
1
|
+
{"version":3,"file":"include.d.ts","sourceRoot":"","sources":["../../src/engines/include.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAaxF,MAAM,WAAW,qBAAqB;IACpC;kEAC8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACxC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,qBAAqB,CA0E1E;AA8CD,kDAAkD;AAClD,eAAO,MAAM,iBAAiB,EAAE,gBAE9B,CAAC;AAEH;;;;YAIY;AACZ,eAAO,MAAM,kBAAkB,EAAE,gBAE/B,CAAC"}
|
package/dist/engines/include.js
CHANGED
|
@@ -106,12 +106,15 @@ export function normalizeIncludePath(source) {
|
|
|
106
106
|
* `analyze` reports why a path is unusable; `fileClaims` reports the path itself
|
|
107
107
|
* so publish can place the file in a layer without recognising the tag by name.
|
|
108
108
|
*/
|
|
109
|
-
function includeEngine(name) {
|
|
109
|
+
function includeEngine(name, produced) {
|
|
110
110
|
return {
|
|
111
111
|
name,
|
|
112
112
|
compile(source) {
|
|
113
113
|
return makeTaggedSentinel(name, source);
|
|
114
114
|
},
|
|
115
|
+
producedType() {
|
|
116
|
+
return produced;
|
|
117
|
+
},
|
|
115
118
|
analyze(source) {
|
|
116
119
|
const { diagnostic } = normalizeIncludePath(source);
|
|
117
120
|
return { diagnostics: diagnostic ? [diagnostic] : [], calls: [] };
|
|
@@ -126,7 +129,14 @@ function includeEngine(name) {
|
|
|
126
129
|
};
|
|
127
130
|
}
|
|
128
131
|
/** Embeds a file's contents as a UTF-8 string. */
|
|
129
|
-
export const includeTextEngine = includeEngine(INCLUDE_TEXT_ENGINE
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
export const includeTextEngine = includeEngine(INCLUDE_TEXT_ENGINE, {
|
|
133
|
+
type: "string",
|
|
134
|
+
});
|
|
135
|
+
/** Embeds a file's contents as raw bytes — the shape every `Telo.Bytes` slot
|
|
136
|
+
* accepts. The name is written as a literal rather than imported from the SDK:
|
|
137
|
+
* templating is the lower package, and a produced type is a schema fragment,
|
|
138
|
+
* which is data. What checks it is the registry the analyzer and the kernel both
|
|
139
|
+
* read. */
|
|
140
|
+
export const includeBytesEngine = includeEngine(INCLUDE_BYTES_ENGINE, {
|
|
141
|
+
"x-telo-type": "Telo.Bytes",
|
|
142
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -10,9 +10,9 @@ export { literalEngine } from "./engines/literal.js";
|
|
|
10
10
|
export { refEngine } from "./engines/ref.js";
|
|
11
11
|
export { sqlEngine, isParameterizedSql, type ParameterizedSql } from "./engines/sql.js";
|
|
12
12
|
export { TemplatingEngineRegistry } from "./registry.js";
|
|
13
|
-
export { builtinEngines, createDefaultRegistry, defaultRegistry } from "./builtins.js";
|
|
13
|
+
export { builtinEngines, createDefaultRegistry, defaultRegistry, producedTypeOf, } from "./builtins.js";
|
|
14
14
|
export type { AnalyzeEnv, AnalyzeResult, CallSite, CompileEnv, DiagnosticFix, EngineDiagnostic, EngineFileClaim, TemplatingEngine, } from "./engine.js";
|
|
15
|
-
export { INCLUDE_BYTES_ENGINE, INCLUDE_ENGINE_NAMES, INCLUDE_TEXT_ENGINE, isIncludeSentinel, isRefSentinel, isTaggedSentinel, makeTaggedSentinel, type TaggedSentinel, } from "./sentinel.js";
|
|
15
|
+
export { CEL_ENGINE, INCLUDE_BYTES_ENGINE, INCLUDE_ENGINE_NAMES, INCLUDE_TEXT_ENGINE, isIncludeSentinel, isRefSentinel, isTaggedSentinel, makeTaggedSentinel, plainChainOf, type TaggedSentinel, } from "./sentinel.js";
|
|
16
16
|
export { buildCustomTags, defaultCustomTags } from "./yaml-tags.js";
|
|
17
|
-
export { MANIFEST_SCHEMA_URI, ManifestRootSchema, ResourceRefSchema,
|
|
17
|
+
export { MANIFEST_SCHEMA_URI, ManifestRootSchema, ResourceRefSchema, } from "./manifest-schemas.js";
|
|
18
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,WAAW,GACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,cAAc,EACd,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,aAAa,EACb,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACjG,OAAO,EAAE,kBAAkB,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAEpE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAExF,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,WAAW,GACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,cAAc,EACd,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,aAAa,EACb,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACjG,OAAO,EAAE,kBAAkB,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAEpE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAExF,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,eAAe,EACf,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,UAAU,EACV,aAAa,EACb,QAAQ,EACR,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,KAAK,cAAc,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ export { literalEngine } from "./engines/literal.js";
|
|
|
10
10
|
export { refEngine } from "./engines/ref.js";
|
|
11
11
|
export { sqlEngine, isParameterizedSql } from "./engines/sql.js";
|
|
12
12
|
export { TemplatingEngineRegistry } from "./registry.js";
|
|
13
|
-
export { builtinEngines, createDefaultRegistry, defaultRegistry } from "./builtins.js";
|
|
14
|
-
export { INCLUDE_BYTES_ENGINE, INCLUDE_ENGINE_NAMES, INCLUDE_TEXT_ENGINE, isIncludeSentinel, isRefSentinel, isTaggedSentinel, makeTaggedSentinel, } from "./sentinel.js";
|
|
13
|
+
export { builtinEngines, createDefaultRegistry, defaultRegistry, producedTypeOf, } from "./builtins.js";
|
|
14
|
+
export { CEL_ENGINE, INCLUDE_BYTES_ENGINE, INCLUDE_ENGINE_NAMES, INCLUDE_TEXT_ENGINE, isIncludeSentinel, isRefSentinel, isTaggedSentinel, makeTaggedSentinel, plainChainOf, } from "./sentinel.js";
|
|
15
15
|
export { buildCustomTags, defaultCustomTags } from "./yaml-tags.js";
|
|
16
|
-
export { MANIFEST_SCHEMA_URI, ManifestRootSchema, ResourceRefSchema,
|
|
16
|
+
export { MANIFEST_SCHEMA_URI, ManifestRootSchema, ResourceRefSchema, } from "./manifest-schemas.js";
|
|
@@ -70,22 +70,6 @@ export declare const ResourceRefSchema: {
|
|
|
70
70
|
additionalProperties: boolean;
|
|
71
71
|
})[];
|
|
72
72
|
};
|
|
73
|
-
/** Deep-clone `schema`, dropping the stale scalar `type` constraint from every
|
|
74
|
-
* reference-slot node — one carrying an `x-telo-ref` string annotation.
|
|
75
|
-
*
|
|
76
|
-
* A reference slot's value is always a `!ref` sentinel or its resolved
|
|
77
|
-
* `{kind, name, alias?}` object (never a bare string, post-migration). Older
|
|
78
|
-
* published modules still pin `type: "string"` on these slots — the encoding
|
|
79
|
-
* references took when they were written as plain strings — which now rejects
|
|
80
|
-
* the resolved object. Removing only the scalar `type` lets the analyzer and
|
|
81
|
-
* kernel accept references uniformly across module versions during the
|
|
82
|
-
* migration away from `{kind, name}` / string references, without disturbing
|
|
83
|
-
* slots that legitimately accept an inline object (e.g. `inputType` /
|
|
84
|
-
* `outputType`, which take a Telo.Type reference *or* an inline JSON schema).
|
|
85
|
-
* The `x-telo-ref` constraint itself (which kind the reference must satisfy) is
|
|
86
|
-
* checked separately by the analyzer's reference walker, which reads the
|
|
87
|
-
* original schema — not this validation-only copy. */
|
|
88
|
-
export declare function normalizeRefSlots(schema: unknown): unknown;
|
|
89
73
|
/** Stable URI under which the shared manifest root schema is registered
|
|
90
74
|
* with module-side AJV instances. Module YAMLs reach the fragments via
|
|
91
75
|
* `$ref: "telo://manifest#/$defs/<Name>"`. The URI is the contract;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-schemas.d.ts","sourceRoot":"","sources":["../src/manifest-schemas.ts"],"names":[],"mappings":"AAAA;;;;;;kBAMkB;AAElB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yEA+ByE;AACzE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoB7B,CAAC;
|
|
1
|
+
{"version":3,"file":"manifest-schemas.d.ts","sourceRoot":"","sources":["../src/manifest-schemas.ts"],"names":[],"mappings":"AAAA;;;;;;kBAMkB;AAElB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yEA+ByE;AACzE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoB7B,CAAC;AAGF;;;;;+BAK+B;AAC/B,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD;;;8BAG8B;AAC9B,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAK9B,CAAC"}
|
package/dist/manifest-schemas.js
CHANGED
|
@@ -58,101 +58,6 @@ export const ResourceRefSchema = {
|
|
|
58
58
|
},
|
|
59
59
|
],
|
|
60
60
|
};
|
|
61
|
-
const REF_ANNOTATION = "x-telo-ref";
|
|
62
|
-
// The legacy base types a reference slot used to pin when references were
|
|
63
|
-
// written as plain strings. Post-migration a reference resolves to an object
|
|
64
|
-
// (the `{kind, name, alias?}` shape, or an unresolved `!ref` sentinel), so a
|
|
65
|
-
// scalar `type` on a ref slot is a stale constraint that would reject the
|
|
66
|
-
// resolved value. Object / array `type`s are left alone — they already admit
|
|
67
|
-
// the reference object (and any inline value a slot like `inputType` accepts).
|
|
68
|
-
const LEGACY_REF_SCALAR_TYPES = new Set(["string", "number", "integer", "boolean"]);
|
|
69
|
-
// JSON Schema keywords whose values are themselves subschemas. Split by shape so
|
|
70
|
-
// the ref-slot normalizer recurses only into schema positions — never into
|
|
71
|
-
// data-bearing keywords (`default`, `const`, `enum`, `examples`), where a stray
|
|
72
|
-
// `x-telo-ref` key would be data, not an annotation.
|
|
73
|
-
const SUBSCHEMA_SINGLE = [
|
|
74
|
-
"additionalProperties",
|
|
75
|
-
"additionalItems",
|
|
76
|
-
"contains",
|
|
77
|
-
"not",
|
|
78
|
-
"if",
|
|
79
|
-
"then",
|
|
80
|
-
"else",
|
|
81
|
-
"propertyNames",
|
|
82
|
-
"unevaluatedItems",
|
|
83
|
-
"unevaluatedProperties",
|
|
84
|
-
];
|
|
85
|
-
const SUBSCHEMA_LIST = ["allOf", "anyOf", "oneOf", "prefixItems"];
|
|
86
|
-
const SUBSCHEMA_MAP = [
|
|
87
|
-
"properties",
|
|
88
|
-
"patternProperties",
|
|
89
|
-
"$defs",
|
|
90
|
-
"definitions",
|
|
91
|
-
"dependentSchemas",
|
|
92
|
-
];
|
|
93
|
-
/** Deep-clone `schema`, dropping the stale scalar `type` constraint from every
|
|
94
|
-
* reference-slot node — one carrying an `x-telo-ref` string annotation.
|
|
95
|
-
*
|
|
96
|
-
* A reference slot's value is always a `!ref` sentinel or its resolved
|
|
97
|
-
* `{kind, name, alias?}` object (never a bare string, post-migration). Older
|
|
98
|
-
* published modules still pin `type: "string"` on these slots — the encoding
|
|
99
|
-
* references took when they were written as plain strings — which now rejects
|
|
100
|
-
* the resolved object. Removing only the scalar `type` lets the analyzer and
|
|
101
|
-
* kernel accept references uniformly across module versions during the
|
|
102
|
-
* migration away from `{kind, name}` / string references, without disturbing
|
|
103
|
-
* slots that legitimately accept an inline object (e.g. `inputType` /
|
|
104
|
-
* `outputType`, which take a Telo.Type reference *or* an inline JSON schema).
|
|
105
|
-
* The `x-telo-ref` constraint itself (which kind the reference must satisfy) is
|
|
106
|
-
* checked separately by the analyzer's reference walker, which reads the
|
|
107
|
-
* original schema — not this validation-only copy. */
|
|
108
|
-
export function normalizeRefSlots(schema) {
|
|
109
|
-
if (schema === null || typeof schema !== "object" || Array.isArray(schema)) {
|
|
110
|
-
return schema;
|
|
111
|
-
}
|
|
112
|
-
const node = schema;
|
|
113
|
-
const out = { ...node };
|
|
114
|
-
// Reference slot with a stale scalar `type` (legacy string-ref encoding):
|
|
115
|
-
// drop the constraint so the resolved reference object / sentinel validates.
|
|
116
|
-
//
|
|
117
|
-
// A presence test, not a shape test — deliberately, since `templating` sits
|
|
118
|
-
// BELOW the analyzer in the dependency order and cannot reach the shared
|
|
119
|
-
// `readRefSlot` accessor. Presence is the only thing this rule needs, and it
|
|
120
|
-
// is stable across every annotation shape.
|
|
121
|
-
if (node[REF_ANNOTATION] !== undefined &&
|
|
122
|
-
typeof node.type === "string" &&
|
|
123
|
-
LEGACY_REF_SCALAR_TYPES.has(node.type)) {
|
|
124
|
-
delete out.type;
|
|
125
|
-
}
|
|
126
|
-
for (const key of SUBSCHEMA_SINGLE) {
|
|
127
|
-
const value = node[key];
|
|
128
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
129
|
-
out[key] = normalizeRefSlots(value);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
for (const key of SUBSCHEMA_LIST) {
|
|
133
|
-
const value = node[key];
|
|
134
|
-
if (Array.isArray(value))
|
|
135
|
-
out[key] = value.map(normalizeRefSlots);
|
|
136
|
-
}
|
|
137
|
-
// `items` is either a single subschema or a tuple of subschemas.
|
|
138
|
-
if (Array.isArray(node.items)) {
|
|
139
|
-
out.items = node.items.map(normalizeRefSlots);
|
|
140
|
-
}
|
|
141
|
-
else if (node.items && typeof node.items === "object") {
|
|
142
|
-
out.items = normalizeRefSlots(node.items);
|
|
143
|
-
}
|
|
144
|
-
for (const key of SUBSCHEMA_MAP) {
|
|
145
|
-
const value = node[key];
|
|
146
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
147
|
-
const mapped = {};
|
|
148
|
-
for (const [name, sub] of Object.entries(value)) {
|
|
149
|
-
mapped[name] = normalizeRefSlots(sub);
|
|
150
|
-
}
|
|
151
|
-
out[key] = mapped;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return out;
|
|
155
|
-
}
|
|
156
61
|
/** Stable URI under which the shared manifest root schema is registered
|
|
157
62
|
* with module-side AJV instances. Module YAMLs reach the fragments via
|
|
158
63
|
* `$ref: "telo://manifest#/$defs/<Name>"`. The URI is the contract;
|
package/dist/sentinel.d.ts
CHANGED
|
@@ -19,11 +19,29 @@ export declare function makeTaggedSentinel(engine: string, source: string): Tagg
|
|
|
19
19
|
export declare function isRefSentinel(v: unknown): v is TaggedSentinel & {
|
|
20
20
|
engine: "ref";
|
|
21
21
|
};
|
|
22
|
+
/** The CEL engine's name. Beside the other sentinel predicates for the same
|
|
23
|
+
* reason they are: a consumer that spells an engine name inline is a second
|
|
24
|
+
* place the registry's key is written down. */
|
|
25
|
+
export declare const CEL_ENGINE = "cel";
|
|
22
26
|
/** Engine names of the two file-embedding tags. Named here beside the other
|
|
23
27
|
* sentinel predicates so the kernel's resolution pass and the engines
|
|
24
28
|
* themselves agree on one spelling. */
|
|
25
29
|
export declare const INCLUDE_TEXT_ENGINE = "include-text";
|
|
26
30
|
export declare const INCLUDE_BYTES_ENGINE = "include-bytes";
|
|
31
|
+
/**
|
|
32
|
+
* The dotted chain a value names, or undefined.
|
|
33
|
+
*
|
|
34
|
+
* Only a PLAIN CHAIN — `steps.encode.result.output` — in either spelling a
|
|
35
|
+
* manifest may carry it: a `!cel` sentinel or the `${{ }}` string form. An
|
|
36
|
+
* expression that COMPUTES rather than names has no schema to read off a context,
|
|
37
|
+
* so a caller that navigates one gets nothing and reports nothing: silence where
|
|
38
|
+
* the analyzer knows least is the conservative direction.
|
|
39
|
+
*
|
|
40
|
+
* Here rather than in each caller because "is this expression a plain chain" is
|
|
41
|
+
* one question, and two copies of the answer would eventually disagree about a
|
|
42
|
+
* shape like `a.b[0]`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function plainChainOf(value: unknown): string | undefined;
|
|
27
45
|
/** Both file-embedding tag names, for a consumer holding an engine NAME rather
|
|
28
46
|
* than a value (the analyzer's expression walk reports names). */
|
|
29
47
|
export declare const INCLUDE_ENGINE_NAMES: ReadonlySet<string>;
|
package/dist/sentinel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../src/sentinel.ts"],"names":[],"mappings":"AAAA;;;;;YAKY;AACZ,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,cAAc,CAQhE;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAEjF;AAED;;;;4CAI4C;AAC5C,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,cAAc,GAAG;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,CAEjF;AAED;;wCAEwC;AACxC,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAClD,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AAEpD;mEACmE;AACnE,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAGnD,CAAC;AAEH;;;;;;;qCAOqC;AACrC,wBAAgB,iBAAiB,CAC/B,CAAC,EAAE,OAAO,GACT,CAAC,IAAI,cAAc,GAAG;IAAE,MAAM,EAAE,OAAO,mBAAmB,GAAG,OAAO,oBAAoB,CAAA;CAAE,CAI5F"}
|
|
1
|
+
{"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../src/sentinel.ts"],"names":[],"mappings":"AAAA;;;;;YAKY;AACZ,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,cAAc,CAQhE;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAEjF;AAED;;;;4CAI4C;AAC5C,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,cAAc,GAAG;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,CAEjF;AAED;;gDAEgD;AAChD,eAAO,MAAM,UAAU,QAAQ,CAAC;AAEhC;;wCAEwC;AACxC,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAClD,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AAEpD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAa/D;AAED;mEACmE;AACnE,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAGnD,CAAC;AAEH;;;;;;;qCAOqC;AACrC,wBAAgB,iBAAiB,CAC/B,CAAC,EAAE,OAAO,GACT,CAAC,IAAI,cAAc,GAAG;IAAE,MAAM,EAAE,OAAO,mBAAmB,GAAG,OAAO,oBAAoB,CAAA;CAAE,CAI5F"}
|
package/dist/sentinel.js
CHANGED
|
@@ -16,11 +16,43 @@ export function makeTaggedSentinel(engine, source) {
|
|
|
16
16
|
export function isRefSentinel(v) {
|
|
17
17
|
return isTaggedSentinel(v) && v.engine === "ref";
|
|
18
18
|
}
|
|
19
|
+
/** The CEL engine's name. Beside the other sentinel predicates for the same
|
|
20
|
+
* reason they are: a consumer that spells an engine name inline is a second
|
|
21
|
+
* place the registry's key is written down. */
|
|
22
|
+
export const CEL_ENGINE = "cel";
|
|
19
23
|
/** Engine names of the two file-embedding tags. Named here beside the other
|
|
20
24
|
* sentinel predicates so the kernel's resolution pass and the engines
|
|
21
25
|
* themselves agree on one spelling. */
|
|
22
26
|
export const INCLUDE_TEXT_ENGINE = "include-text";
|
|
23
27
|
export const INCLUDE_BYTES_ENGINE = "include-bytes";
|
|
28
|
+
/**
|
|
29
|
+
* The dotted chain a value names, or undefined.
|
|
30
|
+
*
|
|
31
|
+
* Only a PLAIN CHAIN — `steps.encode.result.output` — in either spelling a
|
|
32
|
+
* manifest may carry it: a `!cel` sentinel or the `${{ }}` string form. An
|
|
33
|
+
* expression that COMPUTES rather than names has no schema to read off a context,
|
|
34
|
+
* so a caller that navigates one gets nothing and reports nothing: silence where
|
|
35
|
+
* the analyzer knows least is the conservative direction.
|
|
36
|
+
*
|
|
37
|
+
* Here rather than in each caller because "is this expression a plain chain" is
|
|
38
|
+
* one question, and two copies of the answer would eventually disagree about a
|
|
39
|
+
* shape like `a.b[0]`.
|
|
40
|
+
*/
|
|
41
|
+
export function plainChainOf(value) {
|
|
42
|
+
const source = isTaggedSentinel(value)
|
|
43
|
+
? value.engine === CEL_ENGINE
|
|
44
|
+
? value.source
|
|
45
|
+
: undefined
|
|
46
|
+
: typeof value === "string"
|
|
47
|
+
? /^\s*\$\{\{(.+)\}\}\s*$/.exec(value)?.[1]
|
|
48
|
+
: undefined;
|
|
49
|
+
if (typeof source !== "string")
|
|
50
|
+
return undefined;
|
|
51
|
+
const trimmed = source.trim();
|
|
52
|
+
return /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(trimmed)
|
|
53
|
+
? trimmed
|
|
54
|
+
: undefined;
|
|
55
|
+
}
|
|
24
56
|
/** Both file-embedding tag names, for a consumer holding an engine NAME rather
|
|
25
57
|
* than a value (the analyzer's expression walk reports names). */
|
|
26
58
|
export const INCLUDE_ENGINE_NAMES = new Set([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/templating",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Telo Templating - Engine registry and shared CEL core for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@types/node": "^20.0.0",
|
|
45
45
|
"typescript": "^5.0.0",
|
|
46
46
|
"vitest": "^2.1.8",
|
|
47
|
-
"@telorun/sdk": "0.
|
|
47
|
+
"@telorun/sdk": "0.74.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@telorun/sdk": "*"
|
package/src/builtins.ts
CHANGED
|
@@ -29,6 +29,20 @@ export function createDefaultRegistry(): TemplatingEngineRegistry {
|
|
|
29
29
|
return registry;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* The type a tag always produces, or undefined when its produced type is a
|
|
34
|
+
* function of the slot rather than of the tag (`!cel`, `!ref`).
|
|
35
|
+
*
|
|
36
|
+
* The single reader of `TemplatingEngine.producedType`, so a consumer asks the
|
|
37
|
+
* registry what a tag produces instead of recognising tag names — the same seam
|
|
38
|
+
* `fileClaims` opened for payload membership. Reads the default registry
|
|
39
|
+
* because a produced type is a property of the engine, not of a host's
|
|
40
|
+
* configuration, and every host ships the same built-in set.
|
|
41
|
+
*/
|
|
42
|
+
export function producedTypeOf(engineName: string): Record<string, unknown> | undefined {
|
|
43
|
+
return defaultRegistry().get(engineName)?.producedType?.();
|
|
44
|
+
}
|
|
45
|
+
|
|
32
46
|
let defaultRegistryCache: TemplatingEngineRegistry | undefined;
|
|
33
47
|
|
|
34
48
|
/** Memoized singleton: returns the default registry. Hosts that don't need
|
package/src/cel/analyze.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ASTNode } from "@marcbachmann/cel-js";
|
|
2
|
+
import { isLiveSlot } from "@telorun/sdk";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Extract all member-access chains from a CEL AST.
|
|
@@ -291,7 +292,8 @@ function walkNullable(
|
|
|
291
292
|
* Check whether a member-access chain accesses only fields declared in a JSON Schema.
|
|
292
293
|
* Returns an error string if a field is unknown in a schema that declares explicit
|
|
293
294
|
* properties without `additionalProperties: true`, or if the chain attempts to
|
|
294
|
-
* reach inside
|
|
295
|
+
* reach inside a `live` value type — one whose consumption has effects, so its
|
|
296
|
+
* contents exist only for a consumer that drains it.
|
|
295
297
|
* Returns null when the chain is valid or the schema is too open to judge.
|
|
296
298
|
*/
|
|
297
299
|
export function validateChainAgainstSchema(
|
|
@@ -306,12 +308,7 @@ export function validateChainAgainstSchema(
|
|
|
306
308
|
if (!props) return null;
|
|
307
309
|
if (key in props) {
|
|
308
310
|
const propSchema = props[key];
|
|
309
|
-
if (
|
|
310
|
-
propSchema &&
|
|
311
|
-
typeof propSchema === "object" &&
|
|
312
|
-
propSchema["x-telo-stream"] === true &&
|
|
313
|
-
i < chain.length - 1
|
|
314
|
-
) {
|
|
311
|
+
if (isLiveSlot(propSchema) && i < chain.length - 1) {
|
|
315
312
|
const path = chain.slice(0, i + 1).join(".");
|
|
316
313
|
return `'${path}' yields a stream — pipe it through an Encoder or iterate in a JS.Script step (no member access on stream-typed values)`;
|
|
317
314
|
}
|
package/src/cel/catalog.ts
CHANGED
|
@@ -170,6 +170,53 @@ const dateInZone = (tz: string): string => {
|
|
|
170
170
|
return `${p.year}-${p.month}-${p.day}`;
|
|
171
171
|
};
|
|
172
172
|
|
|
173
|
+
const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
174
|
+
|
|
175
|
+
/** Base64 → bytes, written out rather than delegated: `Buffer` is not browser-
|
|
176
|
+
* safe and `atob` round-trips through a string, which is the corruption this
|
|
177
|
+
* pair exists to avoid. Accepts the URL-safe alphabet and tolerates missing
|
|
178
|
+
* padding, both of which appear in real API payloads; a character outside the
|
|
179
|
+
* alphabet is a hard error rather than a silently dropped byte. */
|
|
180
|
+
function decodeBase64ToBytes(input: string): Uint8Array {
|
|
181
|
+
const clean = input.replace(/[\r\n\t ]/g, "").replace(/-/g, "+").replace(/_/g, "/").replace(/=+$/, "");
|
|
182
|
+
// A remainder of 1 cannot come from any byte sequence: base64 encodes 3 bytes
|
|
183
|
+
// as 4 characters, so the valid remainders are 0, 2 and 3. Left to the loop it
|
|
184
|
+
// would leave 6 bits unwritten and return a SHORT buffer — the silently dropped
|
|
185
|
+
// byte this function refuses for a bad character.
|
|
186
|
+
if (clean.length % 4 === 1) {
|
|
187
|
+
throw new Error("bytesFromBase64: input length is not valid base64");
|
|
188
|
+
}
|
|
189
|
+
const out = new Uint8Array(Math.floor((clean.length * 3) / 4));
|
|
190
|
+
let bits = 0;
|
|
191
|
+
let acc = 0;
|
|
192
|
+
let written = 0;
|
|
193
|
+
for (const ch of clean) {
|
|
194
|
+
const value = BASE64_ALPHABET.indexOf(ch);
|
|
195
|
+
if (value < 0) throw new Error(`bytesFromBase64: '${ch}' is not a base64 character`);
|
|
196
|
+
acc = (acc << 6) | value;
|
|
197
|
+
bits += 6;
|
|
198
|
+
if (bits >= 8) {
|
|
199
|
+
bits -= 8;
|
|
200
|
+
out[written++] = (acc >> bits) & 0xff;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return out.subarray(0, written);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function encodeBytesToBase64(input: Uint8Array): string {
|
|
207
|
+
let out = "";
|
|
208
|
+
for (let i = 0; i < input.length; i += 3) {
|
|
209
|
+
const a = input[i]!;
|
|
210
|
+
const b = i + 1 < input.length ? input[i + 1]! : undefined;
|
|
211
|
+
const c = i + 2 < input.length ? input[i + 2]! : undefined;
|
|
212
|
+
out += BASE64_ALPHABET[a >> 2];
|
|
213
|
+
out += BASE64_ALPHABET[((a & 0x03) << 4) | ((b ?? 0) >> 4)];
|
|
214
|
+
out += b === undefined ? "=" : BASE64_ALPHABET[((b & 0x0f) << 2) | ((c ?? 0) >> 6)];
|
|
215
|
+
out += c === undefined ? "=" : BASE64_ALPHABET[c & 0x3f];
|
|
216
|
+
}
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
|
|
173
220
|
export const CEL_FUNCTIONS: readonly CelFunctionDoc[] = [
|
|
174
221
|
// Collections
|
|
175
222
|
{
|
|
@@ -373,6 +420,37 @@ export const CEL_FUNCTIONS: readonly CelFunctionDoc[] = [
|
|
|
373
420
|
build: () => (s: string, suffix: string) =>
|
|
374
421
|
suffix && s.endsWith(suffix) ? s.slice(0, s.length - suffix.length) : s,
|
|
375
422
|
},
|
|
423
|
+
// One `slice` over the three sequence types rather than three names: CEL
|
|
424
|
+
// already treats `size()` that way, and the alternative teaches an author that
|
|
425
|
+
// slicing bytes is a different operation from slicing a string.
|
|
426
|
+
//
|
|
427
|
+
// ONE `dyn` signature rather than a concrete overload per element type. The
|
|
428
|
+
// concrete set is more precise, but cel-js refuses overloads that overlap, so
|
|
429
|
+
// it cannot also carry the `dyn` case — and a step result whose producer
|
|
430
|
+
// declares no output type IS `dyn`, which is the common receiver. With only the
|
|
431
|
+
// concrete set the checker resolves such a call to whichever overload it tries
|
|
432
|
+
// first and pins the result to that type, so slicing an untyped byte buffer
|
|
433
|
+
// came back typed `string`. Precision here would be lost at the CEL boundary
|
|
434
|
+
// anyway, where type arguments are erased.
|
|
435
|
+
{
|
|
436
|
+
name: "slice",
|
|
437
|
+
signature: "slice(dyn, int, int): dyn",
|
|
438
|
+
// Not "string": it is the one function over strings, bytes AND lists, and the
|
|
439
|
+
// category is what groups it in the generated reference — filing it under
|
|
440
|
+
// strings hides it from the byte and list readers who need it most.
|
|
441
|
+
category: "collection",
|
|
442
|
+
summary: "Take the half-open range [start, end) of a string, bytes or list.",
|
|
443
|
+
deterministic: true,
|
|
444
|
+
hostBacked: false,
|
|
445
|
+
// Indices arrive as BigInt (a CEL int is int64), and both `String.slice` and
|
|
446
|
+
// `TypedArray.subarray` reject one. `subarray` rather than `slice` for bytes:
|
|
447
|
+
// a view costs no copy, and every consumer treats the result as read-only.
|
|
448
|
+
build: () => (value: string | Uint8Array | unknown[], start: bigint, end: bigint) => {
|
|
449
|
+
const from = Number(start);
|
|
450
|
+
const to = Number(end);
|
|
451
|
+
return value instanceof Uint8Array ? value.subarray(from, to) : value.slice(from, to);
|
|
452
|
+
},
|
|
453
|
+
},
|
|
376
454
|
// Math
|
|
377
455
|
{
|
|
378
456
|
name: "abs",
|
|
@@ -488,6 +566,30 @@ export const CEL_FUNCTIONS: readonly CelFunctionDoc[] = [
|
|
|
488
566
|
hostBacked: true,
|
|
489
567
|
build: (h) => (s: string) => h.base64Decode(s),
|
|
490
568
|
},
|
|
569
|
+
// Base64 <-> BYTES, as distinct from the two above, which are UTF-8
|
|
570
|
+
// string<->string and silently corrupt anything that is not text. Those keep
|
|
571
|
+
// their meaning (changing it would change what shipped manifests mean); these
|
|
572
|
+
// are the correct path for binary, and the pair a byte-typed slot needs.
|
|
573
|
+
// Pure-JS on purpose: `Buffer` is what made the string pair host-backed, and
|
|
574
|
+
// this package has to stay browser-safe.
|
|
575
|
+
{
|
|
576
|
+
name: "bytesFromBase64",
|
|
577
|
+
signature: "bytesFromBase64(string): bytes",
|
|
578
|
+
category: "encoding",
|
|
579
|
+
summary: "Decode a base64 string to raw bytes.",
|
|
580
|
+
deterministic: true,
|
|
581
|
+
hostBacked: false,
|
|
582
|
+
build: () => (s: string) => decodeBase64ToBytes(s),
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: "bytesToBase64",
|
|
586
|
+
signature: "bytesToBase64(bytes): string",
|
|
587
|
+
category: "encoding",
|
|
588
|
+
summary: "Encode raw bytes as a base64 string.",
|
|
589
|
+
deterministic: true,
|
|
590
|
+
hostBacked: false,
|
|
591
|
+
build: () => (b: Uint8Array) => encodeBytesToBase64(b),
|
|
592
|
+
},
|
|
491
593
|
{
|
|
492
594
|
name: "urlEncode",
|
|
493
595
|
signature: "urlEncode(string): string",
|
package/src/engine.ts
CHANGED
|
@@ -143,4 +143,24 @@ export interface TemplatingEngine {
|
|
|
143
143
|
* A source the engine considers malformed claims nothing; `analyze` is what
|
|
144
144
|
* reports why. */
|
|
145
145
|
fileClaims?(source: string): readonly EngineFileClaim[];
|
|
146
|
+
|
|
147
|
+
/** The type this tag ALWAYS produces, as a JSON Schema fragment.
|
|
148
|
+
*
|
|
149
|
+
* Declared by the engine, never recognised by a consumer — the `fileClaims`
|
|
150
|
+
* precedent applied to the one fact it left behind. Before this, the analyzer
|
|
151
|
+
* hardcoded two tag names to hand an `!include-bytes` a byte placeholder and
|
|
152
|
+
* an `!include-text` a string one; the only place a tag's produced type was
|
|
153
|
+
* written down was in its consumer, so a future tag producing bytes had to be
|
|
154
|
+
* added to a set rather than declaring it.
|
|
155
|
+
*
|
|
156
|
+
* Absent for an engine whose produced type is a function of the SLOT rather
|
|
157
|
+
* than of the tag — `!cel`, whose type is only derivable from the expression,
|
|
158
|
+
* and `!ref`, which is an identity marker. Their values keep taking a
|
|
159
|
+
* slot-shaped placeholder.
|
|
160
|
+
*
|
|
161
|
+
* What falls out is the property this preserves exactly: because an embed's
|
|
162
|
+
* type is a constant of the tag, a byte embed at a string slot and text at a
|
|
163
|
+
* byte slot both fail statically, through the ordinary schema check and with
|
|
164
|
+
* no diagnostic code of their own. */
|
|
165
|
+
producedType?(): Record<string, unknown>;
|
|
146
166
|
}
|
package/src/engines/include.ts
CHANGED
|
@@ -122,7 +122,7 @@ export function normalizeIncludePath(source: string): NormalizedIncludePath {
|
|
|
122
122
|
* `analyze` reports why a path is unusable; `fileClaims` reports the path itself
|
|
123
123
|
* so publish can place the file in a layer without recognising the tag by name.
|
|
124
124
|
*/
|
|
125
|
-
function includeEngine(name: string): TemplatingEngine {
|
|
125
|
+
function includeEngine(name: string, produced: Record<string, unknown>): TemplatingEngine {
|
|
126
126
|
return {
|
|
127
127
|
name,
|
|
128
128
|
|
|
@@ -130,6 +130,10 @@ function includeEngine(name: string): TemplatingEngine {
|
|
|
130
130
|
return makeTaggedSentinel(name, source);
|
|
131
131
|
},
|
|
132
132
|
|
|
133
|
+
producedType() {
|
|
134
|
+
return produced;
|
|
135
|
+
},
|
|
136
|
+
|
|
133
137
|
analyze(source) {
|
|
134
138
|
const { diagnostic } = normalizeIncludePath(source);
|
|
135
139
|
return { diagnostics: diagnostic ? [diagnostic] : [], calls: [] };
|
|
@@ -146,8 +150,15 @@ function includeEngine(name: string): TemplatingEngine {
|
|
|
146
150
|
}
|
|
147
151
|
|
|
148
152
|
/** Embeds a file's contents as a UTF-8 string. */
|
|
149
|
-
export const includeTextEngine: TemplatingEngine = includeEngine(INCLUDE_TEXT_ENGINE
|
|
153
|
+
export const includeTextEngine: TemplatingEngine = includeEngine(INCLUDE_TEXT_ENGINE, {
|
|
154
|
+
type: "string",
|
|
155
|
+
});
|
|
150
156
|
|
|
151
|
-
/** Embeds a file's contents as raw bytes —
|
|
152
|
-
*
|
|
153
|
-
|
|
157
|
+
/** Embeds a file's contents as raw bytes — the shape every `Telo.Bytes` slot
|
|
158
|
+
* accepts. The name is written as a literal rather than imported from the SDK:
|
|
159
|
+
* templating is the lower package, and a produced type is a schema fragment,
|
|
160
|
+
* which is data. What checks it is the registry the analyzer and the kernel both
|
|
161
|
+
* read. */
|
|
162
|
+
export const includeBytesEngine: TemplatingEngine = includeEngine(INCLUDE_BYTES_ENGINE, {
|
|
163
|
+
"x-telo-type": "Telo.Bytes",
|
|
164
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -39,7 +39,12 @@ export { refEngine } from "./engines/ref.js";
|
|
|
39
39
|
export { sqlEngine, isParameterizedSql, type ParameterizedSql } from "./engines/sql.js";
|
|
40
40
|
|
|
41
41
|
export { TemplatingEngineRegistry } from "./registry.js";
|
|
42
|
-
export {
|
|
42
|
+
export {
|
|
43
|
+
builtinEngines,
|
|
44
|
+
createDefaultRegistry,
|
|
45
|
+
defaultRegistry,
|
|
46
|
+
producedTypeOf,
|
|
47
|
+
} from "./builtins.js";
|
|
43
48
|
export type {
|
|
44
49
|
AnalyzeEnv,
|
|
45
50
|
AnalyzeResult,
|
|
@@ -52,6 +57,7 @@ export type {
|
|
|
52
57
|
} from "./engine.js";
|
|
53
58
|
|
|
54
59
|
export {
|
|
60
|
+
CEL_ENGINE,
|
|
55
61
|
INCLUDE_BYTES_ENGINE,
|
|
56
62
|
INCLUDE_ENGINE_NAMES,
|
|
57
63
|
INCLUDE_TEXT_ENGINE,
|
|
@@ -59,6 +65,7 @@ export {
|
|
|
59
65
|
isRefSentinel,
|
|
60
66
|
isTaggedSentinel,
|
|
61
67
|
makeTaggedSentinel,
|
|
68
|
+
plainChainOf,
|
|
62
69
|
type TaggedSentinel,
|
|
63
70
|
} from "./sentinel.js";
|
|
64
71
|
export { buildCustomTags, defaultCustomTags } from "./yaml-tags.js";
|
|
@@ -66,5 +73,4 @@ export {
|
|
|
66
73
|
MANIFEST_SCHEMA_URI,
|
|
67
74
|
ManifestRootSchema,
|
|
68
75
|
ResourceRefSchema,
|
|
69
|
-
normalizeRefSlots,
|
|
70
76
|
} from "./manifest-schemas.js";
|
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
|
@@ -33,12 +33,45 @@ export function isRefSentinel(v: unknown): v is TaggedSentinel & { engine: "ref"
|
|
|
33
33
|
return isTaggedSentinel(v) && v.engine === "ref";
|
|
34
34
|
}
|
|
35
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
|
+
|
|
36
41
|
/** Engine names of the two file-embedding tags. Named here beside the other
|
|
37
42
|
* sentinel predicates so the kernel's resolution pass and the engines
|
|
38
43
|
* themselves agree on one spelling. */
|
|
39
44
|
export const INCLUDE_TEXT_ENGINE = "include-text";
|
|
40
45
|
export const INCLUDE_BYTES_ENGINE = "include-bytes";
|
|
41
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
|
+
|
|
42
75
|
/** Both file-embedding tag names, for a consumer holding an engine NAME rather
|
|
43
76
|
* than a value (the analyzer's expression walk reports names). */
|
|
44
77
|
export const INCLUDE_ENGINE_NAMES: ReadonlySet<string> = new Set([
|