hyperprop-charting-library 0.1.132 → 0.1.133

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.
@@ -603,14 +603,23 @@ var withCachedSeries = (key, data, compute) => {
603
603
  return values;
604
604
  };
605
605
  var builtInComputationCache = /* @__PURE__ */ new Map();
606
+ var COMPUTATION_CACHE_MAX_ENTRIES = 128;
606
607
  var withCachedComputation = (key, data, compute) => {
607
608
  const fingerprint = getSeriesFingerprint(data);
608
609
  const existing = builtInComputationCache.get(key);
609
610
  if (existing && existing.fingerprint === fingerprint) {
611
+ builtInComputationCache.delete(key);
612
+ builtInComputationCache.set(key, existing);
610
613
  return existing.value;
611
614
  }
612
615
  const value = compute();
616
+ builtInComputationCache.delete(key);
613
617
  builtInComputationCache.set(key, { fingerprint, value });
618
+ while (builtInComputationCache.size > COMPUTATION_CACHE_MAX_ENTRIES) {
619
+ const oldest = builtInComputationCache.keys().next().value;
620
+ if (oldest === void 0) break;
621
+ builtInComputationCache.delete(oldest);
622
+ }
614
623
  return value;
615
624
  };
616
625
  var rollingExtremeSeries = (values, length, mode) => {
@@ -1542,14 +1551,304 @@ var hashScriptSource = (source) => {
1542
1551
  }
1543
1552
  return (hash >>> 0).toString(36);
1544
1553
  };
1545
- var compileScriptCompute = (source) => {
1546
- const factory = new Function(
1554
+ var SCRIPT_GUARD_BUDGET_MS = 1e3;
1555
+ var ScriptBudgetError = class extends Error {
1556
+ constructor() {
1557
+ super(
1558
+ `Script exceeded the ${SCRIPT_GUARD_BUDGET_MS}ms execution budget (possible infinite loop) \u2014 edit the script to retry`
1559
+ );
1560
+ this.name = "ScriptBudgetError";
1561
+ }
1562
+ };
1563
+ var createScriptGuard = () => {
1564
+ let deadline = Date.now() + SCRIPT_GUARD_BUDGET_MS;
1565
+ let count = 0;
1566
+ const guard = (() => {
1567
+ count += 1;
1568
+ if ((count & 1023) === 0 && Date.now() > deadline) {
1569
+ throw new ScriptBudgetError();
1570
+ }
1571
+ return true;
1572
+ });
1573
+ guard.reset = () => {
1574
+ count = 0;
1575
+ deadline = Date.now() + SCRIPT_GUARD_BUDGET_MS;
1576
+ };
1577
+ return guard;
1578
+ };
1579
+ var injectLoopGuards = (source, guardName) => {
1580
+ const n = source.length;
1581
+ const isIdChar = (ch) => ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_" || ch === "$";
1582
+ const scanString = (start) => {
1583
+ const quote = source[start];
1584
+ let j = start + 1;
1585
+ while (j < n) {
1586
+ const cj = source[j];
1587
+ if (cj === "\\") {
1588
+ j += 2;
1589
+ continue;
1590
+ }
1591
+ if (cj === quote) {
1592
+ return j + 1;
1593
+ }
1594
+ if (quote === "`" && cj === "$" && source[j + 1] === "{") {
1595
+ let depth = 1;
1596
+ j += 2;
1597
+ while (j < n && depth > 0) {
1598
+ const ce = source[j];
1599
+ if (ce === "\\") {
1600
+ j += 2;
1601
+ continue;
1602
+ }
1603
+ if (ce === '"' || ce === "'" || ce === "`") {
1604
+ j = scanString(j);
1605
+ continue;
1606
+ }
1607
+ if (ce === "{") depth += 1;
1608
+ else if (ce === "}") depth -= 1;
1609
+ j += 1;
1610
+ }
1611
+ continue;
1612
+ }
1613
+ j += 1;
1614
+ }
1615
+ return n;
1616
+ };
1617
+ const scanRegex = (start) => {
1618
+ let j = start + 1;
1619
+ let inClass = false;
1620
+ while (j < n) {
1621
+ const cj = source[j];
1622
+ if (cj === "\\") {
1623
+ j += 2;
1624
+ continue;
1625
+ }
1626
+ if (cj === "\n") return j;
1627
+ if (cj === "[") inClass = true;
1628
+ else if (cj === "]") inClass = false;
1629
+ else if (cj === "/" && !inClass) return j + 1;
1630
+ j += 1;
1631
+ }
1632
+ return n;
1633
+ };
1634
+ const skipWsAndComments = (k) => {
1635
+ let j = k;
1636
+ while (j < n) {
1637
+ const cj = source[j];
1638
+ if (cj === " " || cj === " " || cj === "\n" || cj === "\r") {
1639
+ j += 1;
1640
+ continue;
1641
+ }
1642
+ if (cj === "/" && source[j + 1] === "/") {
1643
+ const nl = source.indexOf("\n", j);
1644
+ j = nl === -1 ? n : nl + 1;
1645
+ continue;
1646
+ }
1647
+ if (cj === "/" && source[j + 1] === "*") {
1648
+ const end = source.indexOf("*/", j + 2);
1649
+ j = end === -1 ? n : end + 2;
1650
+ continue;
1651
+ }
1652
+ return j;
1653
+ }
1654
+ return n;
1655
+ };
1656
+ const matchParen = (openIdx) => {
1657
+ let depth = 0;
1658
+ let j = openIdx;
1659
+ while (j < n) {
1660
+ const cj = source[j];
1661
+ if (cj === '"' || cj === "'" || cj === "`") {
1662
+ j = scanString(j);
1663
+ continue;
1664
+ }
1665
+ if (cj === "/" && source[j + 1] === "/") {
1666
+ const nl = source.indexOf("\n", j);
1667
+ j = nl === -1 ? n : nl + 1;
1668
+ continue;
1669
+ }
1670
+ if (cj === "/" && source[j + 1] === "*") {
1671
+ const end = source.indexOf("*/", j + 2);
1672
+ j = end === -1 ? n : end + 2;
1673
+ continue;
1674
+ }
1675
+ if (cj === "(") depth += 1;
1676
+ else if (cj === ")") {
1677
+ depth -= 1;
1678
+ if (depth === 0) return j;
1679
+ }
1680
+ j += 1;
1681
+ }
1682
+ return -1;
1683
+ };
1684
+ const splitTopLevelSemicolons = (inner) => {
1685
+ const parts = [];
1686
+ let depth = 0;
1687
+ let j = 0;
1688
+ let last = 0;
1689
+ const m = inner.length;
1690
+ while (j < m) {
1691
+ const cj = inner[j];
1692
+ if (cj === '"' || cj === "'" || cj === "`") {
1693
+ const quote = cj;
1694
+ j += 1;
1695
+ while (j < m) {
1696
+ if (inner[j] === "\\") {
1697
+ j += 2;
1698
+ continue;
1699
+ }
1700
+ if (inner[j] === quote) {
1701
+ j += 1;
1702
+ break;
1703
+ }
1704
+ j += 1;
1705
+ }
1706
+ continue;
1707
+ }
1708
+ if (cj === "(" || cj === "[" || cj === "{") depth += 1;
1709
+ else if (cj === ")" || cj === "]" || cj === "}") depth -= 1;
1710
+ else if (cj === ";" && depth === 0) {
1711
+ parts.push(inner.slice(last, j));
1712
+ last = j + 1;
1713
+ }
1714
+ j += 1;
1715
+ }
1716
+ parts.push(inner.slice(last));
1717
+ return parts;
1718
+ };
1719
+ const REGEX_PRECEDING_WORDS = /* @__PURE__ */ new Set([
1720
+ "return",
1721
+ "typeof",
1722
+ "case",
1723
+ "do",
1724
+ "else",
1725
+ "in",
1726
+ "of",
1727
+ "new",
1728
+ "delete",
1729
+ "void",
1730
+ "instanceof",
1731
+ "yield",
1732
+ "await"
1733
+ ]);
1734
+ let out = "";
1735
+ let i = 0;
1736
+ let lastSig = "";
1737
+ let lastWord = "";
1738
+ while (i < n) {
1739
+ const ch = source[i];
1740
+ if (ch === "/" && source[i + 1] === "/") {
1741
+ const nl = source.indexOf("\n", i);
1742
+ const end = nl === -1 ? n : nl;
1743
+ out += source.slice(i, end);
1744
+ i = end;
1745
+ continue;
1746
+ }
1747
+ if (ch === "/" && source[i + 1] === "*") {
1748
+ const close = source.indexOf("*/", i + 2);
1749
+ const end = close === -1 ? n : close + 2;
1750
+ out += source.slice(i, end);
1751
+ i = end;
1752
+ continue;
1753
+ }
1754
+ if (ch === '"' || ch === "'" || ch === "`") {
1755
+ const end = scanString(i);
1756
+ out += source.slice(i, end);
1757
+ lastSig = ch;
1758
+ lastWord = "";
1759
+ i = end;
1760
+ continue;
1761
+ }
1762
+ if (ch === "/") {
1763
+ const regexLikely = lastSig === "" || "(,=:;!&|?{}[+-*%~^<>".includes(lastSig) || REGEX_PRECEDING_WORDS.has(lastWord);
1764
+ if (regexLikely) {
1765
+ const end = scanRegex(i);
1766
+ out += source.slice(i, end);
1767
+ lastSig = "/";
1768
+ lastWord = "";
1769
+ i = end;
1770
+ continue;
1771
+ }
1772
+ out += ch;
1773
+ lastSig = ch;
1774
+ lastWord = "";
1775
+ i += 1;
1776
+ continue;
1777
+ }
1778
+ if (isIdChar(ch)) {
1779
+ let j = i;
1780
+ while (j < n && isIdChar(source[j])) j += 1;
1781
+ const word = source.slice(i, j);
1782
+ if ((word === "while" || word === "for") && lastSig !== ".") {
1783
+ const k = skipWsAndComments(j);
1784
+ if (source[k] === "(") {
1785
+ const close = matchParen(k);
1786
+ if (close !== -1) {
1787
+ const inner = source.slice(k + 1, close);
1788
+ if (word === "while") {
1789
+ out += `while (${guardName}() && (${inner}))`;
1790
+ lastSig = ")";
1791
+ lastWord = "";
1792
+ i = close + 1;
1793
+ continue;
1794
+ }
1795
+ const parts = splitTopLevelSemicolons(inner);
1796
+ if (parts.length === 3) {
1797
+ const cond = parts[1].trim();
1798
+ const guardedCond = cond ? `${guardName}() && (${cond})` : `${guardName}()`;
1799
+ out += `for (${parts[0]}; ${guardedCond}; ${parts[2]})`;
1800
+ lastSig = ")";
1801
+ lastWord = "";
1802
+ i = close + 1;
1803
+ continue;
1804
+ }
1805
+ out += source.slice(i, close + 1);
1806
+ lastSig = ")";
1807
+ lastWord = "";
1808
+ i = close + 1;
1809
+ continue;
1810
+ }
1811
+ }
1812
+ }
1813
+ out += word;
1814
+ lastSig = word[word.length - 1];
1815
+ lastWord = word;
1816
+ i = j;
1817
+ continue;
1818
+ }
1819
+ out += ch;
1820
+ if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r") {
1821
+ lastSig = ch;
1822
+ lastWord = "";
1823
+ }
1824
+ i += 1;
1825
+ }
1826
+ return out;
1827
+ };
1828
+ var compileScriptCompute = (source, guard) => {
1829
+ const makeFactory = (body) => new Function(
1830
+ "__hpGuard",
1547
1831
  `"use strict";
1548
- ${source}
1832
+ ${body}
1549
1833
  ;if (typeof compute !== "function") { throw new Error("Script must define function compute(bars, inputs, hp)"); }
1550
1834
  return compute;`
1551
1835
  );
1552
- return factory();
1836
+ let factory;
1837
+ try {
1838
+ factory = makeFactory(injectLoopGuards(source, "__hpGuard"));
1839
+ } catch {
1840
+ factory = makeFactory(source);
1841
+ }
1842
+ guard.reset();
1843
+ return factory(guard);
1844
+ };
1845
+ var validateScriptSource = (source) => {
1846
+ try {
1847
+ compileScriptCompute(source, createScriptGuard());
1848
+ return null;
1849
+ } catch (error) {
1850
+ return error instanceof Error ? error.message : String(error);
1851
+ }
1553
1852
  };
1554
1853
  var drawScriptError = (ctx, renderContext, name, message) => {
1555
1854
  ctx.save();
@@ -1569,17 +1868,23 @@ var compileScriptIndicator = (definition) => {
1569
1868
  }
1570
1869
  let compiled = null;
1571
1870
  let compileError = null;
1871
+ const guard = createScriptGuard();
1572
1872
  try {
1573
- compiled = compileScriptCompute(definition.source);
1873
+ compiled = compileScriptCompute(definition.source, guard);
1574
1874
  } catch (error) {
1575
1875
  compileError = error instanceof Error ? error.message : String(error);
1576
1876
  }
1577
1877
  const sourceKey = hashScriptSource(definition.source);
1878
+ let trippedError = null;
1578
1879
  const runCompute = (data, inputs) => {
1579
1880
  if (!compiled) {
1580
1881
  return { error: compileError ?? "Script failed to compile" };
1581
1882
  }
1883
+ if (trippedError) {
1884
+ return { error: trippedError };
1885
+ }
1582
1886
  try {
1887
+ guard.reset();
1583
1888
  const result = compiled(data, inputs, SCRIPT_HELPERS);
1584
1889
  if (!result || !Array.isArray(result.plots)) {
1585
1890
  return { error: "compute() must return { plots: [...] }" };
@@ -1591,7 +1896,11 @@ var compileScriptIndicator = (definition) => {
1591
1896
  }
1592
1897
  return { result };
1593
1898
  } catch (error) {
1594
- return { error: error instanceof Error ? error.message : String(error) };
1899
+ const message = error instanceof Error ? error.message : String(error);
1900
+ if (error instanceof ScriptBudgetError) {
1901
+ trippedError = message;
1902
+ }
1903
+ return { error: message };
1595
1904
  }
1596
1905
  };
1597
1906
  const cachedCompute = (data, inputs) => withCachedComputation(
@@ -8040,5 +8349,6 @@ export {
8040
8349
  FIB_DEFAULT_PALETTE,
8041
8350
  POSITION_DEFAULT_COLORS,
8042
8351
  compileScriptIndicator,
8043
- createChart
8352
+ createChart,
8353
+ validateScriptSource
8044
8354
  };