@taphubhq/sdk-core 0.23.2 → 0.23.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.
package/dist/index.cjs CHANGED
@@ -1636,15 +1636,15 @@ function normaliseMeResponse(body) {
1636
1636
  };
1637
1637
  }
1638
1638
  function normaliseCurrencies(body) {
1639
- if (!Array.isArray(body)) {
1639
+ if (!body || !Array.isArray(body.listEnabledCurrencies)) {
1640
1640
  throw new TaphubServerError("Invalid response from server", {
1641
1641
  code: "InvalidResponse"
1642
1642
  });
1643
1643
  }
1644
- return body.map((entry) => ({
1644
+ return body.listEnabledCurrencies.map((entry) => ({
1645
1645
  code: entry.code,
1646
1646
  unit: entry.unit,
1647
- unitSymbol: entry.unit_symbol
1647
+ unitSymbol: entry.unitSymbol
1648
1648
  }));
1649
1649
  }
1650
1650
  function normaliseWalletNode(node) {
@@ -1700,6 +1700,9 @@ function normaliseUserPnL(node) {
1700
1700
  // src/modules/user/queries.ts
1701
1701
  var ME_QUERY = "query Me { me { id username wallet_address balance is_demo } }";
1702
1702
  var MY_WALLETS_QUERY = "query MyWallets { myWallets { id amount currency isEnable } }";
1703
+ var LIST_ENABLED_CURRENCIES_QUERY = `query ListEnabledCurrencies($input: ListEnabledCurrenciesInput!) {
1704
+ listEnabledCurrencies(input: $input) { code unit unitSymbol }
1705
+ }`;
1703
1706
  var MY_WALLET_BY_CURRENCY_QUERY = "query MyWalletByCurrency($currency: String!) { myWalletByCurrency(currency: $currency) { id amount currency isEnable } }";
1704
1707
  var USER_PNL_QUERY = `query UserPnL($period: String) {
1705
1708
  userPnL(period: $period) {
@@ -1709,12 +1712,10 @@ var USER_PNL_QUERY = `query UserPnL($period: String) {
1709
1712
 
1710
1713
  // src/modules/user/index.ts
1711
1714
  var UserModule = class {
1712
- #rest;
1713
1715
  #graphql;
1714
1716
  #graphqlUser;
1715
1717
  #currencies = null;
1716
1718
  constructor(deps) {
1717
- this.#rest = deps.rest;
1718
1719
  this.#graphql = deps.graphql;
1719
1720
  this.#graphqlUser = deps.graphqlUser;
1720
1721
  }
@@ -1756,7 +1757,10 @@ var UserModule = class {
1756
1757
  return this.#currencies;
1757
1758
  }
1758
1759
  async refreshCurrencies() {
1759
- const body = await this.#rest.post("/currency/list-enable");
1760
+ const body = await this.#graphqlUser.request(
1761
+ LIST_ENABLED_CURRENCIES_QUERY,
1762
+ { input: {} }
1763
+ );
1760
1764
  const currencies = normaliseCurrencies(body);
1761
1765
  this.#currencies = currencies;
1762
1766
  return currencies;
@@ -1932,23 +1936,28 @@ var NetworkQualityMonitor = class {
1932
1936
  mqttDisconnectedAt = null;
1933
1937
  committedNetwork = "good";
1934
1938
  committedBackend = "ok";
1939
+ // The last snapshot we emitted, used to decide whether the next recompute is
1940
+ // worth emitting. Seeded with the initial default so the first real change
1941
+ // (e.g. first backend5xx, first ping) emits against the true starting state.
1942
+ lastEmitted;
1935
1943
  tickHandle = null;
1936
1944
  onlineListener = null;
1937
1945
  offlineListener = null;
1938
1946
  disposed = false;
1939
1947
  constructor(opts) {
1940
1948
  this.bus = opts.bus;
1949
+ this.lastEmitted = this.snapshot();
1941
1950
  this.attachBrowserListeners();
1942
1951
  this.startTick();
1943
1952
  }
1944
- addSample(sample, prevMqttConnectedOverride) {
1953
+ addSample(sample) {
1945
1954
  if (this.disposed) return;
1946
1955
  const bucket = this.buckets[sample.source];
1947
1956
  bucket.push(sample);
1948
1957
  if (bucket.length > WINDOW_MAX_SAMPLES_PER_SOURCE) {
1949
1958
  bucket.shift();
1950
1959
  }
1951
- this.recompute(prevMqttConnectedOverride);
1960
+ this.recompute();
1952
1961
  }
1953
1962
  seedFromConnection(seed) {
1954
1963
  const rtt = this.resolveSeedRtt(seed);
@@ -1961,24 +1970,19 @@ var NetworkQualityMonitor = class {
1961
1970
  });
1962
1971
  }
1963
1972
  notifyMqttConnect(rttMs) {
1964
- const prevMqttConnected = this.mqttConnected;
1965
1973
  this.mqttConnected = true;
1966
1974
  this.mqttDisconnectedAt = null;
1967
- this.addSample(
1968
- {
1969
- ts: this.now(),
1970
- rtt: rttMs,
1971
- source: "mqtt",
1972
- reason: "ok"
1973
- },
1974
- prevMqttConnected
1975
- );
1975
+ this.addSample({
1976
+ ts: this.now(),
1977
+ rtt: rttMs,
1978
+ source: "mqtt",
1979
+ reason: "connect"
1980
+ });
1976
1981
  }
1977
1982
  notifyMqttDisconnect() {
1978
- const prevMqttConnected = this.mqttConnected;
1979
1983
  this.mqttConnected = false;
1980
1984
  this.mqttDisconnectedAt = this.now();
1981
- this.recompute(prevMqttConnected);
1985
+ this.recompute();
1982
1986
  }
1983
1987
  notifyMqttPing(rttMs) {
1984
1988
  this.smoother.add(rttMs);
@@ -2002,7 +2006,6 @@ var NetworkQualityMonitor = class {
2002
2006
  return this.snapshot();
2003
2007
  }
2004
2008
  reset() {
2005
- const prev = this.snapshot();
2006
2009
  for (const key of Object.keys(this.buckets)) {
2007
2010
  this.buckets[key] = [];
2008
2011
  }
@@ -2011,10 +2014,7 @@ var NetworkQualityMonitor = class {
2011
2014
  this.mqttDisconnectedAt = null;
2012
2015
  this.committedNetwork = "good";
2013
2016
  this.committedBackend = "ok";
2014
- const current = this.snapshot();
2015
- if (prev.network !== current.network || prev.backend !== current.backend) {
2016
- this.bus.emit("network:change", { previous: prev, current });
2017
- }
2017
+ this.emitIfChanged(this.snapshot());
2018
2018
  }
2019
2019
  tick() {
2020
2020
  this.pruneWindow();
@@ -2069,9 +2069,8 @@ var NetworkQualityMonitor = class {
2069
2069
  this.buckets[key] = this.buckets[key].filter((s) => s.ts >= cutoff);
2070
2070
  }
2071
2071
  }
2072
- recompute(prevMqttConnectedOverride) {
2072
+ recompute() {
2073
2073
  this.pruneWindow();
2074
- const prev = this.snapshot();
2075
2074
  const allSamples = this.allHttpAndMqttSamples();
2076
2075
  const realSamples = allSamples.filter((s) => s.source !== "connection");
2077
2076
  const useConnection = realSamples.length < COLD_START_REAL_SAMPLE_THRESHOLD;
@@ -2095,12 +2094,26 @@ var NetworkQualityMonitor = class {
2095
2094
  this.smoother.releaseOffline();
2096
2095
  nextNetwork = this.smoother.level;
2097
2096
  }
2098
- const prevMqttConnected = prevMqttConnectedOverride ?? prev.mqttConnected;
2099
- const changed = nextNetwork !== this.committedNetwork || backend !== this.committedBackend || this.mqttConnected !== prevMqttConnected;
2100
2097
  this.committedNetwork = nextNetwork;
2101
2098
  this.committedBackend = backend;
2099
+ this.emitIfChanged(this.snapshot());
2100
+ }
2101
+ // Emits network:change when any *displayed* field of the snapshot moves. This
2102
+ // is compared against the last emitted snapshot (not a snapshot taken at the
2103
+ // top of recompute): notifyMqttPing feeds the smoother BEFORE recompute, so a
2104
+ // same-call snapshot would already carry the new rtt and the change would be
2105
+ // invisible (the original "rtt label freezes on a stable connection" bug).
2106
+ //
2107
+ // Trigger fields: network, backend, mqttConnected, rtt, jitter. rtt/jitter are
2108
+ // bounded by the MQTT ping cadence (~keepalive), so this cannot spam. lossRate
2109
+ // and samplesInWindow are deliberately excluded — they move on every HTTP
2110
+ // sample (request-volume driven) and are diagnostics only; consumers that show
2111
+ // them poll getCurrent().
2112
+ emitIfChanged(current) {
2113
+ const prev = this.lastEmitted;
2114
+ this.lastEmitted = current;
2115
+ const changed = current.network !== prev.network || current.backend !== prev.backend || current.mqttConnected !== prev.mqttConnected || current.rtt !== prev.rtt || current.jitter !== prev.jitter;
2102
2116
  if (changed) {
2103
- const current = this.snapshot();
2104
2117
  this.bus.emit("network:change", { previous: prev, current });
2105
2118
  }
2106
2119
  }
@@ -2740,7 +2753,6 @@ var TaphubClient = class {
2740
2753
  onRequest: graphqlProbe
2741
2754
  });
2742
2755
  this.user = new UserModule({
2743
- rest: this.#rest,
2744
2756
  graphql: this.#graphql,
2745
2757
  graphqlUser: this.#graphqlUser
2746
2758
  });
package/dist/index.d.mts CHANGED
@@ -35,7 +35,7 @@ interface TaphubClientConfig {
35
35
  }
36
36
 
37
37
  type SignalSource = 'graphql' | 'rest' | 'mqtt' | 'browser' | 'connection';
38
- type SampleReason = 'ok' | 'network' | 'timeout' | 'offline' | 'backend5xx' | 'backend4xx' | 'gqlError';
38
+ type SampleReason = 'ok' | 'connect' | 'network' | 'timeout' | 'offline' | 'backend5xx' | 'backend4xx' | 'gqlError';
39
39
  type Sample = {
40
40
  ts: number;
41
41
  rtt: number;
@@ -992,7 +992,6 @@ interface UserPnL {
992
992
  }
993
993
 
994
994
  interface UserModuleDeps {
995
- rest: RestTransport;
996
995
  graphql: GraphQLTransport;
997
996
  graphqlUser: GraphQLTransport;
998
997
  }
@@ -1039,6 +1038,7 @@ declare class NetworkQualityMonitor {
1039
1038
  private mqttDisconnectedAt;
1040
1039
  private committedNetwork;
1041
1040
  private committedBackend;
1041
+ private lastEmitted;
1042
1042
  private tickHandle;
1043
1043
  private onlineListener;
1044
1044
  private offlineListener;
@@ -1046,7 +1046,7 @@ declare class NetworkQualityMonitor {
1046
1046
  constructor(opts: {
1047
1047
  bus: TaphubEventBus<EventMapWithNetwork>;
1048
1048
  });
1049
- addSample(sample: Sample, prevMqttConnectedOverride?: boolean): void;
1049
+ addSample(sample: Sample): void;
1050
1050
  seedFromConnection(seed: ConnectionSeed): void;
1051
1051
  notifyMqttConnect(rttMs: number): void;
1052
1052
  notifyMqttDisconnect(): void;
@@ -1062,6 +1062,7 @@ declare class NetworkQualityMonitor {
1062
1062
  private detachBrowserListeners;
1063
1063
  private pruneWindow;
1064
1064
  private recompute;
1065
+ private emitIfChanged;
1065
1066
  private deriveMetricsFrom;
1066
1067
  private isHardOffline;
1067
1068
  private allHttpAndMqttSamples;
package/dist/index.d.ts CHANGED
@@ -35,7 +35,7 @@ interface TaphubClientConfig {
35
35
  }
36
36
 
37
37
  type SignalSource = 'graphql' | 'rest' | 'mqtt' | 'browser' | 'connection';
38
- type SampleReason = 'ok' | 'network' | 'timeout' | 'offline' | 'backend5xx' | 'backend4xx' | 'gqlError';
38
+ type SampleReason = 'ok' | 'connect' | 'network' | 'timeout' | 'offline' | 'backend5xx' | 'backend4xx' | 'gqlError';
39
39
  type Sample = {
40
40
  ts: number;
41
41
  rtt: number;
@@ -992,7 +992,6 @@ interface UserPnL {
992
992
  }
993
993
 
994
994
  interface UserModuleDeps {
995
- rest: RestTransport;
996
995
  graphql: GraphQLTransport;
997
996
  graphqlUser: GraphQLTransport;
998
997
  }
@@ -1039,6 +1038,7 @@ declare class NetworkQualityMonitor {
1039
1038
  private mqttDisconnectedAt;
1040
1039
  private committedNetwork;
1041
1040
  private committedBackend;
1041
+ private lastEmitted;
1042
1042
  private tickHandle;
1043
1043
  private onlineListener;
1044
1044
  private offlineListener;
@@ -1046,7 +1046,7 @@ declare class NetworkQualityMonitor {
1046
1046
  constructor(opts: {
1047
1047
  bus: TaphubEventBus<EventMapWithNetwork>;
1048
1048
  });
1049
- addSample(sample: Sample, prevMqttConnectedOverride?: boolean): void;
1049
+ addSample(sample: Sample): void;
1050
1050
  seedFromConnection(seed: ConnectionSeed): void;
1051
1051
  notifyMqttConnect(rttMs: number): void;
1052
1052
  notifyMqttDisconnect(): void;
@@ -1062,6 +1062,7 @@ declare class NetworkQualityMonitor {
1062
1062
  private detachBrowserListeners;
1063
1063
  private pruneWindow;
1064
1064
  private recompute;
1065
+ private emitIfChanged;
1065
1066
  private deriveMetricsFrom;
1066
1067
  private isHardOffline;
1067
1068
  private allHttpAndMqttSamples;
package/dist/index.js CHANGED
@@ -1571,15 +1571,15 @@ function normaliseMeResponse(body) {
1571
1571
  };
1572
1572
  }
1573
1573
  function normaliseCurrencies(body) {
1574
- if (!Array.isArray(body)) {
1574
+ if (!body || !Array.isArray(body.listEnabledCurrencies)) {
1575
1575
  throw new TaphubServerError("Invalid response from server", {
1576
1576
  code: "InvalidResponse"
1577
1577
  });
1578
1578
  }
1579
- return body.map((entry) => ({
1579
+ return body.listEnabledCurrencies.map((entry) => ({
1580
1580
  code: entry.code,
1581
1581
  unit: entry.unit,
1582
- unitSymbol: entry.unit_symbol
1582
+ unitSymbol: entry.unitSymbol
1583
1583
  }));
1584
1584
  }
1585
1585
  function normaliseWalletNode(node) {
@@ -1635,6 +1635,9 @@ function normaliseUserPnL(node) {
1635
1635
  // src/modules/user/queries.ts
1636
1636
  var ME_QUERY = "query Me { me { id username wallet_address balance is_demo } }";
1637
1637
  var MY_WALLETS_QUERY = "query MyWallets { myWallets { id amount currency isEnable } }";
1638
+ var LIST_ENABLED_CURRENCIES_QUERY = `query ListEnabledCurrencies($input: ListEnabledCurrenciesInput!) {
1639
+ listEnabledCurrencies(input: $input) { code unit unitSymbol }
1640
+ }`;
1638
1641
  var MY_WALLET_BY_CURRENCY_QUERY = "query MyWalletByCurrency($currency: String!) { myWalletByCurrency(currency: $currency) { id amount currency isEnable } }";
1639
1642
  var USER_PNL_QUERY = `query UserPnL($period: String) {
1640
1643
  userPnL(period: $period) {
@@ -1644,12 +1647,10 @@ var USER_PNL_QUERY = `query UserPnL($period: String) {
1644
1647
 
1645
1648
  // src/modules/user/index.ts
1646
1649
  var UserModule = class {
1647
- #rest;
1648
1650
  #graphql;
1649
1651
  #graphqlUser;
1650
1652
  #currencies = null;
1651
1653
  constructor(deps) {
1652
- this.#rest = deps.rest;
1653
1654
  this.#graphql = deps.graphql;
1654
1655
  this.#graphqlUser = deps.graphqlUser;
1655
1656
  }
@@ -1691,7 +1692,10 @@ var UserModule = class {
1691
1692
  return this.#currencies;
1692
1693
  }
1693
1694
  async refreshCurrencies() {
1694
- const body = await this.#rest.post("/currency/list-enable");
1695
+ const body = await this.#graphqlUser.request(
1696
+ LIST_ENABLED_CURRENCIES_QUERY,
1697
+ { input: {} }
1698
+ );
1695
1699
  const currencies = normaliseCurrencies(body);
1696
1700
  this.#currencies = currencies;
1697
1701
  return currencies;
@@ -1867,23 +1871,28 @@ var NetworkQualityMonitor = class {
1867
1871
  mqttDisconnectedAt = null;
1868
1872
  committedNetwork = "good";
1869
1873
  committedBackend = "ok";
1874
+ // The last snapshot we emitted, used to decide whether the next recompute is
1875
+ // worth emitting. Seeded with the initial default so the first real change
1876
+ // (e.g. first backend5xx, first ping) emits against the true starting state.
1877
+ lastEmitted;
1870
1878
  tickHandle = null;
1871
1879
  onlineListener = null;
1872
1880
  offlineListener = null;
1873
1881
  disposed = false;
1874
1882
  constructor(opts) {
1875
1883
  this.bus = opts.bus;
1884
+ this.lastEmitted = this.snapshot();
1876
1885
  this.attachBrowserListeners();
1877
1886
  this.startTick();
1878
1887
  }
1879
- addSample(sample, prevMqttConnectedOverride) {
1888
+ addSample(sample) {
1880
1889
  if (this.disposed) return;
1881
1890
  const bucket = this.buckets[sample.source];
1882
1891
  bucket.push(sample);
1883
1892
  if (bucket.length > WINDOW_MAX_SAMPLES_PER_SOURCE) {
1884
1893
  bucket.shift();
1885
1894
  }
1886
- this.recompute(prevMqttConnectedOverride);
1895
+ this.recompute();
1887
1896
  }
1888
1897
  seedFromConnection(seed) {
1889
1898
  const rtt = this.resolveSeedRtt(seed);
@@ -1896,24 +1905,19 @@ var NetworkQualityMonitor = class {
1896
1905
  });
1897
1906
  }
1898
1907
  notifyMqttConnect(rttMs) {
1899
- const prevMqttConnected = this.mqttConnected;
1900
1908
  this.mqttConnected = true;
1901
1909
  this.mqttDisconnectedAt = null;
1902
- this.addSample(
1903
- {
1904
- ts: this.now(),
1905
- rtt: rttMs,
1906
- source: "mqtt",
1907
- reason: "ok"
1908
- },
1909
- prevMqttConnected
1910
- );
1910
+ this.addSample({
1911
+ ts: this.now(),
1912
+ rtt: rttMs,
1913
+ source: "mqtt",
1914
+ reason: "connect"
1915
+ });
1911
1916
  }
1912
1917
  notifyMqttDisconnect() {
1913
- const prevMqttConnected = this.mqttConnected;
1914
1918
  this.mqttConnected = false;
1915
1919
  this.mqttDisconnectedAt = this.now();
1916
- this.recompute(prevMqttConnected);
1920
+ this.recompute();
1917
1921
  }
1918
1922
  notifyMqttPing(rttMs) {
1919
1923
  this.smoother.add(rttMs);
@@ -1937,7 +1941,6 @@ var NetworkQualityMonitor = class {
1937
1941
  return this.snapshot();
1938
1942
  }
1939
1943
  reset() {
1940
- const prev = this.snapshot();
1941
1944
  for (const key of Object.keys(this.buckets)) {
1942
1945
  this.buckets[key] = [];
1943
1946
  }
@@ -1946,10 +1949,7 @@ var NetworkQualityMonitor = class {
1946
1949
  this.mqttDisconnectedAt = null;
1947
1950
  this.committedNetwork = "good";
1948
1951
  this.committedBackend = "ok";
1949
- const current = this.snapshot();
1950
- if (prev.network !== current.network || prev.backend !== current.backend) {
1951
- this.bus.emit("network:change", { previous: prev, current });
1952
- }
1952
+ this.emitIfChanged(this.snapshot());
1953
1953
  }
1954
1954
  tick() {
1955
1955
  this.pruneWindow();
@@ -2004,9 +2004,8 @@ var NetworkQualityMonitor = class {
2004
2004
  this.buckets[key] = this.buckets[key].filter((s) => s.ts >= cutoff);
2005
2005
  }
2006
2006
  }
2007
- recompute(prevMqttConnectedOverride) {
2007
+ recompute() {
2008
2008
  this.pruneWindow();
2009
- const prev = this.snapshot();
2010
2009
  const allSamples = this.allHttpAndMqttSamples();
2011
2010
  const realSamples = allSamples.filter((s) => s.source !== "connection");
2012
2011
  const useConnection = realSamples.length < COLD_START_REAL_SAMPLE_THRESHOLD;
@@ -2030,12 +2029,26 @@ var NetworkQualityMonitor = class {
2030
2029
  this.smoother.releaseOffline();
2031
2030
  nextNetwork = this.smoother.level;
2032
2031
  }
2033
- const prevMqttConnected = prevMqttConnectedOverride ?? prev.mqttConnected;
2034
- const changed = nextNetwork !== this.committedNetwork || backend !== this.committedBackend || this.mqttConnected !== prevMqttConnected;
2035
2032
  this.committedNetwork = nextNetwork;
2036
2033
  this.committedBackend = backend;
2034
+ this.emitIfChanged(this.snapshot());
2035
+ }
2036
+ // Emits network:change when any *displayed* field of the snapshot moves. This
2037
+ // is compared against the last emitted snapshot (not a snapshot taken at the
2038
+ // top of recompute): notifyMqttPing feeds the smoother BEFORE recompute, so a
2039
+ // same-call snapshot would already carry the new rtt and the change would be
2040
+ // invisible (the original "rtt label freezes on a stable connection" bug).
2041
+ //
2042
+ // Trigger fields: network, backend, mqttConnected, rtt, jitter. rtt/jitter are
2043
+ // bounded by the MQTT ping cadence (~keepalive), so this cannot spam. lossRate
2044
+ // and samplesInWindow are deliberately excluded — they move on every HTTP
2045
+ // sample (request-volume driven) and are diagnostics only; consumers that show
2046
+ // them poll getCurrent().
2047
+ emitIfChanged(current) {
2048
+ const prev = this.lastEmitted;
2049
+ this.lastEmitted = current;
2050
+ const changed = current.network !== prev.network || current.backend !== prev.backend || current.mqttConnected !== prev.mqttConnected || current.rtt !== prev.rtt || current.jitter !== prev.jitter;
2037
2051
  if (changed) {
2038
- const current = this.snapshot();
2039
2052
  this.bus.emit("network:change", { previous: prev, current });
2040
2053
  }
2041
2054
  }
@@ -2675,7 +2688,6 @@ var TaphubClient = class {
2675
2688
  onRequest: graphqlProbe
2676
2689
  });
2677
2690
  this.user = new UserModule({
2678
- rest: this.#rest,
2679
2691
  graphql: this.#graphql,
2680
2692
  graphqlUser: this.#graphqlUser
2681
2693
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taphubhq/sdk-core",
3
- "version": "0.23.2",
3
+ "version": "0.23.3",
4
4
  "description": "Core SDK for building on the TabHub platform",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.cjs",