@telorun/templating 0.14.0 → 0.16.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.
@@ -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,EA+hBlD,CAAC;AAEF,oEAAoE;AACpE,wBAAgB,kBAAkB,IAAI,eAAe,EAAE,CAEtD"}
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,EA8lBlD,CAAC;AAEF,oEAAoE;AACpE,wBAAgB,kBAAkB,IAAI,eAAe,EAAE,CAEtD"}
@@ -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,45 @@ 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
+ // OVERLOADED BY PARAMETER TYPE, so a wrong receiver is rejected — `slice(42, 0,
356
+ // 1)` matches nothing. What cannot ALSO be registered is a `dyn` parameter
357
+ // variant: cel-js refuses overloads that overlap, and a `dyn` first parameter
358
+ // overlaps all three. That is why the RETURN is `dyn` rather than the input's
359
+ // own type. A step result whose producer declares no output type is `dyn`, and
360
+ // with concrete returns the checker resolves such a call to whichever overload
361
+ // was registered first and pins the result to it — so slicing an untyped byte
362
+ // buffer came back typed `string`, and a byte consumer downstream reported a
363
+ // mismatch against perfectly valid CEL. Precision on the way out would be lost
364
+ // at the CEL boundary anyway, where type arguments are erased; being wrong
365
+ // about an untyped receiver would not be.
366
+ {
367
+ name: "slice",
368
+ signature: "slice(dyn, int, int): dyn",
369
+ register: [
370
+ "slice(bytes, int, int): dyn",
371
+ "slice(string, int, int): dyn",
372
+ "slice(list, int, int): dyn",
373
+ ],
374
+ // Not "string": it is the one function over strings, bytes AND lists, and the
375
+ // category is what groups it in the generated reference — filing it under
376
+ // strings hides it from the byte and list readers who need it most.
377
+ category: "collection",
378
+ summary: "Take the half-open range [start, end) of a string, bytes or list.",
379
+ deterministic: true,
380
+ hostBacked: false,
381
+ // Indices arrive as BigInt (a CEL int is int64), and both `String.slice` and
382
+ // `TypedArray.subarray` reject one. `subarray` rather than `slice` for bytes:
383
+ // a view costs no copy, and every consumer treats the result as read-only.
384
+ build: () => (value, start, end) => {
385
+ const from = Number(start);
386
+ const to = Number(end);
387
+ return value instanceof Uint8Array ? value.subarray(from, to) : value.slice(from, to);
388
+ },
389
+ },
306
390
  // Math
307
391
  {
308
392
  name: "abs",
@@ -416,6 +500,30 @@ export const CEL_FUNCTIONS = [
416
500
  hostBacked: true,
417
501
  build: (h) => (s) => h.base64Decode(s),
418
502
  },
503
+ // Base64 <-> BYTES, as distinct from the two above, which are UTF-8
504
+ // string<->string and silently corrupt anything that is not text. Those keep
505
+ // their meaning (changing it would change what shipped manifests mean); these
506
+ // are the correct path for binary, and the pair a byte-typed slot needs.
507
+ // Pure-JS on purpose: `Buffer` is what made the string pair host-backed, and
508
+ // this package has to stay browser-safe.
509
+ {
510
+ name: "bytesFromBase64",
511
+ signature: "bytesFromBase64(string): bytes",
512
+ category: "encoding",
513
+ summary: "Decode a base64 string to raw bytes.",
514
+ deterministic: true,
515
+ hostBacked: false,
516
+ build: () => (s) => decodeBase64ToBytes(s),
517
+ },
518
+ {
519
+ name: "bytesToBase64",
520
+ signature: "bytesToBase64(bytes): string",
521
+ category: "encoding",
522
+ summary: "Encode raw bytes as a base64 string.",
523
+ deterministic: true,
524
+ hostBacked: false,
525
+ build: () => (b) => encodeBytesToBase64(b),
526
+ },
419
527
  {
420
528
  name: "urlEncode",
421
529
  signature: "urlEncode(string): string",
package/dist/index.d.ts CHANGED
@@ -14,5 +14,4 @@ export { builtinEngines, createDefaultRegistry, defaultRegistry, producedTypeOf,
14
14
  export type { AnalyzeEnv, AnalyzeResult, CallSite, CompileEnv, DiagnosticFix, EngineDiagnostic, EngineFileClaim, TemplatingEngine, } from "./engine.js";
15
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, } from "./manifest-schemas.js";
18
17
  //# sourceMappingURL=index.d.ts.map
@@ -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,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"}
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"}
package/dist/index.js CHANGED
@@ -13,4 +13,3 @@ export { TemplatingEngineRegistry } from "./registry.js";
13
13
  export { builtinEngines, createDefaultRegistry, defaultRegistry, producedTypeOf, } from "./builtins.js";
14
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, } from "./manifest-schemas.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/templating",
3
- "version": "0.14.0",
3
+ "version": "0.16.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.73.0"
47
+ "@telorun/sdk": "0.75.0"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@telorun/sdk": "*"
@@ -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,45 @@ 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
+ // OVERLOADED BY PARAMETER TYPE, so a wrong receiver is rejected — `slice(42, 0,
428
+ // 1)` matches nothing. What cannot ALSO be registered is a `dyn` parameter
429
+ // variant: cel-js refuses overloads that overlap, and a `dyn` first parameter
430
+ // overlaps all three. That is why the RETURN is `dyn` rather than the input's
431
+ // own type. A step result whose producer declares no output type is `dyn`, and
432
+ // with concrete returns the checker resolves such a call to whichever overload
433
+ // was registered first and pins the result to it — so slicing an untyped byte
434
+ // buffer came back typed `string`, and a byte consumer downstream reported a
435
+ // mismatch against perfectly valid CEL. Precision on the way out would be lost
436
+ // at the CEL boundary anyway, where type arguments are erased; being wrong
437
+ // about an untyped receiver would not be.
438
+ {
439
+ name: "slice",
440
+ signature: "slice(dyn, int, int): dyn",
441
+ register: [
442
+ "slice(bytes, int, int): dyn",
443
+ "slice(string, int, int): dyn",
444
+ "slice(list, int, int): dyn",
445
+ ],
446
+ // Not "string": it is the one function over strings, bytes AND lists, and the
447
+ // category is what groups it in the generated reference — filing it under
448
+ // strings hides it from the byte and list readers who need it most.
449
+ category: "collection",
450
+ summary: "Take the half-open range [start, end) of a string, bytes or list.",
451
+ deterministic: true,
452
+ hostBacked: false,
453
+ // Indices arrive as BigInt (a CEL int is int64), and both `String.slice` and
454
+ // `TypedArray.subarray` reject one. `subarray` rather than `slice` for bytes:
455
+ // a view costs no copy, and every consumer treats the result as read-only.
456
+ build: () => (value: string | Uint8Array | unknown[], start: bigint, end: bigint) => {
457
+ const from = Number(start);
458
+ const to = Number(end);
459
+ return value instanceof Uint8Array ? value.subarray(from, to) : value.slice(from, to);
460
+ },
461
+ },
376
462
  // Math
377
463
  {
378
464
  name: "abs",
@@ -488,6 +574,30 @@ export const CEL_FUNCTIONS: readonly CelFunctionDoc[] = [
488
574
  hostBacked: true,
489
575
  build: (h) => (s: string) => h.base64Decode(s),
490
576
  },
577
+ // Base64 <-> BYTES, as distinct from the two above, which are UTF-8
578
+ // string<->string and silently corrupt anything that is not text. Those keep
579
+ // their meaning (changing it would change what shipped manifests mean); these
580
+ // are the correct path for binary, and the pair a byte-typed slot needs.
581
+ // Pure-JS on purpose: `Buffer` is what made the string pair host-backed, and
582
+ // this package has to stay browser-safe.
583
+ {
584
+ name: "bytesFromBase64",
585
+ signature: "bytesFromBase64(string): bytes",
586
+ category: "encoding",
587
+ summary: "Decode a base64 string to raw bytes.",
588
+ deterministic: true,
589
+ hostBacked: false,
590
+ build: () => (s: string) => decodeBase64ToBytes(s),
591
+ },
592
+ {
593
+ name: "bytesToBase64",
594
+ signature: "bytesToBase64(bytes): string",
595
+ category: "encoding",
596
+ summary: "Encode raw bytes as a base64 string.",
597
+ deterministic: true,
598
+ hostBacked: false,
599
+ build: () => (b: Uint8Array) => encodeBytesToBase64(b),
600
+ },
491
601
  {
492
602
  name: "urlEncode",
493
603
  signature: "urlEncode(string): string",
package/src/index.ts CHANGED
@@ -69,8 +69,3 @@ export {
69
69
  type TaggedSentinel,
70
70
  } from "./sentinel.js";
71
71
  export { buildCustomTags, defaultCustomTags } from "./yaml-tags.js";
72
- export {
73
- MANIFEST_SCHEMA_URI,
74
- ManifestRootSchema,
75
- ResourceRefSchema,
76
- } from "./manifest-schemas.js";
@@ -1,122 +0,0 @@
1
- /** Shared JSON-Schema fragments referenced from every module's `telo.yaml`
2
- * via `$ref: "telo://manifest#/$defs/<Name>"`. Lives in the templating
3
- * package (alongside the YAML tag mechanism) because the analyzer cannot
4
- * depend on the kernel — both consume this package, so this is the only
5
- * layer where a single source of truth can sit. The kernel re-exports
6
- * these symbols from its own `manifest-schemas` surface for downstream
7
- * ergonomics. */
8
- /** Schema fragment for a resource-reference slot. The only form a manifest
9
- * author writes is the `!ref <name>` (or `!ref <Alias>.<name>`) YAML tag,
10
- * which parses to a `TaggedSentinel` (engine "ref") whose `source` is the
11
- * bare resource name. In practice module schemas mark a ref slot with a bare
12
- * `x-telo-ref` annotation (plus, where the slot only ever holds a reference,
13
- * `type: object` to reject a stray scalar); the analyzer's reference walker
14
- * reads `x-telo-ref` to look the name up against that constraint, independent
15
- * of this fragment. A slot opts into `$ref:
16
- * "telo://manifest#/$defs/ResourceRef"` only when it wants this exact
17
- * two-branch shape enforced at the AJV layer too — it is not required, and
18
- * slots that also accept an inline value (e.g. `inputType` / `outputType`)
19
- * deliberately do not use it (an inline JSON Schema has no `kind`).
20
- *
21
- * Two `anyOf` branches because the value's shape depends on the phase at
22
- * which it is validated:
23
- *
24
- * 1. The raw `!ref` sentinel — what survives to AJV when a cross-module
25
- * reference can't be resolved in standalone single-file analysis (the
26
- * imported module isn't loaded). `substituteCelFields` deliberately
27
- * keeps the sentinel so this branch matches.
28
- * 2. A resolved reference object — `{kind, name, alias?}` substituted in
29
- * place of a sentinel (or an inline definition `{kind, ...config}`
30
- * reached through a local `$ref` that escapes extraction). Both the
31
- * kernel and the analyzer validate ref slots *after* sentinel
32
- * resolution, so this is the shape AJV usually sees.
33
- *
34
- * The object-form `{kind, name}` reference a user could once type directly
35
- * is gone: a plain object at a ref slot is only ever an inline definition
36
- * or the resolver's own substitution, never an author-written reference.
37
- * That removal is enforced by the analyzer (it rejects an author-written
38
- * `{kind, name}` before normalization), not by this schema — branch 2
39
- * cannot distinguish an author's `{kind, name}` from the resolver's. */
40
- export declare const ResourceRefSchema: {
41
- title: string;
42
- anyOf: ({
43
- type: string;
44
- required: string[];
45
- properties: {
46
- __tagged: {
47
- const: boolean;
48
- };
49
- engine: {
50
- const: string;
51
- };
52
- source: {
53
- type: string;
54
- minLength: number;
55
- };
56
- kind?: undefined;
57
- };
58
- additionalProperties: boolean;
59
- } | {
60
- type: string;
61
- required: string[];
62
- properties: {
63
- kind: {
64
- type: string;
65
- };
66
- __tagged?: undefined;
67
- engine?: undefined;
68
- source?: undefined;
69
- };
70
- additionalProperties: boolean;
71
- })[];
72
- };
73
- /** Stable URI under which the shared manifest root schema is registered
74
- * with module-side AJV instances. Module YAMLs reach the fragments via
75
- * `$ref: "telo://manifest#/$defs/<Name>"`. The URI is the contract;
76
- * the symbol names intentionally omit a host-specific prefix since the
77
- * fragments live in `@telorun/templating` (the only layer both kernel
78
- * and analyzer depend on). */
79
- export declare const MANIFEST_SCHEMA_URI = "telo://manifest";
80
- /** Root schema registered with AJV under `MANIFEST_SCHEMA_URI`. Carries
81
- * `$defs` only — it isn't validated against directly. Adding a new
82
- * shared fragment means putting it under `$defs` here and `$ref`-ing
83
- * it from module schemas. */
84
- export declare const ManifestRootSchema: {
85
- $id: string;
86
- $defs: {
87
- ResourceRef: {
88
- title: string;
89
- anyOf: ({
90
- type: string;
91
- required: string[];
92
- properties: {
93
- __tagged: {
94
- const: boolean;
95
- };
96
- engine: {
97
- const: string;
98
- };
99
- source: {
100
- type: string;
101
- minLength: number;
102
- };
103
- kind?: undefined;
104
- };
105
- additionalProperties: boolean;
106
- } | {
107
- type: string;
108
- required: string[];
109
- properties: {
110
- kind: {
111
- type: string;
112
- };
113
- __tagged?: undefined;
114
- engine?: undefined;
115
- source?: undefined;
116
- };
117
- additionalProperties: boolean;
118
- })[];
119
- };
120
- };
121
- };
122
- //# sourceMappingURL=manifest-schemas.d.ts.map
@@ -1 +0,0 @@
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"}
@@ -1,77 +0,0 @@
1
- /** Shared JSON-Schema fragments referenced from every module's `telo.yaml`
2
- * via `$ref: "telo://manifest#/$defs/<Name>"`. Lives in the templating
3
- * package (alongside the YAML tag mechanism) because the analyzer cannot
4
- * depend on the kernel — both consume this package, so this is the only
5
- * layer where a single source of truth can sit. The kernel re-exports
6
- * these symbols from its own `manifest-schemas` surface for downstream
7
- * ergonomics. */
8
- /** Schema fragment for a resource-reference slot. The only form a manifest
9
- * author writes is the `!ref <name>` (or `!ref <Alias>.<name>`) YAML tag,
10
- * which parses to a `TaggedSentinel` (engine "ref") whose `source` is the
11
- * bare resource name. In practice module schemas mark a ref slot with a bare
12
- * `x-telo-ref` annotation (plus, where the slot only ever holds a reference,
13
- * `type: object` to reject a stray scalar); the analyzer's reference walker
14
- * reads `x-telo-ref` to look the name up against that constraint, independent
15
- * of this fragment. A slot opts into `$ref:
16
- * "telo://manifest#/$defs/ResourceRef"` only when it wants this exact
17
- * two-branch shape enforced at the AJV layer too — it is not required, and
18
- * slots that also accept an inline value (e.g. `inputType` / `outputType`)
19
- * deliberately do not use it (an inline JSON Schema has no `kind`).
20
- *
21
- * Two `anyOf` branches because the value's shape depends on the phase at
22
- * which it is validated:
23
- *
24
- * 1. The raw `!ref` sentinel — what survives to AJV when a cross-module
25
- * reference can't be resolved in standalone single-file analysis (the
26
- * imported module isn't loaded). `substituteCelFields` deliberately
27
- * keeps the sentinel so this branch matches.
28
- * 2. A resolved reference object — `{kind, name, alias?}` substituted in
29
- * place of a sentinel (or an inline definition `{kind, ...config}`
30
- * reached through a local `$ref` that escapes extraction). Both the
31
- * kernel and the analyzer validate ref slots *after* sentinel
32
- * resolution, so this is the shape AJV usually sees.
33
- *
34
- * The object-form `{kind, name}` reference a user could once type directly
35
- * is gone: a plain object at a ref slot is only ever an inline definition
36
- * or the resolver's own substitution, never an author-written reference.
37
- * That removal is enforced by the analyzer (it rejects an author-written
38
- * `{kind, name}` before normalization), not by this schema — branch 2
39
- * cannot distinguish an author's `{kind, name}` from the resolver's. */
40
- export const ResourceRefSchema = {
41
- title: "Resource reference",
42
- anyOf: [
43
- {
44
- type: "object",
45
- required: ["__tagged", "engine", "source"],
46
- properties: {
47
- __tagged: { const: true },
48
- engine: { const: "ref" },
49
- source: { type: "string", minLength: 1 },
50
- },
51
- additionalProperties: false,
52
- },
53
- {
54
- type: "object",
55
- required: ["kind"],
56
- properties: { kind: { type: "string" } },
57
- additionalProperties: true,
58
- },
59
- ],
60
- };
61
- /** Stable URI under which the shared manifest root schema is registered
62
- * with module-side AJV instances. Module YAMLs reach the fragments via
63
- * `$ref: "telo://manifest#/$defs/<Name>"`. The URI is the contract;
64
- * the symbol names intentionally omit a host-specific prefix since the
65
- * fragments live in `@telorun/templating` (the only layer both kernel
66
- * and analyzer depend on). */
67
- export const MANIFEST_SCHEMA_URI = "telo://manifest";
68
- /** Root schema registered with AJV under `MANIFEST_SCHEMA_URI`. Carries
69
- * `$defs` only — it isn't validated against directly. Adding a new
70
- * shared fragment means putting it under `$defs` here and `$ref`-ing
71
- * it from module schemas. */
72
- export const ManifestRootSchema = {
73
- $id: MANIFEST_SCHEMA_URI,
74
- $defs: {
75
- ResourceRef: ResourceRefSchema,
76
- },
77
- };
@@ -1,81 +0,0 @@
1
- /** Shared JSON-Schema fragments referenced from every module's `telo.yaml`
2
- * via `$ref: "telo://manifest#/$defs/<Name>"`. Lives in the templating
3
- * package (alongside the YAML tag mechanism) because the analyzer cannot
4
- * depend on the kernel — both consume this package, so this is the only
5
- * layer where a single source of truth can sit. The kernel re-exports
6
- * these symbols from its own `manifest-schemas` surface for downstream
7
- * ergonomics. */
8
-
9
- /** Schema fragment for a resource-reference slot. The only form a manifest
10
- * author writes is the `!ref <name>` (or `!ref <Alias>.<name>`) YAML tag,
11
- * which parses to a `TaggedSentinel` (engine "ref") whose `source` is the
12
- * bare resource name. In practice module schemas mark a ref slot with a bare
13
- * `x-telo-ref` annotation (plus, where the slot only ever holds a reference,
14
- * `type: object` to reject a stray scalar); the analyzer's reference walker
15
- * reads `x-telo-ref` to look the name up against that constraint, independent
16
- * of this fragment. A slot opts into `$ref:
17
- * "telo://manifest#/$defs/ResourceRef"` only when it wants this exact
18
- * two-branch shape enforced at the AJV layer too — it is not required, and
19
- * slots that also accept an inline value (e.g. `inputType` / `outputType`)
20
- * deliberately do not use it (an inline JSON Schema has no `kind`).
21
- *
22
- * Two `anyOf` branches because the value's shape depends on the phase at
23
- * which it is validated:
24
- *
25
- * 1. The raw `!ref` sentinel — what survives to AJV when a cross-module
26
- * reference can't be resolved in standalone single-file analysis (the
27
- * imported module isn't loaded). `substituteCelFields` deliberately
28
- * keeps the sentinel so this branch matches.
29
- * 2. A resolved reference object — `{kind, name, alias?}` substituted in
30
- * place of a sentinel (or an inline definition `{kind, ...config}`
31
- * reached through a local `$ref` that escapes extraction). Both the
32
- * kernel and the analyzer validate ref slots *after* sentinel
33
- * resolution, so this is the shape AJV usually sees.
34
- *
35
- * The object-form `{kind, name}` reference a user could once type directly
36
- * is gone: a plain object at a ref slot is only ever an inline definition
37
- * or the resolver's own substitution, never an author-written reference.
38
- * That removal is enforced by the analyzer (it rejects an author-written
39
- * `{kind, name}` before normalization), not by this schema — branch 2
40
- * cannot distinguish an author's `{kind, name}` from the resolver's. */
41
- export const ResourceRefSchema = {
42
- title: "Resource reference",
43
- anyOf: [
44
- {
45
- type: "object",
46
- required: ["__tagged", "engine", "source"],
47
- properties: {
48
- __tagged: { const: true },
49
- engine: { const: "ref" },
50
- source: { type: "string", minLength: 1 },
51
- },
52
- additionalProperties: false,
53
- },
54
- {
55
- type: "object",
56
- required: ["kind"],
57
- properties: { kind: { type: "string" } },
58
- additionalProperties: true,
59
- },
60
- ],
61
- };
62
-
63
-
64
- /** Stable URI under which the shared manifest root schema is registered
65
- * with module-side AJV instances. Module YAMLs reach the fragments via
66
- * `$ref: "telo://manifest#/$defs/<Name>"`. The URI is the contract;
67
- * the symbol names intentionally omit a host-specific prefix since the
68
- * fragments live in `@telorun/templating` (the only layer both kernel
69
- * and analyzer depend on). */
70
- export const MANIFEST_SCHEMA_URI = "telo://manifest";
71
-
72
- /** Root schema registered with AJV under `MANIFEST_SCHEMA_URI`. Carries
73
- * `$defs` only — it isn't validated against directly. Adding a new
74
- * shared fragment means putting it under `$defs` here and `$ref`-ing
75
- * it from module schemas. */
76
- export const ManifestRootSchema = {
77
- $id: MANIFEST_SCHEMA_URI,
78
- $defs: {
79
- ResourceRef: ResourceRefSchema,
80
- },
81
- };