@telorun/cel 0.0.0-stage → 0.107.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.
Files changed (223) hide show
  1. package/LICENSE +17 -0
  2. package/README.md +281 -2
  3. package/dist/activation.d.ts +27 -0
  4. package/dist/activation.d.ts.map +1 -0
  5. package/dist/activation.js +24 -0
  6. package/dist/backend-runtime.d.ts +146 -0
  7. package/dist/backend-runtime.d.ts.map +1 -0
  8. package/dist/backend-runtime.js +322 -0
  9. package/dist/bounded-cache.d.ts +21 -0
  10. package/dist/bounded-cache.d.ts.map +1 -0
  11. package/dist/bounded-cache.js +42 -0
  12. package/dist/catalog-runtime.d.ts +59 -0
  13. package/dist/catalog-runtime.d.ts.map +1 -0
  14. package/dist/catalog-runtime.js +785 -0
  15. package/dist/cel-expression.d.ts +32 -0
  16. package/dist/cel-expression.d.ts.map +1 -0
  17. package/dist/cel-expression.js +29 -0
  18. package/dist/cel-map-value.d.ts +34 -0
  19. package/dist/cel-map-value.d.ts.map +1 -0
  20. package/dist/cel-map-value.js +74 -0
  21. package/dist/cel-program.d.ts +44 -0
  22. package/dist/cel-program.d.ts.map +1 -0
  23. package/dist/cel-program.js +72 -0
  24. package/dist/cel-type.d.ts +131 -0
  25. package/dist/cel-type.d.ts.map +1 -0
  26. package/dist/cel-type.js +293 -0
  27. package/dist/cel-value.d.ts +159 -0
  28. package/dist/cel-value.d.ts.map +1 -0
  29. package/dist/cel-value.js +225 -0
  30. package/dist/check-diagnostic.d.ts +53 -0
  31. package/dist/check-diagnostic.d.ts.map +1 -0
  32. package/dist/check-diagnostic.js +66 -0
  33. package/dist/checker.d.ts +77 -0
  34. package/dist/checker.d.ts.map +1 -0
  35. package/dist/checker.js +721 -0
  36. package/dist/closure-backend.d.ts +21 -0
  37. package/dist/closure-backend.d.ts.map +1 -0
  38. package/dist/closure-backend.js +436 -0
  39. package/dist/comprehension-bindings.d.ts +33 -0
  40. package/dist/comprehension-bindings.d.ts.map +1 -0
  41. package/dist/comprehension-bindings.js +50 -0
  42. package/dist/comprehension-runtime.d.ts +44 -0
  43. package/dist/comprehension-runtime.d.ts.map +1 -0
  44. package/dist/comprehension-runtime.js +137 -0
  45. package/dist/declared-chain.d.ts +35 -0
  46. package/dist/declared-chain.d.ts.map +1 -0
  47. package/dist/declared-chain.js +36 -0
  48. package/dist/duration-value.d.ts +59 -0
  49. package/dist/duration-value.d.ts.map +1 -0
  50. package/dist/duration-value.js +135 -0
  51. package/dist/emitted-module.d.ts +207 -0
  52. package/dist/emitted-module.d.ts.map +1 -0
  53. package/dist/emitted-module.js +359 -0
  54. package/dist/engine-version.d.ts +3 -0
  55. package/dist/engine-version.d.ts.map +1 -0
  56. package/dist/engine-version.js +8 -0
  57. package/dist/environment-digest.d.ts +44 -0
  58. package/dist/environment-digest.d.ts.map +1 -0
  59. package/dist/environment-digest.js +98 -0
  60. package/dist/environment.d.ts +283 -0
  61. package/dist/environment.d.ts.map +1 -0
  62. package/dist/environment.js +459 -0
  63. package/dist/function-catalog.d.ts +66 -0
  64. package/dist/function-catalog.d.ts.map +1 -0
  65. package/dist/function-catalog.js +77 -0
  66. package/dist/function-registry.d.ts +78 -0
  67. package/dist/function-registry.d.ts.map +1 -0
  68. package/dist/function-registry.js +189 -0
  69. package/dist/index.d.ts +91 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +59 -0
  72. package/dist/integer-arithmetic.d.ts +27 -0
  73. package/dist/integer-arithmetic.d.ts.map +1 -0
  74. package/dist/integer-arithmetic.js +58 -0
  75. package/dist/js-emitter.d.ts +132 -0
  76. package/dist/js-emitter.d.ts.map +1 -0
  77. package/dist/js-emitter.js +562 -0
  78. package/dist/json-schema-type.d.ts +182 -0
  79. package/dist/json-schema-type.d.ts.map +1 -0
  80. package/dist/json-schema-type.js +487 -0
  81. package/dist/json-text-scan.d.ts +28 -0
  82. package/dist/json-text-scan.d.ts.map +1 -0
  83. package/dist/json-text-scan.js +159 -0
  84. package/dist/lexer.d.ts +103 -0
  85. package/dist/lexer.d.ts.map +1 -0
  86. package/dist/lexer.js +458 -0
  87. package/dist/macro-check.d.ts +33 -0
  88. package/dist/macro-check.d.ts.map +1 -0
  89. package/dist/macro-check.js +162 -0
  90. package/dist/macro-shape.d.ts +24 -0
  91. package/dist/macro-shape.d.ts.map +1 -0
  92. package/dist/macro-shape.js +55 -0
  93. package/dist/member-read.d.ts +52 -0
  94. package/dist/member-read.d.ts.map +1 -0
  95. package/dist/member-read.js +125 -0
  96. package/dist/namespace-resolution.d.ts +35 -0
  97. package/dist/namespace-resolution.d.ts.map +1 -0
  98. package/dist/namespace-resolution.js +160 -0
  99. package/dist/nominal-type.d.ts +63 -0
  100. package/dist/nominal-type.d.ts.map +1 -0
  101. package/dist/nominal-type.js +98 -0
  102. package/dist/nullable-access.d.ts +38 -0
  103. package/dist/nullable-access.d.ts.map +1 -0
  104. package/dist/nullable-access.js +93 -0
  105. package/dist/parse-limits.d.ts +26 -0
  106. package/dist/parse-limits.d.ts.map +1 -0
  107. package/dist/parse-limits.js +21 -0
  108. package/dist/parser.d.ts +48 -0
  109. package/dist/parser.d.ts.map +1 -0
  110. package/dist/parser.js +503 -0
  111. package/dist/qualified-calls.d.ts +22 -0
  112. package/dist/qualified-calls.d.ts.map +1 -0
  113. package/dist/qualified-calls.js +27 -0
  114. package/dist/regular-expression.d.ts +48 -0
  115. package/dist/regular-expression.d.ts.map +1 -0
  116. package/dist/regular-expression.js +77 -0
  117. package/dist/reserved-words.d.ts +52 -0
  118. package/dist/reserved-words.d.ts.map +1 -0
  119. package/dist/reserved-words.js +77 -0
  120. package/dist/resolved-call.d.ts +33 -0
  121. package/dist/resolved-call.d.ts.map +1 -0
  122. package/dist/resolved-call.js +15 -0
  123. package/dist/root-references.d.ts +23 -0
  124. package/dist/root-references.d.ts.map +1 -0
  125. package/dist/root-references.js +120 -0
  126. package/dist/runtime-library.d.ts +56 -0
  127. package/dist/runtime-library.d.ts.map +1 -0
  128. package/dist/runtime-library.js +545 -0
  129. package/dist/serializer.d.ts +24 -0
  130. package/dist/serializer.d.ts.map +1 -0
  131. package/dist/serializer.js +240 -0
  132. package/dist/sha256.d.ts +20 -0
  133. package/dist/sha256.d.ts.map +1 -0
  134. package/dist/sha256.js +103 -0
  135. package/dist/signature.d.ts +72 -0
  136. package/dist/signature.d.ts.map +1 -0
  137. package/dist/signature.js +61 -0
  138. package/dist/signatures/function-catalog.json +788 -0
  139. package/dist/signatures/standard-library.json +229 -0
  140. package/dist/standard-library.d.ts +41 -0
  141. package/dist/standard-library.d.ts.map +1 -0
  142. package/dist/standard-library.js +85 -0
  143. package/dist/syntax-diagnostic.d.ts +61 -0
  144. package/dist/syntax-diagnostic.d.ts.map +1 -0
  145. package/dist/syntax-diagnostic.js +28 -0
  146. package/dist/syntax-tree.d.ts +160 -0
  147. package/dist/syntax-tree.d.ts.map +1 -0
  148. package/dist/syntax-tree.js +58 -0
  149. package/dist/timestamp-value.d.ts +53 -0
  150. package/dist/timestamp-value.d.ts.map +1 -0
  151. package/dist/timestamp-value.js +223 -0
  152. package/dist/tree-equality.d.ts +15 -0
  153. package/dist/tree-equality.d.ts.map +1 -0
  154. package/dist/tree-equality.js +105 -0
  155. package/dist/type-expression.d.ts +26 -0
  156. package/dist/type-expression.d.ts.map +1 -0
  157. package/dist/type-expression.js +134 -0
  158. package/dist/value-equality.d.ts +38 -0
  159. package/dist/value-equality.d.ts.map +1 -0
  160. package/dist/value-equality.js +196 -0
  161. package/dist/value-text.d.ts +25 -0
  162. package/dist/value-text.d.ts.map +1 -0
  163. package/dist/value-text.js +44 -0
  164. package/dist/zoned-calendar.d.ts +61 -0
  165. package/dist/zoned-calendar.d.ts.map +1 -0
  166. package/dist/zoned-calendar.js +143 -0
  167. package/package.json +56 -3
  168. package/src/activation.ts +32 -0
  169. package/src/backend-runtime.ts +454 -0
  170. package/src/bounded-cache.ts +45 -0
  171. package/src/catalog-runtime.ts +921 -0
  172. package/src/cel-expression.ts +53 -0
  173. package/src/cel-map-value.ts +86 -0
  174. package/src/cel-program.ts +103 -0
  175. package/src/cel-type.ts +359 -0
  176. package/src/cel-value.ts +353 -0
  177. package/src/check-diagnostic.ts +102 -0
  178. package/src/checker.ts +932 -0
  179. package/src/closure-backend.ts +502 -0
  180. package/src/comprehension-bindings.ts +66 -0
  181. package/src/comprehension-runtime.ts +157 -0
  182. package/src/declared-chain.ts +45 -0
  183. package/src/duration-value.ts +145 -0
  184. package/src/emitted-module.ts +494 -0
  185. package/src/engine-version.ts +9 -0
  186. package/src/environment-digest.ts +111 -0
  187. package/src/environment.ts +740 -0
  188. package/src/function-catalog.ts +140 -0
  189. package/src/function-registry.ts +229 -0
  190. package/src/index.ts +386 -0
  191. package/src/integer-arithmetic.ts +64 -0
  192. package/src/js-emitter.ts +713 -0
  193. package/src/json-schema-type.ts +664 -0
  194. package/src/json-text-scan.ts +163 -0
  195. package/src/lexer.ts +562 -0
  196. package/src/macro-check.ts +191 -0
  197. package/src/macro-shape.ts +66 -0
  198. package/src/member-read.ts +126 -0
  199. package/src/namespace-resolution.ts +167 -0
  200. package/src/nominal-type.ts +149 -0
  201. package/src/nullable-access.ts +95 -0
  202. package/src/parse-limits.ts +36 -0
  203. package/src/parser.ts +554 -0
  204. package/src/qualified-calls.ts +39 -0
  205. package/src/regular-expression.ts +101 -0
  206. package/src/reserved-words.ts +94 -0
  207. package/src/resolved-call.ts +34 -0
  208. package/src/root-references.ts +126 -0
  209. package/src/runtime-library.ts +615 -0
  210. package/src/serializer.ts +246 -0
  211. package/src/sha256.ts +112 -0
  212. package/src/signature.ts +127 -0
  213. package/src/signatures/function-catalog.json +788 -0
  214. package/src/signatures/standard-library.json +235 -0
  215. package/src/standard-library.ts +149 -0
  216. package/src/syntax-diagnostic.ts +72 -0
  217. package/src/syntax-tree.ts +229 -0
  218. package/src/timestamp-value.ts +294 -0
  219. package/src/tree-equality.ts +130 -0
  220. package/src/type-expression.ts +160 -0
  221. package/src/value-equality.ts +201 -0
  222. package/src/value-text.ts +45 -0
  223. package/src/zoned-calendar.ts +178 -0
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Equality and ordering over values — **one comparison, serving both.**
3
+ *
4
+ * `<`, `<=`, `>`, `>=`, cross-numeric equality, `in` over a list and a list index all read
5
+ * the same rule, because two readers of "is this the same value" is how a container and an
6
+ * operator come to disagree about one expression.
7
+ *
8
+ * **Across the numeric types it CONVERTS.** The checker is strict — `1.0 == 1` is a type
9
+ * error — but `dyn(1.0) == 1` checks, so the runtime is reached with two numeric types in
10
+ * hand and cel-spec's answer is `true`. Where the double lies outside the integer type's
11
+ * range the sign decides; otherwise the integer becomes a double and two doubles are
12
+ * compared, **lossily**, so `dyn(9223372036854775807) < 9223372036854775808.0` is `false`.
13
+ * That is cel-spec's own rule: its corpus comments the case ("the conversion of the int to
14
+ * double is lossy") and names the test `not_lt_dyn_int_big_lossy_double`. `int(double)`
15
+ * refuses a double at or beyond **either** int64 extreme for the same reason.
16
+ *
17
+ * An exact comparison is defensible alone and indefensible as a cross-engine contract: a
18
+ * second engine built on a conformant library would answer the other way on a comparison
19
+ * that can decide an authorization or a retry bound, silently, at a level no static check
20
+ * reaches.
21
+ *
22
+ * **A map's key identity is NOT this comparison** and does not convert: a map is keyed by
23
+ * the canonical text of its key, so `{1u: 1.0}[?1.0]` reads the entry and `[?3.1]` names
24
+ * none.
25
+ *
26
+ * Equality across unrelated types is `false`, not an error — two values of different
27
+ * types are not equal, and asking is not a mistake. Ordering across unrelated types has
28
+ * no answer, so the caller reports no overload.
29
+ */
30
+ import { durationNanos } from "./duration-value.js";
31
+ import { mapKeyIdentity } from "./cel-map-value.js";
32
+ import { celTypeNameOf, isCelBytes, isCelDuration, isCelMap, isCelOptional, isCelRecord, isCelTimestamp, isCelTypeValue, isCelUint, } from "./cel-value.js";
33
+ import { timestampNanos } from "./timestamp-value.js";
34
+ /** The int64 and uint64 extremes as doubles. Each reads as the nearest double there is. */
35
+ const MIN_INT_AS_DOUBLE = -9223372036854775808;
36
+ const MAX_INT_AS_DOUBLE = 9223372036854775807;
37
+ const MAX_UINT_AS_DOUBLE = 18446744073709551615;
38
+ /**
39
+ * Comparing an integer against a double, cel-spec's way: outside the integer type's range
40
+ * the sign decides, inside it both sides are doubles. Nothing for NaN, which orders with
41
+ * nothing including itself.
42
+ */
43
+ function compareIntegerDouble(left, unsigned, right) {
44
+ if (Number.isNaN(right))
45
+ return undefined;
46
+ if (right < (unsigned ? 0 : MIN_INT_AS_DOUBLE))
47
+ return 1;
48
+ if (right > (unsigned ? MAX_UINT_AS_DOUBLE : MAX_INT_AS_DOUBLE))
49
+ return -1;
50
+ const converted = Number(left);
51
+ return converted < right ? -1 : converted > right ? 1 : 0;
52
+ }
53
+ function compareNumbers(left, right) {
54
+ if (Number.isNaN(left) || Number.isNaN(right))
55
+ return undefined;
56
+ return left < right ? -1 : left > right ? 1 : 0;
57
+ }
58
+ function compareBigints(left, right) {
59
+ return left < right ? -1 : left > right ? 1 : 0;
60
+ }
61
+ /** The numeric magnitude of a value, where it has one, and which range it belongs to. */
62
+ function numeric(value) {
63
+ if (typeof value === "bigint")
64
+ return { integer: value, unsigned: false };
65
+ if (typeof value === "number")
66
+ return { double: value };
67
+ if (isCelUint(value))
68
+ return { integer: value.value, unsigned: true };
69
+ return undefined;
70
+ }
71
+ /** Whether two numeric values compare, and how. Nothing where one is NaN. */
72
+ function compareNumeric(left, right) {
73
+ const a = numeric(left);
74
+ const b = numeric(right);
75
+ if (!a || !b)
76
+ return undefined;
77
+ if ("integer" in a && "integer" in b) {
78
+ // An int and a uint are both exact integers, so one comparison serves both.
79
+ return compareBigints(a.integer, b.integer);
80
+ }
81
+ if ("integer" in a) {
82
+ return compareIntegerDouble(a.integer, a.unsigned, b.double);
83
+ }
84
+ if ("integer" in b) {
85
+ const compared = compareIntegerDouble(b.integer, b.unsigned, a.double);
86
+ return compared === undefined ? undefined : -compared;
87
+ }
88
+ return compareNumbers(a.double, b.double);
89
+ }
90
+ function compareBytes(left, right) {
91
+ const shared = Math.min(left.length, right.length);
92
+ for (let at = 0; at < shared; at += 1) {
93
+ if (left[at] !== right[at])
94
+ return left[at] < right[at] ? -1 : 1;
95
+ }
96
+ return left.length === right.length ? 0 : left.length < right.length ? -1 : 1;
97
+ }
98
+ /**
99
+ * How two values order: `-1`, `0`, `1`, or nothing when they do not order at all —
100
+ * unrelated types, or a NaN, which orders with nothing including itself.
101
+ */
102
+ export function celCompare(left, right) {
103
+ if (numeric(left) && numeric(right))
104
+ return compareNumeric(left, right);
105
+ if (typeof left === "string" && typeof right === "string") {
106
+ return left < right ? -1 : left > right ? 1 : 0;
107
+ }
108
+ if (typeof left === "boolean" && typeof right === "boolean") {
109
+ return left === right ? 0 : left ? 1 : -1;
110
+ }
111
+ if (isCelBytes(left) && isCelBytes(right))
112
+ return compareBytes(left, right);
113
+ if (isCelTimestamp(left) && isCelTimestamp(right)) {
114
+ return compareBigints(timestampNanos(left), timestampNanos(right));
115
+ }
116
+ if (isCelDuration(left) && isCelDuration(right)) {
117
+ return compareBigints(durationNanos(left), durationNanos(right));
118
+ }
119
+ return undefined;
120
+ }
121
+ /** CEL equality: by value, across the numeric types, and `false` across unrelated ones. */
122
+ export function celEqual(left, right) {
123
+ // One identical primitive is equal to itself, and the two edges agree: `-0 === 0` and CEL
124
+ // says they are equal, `NaN !== NaN` and CEL says they are not — so the slow path below
125
+ // answers NaN correctly after this check declines it.
126
+ if (left === right && typeof left !== "object")
127
+ return true;
128
+ if (numeric(left) && numeric(right))
129
+ return compareNumeric(left, right) === 0;
130
+ if (left === null || right === null)
131
+ return left === right;
132
+ const leftType = celTypeNameOf(left);
133
+ if (leftType !== celTypeNameOf(right))
134
+ return false;
135
+ switch (leftType) {
136
+ case "bool":
137
+ case "string":
138
+ return left === right;
139
+ case "bytes":
140
+ return compareBytes(left, right) === 0;
141
+ case "google.protobuf.Timestamp":
142
+ return timestampNanos(left) === timestampNanos(right);
143
+ case "google.protobuf.Duration":
144
+ return durationNanos(left) === durationNanos(right);
145
+ case "type":
146
+ return isCelTypeValue(left) && isCelTypeValue(right) && left.name === right.name;
147
+ case "list":
148
+ return equalLists(left, right);
149
+ case "map":
150
+ return equalMaps(left, right);
151
+ case "optional":
152
+ return equalOptionals(left, right);
153
+ default:
154
+ // A host's own named type: equal when the host's operator says so, which it
155
+ // registers itself. With none registered, identity is all this engine knows.
156
+ return left === right;
157
+ }
158
+ }
159
+ function equalLists(left, right) {
160
+ if (left.length !== right.length)
161
+ return false;
162
+ return left.every((value, at) => celEqual(value, right[at]));
163
+ }
164
+ /** Every key of a map, as the pairs equality walks. */
165
+ function mapPairs(value) {
166
+ if (isCelMap(value)) {
167
+ return new Map([...value.entries].map(([identity, entry]) => [identity, entry.value]));
168
+ }
169
+ if (isCelRecord(value)) {
170
+ const pairs = new Map();
171
+ for (const key of Object.keys(value))
172
+ pairs.set(mapKeyIdentity(key), value[key]);
173
+ return pairs;
174
+ }
175
+ return undefined;
176
+ }
177
+ function equalMaps(left, right) {
178
+ const a = mapPairs(left);
179
+ const b = mapPairs(right);
180
+ if (!a || !b || a.size !== b.size)
181
+ return false;
182
+ for (const [identity, value] of a) {
183
+ if (!b.has(identity))
184
+ return false;
185
+ if (!celEqual(value, b.get(identity)))
186
+ return false;
187
+ }
188
+ return true;
189
+ }
190
+ function equalOptionals(left, right) {
191
+ if (!isCelOptional(left) || !isCelOptional(right))
192
+ return false;
193
+ if (left.present !== right.present)
194
+ return false;
195
+ return !left.present || celEqual(left.held, right.held);
196
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Text: how a value is written as a string, and how text becomes bytes.
3
+ *
4
+ * `string()` is the one conversion a consumer renders a value through — an interpolated
5
+ * hole is defined as the CEL join of `string(<hole>)` — so each form here is the value's
6
+ * single written form, the same one the plain encoding uses: RFC 3339 for an instant,
7
+ * seconds with an `s` for a duration, UTF-8 text for bytes.
8
+ */
9
+ import { type CelError } from "./cel-value.js";
10
+ import type { SourceRange } from "./syntax-tree.js";
11
+ /** The UTF-8 bytes of text. */
12
+ export declare function textToBytes(text: string): Uint8Array;
13
+ /**
14
+ * The text bytes hold. Bytes that are not UTF-8 are a conversion error rather than text
15
+ * with a replacement character in it: a replacement character is a value the author
16
+ * never wrote, and it would read back as different bytes.
17
+ */
18
+ export declare function bytesToText(bytes: Uint8Array, range?: SourceRange): string | CelError;
19
+ /**
20
+ * A double as text. The shortest digits that read back as the same double, which is the
21
+ * host's own shortest-round-trip form; the non-finite doubles are named as cel-spec
22
+ * writes them.
23
+ */
24
+ export declare function doubleText(value: number): string;
25
+ //# sourceMappingURL=value-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"value-text.d.ts","sourceRoot":"","sources":["../src/value-text.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAKpD,+BAA+B;AAC/B,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAAQ,CAMrF;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhD"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Text: how a value is written as a string, and how text becomes bytes.
3
+ *
4
+ * `string()` is the one conversion a consumer renders a value through — an interpolated
5
+ * hole is defined as the CEL join of `string(<hole>)` — so each form here is the value's
6
+ * single written form, the same one the plain encoding uses: RFC 3339 for an instant,
7
+ * seconds with an `s` for a duration, UTF-8 text for bytes.
8
+ */
9
+ import { celError } from "./cel-value.js";
10
+ const encoder = new TextEncoder();
11
+ const decoder = new TextDecoder("utf-8", { fatal: true });
12
+ /** The UTF-8 bytes of text. */
13
+ export function textToBytes(text) {
14
+ return encoder.encode(text);
15
+ }
16
+ /**
17
+ * The text bytes hold. Bytes that are not UTF-8 are a conversion error rather than text
18
+ * with a replacement character in it: a replacement character is a value the author
19
+ * never wrote, and it would read back as different bytes.
20
+ */
21
+ export function bytesToText(bytes, range) {
22
+ try {
23
+ return decoder.decode(bytes);
24
+ }
25
+ catch {
26
+ return celError("invalid_conversion", "the bytes are not UTF-8 text", range);
27
+ }
28
+ }
29
+ /**
30
+ * A double as text. The shortest digits that read back as the same double, which is the
31
+ * host's own shortest-round-trip form; the non-finite doubles are named as cel-spec
32
+ * writes them.
33
+ */
34
+ export function doubleText(value) {
35
+ if (Number.isNaN(value))
36
+ return "NaN";
37
+ if (value === Number.POSITIVE_INFINITY)
38
+ return "+Inf";
39
+ if (value === Number.NEGATIVE_INFINITY)
40
+ return "-Inf";
41
+ if (value === 0 && Object.is(value, -0))
42
+ return "-0";
43
+ return String(value);
44
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The calendar in a time zone: an instant's wall-clock fields, and the instant a wall
3
+ * clock names.
4
+ *
5
+ * An instant is only a date once a zone is chosen, so every calendar function of the
6
+ * function catalog reads its fields through here. The zone's RULES are the host's data
7
+ * (`Intl.DateTimeFormat`, ECMA-402, present in a browser as in Node), never this
8
+ * engine's: what is written here is only how those rules are applied, which is the half
9
+ * a second engine has to reproduce.
10
+ *
11
+ * It is separate from `timestamp-value.ts` on purpose. That file is the language's own
12
+ * zone reading — cel-spec's getters, which also accept a fixed `HH:MM` offset and word
13
+ * their refusal as the language does. This one is the catalog's: it accepts an IANA name
14
+ * alone, it is millisecond-granular because the functions built on it render
15
+ * milliseconds, and it answers a gap or a repeated wall clock the way a calendar
16
+ * library does. Folding the two would make one of the two behaviours wrong.
17
+ */
18
+ /** A wall clock, to the second: what a zone makes of an instant. */
19
+ export interface CivilTime {
20
+ year: number;
21
+ month: number;
22
+ day: number;
23
+ hour: number;
24
+ minute: number;
25
+ second: number;
26
+ }
27
+ /**
28
+ * Whether the host knows the zone. The catalog's refusal is worded by its caller, so
29
+ * this answers only yes or no — left to `Intl` the failure is a `RangeError` naming
30
+ * neither the function nor what was wrong with the argument.
31
+ */
32
+ export declare function knownTimeZone(zone: string): boolean;
33
+ /** The wall clock an instant reads as in a zone. */
34
+ export declare function civilTimeIn(instant: number, zone: string): CivilTime;
35
+ /**
36
+ * The instant whose wall clock in `zone` is these fields.
37
+ *
38
+ * The offset depends on the instant being solved for, so this takes the offset at the
39
+ * UTC reading, corrects, and then **checks** by reading the result back. That check is
40
+ * the whole point: a plain fixpoint settles on an instant whose wall clock is not the one
41
+ * asked for whenever the requested time does not exist, and it settles BACKWARDS — which
42
+ * silently moves the calendar day, the one thing `addMonths` and `startOfMonth` exist to
43
+ * control. Chile jumps 00:00 to 01:00 on 2026-09-06 and Cuba on 2026-03-08, so "the 6th
44
+ * at midnight" there is not a time; a fixpoint answered "the 5th at 23:00".
45
+ *
46
+ * Resolution follows Java's `ZonedDateTime` and Temporal's `compatible`: a wall clock
47
+ * that exists twice (a fall-back) takes the EARLIER instant, and one that does not exist
48
+ * (a spring-forward gap) shifts FORWARD out of the gap, which keeps the requested day.
49
+ */
50
+ export declare function instantOfCivilTime(fields: CivilTime, zone: string): number;
51
+ /** `Date.UTC` maps years 0-99 to 1900-1999, so the year is set explicitly. */
52
+ export declare function daysInMonth(year: number, month: number): number;
53
+ /** The calendar date (`YYYY-MM-DD`) an instant reads as in a zone. */
54
+ export declare function dateTextIn(instant: number, zone: string): string;
55
+ /**
56
+ * An instant as ISO-8601 in a zone, with milliseconds: UTC as `…Z`, else the zone's own
57
+ * offset (`2026-06-06T18:30:00.000-05:00`). The offset is derived arithmetically from
58
+ * standard `Intl` fields, so nothing here needs a newer ECMA-402 surface.
59
+ */
60
+ export declare function isoTextIn(instant: number, zone: string): string;
61
+ //# sourceMappingURL=zoned-calendar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zoned-calendar.d.ts","sourceRoot":"","sources":["../src/zoned-calendar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,oEAAoE;AACpE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOnD;AA0BD,oDAAoD;AACpD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAUpE;AAsBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAqB1E;AAED,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAI/D;AAMD,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAIhE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAiB/D"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * The calendar in a time zone: an instant's wall-clock fields, and the instant a wall
3
+ * clock names.
4
+ *
5
+ * An instant is only a date once a zone is chosen, so every calendar function of the
6
+ * function catalog reads its fields through here. The zone's RULES are the host's data
7
+ * (`Intl.DateTimeFormat`, ECMA-402, present in a browser as in Node), never this
8
+ * engine's: what is written here is only how those rules are applied, which is the half
9
+ * a second engine has to reproduce.
10
+ *
11
+ * It is separate from `timestamp-value.ts` on purpose. That file is the language's own
12
+ * zone reading — cel-spec's getters, which also accept a fixed `HH:MM` offset and word
13
+ * their refusal as the language does. This one is the catalog's: it accepts an IANA name
14
+ * alone, it is millisecond-granular because the functions built on it render
15
+ * milliseconds, and it answers a gap or a repeated wall clock the way a calendar
16
+ * library does. Folding the two would make one of the two behaviours wrong.
17
+ */
18
+ /**
19
+ * Whether the host knows the zone. The catalog's refusal is worded by its caller, so
20
+ * this answers only yes or no — left to `Intl` the failure is a `RangeError` naming
21
+ * neither the function nor what was wrong with the argument.
22
+ */
23
+ export function knownTimeZone(zone) {
24
+ try {
25
+ new Intl.DateTimeFormat("en-US", { timeZone: zone });
26
+ return true;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ const FIELD_OPTIONS = {
33
+ hourCycle: "h23",
34
+ year: "numeric",
35
+ month: "2-digit",
36
+ day: "2-digit",
37
+ hour: "2-digit",
38
+ minute: "2-digit",
39
+ second: "2-digit",
40
+ };
41
+ function zoneParts(instant, zone, options) {
42
+ const parts = {};
43
+ for (const part of new Intl.DateTimeFormat("en-US", { timeZone: zone, ...options }).formatToParts(new Date(instant))) {
44
+ parts[part.type] = part.value;
45
+ }
46
+ return parts;
47
+ }
48
+ /** The wall clock an instant reads as in a zone. */
49
+ export function civilTimeIn(instant, zone) {
50
+ const parts = zoneParts(instant, zone, FIELD_OPTIONS);
51
+ return {
52
+ year: +parts.year,
53
+ month: +parts.month,
54
+ day: +parts.day,
55
+ hour: +parts.hour,
56
+ minute: +parts.minute,
57
+ second: +parts.second,
58
+ };
59
+ }
60
+ /** The zone's offset at an instant, in milliseconds: its wall clock read as UTC, minus it. */
61
+ function offsetAt(instant, zone) {
62
+ const fields = civilTimeIn(instant, zone);
63
+ return (Date.UTC(fields.year, fields.month - 1, fields.day, fields.hour, fields.minute, fields.second) -
64
+ instant);
65
+ }
66
+ function sameWallClock(left, right) {
67
+ return (left.year === right.year &&
68
+ left.month === right.month &&
69
+ left.day === right.day &&
70
+ left.hour === right.hour &&
71
+ left.minute === right.minute &&
72
+ left.second === right.second);
73
+ }
74
+ /**
75
+ * The instant whose wall clock in `zone` is these fields.
76
+ *
77
+ * The offset depends on the instant being solved for, so this takes the offset at the
78
+ * UTC reading, corrects, and then **checks** by reading the result back. That check is
79
+ * the whole point: a plain fixpoint settles on an instant whose wall clock is not the one
80
+ * asked for whenever the requested time does not exist, and it settles BACKWARDS — which
81
+ * silently moves the calendar day, the one thing `addMonths` and `startOfMonth` exist to
82
+ * control. Chile jumps 00:00 to 01:00 on 2026-09-06 and Cuba on 2026-03-08, so "the 6th
83
+ * at midnight" there is not a time; a fixpoint answered "the 5th at 23:00".
84
+ *
85
+ * Resolution follows Java's `ZonedDateTime` and Temporal's `compatible`: a wall clock
86
+ * that exists twice (a fall-back) takes the EARLIER instant, and one that does not exist
87
+ * (a spring-forward gap) shifts FORWARD out of the gap, which keeps the requested day.
88
+ */
89
+ export function instantOfCivilTime(fields, zone) {
90
+ const asUtc = Date.UTC(fields.year, fields.month - 1, fields.day, fields.hour, fields.minute, fields.second);
91
+ const offsetA = offsetAt(asUtc, zone);
92
+ const candidateA = asUtc - offsetA;
93
+ const offsetB = offsetAt(candidateA, zone);
94
+ if (offsetA === offsetB)
95
+ return candidateA;
96
+ const candidateB = asUtc - offsetB;
97
+ const aHolds = sameWallClock(civilTimeIn(candidateA, zone), fields);
98
+ const bHolds = sameWallClock(civilTimeIn(candidateB, zone), fields);
99
+ if (aHolds && bHolds)
100
+ return Math.min(candidateA, candidateB);
101
+ if (aHolds)
102
+ return candidateA;
103
+ if (bHolds)
104
+ return candidateB;
105
+ return Math.max(candidateA, candidateB);
106
+ }
107
+ /** `Date.UTC` maps years 0-99 to 1900-1999, so the year is set explicitly. */
108
+ export function daysInMonth(year, month) {
109
+ const date = new Date(Date.UTC(2000, month, 0));
110
+ date.setUTCFullYear(year, month, 0);
111
+ return date.getUTCDate();
112
+ }
113
+ function pad(value, width) {
114
+ return String(value).padStart(width, "0");
115
+ }
116
+ /** The calendar date (`YYYY-MM-DD`) an instant reads as in a zone. */
117
+ export function dateTextIn(instant, zone) {
118
+ if (zone === "UTC" || zone === "Z")
119
+ return new Date(instant).toISOString().slice(0, 10);
120
+ const parts = zoneParts(instant, zone, { year: "numeric", month: "2-digit", day: "2-digit" });
121
+ return `${parts.year}-${parts.month}-${parts.day}`;
122
+ }
123
+ /**
124
+ * An instant as ISO-8601 in a zone, with milliseconds: UTC as `…Z`, else the zone's own
125
+ * offset (`2026-06-06T18:30:00.000-05:00`). The offset is derived arithmetically from
126
+ * standard `Intl` fields, so nothing here needs a newer ECMA-402 surface.
127
+ */
128
+ export function isoTextIn(instant, zone) {
129
+ if (zone === "UTC" || zone === "Z")
130
+ return new Date(instant).toISOString();
131
+ const fields = civilTimeIn(instant, zone);
132
+ // Sub-second is zone-independent; read it off the instant directly.
133
+ const milliseconds = pad(((instant % 1000) + 1000) % 1000, 3);
134
+ const minutes = Math.round((Date.UTC(fields.year, fields.month - 1, fields.day, fields.hour, fields.minute, fields.second) -
135
+ instant) /
136
+ 60000);
137
+ const offset = minutes === 0
138
+ ? "Z"
139
+ : `${minutes > 0 ? "+" : "-"}${pad(Math.floor(Math.abs(minutes) / 60), 2)}:${pad(Math.abs(minutes) % 60, 2)}`;
140
+ const date = `${pad(fields.year, 4)}-${pad(fields.month, 2)}-${pad(fields.day, 2)}`;
141
+ const time = `${pad(fields.hour, 2)}:${pad(fields.minute, 2)}:${pad(fields.second, 2)}`;
142
+ return `${date}T${time}.${milliseconds}${offset}`;
143
+ }
package/package.json CHANGED
@@ -1,6 +1,59 @@
1
1
  {
2
2
  "name": "@telorun/cel",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.107.0",
4
+ "description": "The Common Expression Language for Telo: read, type-check and evaluate an expression, hold it as a tree, write it back.",
5
+ "keywords": [
6
+ "telo",
7
+ "cel",
8
+ "expression",
9
+ "parser"
10
+ ],
11
+ "author": "Bartosz Pasiński <bartosz.pasinski@codenet.pl>",
12
+ "license": "SEE LICENSE IN LICENSE",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/telorun/telo.git",
16
+ "directory": "cel/nodejs"
17
+ },
18
+ "homepage": "https://github.com/telorun/telo#readme",
19
+ "bugs": {
20
+ "url": "https://github.com/telorun/telo/issues"
21
+ },
22
+ "type": "module",
23
+ "main": "./dist/index.js",
24
+ "exports": {
25
+ ".": {
26
+ "source": "./src/index.ts",
27
+ "types": "./dist/index.d.ts",
28
+ "bun": "./src/index.ts",
29
+ "import": "./dist/index.js",
30
+ "default": "./dist/index.js"
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
34
+ "files": [
35
+ "dist/**",
36
+ "src/**"
37
+ ],
38
+ "dependencies": {
39
+ "d3-format": "3.1.2",
40
+ "re2js": "2.8.3",
41
+ "uuid": "14.0.1"
42
+ },
43
+ "devDependencies": {
44
+ "@types/d3-format": "^3.0.4",
45
+ "@types/node": "^20.0.0",
46
+ "esbuild": "^0.25.12",
47
+ "typescript": "^5.0.0",
48
+ "vitest": "^2.1.8"
49
+ },
50
+ "scripts": {
51
+ "build": "tsc -p tsconfig.lib.json",
52
+ "test": "vitest run",
53
+ "test:watch": "vitest",
54
+ "test:conformance": "vitest run --config vitest.conformance.config.ts",
55
+ "check:types": "tsc -p tsconfig.json --noEmit",
56
+ "check:browser-safe": "node scripts/check-browser-safe.mjs",
57
+ "check:signatures": "pnpm run build && node scripts/check-signatures.mjs"
58
+ }
6
59
  }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The activation: the values an expression reads names from.
3
+ *
4
+ * Two rules, both of which the checker already applies to types, so the evaluator must
5
+ * apply them to values or `telo check` and the runtime disagree:
6
+ *
7
+ * - **A dotted declaration is one name, and the longest one wins.** A host may hold
8
+ * `a.b.c`, or `a.b` holding a map, or both; `a.b.c` reads the entry of that name where
9
+ * it is held and the map's entry where only `a.b` is. So resolution tries the longest
10
+ * prefix first, exactly as `qualifiedVariableType` does at check.
11
+ * - **A read is an OWN entry**, never an inherited one, so an activation whose prototype
12
+ * is `Object.prototype` cannot answer `constructor` or `toString`.
13
+ *
14
+ * A name nothing holds is `no_such_variable` — the error value, so it short-circuits like
15
+ * any other. A value read from here is a HOST's, so it is admitted through the backend's
16
+ * one entry point for a host value, which is where a thenable is refused.
17
+ */
18
+
19
+
20
+
21
+ /** What a host binds names to. A value is read through the guard below, never trusted. */
22
+ export interface CelActivation {
23
+ readonly [name: string]: unknown;
24
+ }
25
+
26
+ /**
27
+ * Whether the activation holds a name itself. The backend asks this for each prefix of a
28
+ * dotted chain, longest first, having built the prefixes once at compile time.
29
+ */
30
+ export function activationHolds(activation: CelActivation, name: string): boolean {
31
+ return Object.prototype.hasOwnProperty.call(activation, name);
32
+ }