@telorun/templating 0.14.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.
@@ -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,EAslBlD,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,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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/templating",
3
- "version": "0.14.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.73.0"
47
+ "@telorun/sdk": "0.74.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,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",