solve-engine 1.0.0-beta.7 → 1.0.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 (205) hide show
  1. package/dist/{BytecodeBuilder-3egMyuDi.d.cts → BytecodeBuilder-B0xskcv5.d.cts} +2 -1
  2. package/dist/{BytecodeBuilder-3egMyuDi.d.ts → BytecodeBuilder-B0xskcv5.d.ts} +2 -1
  3. package/dist/{Configuration-fVpd-HC-.d.cts → Configuration-B-G5gTRn.d.cts} +107 -29
  4. package/dist/{Configuration-fVpd-HC-.d.ts → Configuration-B-G5gTRn.d.ts} +107 -29
  5. package/dist/{EngineError-DvPSnhZi.d.cts → EngineError-LU7W7AgI.d.cts} +96 -16
  6. package/dist/{EngineError-DvPSnhZi.d.ts → EngineError-LU7W7AgI.d.ts} +96 -16
  7. package/dist/{Lexer-W9MBOP0V.d.ts → Lexer-BN5mt30n.d.ts} +16 -0
  8. package/dist/{Lexer-Cfm79Dak.d.cts → Lexer-D78E2yDW.d.cts} +16 -0
  9. package/dist/{PackageRegistry-CjDt-Jy_.d.ts → PackageRegistry-DFeINv2O.d.ts} +54 -13
  10. package/dist/{PackageRegistry-pHtKythi.d.cts → PackageRegistry-u2zdfFaj.d.cts} +54 -13
  11. package/dist/{Parselet-DEdF9I7n.d.ts → Parselet-BaySkMV3.d.ts} +37 -5
  12. package/dist/{Parselet-Cu0bLSis.d.cts → Parselet-DuI1Pjiq.d.cts} +37 -5
  13. package/dist/{ScopeManager-CKfQtl7l.d.cts → ScopeManager-B6GzdhVG.d.cts} +75 -6
  14. package/dist/{ScopeManager-B5k0nk05.d.ts → ScopeManager-udv4Twwq.d.ts} +75 -6
  15. package/dist/{VMCheckpoints-BmDwIXgK.d.ts → VMCheckpoints-BiaIlOOY.d.ts} +4 -4
  16. package/dist/{VMCheckpoints-CqFuLwrT.d.cts → VMCheckpoints-DwLjivM7.d.cts} +4 -4
  17. package/dist/{Value-KtAQNHur.d.cts → Value-CXJqDH9J.d.cts} +5 -2
  18. package/dist/{Value-KtAQNHur.d.ts → Value-CXJqDH9J.d.ts} +5 -2
  19. package/dist/{chunk-CSG5XVNJ.cjs → chunk-3AFRJYP4.cjs} +556 -414
  20. package/dist/chunk-3AFRJYP4.cjs.map +1 -0
  21. package/dist/chunk-3BOK2LDK.js +17 -0
  22. package/dist/chunk-3BOK2LDK.js.map +1 -0
  23. package/dist/{chunk-6RDSQURE.js → chunk-3D7V24DG.js} +57 -6
  24. package/dist/chunk-3D7V24DG.js.map +1 -0
  25. package/dist/{chunk-MEOHSQEH.js → chunk-3LAEG75D.js} +47 -15
  26. package/dist/chunk-3LAEG75D.js.map +1 -0
  27. package/dist/{chunk-P75775WC.js → chunk-3VNGREMU.js} +118 -41
  28. package/dist/chunk-3VNGREMU.js.map +1 -0
  29. package/dist/{chunk-4AVD7NZW.cjs → chunk-4B2CNWQU.cjs} +13 -7
  30. package/dist/chunk-4B2CNWQU.cjs.map +1 -0
  31. package/dist/{chunk-6GCKCWLB.js → chunk-526PMQOA.js} +126 -69
  32. package/dist/chunk-526PMQOA.js.map +1 -0
  33. package/dist/{chunk-2CS6OMZK.js → chunk-5F4C26RU.js} +8 -5
  34. package/dist/chunk-5F4C26RU.js.map +1 -0
  35. package/dist/{chunk-M4N5GA5Z.cjs → chunk-5KMIY374.cjs} +57 -6
  36. package/dist/chunk-5KMIY374.cjs.map +1 -0
  37. package/dist/{chunk-R24DI24X.cjs → chunk-5LI5EPGJ.cjs} +6 -5
  38. package/dist/chunk-5LI5EPGJ.cjs.map +1 -0
  39. package/dist/{chunk-BAMZT3GL.js → chunk-5WVP4YHP.js} +939 -134
  40. package/dist/chunk-5WVP4YHP.js.map +1 -0
  41. package/dist/{chunk-MZOVP3WI.cjs → chunk-6KFYJ6TD.cjs} +3 -2
  42. package/dist/chunk-6KFYJ6TD.cjs.map +1 -0
  43. package/dist/{chunk-BXKJLM5R.cjs → chunk-6WFMPTGB.cjs} +972 -636
  44. package/dist/chunk-6WFMPTGB.cjs.map +1 -0
  45. package/dist/{chunk-2MV4HBKC.cjs → chunk-A2N2GFCG.cjs} +10 -10
  46. package/dist/{chunk-2MV4HBKC.cjs.map → chunk-A2N2GFCG.cjs.map} +1 -1
  47. package/dist/chunk-ADBA3XBE.cjs +20 -0
  48. package/dist/chunk-ADBA3XBE.cjs.map +1 -0
  49. package/dist/{chunk-MBNQVDVC.cjs → chunk-AMOJYLG2.cjs} +16 -10
  50. package/dist/chunk-AMOJYLG2.cjs.map +1 -0
  51. package/dist/{chunk-CLVQBF5C.cjs → chunk-AQNHHJMJ.cjs} +5 -5
  52. package/dist/{chunk-CLVQBF5C.cjs.map → chunk-AQNHHJMJ.cjs.map} +1 -1
  53. package/dist/{chunk-AHCLWAM5.cjs → chunk-BHTNFEGZ.cjs} +13 -13
  54. package/dist/chunk-BHTNFEGZ.cjs.map +1 -0
  55. package/dist/{chunk-35IWNZPJ.cjs → chunk-E4NUZXVK.cjs} +58 -27
  56. package/dist/chunk-E4NUZXVK.cjs.map +1 -0
  57. package/dist/{chunk-P33SBYGG.js → chunk-GLG5ZMX7.js} +54 -23
  58. package/dist/chunk-GLG5ZMX7.js.map +1 -0
  59. package/dist/{chunk-BVAR5J2F.js → chunk-GQCOSXMG.js} +117 -6
  60. package/dist/chunk-GQCOSXMG.js.map +1 -0
  61. package/dist/{chunk-QDMPOAI7.cjs → chunk-HDP7VK3C.cjs} +1118 -313
  62. package/dist/chunk-HDP7VK3C.cjs.map +1 -0
  63. package/dist/{chunk-GVWHCL2N.js → chunk-HMOISHXR.js} +217 -75
  64. package/dist/chunk-HMOISHXR.js.map +1 -0
  65. package/dist/{chunk-MTX2KVU7.cjs → chunk-HVQFNJKE.cjs} +122 -90
  66. package/dist/chunk-HVQFNJKE.cjs.map +1 -0
  67. package/dist/{chunk-OC6LTXYS.cjs → chunk-JMXUNXQS.cjs} +126 -7
  68. package/dist/chunk-JMXUNXQS.cjs.map +1 -0
  69. package/dist/{chunk-WWOLFXTX.cjs → chunk-KBSXGXPM.cjs} +10 -7
  70. package/dist/chunk-KBSXGXPM.cjs.map +1 -0
  71. package/dist/{chunk-YU2CUNFO.js → chunk-KV7UW6T6.js} +11 -5
  72. package/dist/chunk-KV7UW6T6.js.map +1 -0
  73. package/dist/{chunk-CCJPSHGI.cjs → chunk-N7FNVFXW.cjs} +4 -12
  74. package/dist/chunk-N7FNVFXW.cjs.map +1 -0
  75. package/dist/{chunk-CYFK5SY2.js → chunk-O3ANBHSA.js} +169 -39
  76. package/dist/chunk-O3ANBHSA.js.map +1 -0
  77. package/dist/{chunk-NZFKROS7.cjs → chunk-PA4VC73I.cjs} +130 -72
  78. package/dist/chunk-PA4VC73I.cjs.map +1 -0
  79. package/dist/{chunk-NJTXJ5AG.cjs → chunk-PFUESQTW.cjs} +18 -18
  80. package/dist/{chunk-NJTXJ5AG.cjs.map → chunk-PFUESQTW.cjs.map} +1 -1
  81. package/dist/{chunk-SFQWJMKT.js → chunk-QY25VWBF.js} +51 -12
  82. package/dist/chunk-QY25VWBF.js.map +1 -0
  83. package/dist/{chunk-WC5FFSHB.js → chunk-R3PY4G7J.js} +3 -3
  84. package/dist/{chunk-WC5FFSHB.js.map → chunk-R3PY4G7J.js.map} +1 -1
  85. package/dist/{chunk-M5E34VG5.js → chunk-SBUFNJVE.js} +3 -3
  86. package/dist/{chunk-M5E34VG5.js.map → chunk-SBUFNJVE.js.map} +1 -1
  87. package/dist/{chunk-UKPGSAZW.js → chunk-TMA4RCEN.js} +3 -3
  88. package/dist/{chunk-UKPGSAZW.js.map → chunk-TMA4RCEN.js.map} +1 -1
  89. package/dist/{chunk-V6HA6JJZ.cjs → chunk-U5VAY32K.cjs} +215 -138
  90. package/dist/chunk-U5VAY32K.cjs.map +1 -0
  91. package/dist/{chunk-DUEZFTK2.js → chunk-UKQHXPGW.js} +4 -12
  92. package/dist/chunk-UKQHXPGW.js.map +1 -0
  93. package/dist/{chunk-4CVLFLOB.cjs → chunk-UM6BVY2S.cjs} +201 -69
  94. package/dist/chunk-UM6BVY2S.cjs.map +1 -0
  95. package/dist/{chunk-75JP4BZX.js → chunk-UQ3UIZJC.js} +3 -2
  96. package/dist/chunk-UQ3UIZJC.js.map +1 -0
  97. package/dist/{chunk-PCPX42KL.js → chunk-V4XG5QF6.js} +16 -10
  98. package/dist/chunk-V4XG5QF6.js.map +1 -0
  99. package/dist/{chunk-GPPLSM2Z.js → chunk-VB37OC6I.js} +6 -5
  100. package/dist/chunk-VB37OC6I.js.map +1 -0
  101. package/dist/{chunk-MVBEL57Q.js → chunk-Y7FT4IQT.js} +500 -170
  102. package/dist/chunk-Y7FT4IQT.js.map +1 -0
  103. package/dist/{chunk-OFXOTECC.js → chunk-YPPPYLSR.js} +7 -7
  104. package/dist/chunk-YPPPYLSR.js.map +1 -0
  105. package/dist/{chunk-M4F66R4O.cjs → chunk-YWQ6V4ZN.cjs} +121 -82
  106. package/dist/chunk-YWQ6V4ZN.cjs.map +1 -0
  107. package/dist/constants.cjs +5 -5
  108. package/dist/constants.d.cts +1 -1
  109. package/dist/constants.d.ts +1 -1
  110. package/dist/constants.js +2 -2
  111. package/dist/engine.cjs +31 -31
  112. package/dist/engine.d.cts +11 -11
  113. package/dist/engine.d.ts +11 -11
  114. package/dist/engine.js +21 -21
  115. package/dist/errors.cjs +19 -19
  116. package/dist/errors.d.cts +2 -2
  117. package/dist/errors.d.ts +2 -2
  118. package/dist/errors.js +1 -1
  119. package/dist/format.cjs +29 -22
  120. package/dist/format.cjs.map +1 -1
  121. package/dist/format.d.cts +1 -2
  122. package/dist/format.d.ts +1 -2
  123. package/dist/format.js +23 -16
  124. package/dist/format.js.map +1 -1
  125. package/dist/index.cjs +31 -31
  126. package/dist/index.d.cts +10 -10
  127. package/dist/index.d.ts +10 -10
  128. package/dist/index.js +22 -22
  129. package/dist/language.cjs +8 -8
  130. package/dist/language.cjs.map +1 -1
  131. package/dist/language.d.cts +10 -10
  132. package/dist/language.d.ts +10 -10
  133. package/dist/language.js +6 -6
  134. package/dist/language.js.map +1 -1
  135. package/dist/lexer.cjs +18 -18
  136. package/dist/lexer.d.cts +2 -2
  137. package/dist/lexer.d.ts +2 -2
  138. package/dist/lexer.js +6 -6
  139. package/dist/normalizer.cjs +12 -12
  140. package/dist/normalizer.js +6 -6
  141. package/dist/packages.cjs +39 -39
  142. package/dist/packages.d.cts +9 -9
  143. package/dist/packages.d.ts +9 -9
  144. package/dist/packages.js +16 -16
  145. package/dist/parser.cjs +15 -15
  146. package/dist/parser.d.cts +23 -9
  147. package/dist/parser.d.ts +23 -9
  148. package/dist/parser.js +5 -5
  149. package/dist/{pipeline-CrsRPmsv.d.ts → pipeline-B6k5lCB7.d.ts} +1 -1
  150. package/dist/{pipeline-DPUQt7yL.d.cts → pipeline-BEb3hujr.d.cts} +1 -1
  151. package/dist/resolvers.d.cts +2 -2
  152. package/dist/resolvers.d.ts +2 -2
  153. package/dist/uom.cjs +17 -17
  154. package/dist/uom.d.cts +2 -2
  155. package/dist/uom.d.ts +2 -2
  156. package/dist/uom.js +7 -7
  157. package/dist/utilities.cjs +8 -4
  158. package/dist/utilities.d.cts +19 -3
  159. package/dist/utilities.d.ts +19 -3
  160. package/dist/utilities.js +2 -2
  161. package/dist/vm.cjs +43 -31
  162. package/dist/vm.d.cts +105 -9
  163. package/dist/vm.d.ts +105 -9
  164. package/dist/vm.js +9 -9
  165. package/package.json +1 -1
  166. package/dist/chunk-2CS6OMZK.js.map +0 -1
  167. package/dist/chunk-35IWNZPJ.cjs.map +0 -1
  168. package/dist/chunk-4AVD7NZW.cjs.map +0 -1
  169. package/dist/chunk-4CVLFLOB.cjs.map +0 -1
  170. package/dist/chunk-6GCKCWLB.js.map +0 -1
  171. package/dist/chunk-6RDSQURE.js.map +0 -1
  172. package/dist/chunk-75JP4BZX.js.map +0 -1
  173. package/dist/chunk-AHCLWAM5.cjs.map +0 -1
  174. package/dist/chunk-BAMZT3GL.js.map +0 -1
  175. package/dist/chunk-BVAR5J2F.js.map +0 -1
  176. package/dist/chunk-BXKJLM5R.cjs.map +0 -1
  177. package/dist/chunk-CCJPSHGI.cjs.map +0 -1
  178. package/dist/chunk-CSG5XVNJ.cjs.map +0 -1
  179. package/dist/chunk-CYFK5SY2.js.map +0 -1
  180. package/dist/chunk-DUEZFTK2.js.map +0 -1
  181. package/dist/chunk-GPPLSM2Z.js.map +0 -1
  182. package/dist/chunk-GVWHCL2N.js.map +0 -1
  183. package/dist/chunk-M4F66R4O.cjs.map +0 -1
  184. package/dist/chunk-M4N5GA5Z.cjs.map +0 -1
  185. package/dist/chunk-MBNQVDVC.cjs.map +0 -1
  186. package/dist/chunk-MEOHSQEH.js.map +0 -1
  187. package/dist/chunk-MTX2KVU7.cjs.map +0 -1
  188. package/dist/chunk-MVBEL57Q.js.map +0 -1
  189. package/dist/chunk-MZOVP3WI.cjs.map +0 -1
  190. package/dist/chunk-N5YFR4UP.cjs +0 -10
  191. package/dist/chunk-N5YFR4UP.cjs.map +0 -1
  192. package/dist/chunk-NZFKROS7.cjs.map +0 -1
  193. package/dist/chunk-OC6LTXYS.cjs.map +0 -1
  194. package/dist/chunk-OFXOTECC.js.map +0 -1
  195. package/dist/chunk-P33SBYGG.js.map +0 -1
  196. package/dist/chunk-P75775WC.js.map +0 -1
  197. package/dist/chunk-PCPX42KL.js.map +0 -1
  198. package/dist/chunk-PFSDGGYL.js +0 -8
  199. package/dist/chunk-PFSDGGYL.js.map +0 -1
  200. package/dist/chunk-QDMPOAI7.cjs.map +0 -1
  201. package/dist/chunk-R24DI24X.cjs.map +0 -1
  202. package/dist/chunk-SFQWJMKT.js.map +0 -1
  203. package/dist/chunk-V6HA6JJZ.cjs.map +0 -1
  204. package/dist/chunk-WWOLFXTX.cjs.map +0 -1
  205. package/dist/chunk-YU2CUNFO.js.map +0 -1
@@ -87,7 +87,8 @@ declare enum OpCode {
87
87
  MAT_SLICE = 155,// (matrix, rowRange, colRange already on stack) -> sub-matrix via two Range values
88
88
  RANGE_NEW = 156,// (min, max already on stack) -> push a Range value `min:max`
89
89
  MAP_INVOKE = 157,// map(...) — see parser/BytecodeBuilder.ts's `anonymousBodies` side-table
90
- REDUCE_INVOKE = 158
90
+ REDUCE_INVOKE = 158,// reduce(...) — same anonymousBodies mechanism as MAP_INVOKE
91
+ BIND_UNKNOWN = 159
91
92
  }
92
93
  /**
93
94
  * Gets the name of an OpCode as a string.
@@ -87,7 +87,8 @@ declare enum OpCode {
87
87
  MAT_SLICE = 155,// (matrix, rowRange, colRange already on stack) -> sub-matrix via two Range values
88
88
  RANGE_NEW = 156,// (min, max already on stack) -> push a Range value `min:max`
89
89
  MAP_INVOKE = 157,// map(...) — see parser/BytecodeBuilder.ts's `anonymousBodies` side-table
90
- REDUCE_INVOKE = 158
90
+ REDUCE_INVOKE = 158,// reduce(...) — same anonymousBodies mechanism as MAP_INVOKE
91
+ BIND_UNKNOWN = 159
91
92
  }
92
93
  /**
93
94
  * Gets the name of an OpCode as a string.
@@ -27,29 +27,30 @@
27
27
  * (e.g., `today + 20 days`, `last monday`).
28
28
  */
29
29
  interface DateConfig {
30
- /** Default offset in days for relative date calculations when no offset is specified */
31
- readonly defaultOffsetDays: number;
32
- /** Maximum allowed positive offset in years (safety limit) */
30
+ /**
31
+ * How far forward a date offset whose COST grows with the offset may reach,
32
+ * in years. Enforced by `vm/VM.ts`'s `addBusinessDays()`.
33
+ *
34
+ * Bounds the walk, not the calendar. Every other date offset in the engine
35
+ * is arithmetic on a Date field, so `today + 100000 days` costs exactly what
36
+ * one day costs and needs no ceiling; workdays are the one offset that has
37
+ * to step day by day, because which days are skipped depends on where each
38
+ * step lands. `today + 100000000 workdays` therefore froze the host for
39
+ * thirteen seconds inside a single ADD opcode (where `vm.maxInstructions`
40
+ * cannot see it) and then answered "Invalid Date", and a trillion never
41
+ * returned at all.
42
+ *
43
+ * Release hardening: this field was declared, documented as a "safety
44
+ * limit", and read nowhere, so it bounded nothing. A limit a host can
45
+ * configure and the engine ignores is worse than no limit, because it reads
46
+ * as protection that is not there.
47
+ */
33
48
  readonly maxOffsetYears: number;
34
- /** Maximum allowed negative offset in years (safety limit) */
49
+ /** How far BACK the same walk may reach, in years, as a negative number. See {@link maxOffsetYears}. */
35
50
  readonly minOffsetYears: number;
36
51
  /** Default date string format for display (moment.js format string) */
37
52
  readonly defaultFormat: string;
38
53
  }
39
- /**
40
- * Dice-related configuration.
41
- * Controls dice expression evaluation (e.g., `roll(1, 100)`, `3d6`).
42
- */
43
- interface DiceConfig {
44
- /** Default number of sides on a die when not specified */
45
- readonly defaultSides: number;
46
- /** Maximum allowed sides per die (prevents excessive allocation) */
47
- readonly maxSides: number;
48
- /** Maximum number of dice in a single roll expression */
49
- readonly maxDice: number;
50
- /** Default number of dice when not specified */
51
- readonly defaultDice: number;
52
- }
53
54
  /**
54
55
  * Performance-related configuration.
55
56
  * Controls caching, timeouts, and processing limits to prevent runaway
@@ -67,12 +68,23 @@ interface PerformanceConfig {
67
68
  * size limit of its own.
68
69
  */
69
70
  readonly defaultCacheSize: number;
70
- /** Maximum number of document lines processed in a single pass */
71
+ /**
72
+ * Maximum lines a document may have for the engine to process it in one
73
+ * pass. Enforced by `ExpressionEngine.parseDocument()` and by
74
+ * `engine/DocumentModel.ts`'s `setDocument()`, the two entry points that
75
+ * take a whole document.
76
+ *
77
+ * A document costs memory per line whatever each line says: a parsed line
78
+ * record, a cache entry, a dependency-graph node. Two hundred thousand
79
+ * lines of `1 + 1` therefore exhausted the heap and aborted the process
80
+ * before any per-line limit had anything to object to, which is the same
81
+ * shape of hole as an unbounded expression and needs the same kind of
82
+ * ceiling. Refusing the document names the limit; a host that genuinely has
83
+ * a larger one raises this field.
84
+ *
85
+ * Release hardening: declared and read nowhere until now.
86
+ */
71
87
  readonly maxDocumentLines: number;
72
- /** Maximum time (ms) allowed for parsing a single expression before timeout */
73
- readonly parseTimeoutMs: number;
74
- /** Maximum time (ms) allowed for executing a single expression before timeout */
75
- readonly executionTimeoutMs: number;
76
88
  }
77
89
  /**
78
90
  * Validation / safety-limit configuration.
@@ -131,6 +143,60 @@ interface VMConfig {
131
143
  readonly maxStackDepth: number;
132
144
  /** Maximum opcodes executed per expression, halts runaway infinite loops */
133
145
  readonly maxInstructions: number;
146
+ /**
147
+ * Maximum elements a collection may be expanded to before `map`/`reduce`/
148
+ * `sum`/`prod` will iterate it.
149
+ *
150
+ * A Range is stored as its two bounds and costs nothing until something
151
+ * materializes it, at which point it becomes one Value per element. Twenty
152
+ * characters (`sum(x, 1:100000000)`) therefore asked for a hundred million
153
+ * of them, and neither `maxInstructions` nor `maxStackDepth` could see it:
154
+ * the expansion happens inside a single opcode, so the instruction counter
155
+ * is never consulted while it runs, and the elements never reach the value
156
+ * stack. V8 aborted the whole process with "Reached heap limit", which a
157
+ * host embedding the engine cannot catch.
158
+ */
159
+ readonly maxCollectionSize: number;
160
+ /**
161
+ * Maximum elements (collection Values, matrix cells) one evaluation may
162
+ * materialize in total.
163
+ *
164
+ * `maxCollectionSize` above bounds a single collection. This bounds the sum
165
+ * of everything an expression asks for, which is a different question and
166
+ * the one that actually protects the host: two collections that are
167
+ * individually legal are legal together, and an operation whose result is
168
+ * the PRODUCT of two legal operands is bounded by neither of them. A matrix
169
+ * multiply is exactly that shape, so three lines within every other limit
170
+ * (`:a = map(1*x, 0:20000)`, `:b = transpose(a)`, `b * a`) asked for four
171
+ * hundred million cells and aborted the process.
172
+ *
173
+ * Counted in elements rather than bytes, because a count is what a call
174
+ * site has before it allocates. An element is 8 bytes as a numeric matrix
175
+ * cell and closer to a hundred as a full Value, so the default is worth
176
+ * roughly 16 MB of matrix or 200 MB of expanded collection: far past any
177
+ * document and far short of what an editor cannot survive.
178
+ */
179
+ readonly maxAllocatedElements: number;
180
+ /**
181
+ * Maximum user-defined-function calls one evaluation may make in TOTAL,
182
+ * however deeply or widely they nest.
183
+ *
184
+ * `maxFunctionRecursionDepth` bounds how DEEP calls nest and cannot see
185
+ * how MANY there are, and those are different numbers. Twenty-two lines of
186
+ * `f(n)(v) = f(n-1)(v) + f(n-1)(v)` reach a depth of only 22 against a
187
+ * limit of 50, and make 2,097,152 calls doing it: a fatal heap abort in
188
+ * under a second. `maxInstructions` cannot bound it either, because
189
+ * `executeBytecode()` re-enters itself per call and each reentrant call
190
+ * gets its OWN instruction count, so recursion refreshes its allowance on
191
+ * the way in. This is the tally that does not refresh; see
192
+ * `vm/AllocationBudget.ts`, which holds it for the same reason it holds
193
+ * the element tally.
194
+ *
195
+ * Counted in calls rather than in the instructions they run, because the
196
+ * call is the thing that multiplies: every call allocates a frame, its
197
+ * arguments and its result whatever its body says.
198
+ */
199
+ readonly maxFunctionCalls: number;
134
200
  }
135
201
  /**
136
202
  * Complete engine configuration.
@@ -148,15 +214,13 @@ interface VMConfig {
148
214
  * maxExpressionLength: 1000,
149
215
  * maxComplexity: 200,
150
216
  * },
151
- * // date, dice, performance, vm, worker, diagnostic all use defaults
217
+ * // date, performance, vm, worker, diagnostic all use defaults
152
218
  * });
153
219
  * ```
154
220
  */
155
221
  interface EngineConfig {
156
222
  /** Date/time expression evaluation bounds and formatting */
157
223
  readonly date: DateConfig;
158
- /** Dice roll expression controls */
159
- readonly dice: DiceConfig;
160
224
  /** Performance budgets and cache sizing */
161
225
  readonly performance: PerformanceConfig;
162
226
  /** Safety limits for expression complexity */
@@ -200,7 +264,12 @@ declare class ConfigManager {
200
264
  */
201
265
  set<T>(path: string, value: T): void;
202
266
  /**
203
- * Get complete configuration
267
+ * Get complete configuration.
268
+ *
269
+ * A detached copy, section objects included. A top-level spread would hand
270
+ * the caller this manager's own section objects, so `getConfig().vm.x = 1`
271
+ * would be an undeclared back door into {@link set}, bypassing its path
272
+ * validation and reaching every later {@link get}.
204
273
  */
205
274
  getConfig(): EngineConfig;
206
275
  /**
@@ -208,7 +277,16 @@ declare class ConfigManager {
208
277
  */
209
278
  update(config: Partial<EngineConfig>): void;
210
279
  /**
211
- * Reset to default configuration
280
+ * Reset to default configuration.
281
+ *
282
+ * Goes through {@link mergeEngineConfig} for the same reason the constructor
283
+ * does. A top-level `{ ...DEFAULT_CONFIG }` copies the six section
284
+ * references, not the sections, so after a reset this manager's
285
+ * `performance` object WAS `DEFAULT_CONFIG.performance` and the next
286
+ * `set('performance.x', ...)` wrote into the module constant. That constant
287
+ * is what every `ExpressionEngine` is built from, so one manager's reset
288
+ * could change the cache size, instruction ceiling or allocation budget of
289
+ * every engine constructed later in the process.
212
290
  */
213
291
  reset(): void;
214
292
  /**
@@ -225,4 +303,4 @@ interface ValidationResult {
225
303
  warnings?: string[];
226
304
  }
227
305
 
228
- export { ConfigManager as C, DEFAULT_CONFIG as D, type EngineConfig as E, type PerformanceConfig as P, type VMConfig as V, type WorkerConfig as W, type DateConfig as a, type DiagnosticConfig as b, type DiceConfig as c, type ValidationConfig as d, type ValidationResult as e };
306
+ export { ConfigManager as C, DEFAULT_CONFIG as D, type EngineConfig as E, type PerformanceConfig as P, type VMConfig as V, type WorkerConfig as W, type DateConfig as a, type DiagnosticConfig as b, type ValidationConfig as c, type ValidationResult as d };
@@ -27,29 +27,30 @@
27
27
  * (e.g., `today + 20 days`, `last monday`).
28
28
  */
29
29
  interface DateConfig {
30
- /** Default offset in days for relative date calculations when no offset is specified */
31
- readonly defaultOffsetDays: number;
32
- /** Maximum allowed positive offset in years (safety limit) */
30
+ /**
31
+ * How far forward a date offset whose COST grows with the offset may reach,
32
+ * in years. Enforced by `vm/VM.ts`'s `addBusinessDays()`.
33
+ *
34
+ * Bounds the walk, not the calendar. Every other date offset in the engine
35
+ * is arithmetic on a Date field, so `today + 100000 days` costs exactly what
36
+ * one day costs and needs no ceiling; workdays are the one offset that has
37
+ * to step day by day, because which days are skipped depends on where each
38
+ * step lands. `today + 100000000 workdays` therefore froze the host for
39
+ * thirteen seconds inside a single ADD opcode (where `vm.maxInstructions`
40
+ * cannot see it) and then answered "Invalid Date", and a trillion never
41
+ * returned at all.
42
+ *
43
+ * Release hardening: this field was declared, documented as a "safety
44
+ * limit", and read nowhere, so it bounded nothing. A limit a host can
45
+ * configure and the engine ignores is worse than no limit, because it reads
46
+ * as protection that is not there.
47
+ */
33
48
  readonly maxOffsetYears: number;
34
- /** Maximum allowed negative offset in years (safety limit) */
49
+ /** How far BACK the same walk may reach, in years, as a negative number. See {@link maxOffsetYears}. */
35
50
  readonly minOffsetYears: number;
36
51
  /** Default date string format for display (moment.js format string) */
37
52
  readonly defaultFormat: string;
38
53
  }
39
- /**
40
- * Dice-related configuration.
41
- * Controls dice expression evaluation (e.g., `roll(1, 100)`, `3d6`).
42
- */
43
- interface DiceConfig {
44
- /** Default number of sides on a die when not specified */
45
- readonly defaultSides: number;
46
- /** Maximum allowed sides per die (prevents excessive allocation) */
47
- readonly maxSides: number;
48
- /** Maximum number of dice in a single roll expression */
49
- readonly maxDice: number;
50
- /** Default number of dice when not specified */
51
- readonly defaultDice: number;
52
- }
53
54
  /**
54
55
  * Performance-related configuration.
55
56
  * Controls caching, timeouts, and processing limits to prevent runaway
@@ -67,12 +68,23 @@ interface PerformanceConfig {
67
68
  * size limit of its own.
68
69
  */
69
70
  readonly defaultCacheSize: number;
70
- /** Maximum number of document lines processed in a single pass */
71
+ /**
72
+ * Maximum lines a document may have for the engine to process it in one
73
+ * pass. Enforced by `ExpressionEngine.parseDocument()` and by
74
+ * `engine/DocumentModel.ts`'s `setDocument()`, the two entry points that
75
+ * take a whole document.
76
+ *
77
+ * A document costs memory per line whatever each line says: a parsed line
78
+ * record, a cache entry, a dependency-graph node. Two hundred thousand
79
+ * lines of `1 + 1` therefore exhausted the heap and aborted the process
80
+ * before any per-line limit had anything to object to, which is the same
81
+ * shape of hole as an unbounded expression and needs the same kind of
82
+ * ceiling. Refusing the document names the limit; a host that genuinely has
83
+ * a larger one raises this field.
84
+ *
85
+ * Release hardening: declared and read nowhere until now.
86
+ */
71
87
  readonly maxDocumentLines: number;
72
- /** Maximum time (ms) allowed for parsing a single expression before timeout */
73
- readonly parseTimeoutMs: number;
74
- /** Maximum time (ms) allowed for executing a single expression before timeout */
75
- readonly executionTimeoutMs: number;
76
88
  }
77
89
  /**
78
90
  * Validation / safety-limit configuration.
@@ -131,6 +143,60 @@ interface VMConfig {
131
143
  readonly maxStackDepth: number;
132
144
  /** Maximum opcodes executed per expression, halts runaway infinite loops */
133
145
  readonly maxInstructions: number;
146
+ /**
147
+ * Maximum elements a collection may be expanded to before `map`/`reduce`/
148
+ * `sum`/`prod` will iterate it.
149
+ *
150
+ * A Range is stored as its two bounds and costs nothing until something
151
+ * materializes it, at which point it becomes one Value per element. Twenty
152
+ * characters (`sum(x, 1:100000000)`) therefore asked for a hundred million
153
+ * of them, and neither `maxInstructions` nor `maxStackDepth` could see it:
154
+ * the expansion happens inside a single opcode, so the instruction counter
155
+ * is never consulted while it runs, and the elements never reach the value
156
+ * stack. V8 aborted the whole process with "Reached heap limit", which a
157
+ * host embedding the engine cannot catch.
158
+ */
159
+ readonly maxCollectionSize: number;
160
+ /**
161
+ * Maximum elements (collection Values, matrix cells) one evaluation may
162
+ * materialize in total.
163
+ *
164
+ * `maxCollectionSize` above bounds a single collection. This bounds the sum
165
+ * of everything an expression asks for, which is a different question and
166
+ * the one that actually protects the host: two collections that are
167
+ * individually legal are legal together, and an operation whose result is
168
+ * the PRODUCT of two legal operands is bounded by neither of them. A matrix
169
+ * multiply is exactly that shape, so three lines within every other limit
170
+ * (`:a = map(1*x, 0:20000)`, `:b = transpose(a)`, `b * a`) asked for four
171
+ * hundred million cells and aborted the process.
172
+ *
173
+ * Counted in elements rather than bytes, because a count is what a call
174
+ * site has before it allocates. An element is 8 bytes as a numeric matrix
175
+ * cell and closer to a hundred as a full Value, so the default is worth
176
+ * roughly 16 MB of matrix or 200 MB of expanded collection: far past any
177
+ * document and far short of what an editor cannot survive.
178
+ */
179
+ readonly maxAllocatedElements: number;
180
+ /**
181
+ * Maximum user-defined-function calls one evaluation may make in TOTAL,
182
+ * however deeply or widely they nest.
183
+ *
184
+ * `maxFunctionRecursionDepth` bounds how DEEP calls nest and cannot see
185
+ * how MANY there are, and those are different numbers. Twenty-two lines of
186
+ * `f(n)(v) = f(n-1)(v) + f(n-1)(v)` reach a depth of only 22 against a
187
+ * limit of 50, and make 2,097,152 calls doing it: a fatal heap abort in
188
+ * under a second. `maxInstructions` cannot bound it either, because
189
+ * `executeBytecode()` re-enters itself per call and each reentrant call
190
+ * gets its OWN instruction count, so recursion refreshes its allowance on
191
+ * the way in. This is the tally that does not refresh; see
192
+ * `vm/AllocationBudget.ts`, which holds it for the same reason it holds
193
+ * the element tally.
194
+ *
195
+ * Counted in calls rather than in the instructions they run, because the
196
+ * call is the thing that multiplies: every call allocates a frame, its
197
+ * arguments and its result whatever its body says.
198
+ */
199
+ readonly maxFunctionCalls: number;
134
200
  }
135
201
  /**
136
202
  * Complete engine configuration.
@@ -148,15 +214,13 @@ interface VMConfig {
148
214
  * maxExpressionLength: 1000,
149
215
  * maxComplexity: 200,
150
216
  * },
151
- * // date, dice, performance, vm, worker, diagnostic all use defaults
217
+ * // date, performance, vm, worker, diagnostic all use defaults
152
218
  * });
153
219
  * ```
154
220
  */
155
221
  interface EngineConfig {
156
222
  /** Date/time expression evaluation bounds and formatting */
157
223
  readonly date: DateConfig;
158
- /** Dice roll expression controls */
159
- readonly dice: DiceConfig;
160
224
  /** Performance budgets and cache sizing */
161
225
  readonly performance: PerformanceConfig;
162
226
  /** Safety limits for expression complexity */
@@ -200,7 +264,12 @@ declare class ConfigManager {
200
264
  */
201
265
  set<T>(path: string, value: T): void;
202
266
  /**
203
- * Get complete configuration
267
+ * Get complete configuration.
268
+ *
269
+ * A detached copy, section objects included. A top-level spread would hand
270
+ * the caller this manager's own section objects, so `getConfig().vm.x = 1`
271
+ * would be an undeclared back door into {@link set}, bypassing its path
272
+ * validation and reaching every later {@link get}.
204
273
  */
205
274
  getConfig(): EngineConfig;
206
275
  /**
@@ -208,7 +277,16 @@ declare class ConfigManager {
208
277
  */
209
278
  update(config: Partial<EngineConfig>): void;
210
279
  /**
211
- * Reset to default configuration
280
+ * Reset to default configuration.
281
+ *
282
+ * Goes through {@link mergeEngineConfig} for the same reason the constructor
283
+ * does. A top-level `{ ...DEFAULT_CONFIG }` copies the six section
284
+ * references, not the sections, so after a reset this manager's
285
+ * `performance` object WAS `DEFAULT_CONFIG.performance` and the next
286
+ * `set('performance.x', ...)` wrote into the module constant. That constant
287
+ * is what every `ExpressionEngine` is built from, so one manager's reset
288
+ * could change the cache size, instruction ceiling or allocation budget of
289
+ * every engine constructed later in the process.
212
290
  */
213
291
  reset(): void;
214
292
  /**
@@ -225,4 +303,4 @@ interface ValidationResult {
225
303
  warnings?: string[];
226
304
  }
227
305
 
228
- export { ConfigManager as C, DEFAULT_CONFIG as D, type EngineConfig as E, type PerformanceConfig as P, type VMConfig as V, type WorkerConfig as W, type DateConfig as a, type DiagnosticConfig as b, type DiceConfig as c, type ValidationConfig as d, type ValidationResult as e };
306
+ export { ConfigManager as C, DEFAULT_CONFIG as D, type EngineConfig as E, type PerformanceConfig as P, type VMConfig as V, type WorkerConfig as W, type DateConfig as a, type DiagnosticConfig as b, type ValidationConfig as c, type ValidationResult as d };
@@ -28,6 +28,8 @@
28
28
  */
29
29
  declare const CoreErrorCodes: {
30
30
  readonly INVALID_NUMBER_LITERAL: "INVALID_NUMBER_LITERAL";
31
+ /** A `"` that never meets its closing partner (`lexer/ExpressionLexer.ts`'s `tokenizeString()`). Previously the tokenizer ran off the end of the input and returned what it had, so `"abc` lexed to an ordinary String and an unterminated literal was indistinguishable from a terminated one. */
32
+ readonly UNTERMINATED_STRING: "UNTERMINATED_STRING";
31
33
  readonly NO_PREFIX_PARSELET: "NO_PREFIX_PARSELET";
32
34
  readonly UNEXPECTED_END_OF_INPUT: "UNEXPECTED_END_OF_INPUT";
33
35
  readonly UNEXPECTED_TOKEN_TYPE: "UNEXPECTED_TOKEN_TYPE";
@@ -53,7 +55,9 @@ declare const CoreErrorCodes: {
53
55
  readonly EVALUATION_ERROR: "EVALUATION_ERROR";
54
56
  readonly INSTRUCTION_LIMIT_EXCEEDED: "INSTRUCTION_LIMIT_EXCEEDED";
55
57
  readonly STACK_LIMIT_EXCEEDED: "STACK_LIMIT_EXCEEDED";
56
- /** New this phase. See `VM.ts`'s `safePop()`: a stack-underflow (corrupted bytecode, a buggy plugin) is now a controlled EngineError instead of a raw TypeError. Always `recoverable: false` (internal invariant violation). */
58
+ /** One evaluation asking for more elements (collection Values, matrix cells) than `vm.maxAllocatedElements` allows. The counter the two limits above cannot be: both are checked between opcodes, so neither can see what a single opcode allocates inside a loop of its own. See `vm/AllocationBudget.ts`. Recoverable, since it describes this expression rather than the engine. */
59
+ readonly ALLOCATION_LIMIT_EXCEEDED: "ALLOCATION_LIMIT_EXCEEDED";
60
+ /** New this phase. See `VM.ts`'s `safePop()`: a stack-underflow (corrupted bytecode, a buggy plugin) is now a controlled EngineError instead of a raw TypeError. Category INTERNAL, since it is an engine or plugin fault rather than a typed line, but recoverable: it happened on one line and the engine is still usable. */
57
61
  readonly STACK_UNDERFLOW: "STACK_UNDERFLOW";
58
62
  readonly UNDEFINED_VARIABLE: "UNDEFINED_VARIABLE";
59
63
  /** New this phase, the Tier-2/`LOAD_GLOBAL_VAR` hardening: a global variable read before its async preflight resolved, surfaced as a controlled error instead of pushing `undefined`. */
@@ -64,15 +68,43 @@ declare const CoreErrorCodes: {
64
68
  /** User-defined-function call/definition errors (`CALL_USER_FUNCTION`/`DEFINE_USER_FUNCTION` opcodes), calling a name with no matching definition, calling with the wrong argument count, and the deliberate v1 restriction that a function body can't itself contain async work. */
65
69
  readonly UNDEFINED_FUNCTION: "UNDEFINED_FUNCTION";
66
70
  readonly FUNCTION_ARITY_MISMATCH: "FUNCTION_ARITY_MISMATCH";
71
+ /** The same mistake against a BUILT-IN rather than a user-defined function (`sqrt()`, `atan2(1)`, `sqrt(1,2,3)`). Separate from `FUNCTION_ARITY_MISMATCH` above because the two are raised by different opcodes and a host may want to word them differently. Checked at the `CALL_BUILTIN` dispatch against `vm/VMBuiltinArity.ts`; recoverable, since it is a typo in the line, not an engine fault. */
72
+ readonly BUILTIN_ARITY_MISMATCH: "BUILTIN_ARITY_MISMATCH";
67
73
  readonly USER_FUNCTION_ASYNC_UNSUPPORTED: "USER_FUNCTION_ASYNC_UNSUPPORTED";
68
74
  /** A map/reduce transform body (inline expression or user-defined function) calling an async plugin. Same v1 scope restriction as `USER_FUNCTION_ASYNC_UNSUPPORTED` above, enforced both at parse time (`MAP_REDUCE_TRANSFORM_MUST_BE_SYNCHRONOUS`, packages/mapreduce/) and as a defense-in-depth runtime backstop here. */
69
75
  readonly MAP_REDUCE_ASYNC_UNSUPPORTED: "MAP_REDUCE_ASYNC_UNSUPPORTED";
76
+ /** An algebra verb's expression argument (`BIND_UNKNOWN`'s body) calling an async plugin. Same v1 scope restriction as the two above, and likewise refused at parse time first (`SYMBOLIC_ARGUMENT_MUST_BE_SYNCHRONOUS`, packages/symbolic/). */
77
+ readonly SYMBOLIC_ASYNC_UNSUPPORTED: "SYMBOLIC_ASYNC_UNSUPPORTED";
70
78
  /** `pushCallFrame()`'s recursion guard, a nested `CALL_USER_FUNCTION` re-enters `executeBytecode()`, so `maxInstructions` alone can't catch e.g. `f(x) = f(x)`; this is the dedicated backstop. `recoverable: true` (the default for `.execution()`), ordinary user-written infinite recursion, not an engine bug; the guard exists precisely so it surfaces as a clear error instead of overflowing the native call stack uncatchably. */
71
79
  readonly FUNCTION_RECURSION_LIMIT_EXCEEDED: "FUNCTION_RECURSION_LIMIT_EXCEEDED";
80
+ /** The companion to the code above, and the half it could never see: how MANY user-defined-function calls one evaluation makes, rather than how deeply they nest. A twenty-two-line doubling chain nests twenty-two deep (legal) and makes two million calls (a fatal heap abort). Counted in `vm/AllocationBudget.ts`, because the tally has to survive `executeBytecode()` re-entering itself. Recoverable. */
81
+ readonly FUNCTION_CALL_LIMIT_EXCEEDED: "FUNCTION_CALL_LIMIT_EXCEEDED";
82
+ /** A `<date> + N workdays` offset outside `date.maxOffsetYears`/`minOffsetYears`. Workdays are the one date offset that walks the calendar a day at a time, so the one whose cost is the offset; every other one moves a Date field once. Recoverable. */
83
+ readonly DATE_OFFSET_LIMIT_EXCEEDED: "DATE_OFFSET_LIMIT_EXCEEDED";
84
+ /** A `<<`/`>>` with a bigint operand whose exact result would pass `MAX_EXACT_SHIFT_BITS`, whichever operand is the bigint. Both spellings refuse as of 1.0.0: a bigint on the left used to fall through to `x * 2^n` in doubles and report a 19,870-digit integer as Infinity. See `vm/VM.ts`'s `bigIntShift()`. Recoverable. */
85
+ readonly BIGINT_SHIFT_LIMIT_EXCEEDED: "BIGINT_SHIFT_LIMIT_EXCEEDED";
86
+ /** The same ceiling for `^`, the operator it was written for: `2n ^ 100000` asks for the same 100,001-bit integer `1n << 100000` does, so the two spellings answer the same way. Also new in 1.0.0, and for the same reason: this used to fall through to the double path and answer Infinity. A fractional or negative exponent (`4n ^ 0.5`, `2n ^ -1`) has no exact answer to bound and still uses the double path. See `vm/VM.ts`'s `MAX_EXACT_POW_BITS`. Recoverable. */
87
+ readonly BIGINT_POW_LIMIT_EXCEEDED: "BIGINT_POW_LIMIT_EXCEEDED";
88
+ /** An operand with no whole-number form (a fraction, an infinity, a NaN) meeting a bigint: `1n + 0.5`, `1n & 1.5`, `5n/pi`. `BigInt()` answers those with a raw RangeError, which the VM relabelled UNEXPECTED_ERROR, so a typo in the line was reported as an engine fault. Recoverable; see `vm/VMConversion.ts`'s `toBigIntOperand()`. */
89
+ readonly BIGINT_INEXACT_OPERAND: "BIGINT_INEXACT_OPERAND";
90
+ /** `10n / 0n` and `10n mod 0n`. Deliberately NOT what `1 / 0` does, which is Infinity: a bigint division is exact integer division (`7n / 2n` is 3n), and integer division by zero has no answer, exactly as it has none in C, Java, Python or JavaScript's own BigInt. Previously V8's own RangeError, relabelled UNEXPECTED_ERROR. Recoverable; see `vm/VMConversion.ts`'s `bigIntDivisionByZero()`. */
91
+ readonly BIGINT_DIVISION_BY_ZERO: "BIGINT_DIVISION_BY_ZERO";
72
92
  /** `DEFINE_USER_FUNCTION`'s body-index lookup failing, a compiler/VM invariant violation (the opcode stream referenced a `userFunctionBodies` slot that doesn't exist), never a user-input error. */
73
93
  readonly INTERNAL_MISSING_FUNCTION_BODY: "INTERNAL_MISSING_FUNCTION_BODY";
74
94
  /** `MAP_INVOKE`/`REDUCE_INVOKE`'s anonymous-body-index lookup failing. Same class as `INTERNAL_MISSING_FUNCTION_BODY` above, for the `anonymousBodies` side-table instead of `userFunctionBodies`. */
75
95
  readonly INTERNAL_MISSING_ANONYMOUS_BODY: "INTERNAL_MISSING_ANONYMOUS_BODY";
96
+ /** An operand byte read past the end of the stream: the program ends in the middle of an instruction. */
97
+ readonly MALFORMED_BYTECODE_TRUNCATED: "MALFORMED_BYTECODE_TRUNCATED";
98
+ /** A constant-pool operand indexing a `numbers`/`strings` entry that does not exist, or that is not of the pool's type. */
99
+ readonly MALFORMED_BYTECODE_CONSTANT_INDEX: "MALFORMED_BYTECODE_CONSTANT_INDEX";
100
+ /** A unit or converter name read off the value stack that is not a string. Distinct from the pool case above: the operand is a Value another opcode pushed, not a pool entry. */
101
+ readonly MALFORMED_BYTECODE_OPERAND_TYPE: "MALFORMED_BYTECODE_OPERAND_TYPE";
102
+ /** `MAP_INVOKE`/`REDUCE_INVOKE` carrying a body kind other than 0/1/2. Used to fall through every arm with no body and recurse into `executeBytecode(undefined)`. */
103
+ readonly MALFORMED_BYTECODE_BODY_KIND: "MALFORMED_BYTECODE_BODY_KIND";
104
+ /** A `PUSH_BIGINT` pool entry that is not a whole number, e.g. `"1.000"`. `BigInt()` answers that with a raw SyntaxError. */
105
+ readonly MALFORMED_BYTECODE_BIGINT_LITERAL: "MALFORMED_BYTECODE_BIGINT_LITERAL";
106
+ /** `executeBytecode()` called with something that is not a runnable program at all. Checked before the destructure that used to throw outside the function's own try/catch. */
107
+ readonly MALFORMED_BYTECODE_PROGRAM: "MALFORMED_BYTECODE_PROGRAM";
76
108
  /** A coefficient grew past `RATIONAL_MAX_BITS`, e.g. repeated exact elimination multiplying denominators together. */
77
109
  readonly SYMBOLIC_RATIONAL_OVERFLOW: "SYMBOLIC_RATIONAL_OVERFLOW";
78
110
  /** `NaN` or `±Infinity` reaching a symbolic expression, neither of which has an exact rational value. */
@@ -87,6 +119,8 @@ declare const CoreErrorCodes: {
87
119
  readonly SYMBOLIC_FACTOR_LIMIT_EXCEEDED: "SYMBOLIC_FACTOR_LIMIT_EXCEEDED";
88
120
  /** An equation outside what the solver attempts: not a polynomial, above the degree ceiling, or non-linear in the unknown while another unknown is present. */
89
121
  readonly SYMBOLIC_SOLVE_UNSUPPORTED: "SYMBOLIC_SOLVE_UNSUPPORTED";
122
+ /** Some but not all of an equation's roots were found. Reported rather than returned, because a partial list of roots looks exactly like a complete one. */
123
+ readonly SYMBOLIC_SOLVE_INCOMPLETE: "SYMBOLIC_SOLVE_INCOMPLETE";
90
124
  /** `solve`'s second argument not being a bare name. */
91
125
  readonly SOLVE_REQUIRES_VARIABLE_NAME: "SOLVE_REQUIRES_VARIABLE_NAME";
92
126
  /** A derivative order outside 0..`DERIVATIVE_MAX_ORDER`. */
@@ -105,11 +139,15 @@ declare const CoreErrorCodes: {
105
139
  readonly INTERNAL_RATIONAL_PARSE: "INTERNAL_RATIONAL_PARSE";
106
140
  readonly EXPRESSION_TOO_LONG: "EXPRESSION_TOO_LONG";
107
141
  readonly EXPRESSION_TOO_COMPLEX: "EXPRESSION_TOO_COMPLEX";
142
+ /** A document with more lines than `performance.maxDocumentLines`. The per-line limits above bound what one line may ask for and say nothing about how many lines there are; two hundred thousand of `1 + 1` exhausted the heap on the line records alone. Recoverable. */
143
+ readonly DOCUMENT_TOO_LARGE: "DOCUMENT_TOO_LARGE";
108
144
  readonly NORMALIZED_TOKEN_LIMIT_EXCEEDED: "NORMALIZED_TOKEN_LIMIT_EXCEEDED";
109
145
  /** `"=>"` with nothing before it, needs an expression or variable name to solve/simplify. */
110
146
  readonly THEREFORE_REQUIRES_EXPRESSION: "THEREFORE_REQUIRES_EXPRESSION";
111
147
  /** A `"=>"`-triggered expression called an async plugin (weather/stocks/currency). Same v1 scope restriction as user-function/map-reduce bodies. */
112
148
  readonly THEREFORE_ASYNC_UNSUPPORTED: "THEREFORE_ASYNC_UNSUPPORTED";
149
+ /** Colon-separated numbers that are not a time any clock can show ("24:00", "9:60", "100:5"). Raised by the labeled-line fallback, which used to answer them with whatever stood after the colon. */
150
+ readonly INVALID_TIME_LITERAL: "INVALID_TIME_LITERAL";
113
151
  readonly CONFIG_PATH_NOT_FOUND: "CONFIG_PATH_NOT_FOUND";
114
152
  readonly INVALID_CONFIG_PATH: "INVALID_CONFIG_PATH";
115
153
  readonly CONFIG_SECTION_NOT_FOUND: "CONFIG_SECTION_NOT_FOUND";
@@ -192,20 +230,29 @@ interface EngineErrorInit {
192
230
  /** An actionable, worked-example fix, e.g. `e.g. "weather in London"`. Mirrors this codebase's best existing messages (`WEATHER_EXPECTED_CITY`, `AS_CONVERTER_EXPECTED_NAME`). */
193
231
  suggestion?: string;
194
232
  /**
195
- * `true` (the default) for every EXPECTED failure mode of user input or
196
- * environment, bad syntax, an unknown variable/function, a safety limit
197
- * exceeded, an external API being down. `false` is reserved for genuine
198
- * ENGINE-INTERNAL invariant violations (corrupted bytecode, a stack
199
- * underflow from a buggy plugin, an "impossible" state)
200
- * `ErrorFactory.internal()`/`.config()` default to `false`, every other
201
- * factory method defaults to `true`.
233
+ * Whether this engine instance is still usable. `true` for everything that
234
+ * went wrong on ONE line, whether the line's fault (bad syntax, an unknown
235
+ * variable, a safety limit exceeded), the environment's (an external API
236
+ * down), or the engine's own (corrupted bytecode, a stack underflow from a
237
+ * buggy plugin). `false` is for the far rarer case where there is no working
238
+ * engine to go on with: a configuration or package-registration failure,
239
+ * which is what `ErrorFactory.config()` is for and the only factory method
240
+ * that still defaults to it.
202
241
  *
203
- * This does NOT gate "does evaluation of the rest of the document
204
- * continue", with this engine's per-line containment, it always does
205
- * even for a `recoverable: false` error (see `ARCHITECTURE.md`'s
206
- * async-batcher/Tier-2 hardening notes). It gates MESSAGE FRAMING and
207
- * telemetry: "fix your syntax" vs. "this is an engine bug, worth
208
- * reporting". See `EngineError.isFatal()`.
242
+ * The category answers a different question, and the two used to be answered
243
+ * as one. Category INTERNAL says whose fault this is, the engine's, worth
244
+ * reporting as a bug. `recoverable` says whether the host may carry on, and
245
+ * after an internal slip on one line it may:
246
+ * `__tests__/hardening/RobustnessEngineLifecycle.spec.ts` alternates a
247
+ * throwing line with a good one five hundred times and every answer stays
248
+ * correct. Reporting that as `isFatal()` told a host the opposite, and a host
249
+ * that honours the name would tear a document down over one bad line.
250
+ *
251
+ * This has never gated "does evaluation of the rest of the document
252
+ * continue"; with this engine's per-line containment it always does, even for
253
+ * a `recoverable: false` error (see `ARCHITECTURE.md`'s async-batcher/Tier-2
254
+ * hardening notes). It gates what a host is TOLD. See
255
+ * {@link EngineError.isFatal}.
209
256
  */
210
257
  recoverable?: boolean;
211
258
  /** Character-offset span into the source expression, when available. Not yet threaded through every call site, populate opportunistically, don't block on retrofitting every existing throw site. */
@@ -281,10 +328,23 @@ declare class ErrorFactory {
281
328
  static execution(init: EngineErrorInit): EngineError;
282
329
  static external(code: string, message: string, context?: Record<string, unknown>): EngineError;
283
330
  static external(init: EngineErrorInit): EngineError;
284
- /** Defaults `recoverable: false`, reserve for genuine engine-internal invariant violations, not user-input errors. */
331
+ /**
332
+ * Reserve for genuine engine-internal invariant violations (corrupted
333
+ * bytecode, a stack underflow from a buggy plugin, an "impossible" state),
334
+ * not for user-input errors: the category is a bug report.
335
+ *
336
+ * Defaults `recoverable: true` all the same, because every one of these sites
337
+ * is an invariant that failed on ONE line and none of them leaves the engine
338
+ * unusable. This used to default to `false`, which conflated "the engine's
339
+ * fault" with "the engine is finished" and told a host to tear a document
340
+ * down over a line it could simply have shown an error against. A site that
341
+ * has looked at its own case and concluded the instance really is gone can
342
+ * still pass `recoverable: false` explicitly. See {@link
343
+ * EngineErrorInit.recoverable}.
344
+ */
285
345
  static internal(code: string, message: string, context?: Record<string, unknown>): EngineError;
286
346
  static internal(init: EngineErrorInit): EngineError;
287
- /** Defaults `recoverable: false`, a bad config is an environment-setup problem, not a per-line user-input error. */
347
+ /** Defaults `recoverable: false`, and is now the only method that does: a configuration or package-registration failure is the one class that leaves no working engine to carry on with, rather than one bad line in a document. */
288
348
  static config(code: string, message: string, context?: Record<string, unknown>): EngineError;
289
349
  static config(init: EngineErrorInit): EngineError;
290
350
  }
@@ -298,6 +358,26 @@ declare class ErrorFactory {
298
358
  * per-line containment) so a raw `TypeError` from a genuine engine bug
299
359
  * still surfaces as a structured, catalogued error instead of an opaque
300
360
  * uncaught exception.
361
+ *
362
+ * Both branches state RECOVERABLE explicitly, while keeping the INTERNAL
363
+ * category. The two fields answer different questions and were previously
364
+ * answering the same one:
365
+ *
366
+ * category INTERNAL whose fault is this? The engine's. Worth reporting.
367
+ * recoverable is this engine instance still usable? Yes.
368
+ *
369
+ * Everything reaching here is by definition something the engine did not
370
+ * anticipate on ONE line, and per-line containment means the next line
371
+ * evaluates normally, which `__tests__/hardening/RobustnessEngineLifecycle`
372
+ * demonstrates over hundreds of alternating failure/success pairs. Reporting
373
+ * that as `isFatal()` told a host the opposite, and a host that honours the
374
+ * name would tear a document down over a single bad line. The "this is an
375
+ * engine bug rather than your syntax" signal a caller wants for telemetry is
376
+ * the CATEGORY, which is unchanged.
377
+ *
378
+ * The flag is written out here rather than inherited from
379
+ * `ErrorFactory.internal()`, which now defaults the same way, so that this
380
+ * function's contract survives a future change to that default.
301
381
  */
302
382
  declare function normalizeUnknownError(error: unknown): EngineError;
303
383