@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.
- package/dist/cel/catalog.d.ts.map +1 -1
- package/dist/cel/catalog.js +100 -0
- package/package.json +2 -2
- package/src/cel/catalog.ts +102 -0
|
@@ -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/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/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",
|