@lotics/app-sdk 0.120.1 → 0.120.3

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.
@@ -185,28 +185,651 @@ function bandOf(tiers, figure) {
185
185
  return { min: tiers[at - 1] ?? null, max: tiers[at] ?? null };
186
186
  }
187
187
 
188
- // ../shared/src/utils.ts
189
- function decimalPlaces(value) {
190
- if (Number.isInteger(value)) return 0;
191
- const [mantissa = "", exponent = "0"] = String(value).split("e");
192
- const fraction = mantissa.split(".")[1]?.length ?? 0;
193
- return Math.max(0, fraction - Number(exponent));
188
+ // ../../node_modules/big.js/big.mjs
189
+ var DP = 20;
190
+ var RM = 1;
191
+ var MAX_DP = 1e6;
192
+ var MAX_POWER = 1e6;
193
+ var NE = -7;
194
+ var PE = 21;
195
+ var STRICT = false;
196
+ var NAME = "[big.js] ";
197
+ var INVALID = NAME + "Invalid ";
198
+ var INVALID_DP = INVALID + "decimal places";
199
+ var INVALID_RM = INVALID + "rounding mode";
200
+ var DIV_BY_ZERO = NAME + "Division by zero";
201
+ var P = {};
202
+ var UNDEFINED = void 0;
203
+ var NUMERIC = /^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i;
204
+ function _Big_() {
205
+ function Big2(n) {
206
+ var x = this;
207
+ if (!(x instanceof Big2)) {
208
+ return n === UNDEFINED && arguments.length === 0 ? _Big_() : new Big2(n);
209
+ }
210
+ if (n instanceof Big2) {
211
+ x.s = n.s;
212
+ x.e = n.e;
213
+ x.c = n.c.slice();
214
+ } else {
215
+ if (typeof n !== "string") {
216
+ if (Big2.strict === true && typeof n !== "bigint") {
217
+ throw TypeError(INVALID + "value");
218
+ }
219
+ n = n === 0 && 1 / n < 0 ? "-0" : String(n);
220
+ }
221
+ parse(x, n);
222
+ }
223
+ x.constructor = Big2;
224
+ }
225
+ Big2.prototype = P;
226
+ Big2.DP = DP;
227
+ Big2.RM = RM;
228
+ Big2.NE = NE;
229
+ Big2.PE = PE;
230
+ Big2.strict = STRICT;
231
+ Big2.roundDown = 0;
232
+ Big2.roundHalfUp = 1;
233
+ Big2.roundHalfEven = 2;
234
+ Big2.roundUp = 3;
235
+ return Big2;
236
+ }
237
+ function parse(x, n) {
238
+ var e, i, nl;
239
+ if (!NUMERIC.test(n)) {
240
+ throw Error(INVALID + "number");
241
+ }
242
+ x.s = n.charAt(0) == "-" ? (n = n.slice(1), -1) : 1;
243
+ if ((e = n.indexOf(".")) > -1) n = n.replace(".", "");
244
+ if ((i = n.search(/e/i)) > 0) {
245
+ if (e < 0) e = i;
246
+ e += +n.slice(i + 1);
247
+ n = n.substring(0, i);
248
+ } else if (e < 0) {
249
+ e = n.length;
250
+ }
251
+ nl = n.length;
252
+ for (i = 0; i < nl && n.charAt(i) == "0"; ) ++i;
253
+ if (i == nl) {
254
+ x.c = [x.e = 0];
255
+ } else {
256
+ for (; nl > 0 && n.charAt(--nl) == "0"; ) ;
257
+ x.e = e - i - 1;
258
+ x.c = [];
259
+ for (e = 0; i <= nl; ) x.c[e++] = +n.charAt(i++);
260
+ }
261
+ return x;
262
+ }
263
+ function round(x, sd, rm, more) {
264
+ var xc = x.c;
265
+ if (rm === UNDEFINED) rm = x.constructor.RM;
266
+ if (rm !== 0 && rm !== 1 && rm !== 2 && rm !== 3) {
267
+ throw Error(INVALID_RM);
268
+ }
269
+ if (sd < 1) {
270
+ more = rm === 3 && (more || !!xc[0]) || sd === 0 && (rm === 1 && xc[0] >= 5 || rm === 2 && (xc[0] > 5 || xc[0] === 5 && (more || xc[1] !== UNDEFINED)));
271
+ xc.length = 1;
272
+ if (more) {
273
+ x.e = x.e - sd + 1;
274
+ xc[0] = 1;
275
+ } else {
276
+ xc[0] = x.e = 0;
277
+ }
278
+ } else if (sd < xc.length) {
279
+ more = rm === 1 && xc[sd] >= 5 || rm === 2 && (xc[sd] > 5 || xc[sd] === 5 && (more || xc[sd + 1] !== UNDEFINED || xc[sd - 1] & 1)) || rm === 3 && (more || !!xc[0]);
280
+ xc.length = sd;
281
+ if (more) {
282
+ for (; ++xc[--sd] > 9; ) {
283
+ xc[sd] = 0;
284
+ if (sd === 0) {
285
+ ++x.e;
286
+ xc.unshift(1);
287
+ break;
288
+ }
289
+ }
290
+ }
291
+ for (sd = xc.length; !xc[--sd]; ) xc.pop();
292
+ }
293
+ return x;
294
+ }
295
+ function stringify(x, doExponential, isNonzero) {
296
+ var e = x.e, s = x.c.join(""), n = s.length;
297
+ if (doExponential) {
298
+ s = s.charAt(0) + (n > 1 ? "." + s.slice(1) : "") + (e < 0 ? "e" : "e+") + e;
299
+ } else if (e < 0) {
300
+ for (; ++e; ) s = "0" + s;
301
+ s = "0." + s;
302
+ } else if (e > 0) {
303
+ if (++e > n) {
304
+ for (e -= n; e--; ) s += "0";
305
+ } else if (e < n) {
306
+ s = s.slice(0, e) + "." + s.slice(e);
307
+ }
308
+ } else if (n > 1) {
309
+ s = s.charAt(0) + "." + s.slice(1);
310
+ }
311
+ return x.s < 0 && isNonzero ? "-" + s : s;
312
+ }
313
+ P.abs = function() {
314
+ var x = new this.constructor(this);
315
+ x.s = 1;
316
+ return x;
317
+ };
318
+ P.cmp = function(y) {
319
+ var isneg, x = this, xc = x.c, yc = (y = new x.constructor(y)).c, i = x.s, j = y.s, k = x.e, l = y.e;
320
+ if (!xc[0] || !yc[0]) return !xc[0] ? !yc[0] ? 0 : -j : i;
321
+ if (i != j) return i;
322
+ isneg = i < 0;
323
+ if (k != l) return k > l ^ isneg ? 1 : -1;
324
+ j = (k = xc.length) < (l = yc.length) ? k : l;
325
+ for (i = -1; ++i < j; ) {
326
+ if (xc[i] != yc[i]) return xc[i] > yc[i] ^ isneg ? 1 : -1;
327
+ }
328
+ return k == l ? 0 : k > l ^ isneg ? 1 : -1;
329
+ };
330
+ P.div = function(y) {
331
+ var x = this, Big2 = x.constructor, a = x.c, b = (y = new Big2(y)).c, k = x.s == y.s ? 1 : -1, dp = Big2.DP;
332
+ if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {
333
+ throw Error(INVALID_DP);
334
+ }
335
+ if (!b[0]) {
336
+ throw Error(DIV_BY_ZERO);
337
+ }
338
+ if (!a[0]) {
339
+ y.s = k;
340
+ y.c = [y.e = 0];
341
+ return y;
342
+ }
343
+ var bl, bt, n, cmp, ri, bz = b.slice(), ai = bl = b.length, al = a.length, r = a.slice(0, bl), rl = r.length, q = y, qc = q.c = [], qi = 0, p = dp + (q.e = x.e - y.e) + 1;
344
+ q.s = k;
345
+ k = p < 0 ? 0 : p;
346
+ bz.unshift(0);
347
+ for (; rl++ < bl; ) r.push(0);
348
+ do {
349
+ for (n = 0; n < 10; n++) {
350
+ if (bl != (rl = r.length)) {
351
+ cmp = bl > rl ? 1 : -1;
352
+ } else {
353
+ for (ri = -1, cmp = 0; ++ri < bl; ) {
354
+ if (b[ri] != r[ri]) {
355
+ cmp = b[ri] > r[ri] ? 1 : -1;
356
+ break;
357
+ }
358
+ }
359
+ }
360
+ if (cmp < 0) {
361
+ for (bt = rl == bl ? b : bz; rl; ) {
362
+ if (r[--rl] < bt[rl]) {
363
+ ri = rl;
364
+ for (; ri && !r[--ri]; ) r[ri] = 9;
365
+ --r[ri];
366
+ r[rl] += 10;
367
+ }
368
+ r[rl] -= bt[rl];
369
+ }
370
+ for (; !r[0]; ) r.shift();
371
+ } else {
372
+ break;
373
+ }
374
+ }
375
+ qc[qi++] = cmp ? n : ++n;
376
+ if (r[0] && cmp) r[rl] = a[ai] || 0;
377
+ else r = [a[ai]];
378
+ } while ((ai++ < al || r[0] !== UNDEFINED) && k--);
379
+ if (!qc[0] && qi != 1) {
380
+ qc.shift();
381
+ q.e--;
382
+ p--;
383
+ }
384
+ if (qi > p) round(q, p, Big2.RM, r[0] !== UNDEFINED);
385
+ return q;
386
+ };
387
+ P.eq = function(y) {
388
+ return this.cmp(y) === 0;
389
+ };
390
+ P.gt = function(y) {
391
+ return this.cmp(y) > 0;
392
+ };
393
+ P.gte = function(y) {
394
+ return this.cmp(y) > -1;
395
+ };
396
+ P.lt = function(y) {
397
+ return this.cmp(y) < 0;
398
+ };
399
+ P.lte = function(y) {
400
+ return this.cmp(y) < 1;
401
+ };
402
+ P.minus = P.sub = function(y) {
403
+ var i, j, t, xlty, x = this, Big2 = x.constructor, a = x.s, b = (y = new Big2(y)).s;
404
+ if (a != b) {
405
+ y.s = -b;
406
+ return x.plus(y);
407
+ }
408
+ var xc = x.c.slice(), xe = x.e, yc = y.c, ye = y.e;
409
+ if (!xc[0] || !yc[0]) {
410
+ if (yc[0]) {
411
+ y.s = -b;
412
+ } else if (xc[0]) {
413
+ y = new Big2(x);
414
+ } else {
415
+ y.s = 1;
416
+ }
417
+ return y;
418
+ }
419
+ if (a = xe - ye) {
420
+ if (xlty = a < 0) {
421
+ a = -a;
422
+ t = xc;
423
+ } else {
424
+ ye = xe;
425
+ t = yc;
426
+ }
427
+ t.reverse();
428
+ for (b = a; b--; ) t.push(0);
429
+ t.reverse();
430
+ } else {
431
+ j = ((xlty = xc.length < yc.length) ? xc : yc).length;
432
+ for (a = b = 0; b < j; b++) {
433
+ if (xc[b] != yc[b]) {
434
+ xlty = xc[b] < yc[b];
435
+ break;
436
+ }
437
+ }
438
+ }
439
+ if (xlty) {
440
+ t = xc;
441
+ xc = yc;
442
+ yc = t;
443
+ y.s = -y.s;
444
+ }
445
+ if ((b = (j = yc.length) - (i = xc.length)) > 0) for (; b--; ) xc[i++] = 0;
446
+ for (b = i; j > a; ) {
447
+ if (xc[--j] < yc[j]) {
448
+ for (i = j; i && !xc[--i]; ) xc[i] = 9;
449
+ --xc[i];
450
+ xc[j] += 10;
451
+ }
452
+ xc[j] -= yc[j];
453
+ }
454
+ for (; xc[--b] === 0; ) xc.pop();
455
+ for (; xc[0] === 0; ) {
456
+ xc.shift();
457
+ --ye;
458
+ }
459
+ if (!xc[0]) {
460
+ y.s = 1;
461
+ xc = [ye = 0];
462
+ }
463
+ y.c = xc;
464
+ y.e = ye;
465
+ return y;
466
+ };
467
+ P.mod = function(y) {
468
+ var ygtx, x = this, Big2 = x.constructor, a = x.s, b = (y = new Big2(y)).s;
469
+ if (!y.c[0]) {
470
+ throw Error(DIV_BY_ZERO);
471
+ }
472
+ x.s = y.s = 1;
473
+ ygtx = y.cmp(x) == 1;
474
+ x.s = a;
475
+ y.s = b;
476
+ if (ygtx) return new Big2(x);
477
+ a = Big2.DP;
478
+ b = Big2.RM;
479
+ Big2.DP = Big2.RM = 0;
480
+ x = x.div(y);
481
+ Big2.DP = a;
482
+ Big2.RM = b;
483
+ return this.minus(x.times(y));
484
+ };
485
+ P.neg = function() {
486
+ var x = new this.constructor(this);
487
+ x.s = -x.s;
488
+ return x;
489
+ };
490
+ P.plus = P.add = function(y) {
491
+ var e, k, t, x = this, Big2 = x.constructor;
492
+ y = new Big2(y);
493
+ if (x.s != y.s) {
494
+ y.s = -y.s;
495
+ return x.minus(y);
496
+ }
497
+ var xe = x.e, xc = x.c, ye = y.e, yc = y.c;
498
+ if (!xc[0] || !yc[0]) {
499
+ if (!yc[0]) {
500
+ if (xc[0]) {
501
+ y = new Big2(x);
502
+ } else {
503
+ y.s = x.s;
504
+ }
505
+ }
506
+ return y;
507
+ }
508
+ xc = xc.slice();
509
+ if (e = xe - ye) {
510
+ if (e > 0) {
511
+ ye = xe;
512
+ t = yc;
513
+ } else {
514
+ e = -e;
515
+ t = xc;
516
+ }
517
+ t.reverse();
518
+ for (; e--; ) t.push(0);
519
+ t.reverse();
520
+ }
521
+ if (xc.length - yc.length < 0) {
522
+ t = yc;
523
+ yc = xc;
524
+ xc = t;
525
+ }
526
+ e = yc.length;
527
+ for (k = 0; e; xc[e] %= 10) k = (xc[--e] = xc[e] + yc[e] + k) / 10 | 0;
528
+ if (k) {
529
+ xc.unshift(k);
530
+ ++ye;
531
+ }
532
+ for (e = xc.length; xc[--e] === 0; ) xc.pop();
533
+ y.c = xc;
534
+ y.e = ye;
535
+ return y;
536
+ };
537
+ P.pow = function(n) {
538
+ var x = this, one = new x.constructor("1"), y = one, isneg = n < 0;
539
+ if (n !== ~~n || n < -MAX_POWER || n > MAX_POWER) {
540
+ throw Error(INVALID + "exponent");
541
+ }
542
+ if (isneg) n = -n;
543
+ for (; ; ) {
544
+ if (n & 1) y = y.times(x);
545
+ n >>= 1;
546
+ if (!n) break;
547
+ x = x.times(x);
548
+ }
549
+ return isneg ? one.div(y) : y;
550
+ };
551
+ P.prec = function(sd, rm) {
552
+ if (sd !== ~~sd || sd < 1 || sd > MAX_DP) {
553
+ throw Error(INVALID + "precision");
554
+ }
555
+ return round(new this.constructor(this), sd, rm);
556
+ };
557
+ P.round = function(dp, rm) {
558
+ if (dp === UNDEFINED) dp = 0;
559
+ else if (dp !== ~~dp || dp < -MAX_DP || dp > MAX_DP) {
560
+ throw Error(INVALID_DP);
561
+ }
562
+ return round(new this.constructor(this), dp + this.e + 1, rm);
563
+ };
564
+ P.sqrt = function() {
565
+ var r, c, t, x = this, Big2 = x.constructor, s = x.s, e = x.e, half = new Big2("0.5");
566
+ if (!x.c[0]) return new Big2(x);
567
+ if (s < 0) {
568
+ throw Error(NAME + "No square root");
569
+ }
570
+ s = Math.sqrt(+stringify(x, true, true));
571
+ if (s === 0 || s === 1 / 0) {
572
+ c = x.c.join("");
573
+ if (!(c.length + e & 1)) c += "0";
574
+ s = Math.sqrt(c);
575
+ e = ((e + 1) / 2 | 0) - (e < 0 || e & 1);
576
+ r = new Big2((s == 1 / 0 ? "5e" : (s = s.toExponential()).slice(0, s.indexOf("e") + 1)) + e);
577
+ } else {
578
+ r = new Big2(s + "");
579
+ }
580
+ e = r.e + (Big2.DP += 4);
581
+ do {
582
+ t = r;
583
+ r = half.times(t.plus(x.div(t)));
584
+ } while (t.c.slice(0, e).join("") !== r.c.slice(0, e).join(""));
585
+ return round(r, (Big2.DP -= 4) + r.e + 1, Big2.RM);
586
+ };
587
+ P.times = P.mul = function(y) {
588
+ var c, x = this, Big2 = x.constructor, xc = x.c, yc = (y = new Big2(y)).c, a = xc.length, b = yc.length, i = x.e, j = y.e;
589
+ y.s = x.s == y.s ? 1 : -1;
590
+ if (!xc[0] || !yc[0]) {
591
+ y.c = [y.e = 0];
592
+ return y;
593
+ }
594
+ y.e = i + j;
595
+ if (a < b) {
596
+ c = xc;
597
+ xc = yc;
598
+ yc = c;
599
+ j = a;
600
+ a = b;
601
+ b = j;
602
+ }
603
+ for (c = new Array(j = a + b); j--; ) c[j] = 0;
604
+ for (i = b; i--; ) {
605
+ b = 0;
606
+ for (j = a + i; j > i; ) {
607
+ b = c[j] + yc[i] * xc[j - i - 1] + b;
608
+ c[j--] = b % 10;
609
+ b = b / 10 | 0;
610
+ }
611
+ c[j] = b;
612
+ }
613
+ if (b) ++y.e;
614
+ else c.shift();
615
+ for (i = c.length; !c[--i]; ) c.pop();
616
+ y.c = c;
617
+ return y;
618
+ };
619
+ P.toExponential = function(dp, rm) {
620
+ var x = this, n = x.c[0];
621
+ if (dp !== UNDEFINED) {
622
+ if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {
623
+ throw Error(INVALID_DP);
624
+ }
625
+ x = round(new x.constructor(x), ++dp, rm);
626
+ for (; x.c.length < dp; ) x.c.push(0);
627
+ }
628
+ return stringify(x, true, !!n);
629
+ };
630
+ P.toFixed = function(dp, rm) {
631
+ var x = this, n = x.c[0];
632
+ if (dp !== UNDEFINED) {
633
+ if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {
634
+ throw Error(INVALID_DP);
635
+ }
636
+ x = round(new x.constructor(x), dp + x.e + 1, rm);
637
+ for (dp = dp + x.e + 1; x.c.length < dp; ) x.c.push(0);
638
+ }
639
+ return stringify(x, false, !!n);
640
+ };
641
+ P.toJSON = P.toString = function() {
642
+ var x = this, Big2 = x.constructor;
643
+ return stringify(x, x.e <= Big2.NE || x.e >= Big2.PE, !!x.c[0]);
644
+ };
645
+ if (typeof Symbol !== "undefined") {
646
+ P[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")] = P.toJSON;
647
+ }
648
+ P.toNumber = function() {
649
+ var n = +stringify(this, true, true);
650
+ if (this.constructor.strict === true && !this.eq(n.toString())) {
651
+ throw Error(NAME + "Imprecise conversion");
652
+ }
653
+ return n;
654
+ };
655
+ P.toPrecision = function(sd, rm) {
656
+ var x = this, Big2 = x.constructor, n = x.c[0];
657
+ if (sd !== UNDEFINED) {
658
+ if (sd !== ~~sd || sd < 1 || sd > MAX_DP) {
659
+ throw Error(INVALID + "precision");
660
+ }
661
+ x = round(new Big2(x), sd, rm);
662
+ for (; x.c.length < sd; ) x.c.push(0);
663
+ }
664
+ return stringify(x, sd <= x.e || x.e <= Big2.NE || x.e >= Big2.PE, !!n);
665
+ };
666
+ P.valueOf = function() {
667
+ var x = this, Big2 = x.constructor;
668
+ if (Big2.strict === true) {
669
+ throw Error(NAME + "valueOf disallowed");
670
+ }
671
+ return stringify(x, x.e <= Big2.NE || x.e >= Big2.PE, true);
672
+ };
673
+ var Big = _Big_();
674
+ var big_default = Big;
675
+
676
+ // ../shared/src/decimal.ts
677
+ var DIVISION_DIGITS = 24;
678
+ var ROUNDING_PLACES = 1e6;
679
+ var POWER_DIGITS = 300;
680
+ var SIGNIFICANT_DIGITS = 15;
681
+ var SHORT_DIGITS = 12;
682
+ var WHOLE_DIGITS_LIMIT = 10 ** SIGNIFICANT_DIGITS;
683
+ var DIVISION_SCALES = [10, 100, 1e3, 1e4, 1e5, 1e6];
684
+ var Decimal = big_default();
685
+ Decimal.DP = DIVISION_DIGITS;
686
+ Decimal.RM = big_default.roundDown;
687
+ var QUOTIENT_STEP = new Decimal(`1e-${DIVISION_DIGITS}`);
688
+ function takesFloat(left, right) {
689
+ return !Number.isFinite(left) || !Number.isFinite(right) || left === 0 || right === 0;
690
+ }
691
+ function isExactInteger(left, right, result) {
692
+ return Number.isSafeInteger(left) && Number.isSafeInteger(right) && Number.isSafeInteger(result);
693
+ }
694
+ function isInexact(value, decimal) {
695
+ return decimal.c.length > SHORT_DIGITS && !Number.isSafeInteger(value);
696
+ }
697
+ function settled(result, inexact) {
698
+ if (!inexact) return result;
699
+ const reading = result.prec(SIGNIFICANT_DIGITS, big_default.roundHalfUp);
700
+ return reading.c.length <= SHORT_DIGITS ? reading : result;
701
+ }
702
+ function addDecimal(left, right) {
703
+ const float = left + right;
704
+ if (takesFloat(left, right) || isExactInteger(left, right, float)) return float;
705
+ const augend = new Decimal(left);
706
+ const addend = new Decimal(right);
707
+ return settled(augend.plus(addend), isInexact(left, augend) || isInexact(right, addend)).toNumber();
708
+ }
709
+ function subtractDecimal(left, right) {
710
+ const float = left - right;
711
+ if (takesFloat(left, right) || isExactInteger(left, right, float)) return float;
712
+ const minuend = new Decimal(left);
713
+ const subtrahend = new Decimal(right);
714
+ return settled(minuend.minus(subtrahend), isInexact(left, minuend) || isInexact(right, subtrahend)).toNumber();
715
+ }
716
+ function multiplyDecimal(left, right) {
717
+ return product(left, right, true);
718
+ }
719
+ function multiplyExactly(left, right) {
720
+ return product(left, right, false);
721
+ }
722
+ function product(left, right, settles) {
723
+ const float = left * right;
724
+ if (takesFloat(left, right) || isExactInteger(left, right, float)) return float;
725
+ const multiplicand = new Decimal(left);
726
+ const multiplier = new Decimal(right);
727
+ return settled(multiplicand.times(multiplier), settles && (isInexact(left, multiplicand) || isInexact(right, multiplier))).toNumber();
728
+ }
729
+ function divideDecimal(left, right) {
730
+ if (takesFloat(left, right) || Number.isSafeInteger(left) && Number.isSafeInteger(right)) return left / right;
731
+ const scaled = scaledQuotient(left, right);
732
+ if (scaled !== void 0) return scaled;
733
+ const dividend = new Decimal(left);
734
+ const divisor = new Decimal(right);
735
+ return quotient(dividend, divisor, isInexact(left, dividend) || isInexact(right, divisor));
736
+ }
737
+ function scaledQuotient(left, right) {
738
+ for (const scale2 of DIVISION_SCALES) {
739
+ const dividend = Math.round(left * scale2);
740
+ if (Math.abs(dividend) >= WHOLE_DIGITS_LIMIT || dividend / scale2 !== left) continue;
741
+ const divisor = Math.round(right * scale2);
742
+ if (Math.abs(divisor) < WHOLE_DIGITS_LIMIT && divisor / scale2 === right) return dividend / divisor;
743
+ }
744
+ return void 0;
745
+ }
746
+ function moduloDecimal(left, right) {
747
+ if (takesFloat(left, right)) return left % right;
748
+ const remainder = new Decimal(left).mod(right);
749
+ if (remainder.eq(0)) return left < 0 ? -0 : 0;
750
+ return remainder.toNumber();
751
+ }
752
+ function readAtSignificantDigits(value) {
753
+ if (Number.isInteger(value) || !Number.isFinite(value)) return value;
754
+ if (Math.abs(value) >= WHOLE_DIGITS_LIMIT) return Math.sign(value) * Math.round(Math.abs(value));
755
+ return Number(value.toPrecision(SIGNIFICANT_DIGITS));
756
+ }
757
+ function compareDecimal(left, right) {
758
+ if (left === right) return 0;
759
+ if (Number.isNaN(left) || Number.isNaN(right)) return NaN;
760
+ const leftRead = readAtSignificantDigits(left);
761
+ const rightRead = readAtSignificantDigits(right);
762
+ if (leftRead === rightRead) return 0;
763
+ return leftRead < rightRead ? -1 : 1;
194
764
  }
195
765
  function sumDecimal(values, divisor = 1) {
196
- const float = values.reduce((total2, value) => total2 + value, 0) / divisor;
197
- if (!values.every(Number.isFinite)) return float;
198
- const factor = 10 ** values.reduce((places, value) => Math.max(places, decimalPlaces(value)), 0);
199
- const scaled = values.map((value) => Math.round(value * factor));
200
- const total = scaled.reduce((sum, value) => sum + value, 0);
201
- const exact = Number.isSafeInteger(total) && scaled.every((one, index) => Number.isSafeInteger(one) && one / factor === values[index]);
202
- return exact ? total / (factor * divisor) : float;
766
+ if (!values.every(Number.isFinite) || !Number.isFinite(divisor) || divisor === 0) {
767
+ return values.reduce((total2, value) => total2 + value, 0) / divisor;
768
+ }
769
+ const { total, inexact } = isWithinSafeIntegers(values) ? { total: new Decimal(values.reduce((sum, value) => sum + value, 0)), inexact: false } : totalOf(values);
770
+ if (total.eq(0)) return 0;
771
+ if (divisor === 1) return total.toNumber();
772
+ const by = new Decimal(divisor);
773
+ return quotient(total, by, inexact || isInexact(divisor, by));
774
+ }
775
+ function totalOf(values) {
776
+ let total = new Decimal(0);
777
+ let inexact = false;
778
+ for (const value of values) {
779
+ const term = new Decimal(value);
780
+ inexact ||= isInexact(value, term);
781
+ total = total.plus(term);
782
+ }
783
+ return { total: settled(total, inexact), inexact };
784
+ }
785
+ function isWithinSafeIntegers(values) {
786
+ return values.every(Number.isSafeInteger) && values.reduce((magnitude, value) => magnitude + Math.abs(value), 0) <= Number.MAX_SAFE_INTEGER;
203
787
  }
204
- function sample(arr) {
205
- if (arr.length === 0) return void 0;
206
- return arr[Math.floor(Math.random() * arr.length)];
788
+ function roundDecimal(value, places) {
789
+ if (!Number.isFinite(value) || value === 0 || !Number.isInteger(places) || Math.abs(places) > ROUNDING_PLACES) {
790
+ const multiplier = Math.pow(10, places);
791
+ return Math.sign(value) * Math.round(Math.abs(value) * multiplier) / multiplier;
792
+ }
793
+ const rounded = new Decimal(value).round(places, big_default.roundHalfUp);
794
+ if (rounded.eq(0)) return value < 0 ? -0 : 0;
795
+ return rounded.toNumber();
796
+ }
797
+ function percentOf(part, total) {
798
+ return roundDecimal(divideDecimal(multiplyDecimal(part, 100), total), 0);
799
+ }
800
+ function fixedDecimal(value, places) {
801
+ if (!Number.isFinite(value) || Math.abs(value) >= 1e21) return value.toFixed(places);
802
+ return new Decimal(value).toFixed(places, big_default.roundHalfUp);
803
+ }
804
+ function powerDecimal(base, exponent) {
805
+ const float = Math.pow(base, exponent);
806
+ if (takesFloat(base, exponent) || !Number.isInteger(exponent)) return float;
807
+ const decimal = new Decimal(base);
808
+ if (decimal.c.length * Math.abs(exponent) > POWER_DIGITS) return float;
809
+ const inexact = isInexact(base, decimal);
810
+ const power = settled(decimal.pow(Math.abs(exponent)), inexact);
811
+ return exponent > 0 ? power.toNumber() : quotient(new Decimal(1), power, inexact);
812
+ }
813
+ function quotient(dividend, divisor, inexact) {
814
+ const sign = dividend.s === divisor.s ? 1 : -1;
815
+ const numerator = coefficientOf(dividend);
816
+ const denominator = coefficientOf(divisor);
817
+ for (let carried = 0; ; carried = carried * 2 + DIVISION_DIGITS) {
818
+ const scaled = new Decimal(`${numerator.digits}e${carried}`);
819
+ const low = scaled.div(denominator.digits);
820
+ const exponent = numerator.exponent - denominator.exponent - carried;
821
+ if (inexact) {
822
+ const short = settled(low, true);
823
+ if (short !== low) return sign * Number(`${short.toFixed()}e${exponent}`);
824
+ }
825
+ const lowNumber = Number(`${low.toFixed()}e${exponent}`);
826
+ const highNumber = Number(`${low.plus(QUOTIENT_STEP).toFixed()}e${exponent}`);
827
+ if (lowNumber === highNumber || low.times(denominator.digits).eq(scaled)) return sign * lowNumber;
828
+ }
207
829
  }
208
- function assertNever(value) {
209
- throw new Error(`Unhandled union member: ${JSON.stringify(value)}`);
830
+ function coefficientOf(decimal) {
831
+ const [digits = "", exponent = "0"] = decimal.abs().toExponential().split("e");
832
+ return { digits, exponent: Number(exponent) };
210
833
  }
211
834
 
212
835
  // ../shared/src/schemas/read_row.ts
@@ -1242,9 +1865,18 @@ export {
1242
1865
  tiersAscend,
1243
1866
  bandKey,
1244
1867
  bandOf,
1868
+ addDecimal,
1869
+ subtractDecimal,
1870
+ multiplyDecimal,
1871
+ multiplyExactly,
1872
+ divideDecimal,
1873
+ moduloDecimal,
1874
+ compareDecimal,
1245
1875
  sumDecimal,
1246
- sample,
1247
- assertNever,
1876
+ roundDecimal,
1877
+ percentOf,
1878
+ fixedDecimal,
1879
+ powerDecimal,
1248
1880
  AGGREGATE_COUNT_COLUMN,
1249
1881
  AGGREGATE_SUM_COLUMN,
1250
1882
  aggregateRows,
package/dist/index.js CHANGED
@@ -3,35 +3,44 @@ import {
3
3
  AGGREGATE_SUM_COLUMN,
4
4
  DEFAULT_IMAGE_FIDELITY,
5
5
  __export,
6
+ addDecimal,
6
7
  aggregateRows,
7
8
  anyRecordingLive,
8
9
  applyPatch,
9
- assertNever,
10
10
  bandKey,
11
11
  bandOf,
12
+ compareDecimal,
12
13
  decodeAll,
14
+ divideDecimal,
13
15
  encodePatch,
16
+ fixedDecimal,
14
17
  getUrlParams,
15
18
  isEmbedded,
19
+ moduloDecimal,
20
+ multiplyDecimal,
21
+ multiplyExactly,
16
22
  paramsEqual,
17
23
  peekUrlParams,
24
+ percentOf,
18
25
  postHostNotification,
26
+ powerDecimal,
19
27
  readRecordingSnapshot,
20
28
  recordingStateOf,
21
29
  recordingsVersion,
22
30
  replaceRecordings,
31
+ roundDecimal,
23
32
  rpc,
24
33
  rpcAgentRun,
25
34
  rpcAgentRunContinue,
26
- sample,
27
35
  setUrlParams,
28
36
  subscribeQueriesChanged,
29
37
  subscribeRecordings,
30
38
  subscribeUrlParams,
39
+ subtractDecimal,
31
40
  sumDecimal,
32
41
  tiersAscend,
33
42
  urlParam
34
- } from "./chunk-NZXJENJ2.js";
43
+ } from "./chunk-ABDSJ25E.js";
35
44
 
36
45
  // src/mount.tsx
37
46
  import { createRoot } from "react-dom/client";
@@ -1729,6 +1738,15 @@ var customRandom = (alphabet, defaultSize, getRandom) => {
1729
1738
  };
1730
1739
  var customAlphabet = (alphabet, size2 = 21) => customRandom(alphabet, size2 | 0, random);
1731
1740
 
1741
+ // ../shared/src/utils.ts
1742
+ function sample(arr) {
1743
+ if (arr.length === 0) return void 0;
1744
+ return arr[Math.floor(Math.random() * arr.length)];
1745
+ }
1746
+ function assertNever(value) {
1747
+ throw new Error(`Unhandled union member: ${JSON.stringify(value)}`);
1748
+ }
1749
+
1732
1750
  // ../shared/src/expression_array.ts
1733
1751
  var expression_array_exports = {};
1734
1752
  __export(expression_array_exports, {
@@ -2782,7 +2800,7 @@ function formatNumber(value, decimals) {
2782
2800
  throw new Error("formatNumber: expected number, got " + typeof value);
2783
2801
  }
2784
2802
  assertDecimals(decimals, "formatNumber");
2785
- return value.toFixed(decimals);
2803
+ return fixedDecimal(value, decimals);
2786
2804
  }
2787
2805
  function formatDecimal(value, decimals, locale) {
2788
2806
  if (value == null) return "";
@@ -2902,9 +2920,7 @@ function round(value, precision) {
2902
2920
  if (precision != null && typeof precision !== "number") {
2903
2921
  throw new Error("round: precision must be a number");
2904
2922
  }
2905
- const prec = precision != null ? precision : 0;
2906
- const multiplier = Math.pow(10, prec);
2907
- return Math.round(value * multiplier) / multiplier;
2923
+ return roundDecimal(value, precision ?? 0);
2908
2924
  }
2909
2925
  function sum(arr) {
2910
2926
  if (arr == null) return 0;
@@ -2970,12 +2986,8 @@ function percentage(part, total, decimals) {
2970
2986
  if (decimals != null && typeof decimals !== "number") {
2971
2987
  throw new Error("percentage: decimals must be a number");
2972
2988
  }
2973
- const percent = part / total * 100;
2974
- if (decimals != null) {
2975
- const multiplier = Math.pow(10, decimals);
2976
- return Math.round(percent * multiplier) / multiplier;
2977
- }
2978
- return percent;
2989
+ const percent = divideDecimal(multiplyDecimal(part, 100), total);
2990
+ return decimals != null ? roundDecimal(percent, decimals) : percent;
2979
2991
  }
2980
2992
  function pow(base, exponent) {
2981
2993
  if (base == null || exponent == null) return null;
@@ -2985,7 +2997,7 @@ function pow(base, exponent) {
2985
2997
  if (typeof exponent !== "number") {
2986
2998
  throw new Error("pow: exponent must be a number");
2987
2999
  }
2988
- return Math.pow(base, exponent);
3000
+ return powerDecimal(base, exponent);
2989
3001
  }
2990
3002
  function sqrt(n) {
2991
3003
  if (n == null) return null;
@@ -3008,7 +3020,7 @@ function mod(n, divisor) {
3008
3020
  if (divisor === 0) {
3009
3021
  throw new Error("mod: divisor cannot be zero");
3010
3022
  }
3011
- return n % divisor;
3023
+ return moduloDecimal(n, divisor);
3012
3024
  }
3013
3025
 
3014
3026
  // ../shared/src/expression_object.ts
@@ -5815,9 +5827,21 @@ function isNothing(value) {
5815
5827
  return value === null || value === void 0;
5816
5828
  }
5817
5829
  var MULTIPLICATIVE_OPERATORS = /* @__PURE__ */ new Map([
5818
- ["*", (left, right) => left * right],
5819
- ["/", (left, right) => left / right],
5820
- ["%", (left, right) => left % right]
5830
+ ["*", multiplyDecimal],
5831
+ ["/", divideDecimal],
5832
+ ["%", moduloDecimal]
5833
+ ]);
5834
+ var COMPARISONS = /* @__PURE__ */ new Map([
5835
+ // eslint-disable-next-line eqeqeq
5836
+ ["==", { ofNumbers: (order) => order === 0, ofOthers: (left, right) => left == right }],
5837
+ // eslint-disable-next-line eqeqeq
5838
+ ["!=", { ofNumbers: (order) => order !== 0, ofOthers: (left, right) => left != right }],
5839
+ ["===", { ofNumbers: (order) => order === 0, ofOthers: (left, right) => left === right }],
5840
+ ["!==", { ofNumbers: (order) => order !== 0, ofOthers: (left, right) => left !== right }],
5841
+ ["<", { ofNumbers: (order) => order < 0, ofOthers: (left, right) => left < right }],
5842
+ ["<=", { ofNumbers: (order) => order <= 0, ofOthers: (left, right) => left <= right }],
5843
+ [">", { ofNumbers: (order) => order > 0, ofOthers: (left, right) => left > right }],
5844
+ [">=", { ofNumbers: (order) => order >= 0, ofOthers: (left, right) => left >= right }]
5821
5845
  ]);
5822
5846
  var SafeEvalAstFactory = class extends EvalAstFactory {
5823
5847
  id(name) {
@@ -5856,6 +5880,26 @@ var SafeEvalAstFactory = class extends EvalAstFactory {
5856
5880
  }
5857
5881
  };
5858
5882
  }
5883
+ const comparison = COMPARISONS.get(op);
5884
+ if (comparison) {
5885
+ return {
5886
+ type: "Binary",
5887
+ operator: op,
5888
+ left,
5889
+ right,
5890
+ evaluate(scope) {
5891
+ const leftValue = this.left.evaluate(scope);
5892
+ const rightValue = this.right.evaluate(scope);
5893
+ if (typeof leftValue === "number" && typeof rightValue === "number") return comparison.ofNumbers(compareDecimal(leftValue, rightValue));
5894
+ return comparison.ofOthers(leftValue, rightValue);
5895
+ },
5896
+ getIds(idents) {
5897
+ this.left.getIds(idents);
5898
+ this.right.getIds(idents);
5899
+ return idents;
5900
+ }
5901
+ };
5902
+ }
5859
5903
  if (op !== "+" && op !== "-") {
5860
5904
  return super.binary(left, op, right);
5861
5905
  }
@@ -5871,7 +5915,8 @@ var SafeEvalAstFactory = class extends EvalAstFactory {
5871
5915
  if (isNothing(leftValue) && isNothing(rightValue)) return null;
5872
5916
  const leftOperand = leftValue ?? 0;
5873
5917
  const rightOperand = rightValue ?? 0;
5874
- return op === "+" ? leftOperand + rightOperand : Number(leftOperand) - Number(rightOperand);
5918
+ if (op === "-") return subtractDecimal(Number(leftOperand), Number(rightOperand));
5919
+ return typeof leftOperand === "number" && typeof rightOperand === "number" ? addDecimal(leftOperand, rightOperand) : leftOperand + rightOperand;
5875
5920
  };
5876
5921
  return op === "+" ? addOrConcat(leftValue, rightValue, combine) : combine();
5877
5922
  },
@@ -21164,7 +21209,7 @@ function aggregateTextField(values2, operation) {
21164
21209
  const emptyCount = values2.filter(
21165
21210
  (value) => value === null || value === void 0 || value === ""
21166
21211
  ).length;
21167
- const percentage2 = Math.round(emptyCount / totalCount * 100);
21212
+ const percentage2 = percentOf(emptyCount, totalCount);
21168
21213
  return { result: percentage2 };
21169
21214
  }
21170
21215
  case "percent_filled": {
@@ -21172,7 +21217,7 @@ function aggregateTextField(values2, operation) {
21172
21217
  const filledCount = values2.filter(
21173
21218
  (value) => value !== null && value !== void 0 && value !== ""
21174
21219
  ).length;
21175
- const percentage2 = Math.round(filledCount / totalCount * 100);
21220
+ const percentage2 = percentOf(filledCount, totalCount);
21176
21221
  return { result: percentage2 };
21177
21222
  }
21178
21223
  case "unique": {
@@ -21188,7 +21233,7 @@ function aggregateTextField(values2, operation) {
21188
21233
  );
21189
21234
  if (filledValues.length === 0) return { result: 0 };
21190
21235
  const uniqueValues = new Set(filledValues);
21191
- const percentage2 = Math.round(uniqueValues.size / filledValues.length * 100);
21236
+ const percentage2 = percentOf(uniqueValues.size, filledValues.length);
21192
21237
  return { result: percentage2 };
21193
21238
  }
21194
21239
  default:
@@ -21212,13 +21257,13 @@ function aggregateNumberField(values2, operation) {
21212
21257
  case "percent_empty": {
21213
21258
  if (totalCount === 0) return { result: 0 };
21214
21259
  const emptyCount = values2.filter((value) => !hasNumberValue(value)).length;
21215
- const percentage2 = Math.round(emptyCount / totalCount * 100);
21260
+ const percentage2 = percentOf(emptyCount, totalCount);
21216
21261
  return { result: percentage2 };
21217
21262
  }
21218
21263
  case "percent_filled": {
21219
21264
  if (totalCount === 0) return { result: 0 };
21220
21265
  const filledCount = values2.filter(hasNumberValue).length;
21221
- const percentage2 = Math.round(filledCount / totalCount * 100);
21266
+ const percentage2 = percentOf(filledCount, totalCount);
21222
21267
  return { result: percentage2 };
21223
21268
  }
21224
21269
  case "unique": {
@@ -21230,7 +21275,7 @@ function aggregateNumberField(values2, operation) {
21230
21275
  const filledValues = values2.filter(hasNumberValue);
21231
21276
  if (filledValues.length === 0) return { result: 0 };
21232
21277
  const uniqueValues = new Set(filledValues);
21233
- const percentage2 = Math.round(uniqueValues.size / filledValues.length * 100);
21278
+ const percentage2 = percentOf(uniqueValues.size, filledValues.length);
21234
21279
  return { result: percentage2 };
21235
21280
  }
21236
21281
  }
@@ -21288,13 +21333,13 @@ function aggregateBooleanField(values2, operation) {
21288
21333
  case "percent_checked": {
21289
21334
  if (totalCount === 0) return { result: 0 };
21290
21335
  const checkedCount = values2.filter((value) => value === true).length;
21291
- const percentage2 = Math.round(checkedCount / totalCount * 100);
21336
+ const percentage2 = percentOf(checkedCount, totalCount);
21292
21337
  return { result: percentage2 };
21293
21338
  }
21294
21339
  case "percent_unchecked": {
21295
21340
  if (totalCount === 0) return { result: 0 };
21296
21341
  const uncheckedCount = values2.filter((value) => !value).length;
21297
- const percentage2 = Math.round(uncheckedCount / totalCount * 100);
21342
+ const percentage2 = percentOf(uncheckedCount, totalCount);
21298
21343
  return { result: percentage2 };
21299
21344
  }
21300
21345
  default:
@@ -21321,7 +21366,7 @@ function aggregateDateField(values2, operation) {
21321
21366
  const emptyCount = values2.filter(
21322
21367
  (value) => value === null || value === void 0 || value === ""
21323
21368
  ).length;
21324
- const percentage2 = Math.round(emptyCount / totalCount * 100);
21369
+ const percentage2 = percentOf(emptyCount, totalCount);
21325
21370
  return { result: percentage2 };
21326
21371
  }
21327
21372
  case "percent_filled": {
@@ -21329,7 +21374,7 @@ function aggregateDateField(values2, operation) {
21329
21374
  const filledCount = values2.filter(
21330
21375
  (value) => value !== null && value !== void 0 && value !== ""
21331
21376
  ).length;
21332
- const percentage2 = Math.round(filledCount / totalCount * 100);
21377
+ const percentage2 = percentOf(filledCount, totalCount);
21333
21378
  return { result: percentage2 };
21334
21379
  }
21335
21380
  case "unique": {
@@ -21345,7 +21390,7 @@ function aggregateDateField(values2, operation) {
21345
21390
  );
21346
21391
  if (filledValues.length === 0) return { result: 0 };
21347
21392
  const uniqueValues = new Set(filledValues);
21348
- const percentage2 = Math.round(uniqueValues.size / filledValues.length * 100);
21393
+ const percentage2 = percentOf(uniqueValues.size, filledValues.length);
21349
21394
  return { result: percentage2 };
21350
21395
  }
21351
21396
  }
@@ -21451,13 +21496,13 @@ function aggregateGenericField(values2, operation) {
21451
21496
  case "percent_empty": {
21452
21497
  if (totalCount === 0) return { result: 0 };
21453
21498
  const emptyCount = values2.filter(isEmpty2).length;
21454
- const percentage2 = Math.round(emptyCount / totalCount * 100);
21499
+ const percentage2 = percentOf(emptyCount, totalCount);
21455
21500
  return { result: percentage2 };
21456
21501
  }
21457
21502
  case "percent_filled": {
21458
21503
  if (totalCount === 0) return { result: 0 };
21459
21504
  const filledCount = values2.filter((value) => !isEmpty2(value)).length;
21460
- const percentage2 = Math.round(filledCount / totalCount * 100);
21505
+ const percentage2 = percentOf(filledCount, totalCount);
21461
21506
  return { result: percentage2 };
21462
21507
  }
21463
21508
  case "unique": {
@@ -21484,7 +21529,7 @@ function aggregateGenericField(values2, operation) {
21484
21529
  uniqueValues.add(value);
21485
21530
  }
21486
21531
  }
21487
- const percentage2 = Math.round(uniqueValues.size / filledValues.length * 100);
21532
+ const percentage2 = percentOf(uniqueValues.size, filledValues.length);
21488
21533
  return { result: percentage2 };
21489
21534
  }
21490
21535
  case "selected":
@@ -21513,13 +21558,13 @@ function aggregateSelectRecordLinkField(values2, operation) {
21513
21558
  case "percent_empty": {
21514
21559
  if (totalCount === 0) return { result: 0 };
21515
21560
  const emptyCount = values2.filter(isEmpty2).length;
21516
- const percentage2 = Math.round(emptyCount / totalCount * 100);
21561
+ const percentage2 = percentOf(emptyCount, totalCount);
21517
21562
  return { result: percentage2 };
21518
21563
  }
21519
21564
  case "percent_filled": {
21520
21565
  if (totalCount === 0) return { result: 0 };
21521
21566
  const filledCount = values2.filter((value) => !isEmpty2(value)).length;
21522
- const percentage2 = Math.round(filledCount / totalCount * 100);
21567
+ const percentage2 = percentOf(filledCount, totalCount);
21523
21568
  return { result: percentage2 };
21524
21569
  }
21525
21570
  case "selected":
@@ -21564,13 +21609,13 @@ function aggregateRollupField(values2, operation) {
21564
21609
  case "percent_empty": {
21565
21610
  if (totalCount === 0) return { result: 0 };
21566
21611
  const emptyCount = values2.filter(isEmpty2).length;
21567
- const percentage2 = Math.round(emptyCount / totalCount * 100);
21612
+ const percentage2 = percentOf(emptyCount, totalCount);
21568
21613
  return { result: percentage2 };
21569
21614
  }
21570
21615
  case "percent_filled": {
21571
21616
  if (totalCount === 0) return { result: 0 };
21572
21617
  const filledCount = values2.filter((value) => !isEmpty2(value)).length;
21573
- const percentage2 = Math.round(filledCount / totalCount * 100);
21618
+ const percentage2 = percentOf(filledCount, totalCount);
21574
21619
  return { result: percentage2 };
21575
21620
  }
21576
21621
  case "unique": {
@@ -21583,7 +21628,7 @@ function aggregateRollupField(values2, operation) {
21583
21628
  const filledValues = values2.filter((value) => !isEmpty2(value));
21584
21629
  if (filledValues.length === 0) return { result: 0 };
21585
21630
  const uniqueValues = new Set(filledValues);
21586
- const percentage2 = Math.round(uniqueValues.size / filledValues.length * 100);
21631
+ const percentage2 = percentOf(uniqueValues.size, filledValues.length);
21587
21632
  return { result: percentage2 };
21588
21633
  }
21589
21634
  }
@@ -21904,9 +21949,8 @@ function currencyCodeOf(written) {
21904
21949
  if (Intl.supportedValuesOf("currency").includes(upper2)) return upper2;
21905
21950
  return DONG_SPELLINGS.includes(unitKey(written)) ? "VND" : void 0;
21906
21951
  }
21907
- var EXACT_DIGITS = 15;
21908
21952
  function convert(value, from, to) {
21909
- return from.code === to.code ? value : Number((value * from.factor / to.factor).toPrecision(EXACT_DIGITS));
21953
+ return from.code === to.code ? value : divideDecimal(multiplyDecimal(value, from.factor), to.factor);
21910
21954
  }
21911
21955
  function scaleIn(own, pack) {
21912
21956
  const kin = MEASURED_UNITS.filter((one) => one.dimension === own.dimension);
@@ -25051,7 +25095,7 @@ var tableDateFieldSchema = tableFieldBaseSchema.extend({
25051
25095
  format: dateFormatSchema.optional(),
25052
25096
  timezone: zod_default.string().optional().describe("IANA timezone"),
25053
25097
  derive_from: zod_default.enum(["created_at", "updated_at"]).optional().describe(
25054
- "Auto-populate from the row's system timestamp. `created_at` stamps once at insert time; `updated_at` re-stamps on every record edit. A derived field is read-only: a write naming it is refused, as for formula/rollup/lookup. Replaces the per-table after_create workflow pattern."
25098
+ "Auto-populate from the row's system timestamp. `created_at` stamps once at insert time; `updated_at` re-stamps on every record edit. A derived field is read-only: a write naming it is refused, as for formula/rollup/lookup."
25055
25099
  ),
25056
25100
  // A derived date is read-only, so derive_from and default_value are mutually
25057
25101
  // exclusive — enforced in validateFieldDefaultValue.
@@ -28163,33 +28207,34 @@ async function evaluateBinary(op, leftExpr, rightExpr, ctx) {
28163
28207
  assertCheap(ctx.evaluation_budget, left, right);
28164
28208
  assertTextWithin("+", typeof left === "string" ? null : left, typeof right === "string" ? null : right);
28165
28209
  }
28166
- const sum2 = addOrConcat(left, right, () => assertNumber(left, "add", "left") + assertNumber(right, "add", "right"));
28210
+ const sum2 = addOrConcat(left, right, () => addDecimal(assertNumber(left, "add", "left"), assertNumber(right, "add", "right")));
28167
28211
  charge(ctx, builtSize(sum2));
28168
28212
  return sum2;
28169
28213
  }
28170
28214
  case "sub":
28171
- return assertNumber(left, "sub", "left") - assertNumber(right, "sub", "right");
28215
+ return subtractDecimal(assertNumber(left, "sub", "left"), assertNumber(right, "sub", "right"));
28172
28216
  case "mul":
28173
- return assertNumber(left, "mul", "left") * assertNumber(right, "mul", "right");
28217
+ return multiplyDecimal(assertNumber(left, "mul", "left"), assertNumber(right, "mul", "right"));
28174
28218
  case "div": {
28175
28219
  const r = assertNumber(right, "div", "right");
28176
28220
  if (r === 0) {
28177
28221
  throw new WorkflowEvalError("Division by zero", { kind: "binary", at: "div" });
28178
28222
  }
28179
- return assertNumber(left, "div", "left") / r;
28223
+ return divideDecimal(assertNumber(left, "div", "left"), r);
28180
28224
  }
28181
28225
  case "mod": {
28182
28226
  const r = assertNumber(right, "mod", "right");
28183
28227
  if (r === 0) {
28184
28228
  throw new WorkflowEvalError("Modulo by zero", { kind: "binary", at: "mod" });
28185
28229
  }
28186
- return assertNumber(left, "mod", "left") % r;
28230
+ return moduloDecimal(assertNumber(left, "mod", "left"), r);
28187
28231
  }
28188
28232
  }
28189
28233
  }
28190
28234
  function looseEq(a, b) {
28191
28235
  if (isComposite(a) && b !== null && b !== void 0 && !isComposite(b)) return looseEq(textOf(a), b);
28192
28236
  if (isComposite(b) && a !== null && a !== void 0 && !isComposite(a)) return looseEq(a, textOf(b));
28237
+ if (typeof a === "number" && typeof b === "number") return compareDecimal(a, b) === 0;
28193
28238
  return a == b;
28194
28239
  }
28195
28240
  function isComposite(value) {
@@ -28204,6 +28249,7 @@ function isScalar(value) {
28204
28249
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint";
28205
28250
  }
28206
28251
  function strictEq(a, b) {
28252
+ if (typeof a === "number" && typeof b === "number") return compareDecimal(a, b) === 0;
28207
28253
  return a === b;
28208
28254
  }
28209
28255
  function compareOrdered(a, b) {
@@ -28211,7 +28257,7 @@ function compareOrdered(a, b) {
28211
28257
  return NaN;
28212
28258
  }
28213
28259
  if (typeof a === "number" && typeof b === "number") {
28214
- return a - b;
28260
+ return compareDecimal(a, b);
28215
28261
  }
28216
28262
  if (typeof a === "string" && typeof b === "string") {
28217
28263
  return a < b ? -1 : a > b ? 1 : 0;
@@ -30510,7 +30556,7 @@ function evaluateCondition(record2, fieldByKey, looseCondition, memberContext, f
30510
30556
  if (reading.unit !== void 0 && filterCondition.value !== null && filterCondition.value !== void 0) {
30511
30557
  const factor = rowUnitFactor(reading.unit, data);
30512
30558
  if (factor === void 0) return false;
30513
- const inBase = { ...filterCondition, value: filterCondition.value * reading.unit.factor };
30559
+ const inBase = { ...filterCondition, value: multiplyExactly(filterCondition.value, reading.unit.factor) };
30514
30560
  return matchCell(columnValue, field, inBase, looseCondition, memberContext, fileFacts, factor);
30515
30561
  }
30516
30562
  }
@@ -30619,7 +30665,7 @@ function matchCell(cell, field, filterCondition, looseCondition, memberContext,
30619
30665
  matches = foldedValue !== void 0 && needles.some((needle) => foldedValue.includes(needle));
30620
30666
  }
30621
30667
  } else if (filterCondition.type === "number") {
30622
- const numberValue = typeof columnValue === "number" ? columnValue * figureFactor : columnValue;
30668
+ const numberValue = typeof columnValue === "number" ? multiplyExactly(columnValue, figureFactor) : columnValue;
30623
30669
  if (filterCondition.operator === "is_empty") {
30624
30670
  matches = !numberValue && numberValue !== 0;
30625
30671
  } else if (filterCondition.operator === "is_not_empty") {
package/dist/router.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  isEmbedded,
3
3
  postHostNotification,
4
4
  setUrlParams
5
- } from "./chunk-NZXJENJ2.js";
5
+ } from "./chunk-ABDSJ25E.js";
6
6
 
7
7
  // src/router.tsx
8
8
  import { useEffect } from "react";
package/docs/mutations.md CHANGED
@@ -397,8 +397,8 @@ Why a full-form snapshot save is a bug, not a style choice — three independent
397
397
  moment *any* field is touched — there is no value-equality pass (deep equality over stored
398
398
  shapes would be a silent lock bypass). A snapshot touches every field, so it turns every
399
399
  save against a locked record into a total rejection even when nothing changed.
400
- 2. **A table's checks and `after_update` workflows run on the write.** They run on every
401
- write that touches the record — a snapshot save runs them for fields the user never edited.
400
+ 2. **A table's checks run on the write.** They run on every write that touches the record —
401
+ a snapshot save runs them for fields the user never edited.
402
402
  3. **Snapshots clobber concurrent edits.** An update writes exactly the fields present in the
403
403
  payload; unsent fields keep their current value. A diff therefore coexists with a
404
404
  colleague's concurrent edit to a *different* field — a snapshot overwrites it with the
package/docs/workflows.md CHANGED
@@ -51,9 +51,7 @@ compile-time "cannot find name", not a runtime `undefined`.
51
51
  |---|---|---|
52
52
  | `trigger` | every context | the trigger namespace. For an app workflow: `trigger.app_workflow.inputs.<name>` |
53
53
  | `runtime` | every context | execution context — see the key table below |
54
- | `record` | button + table-lifecycle workflows | the record the trigger fired on (merged data on update) |
55
- | `prev_record` | table `*_update` / `*_delete` | the prior record state — same shape as `record` |
56
- | `changes` | table `*_update` | per-field diff — a *partial* map, so read it `changes["fld_x"]?.next_value` / `?.prev_value` |
54
+ | `record` | button workflows | the record the button was pressed on |
57
55
  | `index` | inside a `for-of` body | the current 0-based iteration index — always the **innermost** loop's. To use an outer loop's index in a nested body, bind it in the outer one (`const outerIdx = index;`) and read that |
58
56
  | `<bind_name>` | inside a `for-of` body | the current item. Each loop keeps its own, so a nested body reads the outer loop's item by its own bind name |
59
57
  | `<name>` | its block, after the declaration | a `const` — a step's output (`rows.records`, `approval.status`, …) or a bound value |
@@ -61,10 +59,6 @@ compile-time "cannot find name", not a runtime `undefined`.
61
59
  | `<param>` | inside a helper callback | that lambda's parameter |
62
60
  | `(await <tool>({…}))` | anywhere the call runs unconditionally | that call's output, read in place (see *`await` inside a statement*) |
63
61
 
64
- `record`, `prev_record` and `changes` are the **canonical** roots. `trigger.data`,
65
- `trigger.prev_data` and `trigger.changes` are accepted and lower onto them, so a saved body prints
66
- back in the bare form. Write the bare form.
67
-
68
62
  A body bound to an app alias is an app workflow: it sees `trigger` and `runtime` only. There is
69
63
  no `record` — an app workflow is not attached to a table. Pass the record id in as a
70
64
  `record_link` input and `get_record` it.
@@ -72,11 +66,9 @@ no `record` — an app workflow is not attached to a table. Pass the record id i
72
66
  `runtime` keys: `timezone`, `now`, `workflow_id`, `execution_id`, `workspace_id`,
73
67
  `organization_id`, `triggered_by_member_id` (`null` for a system trigger or an anonymous
74
68
  public-app caller), and `change_origin` — a discriminated union over how the **execution** was
75
- started: `button` · `automation` · `app_action` · `app_workflow` · `table_workflow`. It describes
69
+ started: `button` · `automation` · `app_action` · `app_workflow`. It describes
76
70
  the dispatch, never the person, and it is fixed per attachment — an app workflow always reads
77
- `app_workflow`, a table workflow always `table_workflow`. To branch on *who wrote the record*,
78
- read `trigger.change_origin` on a table workflow (that one carries the originating write's
79
- origin, and spans every write path: `member`, `chat_agent`, `app_workflow`, …). `runtime`
71
+ `app_workflow`. `runtime`
80
72
  is never null, so `runtime?.x` is rejected outright. Prefer the helper `now()` over
81
73
  `runtime.now`.
82
74
 
@@ -118,16 +110,13 @@ is a second call (`linked(linked(order.data["fld_a"])[0]["fld_b"])[0]`). A linke
118
110
  deleted, or that the run may not read, stays a positional `null`, so `linked(P)[i]` is always the
119
111
  row `P[i]` names.
120
112
 
121
- The argument is a path ending on a link field, off anything record-shaped: `record` /
122
- `prev_record`, a `for-of` item, a lambda parameter, a `let` binding, or a `get_record` /
113
+ The argument is a path ending on a link field, off anything record-shaped: `record`, a `for-of` item, a lambda parameter, a `let` binding, or a `get_record` /
123
114
  `query_records` output. The server checks the far field at save only where it can name the table
124
115
  before the run — a call with a **literal** `table_id` — and the fetch works either way. Guard **inside** the
125
116
  argument — `linked(record?.["fld_link"])[0]` — never on the call: `linked(P)?.[i]` is rejected,
126
117
  because the call always returns an array and the guard could never fire. Two more rejections:
127
118
  `const ids = record["fld_link"]; linked(ids)`, since the field name comes from the argument's last
128
- member access (`linked(row["fld_link"])` off a bound ROW is fine), and
129
- `linked(changes["fld_link"]?.next_value)`, since a diff is not a record — read
130
- `linked(record["fld_link"])` or `linked(prev_record["fld_link"])`, which hold the same ids.
119
+ member access (`linked(row["fld_link"])` off a bound ROW is fine).
131
120
  `linked` is grammar, not a helper: it cannot be shadowed, bound, called as a method, or awaited.
132
121
 
133
122
  ## Step forms
@@ -257,13 +246,12 @@ only the dialog. What it may name depends on who renders the refusal:
257
246
  | Workflow | `field_key` may name |
258
247
  |---|---|
259
248
  | app workflow | a declared INPUT name (`order_code`, a key of the binding's `inputs`), or the `fld_` key of a field on a table the body names as a literal (a `table_id: "tbl_…"`, or a `const` holding one) |
260
- | table-triggered (lifecycle, button) | the `fld_` key of a field on the trigger table |
249
+ | button | the `fld_` key of a field on the button's table |
261
250
  | recordless automation (schedule, webhook, email) | anything — nothing renders it, so nothing checks it |
262
251
 
263
252
  An app workflow whose alias declares no inputs leaves input-shaped keys unchecked, so a body can
264
253
  be written before its declaration. A key outside its row is refused when the body is SAVED — by
265
- `set_app_workflow`, by an agent binding one, by a table workflow's save — and by a
266
- `verify_only` call before that. `return({ field_errors })` keys are not checked.
254
+ `set_app_workflow`, by an agent binding one — and by a `verify_only` call before that. `return({ field_errors })` keys are not checked.
267
255
 
268
256
  ### `wait_for_approval` — the one wait worth binding
269
257
 
@@ -396,8 +384,8 @@ body does not look like what you typed.
396
384
  | `x.length` (dot only) | `length(x)` — strings and arrays. `x["length"]` still reads a field |
397
385
  | `String(x)`, `Number(x)`, `parseFloat(x)`, `+x` | `toString(x)` / `toNumber(x)` |
398
386
  | `x.toUpperCase()`, `x.toLowerCase()` | `upper(x)`, `lower(x)` |
399
- | `x.toFixed(2)` | `formatNumber(x, 2)` |
400
- | `Math.round/min/max/abs/floor/ceil/pow/sqrt(…)` | the like-named helper |
387
+ | `x.toFixed(2)` | `formatNumber(x, 2)` — rounds the decimal, a half away from zero: `1.005` is `"1.01"` |
388
+ | `Math.round/min/max/abs/floor/ceil/pow/sqrt(…)` | the like-named helper. `round` rounds the decimal, a half away from zero: `round(-2.5)` is `-3` |
401
389
  | `Math.max(...arr)` / `Math.min(...arr)` | `max(arr)` / `min(arr)` |
402
390
  | `JSON.parse`, `JSON.stringify` | `parseJson`, `toJson` |
403
391
  | `Array.isArray`, `Object.keys/values/entries` | `isArray`, `keys`/`values`/`entries` |
@@ -657,9 +645,8 @@ linked record id as `RecordId<"tbl_…">`. They come out branded from a record r
657
645
  or from a workflow input declared `member` / `file` / `record_link` — a `rec_*` string carried in
658
646
  a plain `text` input does *not* type-check into a link field, so declare the input for what it is.
659
647
 
660
- A link **read** is an id array, so it feeds straight back: `set: { fld_link: record["fld_link"] }`,
661
- and the same for `prev_record["fld_link"]` and for a diff's `changes["fld_link"]?.next_value` /
662
- `?.prev_value`. What does not feed back is a ROW: a link field takes ids, so from rows you already
648
+ A link **read** is an id array, so it feeds straight back: `set: { fld_link: record["fld_link"] }`.
649
+ What does not feed back is a ROW: a link field takes ids, so from rows you already
663
650
  hold write `pluck(rows.records, (r) => r.id)`, or `[x.id]` for one. Never route a link read through
664
651
  `linked(...)` to get its ids back — it holds them already, and the fetch costs one read per row and
665
652
  yields `null` where a row is deleted or unreadable, which the write then rejects.
@@ -796,8 +783,7 @@ The rules that are easy to get wrong because the failing code looks correct.
796
783
  `` `Status: ${record["fld_status"]}` `` prints "Done" while
797
784
  `record["fld_status"] == "opt_done"` compares keys — both correct, and neither substitutes for
798
785
  the other. It holds on every route a record arrives by — a `let`, a callback parameter, a row
799
- from a variable `table_id` — and a change-diff read (`changes["fld_status"]?.next_value`)
800
- behaves exactly like the same field read off the record.
786
+ from a variable `table_id`.
801
787
  - **Multi-select `==` quietly desugars.** On a multi-select field,
802
788
  `record["fld_tags"] == "opt_urgent"` rewrites to `includes(…)` at save. Either spelling is
803
789
  fine — but a `switch` on a multi-select discriminant is **rejected**, because its value is an
@@ -810,15 +796,6 @@ The rules that are easy to get wrong because the failing code looks correct.
810
796
  `xs = concat(xs, someArray)` fails to typecheck; appending a whole *array* has no `push`
811
797
  spelling. Start that binding from the first array instead of seeding it `[]`. Appending single
812
798
  **items** (`xs.push(item)`) works on a bare `[]` seed.
813
- - **A mutation on the trigger table of an `after_*` workflow can re-trigger itself** — a lint
814
- warning, not an error. Gate it on `trigger.change_origin` — the originating write's origin —
815
- and name every origin you mean to let through, rather than testing for one. `member` is a
816
- person in the browser and **only** that: the same edit made over MCP, the CLI, or a direct API
817
- call arrives as `api_client`, and one made by the chat agent as `chat_agent`. A gate meaning
818
- "someone did this by hand" is
819
- `includes(["member", "chat_agent", "api_client"], trigger.change_origin.type)`. Not
820
- `runtime.change_origin`: that is the execution's own origin, always `table_workflow` here, so
821
- it discriminates nothing.
822
799
  - **Expressions are bounded.** A helper that builds an array — `range`, `list`, `flatten`,
823
800
  `concat` of arrays, `split`'s parts — makes at most 100,000 elements, and a callback helper
824
801
  takes an array of at most 100,000. `join`, `replaceAll`, `padStart`, `padEnd`, `toString`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/app-sdk",
3
- "version": "0.120.1",
3
+ "version": "0.120.3",
4
4
  "description": "The SDK a Lotics custom-code app reads and writes through — typed hooks over the host bridge, cell readers, mount() and AppRouter",
5
5
  "type": "module",
6
6
  "exports": {