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.
package/dist/index.cjs CHANGED
@@ -23,7 +23,8 @@ __export(index_exports, {
23
23
  FIB_DEFAULT_PALETTE: () => FIB_DEFAULT_PALETTE,
24
24
  POSITION_DEFAULT_COLORS: () => POSITION_DEFAULT_COLORS,
25
25
  compileScriptIndicator: () => compileScriptIndicator,
26
- createChart: () => createChart
26
+ createChart: () => createChart,
27
+ validateScriptSource: () => validateScriptSource
27
28
  });
28
29
  module.exports = __toCommonJS(index_exports);
29
30
  var POSITION_DEFAULT_COLORS = ["#26a69a", "#ef5350", "#ffffff"];
@@ -630,14 +631,23 @@ var withCachedSeries = (key, data, compute) => {
630
631
  return values;
631
632
  };
632
633
  var builtInComputationCache = /* @__PURE__ */ new Map();
634
+ var COMPUTATION_CACHE_MAX_ENTRIES = 128;
633
635
  var withCachedComputation = (key, data, compute) => {
634
636
  const fingerprint = getSeriesFingerprint(data);
635
637
  const existing = builtInComputationCache.get(key);
636
638
  if (existing && existing.fingerprint === fingerprint) {
639
+ builtInComputationCache.delete(key);
640
+ builtInComputationCache.set(key, existing);
637
641
  return existing.value;
638
642
  }
639
643
  const value = compute();
644
+ builtInComputationCache.delete(key);
640
645
  builtInComputationCache.set(key, { fingerprint, value });
646
+ while (builtInComputationCache.size > COMPUTATION_CACHE_MAX_ENTRIES) {
647
+ const oldest = builtInComputationCache.keys().next().value;
648
+ if (oldest === void 0) break;
649
+ builtInComputationCache.delete(oldest);
650
+ }
641
651
  return value;
642
652
  };
643
653
  var rollingExtremeSeries = (values, length, mode) => {
@@ -1569,14 +1579,304 @@ var hashScriptSource = (source) => {
1569
1579
  }
1570
1580
  return (hash >>> 0).toString(36);
1571
1581
  };
1572
- var compileScriptCompute = (source) => {
1573
- const factory = new Function(
1582
+ var SCRIPT_GUARD_BUDGET_MS = 1e3;
1583
+ var ScriptBudgetError = class extends Error {
1584
+ constructor() {
1585
+ super(
1586
+ `Script exceeded the ${SCRIPT_GUARD_BUDGET_MS}ms execution budget (possible infinite loop) \u2014 edit the script to retry`
1587
+ );
1588
+ this.name = "ScriptBudgetError";
1589
+ }
1590
+ };
1591
+ var createScriptGuard = () => {
1592
+ let deadline = Date.now() + SCRIPT_GUARD_BUDGET_MS;
1593
+ let count = 0;
1594
+ const guard = (() => {
1595
+ count += 1;
1596
+ if ((count & 1023) === 0 && Date.now() > deadline) {
1597
+ throw new ScriptBudgetError();
1598
+ }
1599
+ return true;
1600
+ });
1601
+ guard.reset = () => {
1602
+ count = 0;
1603
+ deadline = Date.now() + SCRIPT_GUARD_BUDGET_MS;
1604
+ };
1605
+ return guard;
1606
+ };
1607
+ var injectLoopGuards = (source, guardName) => {
1608
+ const n = source.length;
1609
+ const isIdChar = (ch) => ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_" || ch === "$";
1610
+ const scanString = (start) => {
1611
+ const quote = source[start];
1612
+ let j = start + 1;
1613
+ while (j < n) {
1614
+ const cj = source[j];
1615
+ if (cj === "\\") {
1616
+ j += 2;
1617
+ continue;
1618
+ }
1619
+ if (cj === quote) {
1620
+ return j + 1;
1621
+ }
1622
+ if (quote === "`" && cj === "$" && source[j + 1] === "{") {
1623
+ let depth = 1;
1624
+ j += 2;
1625
+ while (j < n && depth > 0) {
1626
+ const ce = source[j];
1627
+ if (ce === "\\") {
1628
+ j += 2;
1629
+ continue;
1630
+ }
1631
+ if (ce === '"' || ce === "'" || ce === "`") {
1632
+ j = scanString(j);
1633
+ continue;
1634
+ }
1635
+ if (ce === "{") depth += 1;
1636
+ else if (ce === "}") depth -= 1;
1637
+ j += 1;
1638
+ }
1639
+ continue;
1640
+ }
1641
+ j += 1;
1642
+ }
1643
+ return n;
1644
+ };
1645
+ const scanRegex = (start) => {
1646
+ let j = start + 1;
1647
+ let inClass = false;
1648
+ while (j < n) {
1649
+ const cj = source[j];
1650
+ if (cj === "\\") {
1651
+ j += 2;
1652
+ continue;
1653
+ }
1654
+ if (cj === "\n") return j;
1655
+ if (cj === "[") inClass = true;
1656
+ else if (cj === "]") inClass = false;
1657
+ else if (cj === "/" && !inClass) return j + 1;
1658
+ j += 1;
1659
+ }
1660
+ return n;
1661
+ };
1662
+ const skipWsAndComments = (k) => {
1663
+ let j = k;
1664
+ while (j < n) {
1665
+ const cj = source[j];
1666
+ if (cj === " " || cj === " " || cj === "\n" || cj === "\r") {
1667
+ j += 1;
1668
+ continue;
1669
+ }
1670
+ if (cj === "/" && source[j + 1] === "/") {
1671
+ const nl = source.indexOf("\n", j);
1672
+ j = nl === -1 ? n : nl + 1;
1673
+ continue;
1674
+ }
1675
+ if (cj === "/" && source[j + 1] === "*") {
1676
+ const end = source.indexOf("*/", j + 2);
1677
+ j = end === -1 ? n : end + 2;
1678
+ continue;
1679
+ }
1680
+ return j;
1681
+ }
1682
+ return n;
1683
+ };
1684
+ const matchParen = (openIdx) => {
1685
+ let depth = 0;
1686
+ let j = openIdx;
1687
+ while (j < n) {
1688
+ const cj = source[j];
1689
+ if (cj === '"' || cj === "'" || cj === "`") {
1690
+ j = scanString(j);
1691
+ continue;
1692
+ }
1693
+ if (cj === "/" && source[j + 1] === "/") {
1694
+ const nl = source.indexOf("\n", j);
1695
+ j = nl === -1 ? n : nl + 1;
1696
+ continue;
1697
+ }
1698
+ if (cj === "/" && source[j + 1] === "*") {
1699
+ const end = source.indexOf("*/", j + 2);
1700
+ j = end === -1 ? n : end + 2;
1701
+ continue;
1702
+ }
1703
+ if (cj === "(") depth += 1;
1704
+ else if (cj === ")") {
1705
+ depth -= 1;
1706
+ if (depth === 0) return j;
1707
+ }
1708
+ j += 1;
1709
+ }
1710
+ return -1;
1711
+ };
1712
+ const splitTopLevelSemicolons = (inner) => {
1713
+ const parts = [];
1714
+ let depth = 0;
1715
+ let j = 0;
1716
+ let last = 0;
1717
+ const m = inner.length;
1718
+ while (j < m) {
1719
+ const cj = inner[j];
1720
+ if (cj === '"' || cj === "'" || cj === "`") {
1721
+ const quote = cj;
1722
+ j += 1;
1723
+ while (j < m) {
1724
+ if (inner[j] === "\\") {
1725
+ j += 2;
1726
+ continue;
1727
+ }
1728
+ if (inner[j] === quote) {
1729
+ j += 1;
1730
+ break;
1731
+ }
1732
+ j += 1;
1733
+ }
1734
+ continue;
1735
+ }
1736
+ if (cj === "(" || cj === "[" || cj === "{") depth += 1;
1737
+ else if (cj === ")" || cj === "]" || cj === "}") depth -= 1;
1738
+ else if (cj === ";" && depth === 0) {
1739
+ parts.push(inner.slice(last, j));
1740
+ last = j + 1;
1741
+ }
1742
+ j += 1;
1743
+ }
1744
+ parts.push(inner.slice(last));
1745
+ return parts;
1746
+ };
1747
+ const REGEX_PRECEDING_WORDS = /* @__PURE__ */ new Set([
1748
+ "return",
1749
+ "typeof",
1750
+ "case",
1751
+ "do",
1752
+ "else",
1753
+ "in",
1754
+ "of",
1755
+ "new",
1756
+ "delete",
1757
+ "void",
1758
+ "instanceof",
1759
+ "yield",
1760
+ "await"
1761
+ ]);
1762
+ let out = "";
1763
+ let i = 0;
1764
+ let lastSig = "";
1765
+ let lastWord = "";
1766
+ while (i < n) {
1767
+ const ch = source[i];
1768
+ if (ch === "/" && source[i + 1] === "/") {
1769
+ const nl = source.indexOf("\n", i);
1770
+ const end = nl === -1 ? n : nl;
1771
+ out += source.slice(i, end);
1772
+ i = end;
1773
+ continue;
1774
+ }
1775
+ if (ch === "/" && source[i + 1] === "*") {
1776
+ const close = source.indexOf("*/", i + 2);
1777
+ const end = close === -1 ? n : close + 2;
1778
+ out += source.slice(i, end);
1779
+ i = end;
1780
+ continue;
1781
+ }
1782
+ if (ch === '"' || ch === "'" || ch === "`") {
1783
+ const end = scanString(i);
1784
+ out += source.slice(i, end);
1785
+ lastSig = ch;
1786
+ lastWord = "";
1787
+ i = end;
1788
+ continue;
1789
+ }
1790
+ if (ch === "/") {
1791
+ const regexLikely = lastSig === "" || "(,=:;!&|?{}[+-*%~^<>".includes(lastSig) || REGEX_PRECEDING_WORDS.has(lastWord);
1792
+ if (regexLikely) {
1793
+ const end = scanRegex(i);
1794
+ out += source.slice(i, end);
1795
+ lastSig = "/";
1796
+ lastWord = "";
1797
+ i = end;
1798
+ continue;
1799
+ }
1800
+ out += ch;
1801
+ lastSig = ch;
1802
+ lastWord = "";
1803
+ i += 1;
1804
+ continue;
1805
+ }
1806
+ if (isIdChar(ch)) {
1807
+ let j = i;
1808
+ while (j < n && isIdChar(source[j])) j += 1;
1809
+ const word = source.slice(i, j);
1810
+ if ((word === "while" || word === "for") && lastSig !== ".") {
1811
+ const k = skipWsAndComments(j);
1812
+ if (source[k] === "(") {
1813
+ const close = matchParen(k);
1814
+ if (close !== -1) {
1815
+ const inner = source.slice(k + 1, close);
1816
+ if (word === "while") {
1817
+ out += `while (${guardName}() && (${inner}))`;
1818
+ lastSig = ")";
1819
+ lastWord = "";
1820
+ i = close + 1;
1821
+ continue;
1822
+ }
1823
+ const parts = splitTopLevelSemicolons(inner);
1824
+ if (parts.length === 3) {
1825
+ const cond = parts[1].trim();
1826
+ const guardedCond = cond ? `${guardName}() && (${cond})` : `${guardName}()`;
1827
+ out += `for (${parts[0]}; ${guardedCond}; ${parts[2]})`;
1828
+ lastSig = ")";
1829
+ lastWord = "";
1830
+ i = close + 1;
1831
+ continue;
1832
+ }
1833
+ out += source.slice(i, close + 1);
1834
+ lastSig = ")";
1835
+ lastWord = "";
1836
+ i = close + 1;
1837
+ continue;
1838
+ }
1839
+ }
1840
+ }
1841
+ out += word;
1842
+ lastSig = word[word.length - 1];
1843
+ lastWord = word;
1844
+ i = j;
1845
+ continue;
1846
+ }
1847
+ out += ch;
1848
+ if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r") {
1849
+ lastSig = ch;
1850
+ lastWord = "";
1851
+ }
1852
+ i += 1;
1853
+ }
1854
+ return out;
1855
+ };
1856
+ var compileScriptCompute = (source, guard) => {
1857
+ const makeFactory = (body) => new Function(
1858
+ "__hpGuard",
1574
1859
  `"use strict";
1575
- ${source}
1860
+ ${body}
1576
1861
  ;if (typeof compute !== "function") { throw new Error("Script must define function compute(bars, inputs, hp)"); }
1577
1862
  return compute;`
1578
1863
  );
1579
- return factory();
1864
+ let factory;
1865
+ try {
1866
+ factory = makeFactory(injectLoopGuards(source, "__hpGuard"));
1867
+ } catch {
1868
+ factory = makeFactory(source);
1869
+ }
1870
+ guard.reset();
1871
+ return factory(guard);
1872
+ };
1873
+ var validateScriptSource = (source) => {
1874
+ try {
1875
+ compileScriptCompute(source, createScriptGuard());
1876
+ return null;
1877
+ } catch (error) {
1878
+ return error instanceof Error ? error.message : String(error);
1879
+ }
1580
1880
  };
1581
1881
  var drawScriptError = (ctx, renderContext, name, message) => {
1582
1882
  ctx.save();
@@ -1596,17 +1896,23 @@ var compileScriptIndicator = (definition) => {
1596
1896
  }
1597
1897
  let compiled = null;
1598
1898
  let compileError = null;
1899
+ const guard = createScriptGuard();
1599
1900
  try {
1600
- compiled = compileScriptCompute(definition.source);
1901
+ compiled = compileScriptCompute(definition.source, guard);
1601
1902
  } catch (error) {
1602
1903
  compileError = error instanceof Error ? error.message : String(error);
1603
1904
  }
1604
1905
  const sourceKey = hashScriptSource(definition.source);
1906
+ let trippedError = null;
1605
1907
  const runCompute = (data, inputs) => {
1606
1908
  if (!compiled) {
1607
1909
  return { error: compileError ?? "Script failed to compile" };
1608
1910
  }
1911
+ if (trippedError) {
1912
+ return { error: trippedError };
1913
+ }
1609
1914
  try {
1915
+ guard.reset();
1610
1916
  const result = compiled(data, inputs, SCRIPT_HELPERS);
1611
1917
  if (!result || !Array.isArray(result.plots)) {
1612
1918
  return { error: "compute() must return { plots: [...] }" };
@@ -1618,7 +1924,11 @@ var compileScriptIndicator = (definition) => {
1618
1924
  }
1619
1925
  return { result };
1620
1926
  } catch (error) {
1621
- return { error: error instanceof Error ? error.message : String(error) };
1927
+ const message = error instanceof Error ? error.message : String(error);
1928
+ if (error instanceof ScriptBudgetError) {
1929
+ trippedError = message;
1930
+ }
1931
+ return { error: message };
1622
1932
  }
1623
1933
  };
1624
1934
  const cachedCompute = (data, inputs) => withCachedComputation(
@@ -8068,5 +8378,6 @@ function createChart(element, options = {}) {
8068
8378
  FIB_DEFAULT_PALETTE,
8069
8379
  POSITION_DEFAULT_COLORS,
8070
8380
  compileScriptIndicator,
8071
- createChart
8381
+ createChart,
8382
+ validateScriptSource
8072
8383
  });
package/dist/index.d.cts CHANGED
@@ -547,6 +547,13 @@ interface ScriptComputeResult {
547
547
  guides?: number[];
548
548
  decimals?: number;
549
549
  }
550
+ /**
551
+ * Compile-check a script without registering it: returns an error message, or
552
+ * `null` when the script compiles. Runs with the same execution guard as the
553
+ * chart, so a top-level infinite loop returns a budget error instead of
554
+ * hanging the caller (e.g. an editor's save validation).
555
+ */
556
+ declare const validateScriptSource: (source: string) => string | null;
550
557
  /**
551
558
  * Compile a user-authored script into a regular indicator plugin. The plugin
552
559
  * can then be registered with `chart.registerIndicator(...)` and behaves like
@@ -653,4 +660,4 @@ interface ViewportState {
653
660
  }
654
661
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
655
662
 
656
- export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type ChartType, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type ScriptComputeResult, type ScriptIndicatorDefinition, type ScriptIndicatorInputDef, type ScriptPlotSpec, type TickerLineOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, compileScriptIndicator, createChart };
663
+ export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type ChartType, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type ScriptComputeResult, type ScriptIndicatorDefinition, type ScriptIndicatorInputDef, type ScriptPlotSpec, type TickerLineOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, compileScriptIndicator, createChart, validateScriptSource };
package/dist/index.d.ts CHANGED
@@ -547,6 +547,13 @@ interface ScriptComputeResult {
547
547
  guides?: number[];
548
548
  decimals?: number;
549
549
  }
550
+ /**
551
+ * Compile-check a script without registering it: returns an error message, or
552
+ * `null` when the script compiles. Runs with the same execution guard as the
553
+ * chart, so a top-level infinite loop returns a budget error instead of
554
+ * hanging the caller (e.g. an editor's save validation).
555
+ */
556
+ declare const validateScriptSource: (source: string) => string | null;
550
557
  /**
551
558
  * Compile a user-authored script into a regular indicator plugin. The plugin
552
559
  * can then be registered with `chart.registerIndicator(...)` and behaves like
@@ -653,4 +660,4 @@ interface ViewportState {
653
660
  }
654
661
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
655
662
 
656
- export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type ChartType, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type ScriptComputeResult, type ScriptIndicatorDefinition, type ScriptIndicatorInputDef, type ScriptPlotSpec, type TickerLineOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, compileScriptIndicator, createChart };
663
+ export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type ChartType, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type ScriptComputeResult, type ScriptIndicatorDefinition, type ScriptIndicatorInputDef, type ScriptPlotSpec, type TickerLineOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, compileScriptIndicator, createChart, validateScriptSource };