@taphubhq/sdk-core 0.23.1 → 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
@@ -472,6 +472,8 @@ function normaliseBid(node) {
472
472
  price2: node.price2,
473
473
  status: coerceStatus(node.status),
474
474
  payout: node.payout || null,
475
+ refundRate: node.refund_rate ?? null,
476
+ refundAmount: node.refund_amount ?? null,
475
477
  slippage: node.slippage,
476
478
  createdAt: node.created_at || null,
477
479
  meta: parseMeta(node.meta)
@@ -494,12 +496,12 @@ var PLACE_BID_MUTATION = `mutation PlaceBid($input: PlaceBidInput!) {
494
496
  }`;
495
497
  var MY_BIDS_QUERY = `query MyBids($statuses: [BidStatus!], $limit: Int, $offset: Int, $pairId: ID) {
496
498
  myBids(statuses: $statuses, limit: $limit, offset: $offset, pairId: $pairId) {
497
- id user_id pair_id currency amount coefficient time1 time2 price1 price2 status payout slippage created_at meta
499
+ id user_id pair_id currency amount coefficient time1 time2 price1 price2 status payout refund_rate refund_amount slippage created_at meta
498
500
  }
499
501
  }`;
500
502
  var CANCEL_BID_MUTATION = `mutation CancelBid($input: CancelBidInput!) {
501
503
  cancelBid(input: $input) {
502
- bid { id user_id pair_id currency amount coefficient time1 time2 price1 price2 status cancelled payout slippage created_at meta }
504
+ bid { id user_id pair_id currency amount coefficient time1 time2 price1 price2 status cancelled payout refund_rate refund_amount slippage created_at meta }
503
505
  refund_amount
504
506
  new_balance
505
507
  }
@@ -1634,15 +1636,15 @@ function normaliseMeResponse(body) {
1634
1636
  };
1635
1637
  }
1636
1638
  function normaliseCurrencies(body) {
1637
- if (!Array.isArray(body)) {
1639
+ if (!body || !Array.isArray(body.listEnabledCurrencies)) {
1638
1640
  throw new TaphubServerError("Invalid response from server", {
1639
1641
  code: "InvalidResponse"
1640
1642
  });
1641
1643
  }
1642
- return body.map((entry) => ({
1644
+ return body.listEnabledCurrencies.map((entry) => ({
1643
1645
  code: entry.code,
1644
1646
  unit: entry.unit,
1645
- unitSymbol: entry.unit_symbol
1647
+ unitSymbol: entry.unitSymbol
1646
1648
  }));
1647
1649
  }
1648
1650
  function normaliseWalletNode(node) {
@@ -1698,6 +1700,9 @@ function normaliseUserPnL(node) {
1698
1700
  // src/modules/user/queries.ts
1699
1701
  var ME_QUERY = "query Me { me { id username wallet_address balance is_demo } }";
1700
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
+ }`;
1701
1706
  var MY_WALLET_BY_CURRENCY_QUERY = "query MyWalletByCurrency($currency: String!) { myWalletByCurrency(currency: $currency) { id amount currency isEnable } }";
1702
1707
  var USER_PNL_QUERY = `query UserPnL($period: String) {
1703
1708
  userPnL(period: $period) {
@@ -1707,12 +1712,10 @@ var USER_PNL_QUERY = `query UserPnL($period: String) {
1707
1712
 
1708
1713
  // src/modules/user/index.ts
1709
1714
  var UserModule = class {
1710
- #rest;
1711
1715
  #graphql;
1712
1716
  #graphqlUser;
1713
1717
  #currencies = null;
1714
1718
  constructor(deps) {
1715
- this.#rest = deps.rest;
1716
1719
  this.#graphql = deps.graphql;
1717
1720
  this.#graphqlUser = deps.graphqlUser;
1718
1721
  }
@@ -1754,7 +1757,10 @@ var UserModule = class {
1754
1757
  return this.#currencies;
1755
1758
  }
1756
1759
  async refreshCurrencies() {
1757
- const body = await this.#rest.post("/currency/list-enable");
1760
+ const body = await this.#graphqlUser.request(
1761
+ LIST_ENABLED_CURRENCIES_QUERY,
1762
+ { input: {} }
1763
+ );
1758
1764
  const currencies = normaliseCurrencies(body);
1759
1765
  this.#currencies = currencies;
1760
1766
  return currencies;
@@ -1930,23 +1936,28 @@ var NetworkQualityMonitor = class {
1930
1936
  mqttDisconnectedAt = null;
1931
1937
  committedNetwork = "good";
1932
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;
1933
1943
  tickHandle = null;
1934
1944
  onlineListener = null;
1935
1945
  offlineListener = null;
1936
1946
  disposed = false;
1937
1947
  constructor(opts) {
1938
1948
  this.bus = opts.bus;
1949
+ this.lastEmitted = this.snapshot();
1939
1950
  this.attachBrowserListeners();
1940
1951
  this.startTick();
1941
1952
  }
1942
- addSample(sample, prevMqttConnectedOverride) {
1953
+ addSample(sample) {
1943
1954
  if (this.disposed) return;
1944
1955
  const bucket = this.buckets[sample.source];
1945
1956
  bucket.push(sample);
1946
1957
  if (bucket.length > WINDOW_MAX_SAMPLES_PER_SOURCE) {
1947
1958
  bucket.shift();
1948
1959
  }
1949
- this.recompute(prevMqttConnectedOverride);
1960
+ this.recompute();
1950
1961
  }
1951
1962
  seedFromConnection(seed) {
1952
1963
  const rtt = this.resolveSeedRtt(seed);
@@ -1959,24 +1970,19 @@ var NetworkQualityMonitor = class {
1959
1970
  });
1960
1971
  }
1961
1972
  notifyMqttConnect(rttMs) {
1962
- const prevMqttConnected = this.mqttConnected;
1963
1973
  this.mqttConnected = true;
1964
1974
  this.mqttDisconnectedAt = null;
1965
- this.addSample(
1966
- {
1967
- ts: this.now(),
1968
- rtt: rttMs,
1969
- source: "mqtt",
1970
- reason: "ok"
1971
- },
1972
- prevMqttConnected
1973
- );
1975
+ this.addSample({
1976
+ ts: this.now(),
1977
+ rtt: rttMs,
1978
+ source: "mqtt",
1979
+ reason: "connect"
1980
+ });
1974
1981
  }
1975
1982
  notifyMqttDisconnect() {
1976
- const prevMqttConnected = this.mqttConnected;
1977
1983
  this.mqttConnected = false;
1978
1984
  this.mqttDisconnectedAt = this.now();
1979
- this.recompute(prevMqttConnected);
1985
+ this.recompute();
1980
1986
  }
1981
1987
  notifyMqttPing(rttMs) {
1982
1988
  this.smoother.add(rttMs);
@@ -2000,7 +2006,6 @@ var NetworkQualityMonitor = class {
2000
2006
  return this.snapshot();
2001
2007
  }
2002
2008
  reset() {
2003
- const prev = this.snapshot();
2004
2009
  for (const key of Object.keys(this.buckets)) {
2005
2010
  this.buckets[key] = [];
2006
2011
  }
@@ -2009,10 +2014,7 @@ var NetworkQualityMonitor = class {
2009
2014
  this.mqttDisconnectedAt = null;
2010
2015
  this.committedNetwork = "good";
2011
2016
  this.committedBackend = "ok";
2012
- const current = this.snapshot();
2013
- if (prev.network !== current.network || prev.backend !== current.backend) {
2014
- this.bus.emit("network:change", { previous: prev, current });
2015
- }
2017
+ this.emitIfChanged(this.snapshot());
2016
2018
  }
2017
2019
  tick() {
2018
2020
  this.pruneWindow();
@@ -2067,9 +2069,8 @@ var NetworkQualityMonitor = class {
2067
2069
  this.buckets[key] = this.buckets[key].filter((s) => s.ts >= cutoff);
2068
2070
  }
2069
2071
  }
2070
- recompute(prevMqttConnectedOverride) {
2072
+ recompute() {
2071
2073
  this.pruneWindow();
2072
- const prev = this.snapshot();
2073
2074
  const allSamples = this.allHttpAndMqttSamples();
2074
2075
  const realSamples = allSamples.filter((s) => s.source !== "connection");
2075
2076
  const useConnection = realSamples.length < COLD_START_REAL_SAMPLE_THRESHOLD;
@@ -2093,12 +2094,26 @@ var NetworkQualityMonitor = class {
2093
2094
  this.smoother.releaseOffline();
2094
2095
  nextNetwork = this.smoother.level;
2095
2096
  }
2096
- const prevMqttConnected = prevMqttConnectedOverride ?? prev.mqttConnected;
2097
- const changed = nextNetwork !== this.committedNetwork || backend !== this.committedBackend || this.mqttConnected !== prevMqttConnected;
2098
2097
  this.committedNetwork = nextNetwork;
2099
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;
2100
2116
  if (changed) {
2101
- const current = this.snapshot();
2102
2117
  this.bus.emit("network:change", { previous: prev, current });
2103
2118
  }
2104
2119
  }
@@ -2738,7 +2753,6 @@ var TaphubClient = class {
2738
2753
  onRequest: graphqlProbe
2739
2754
  });
2740
2755
  this.user = new UserModule({
2741
- rest: this.#rest,
2742
2756
  graphql: this.#graphql,
2743
2757
  graphqlUser: this.#graphqlUser
2744
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;
@@ -211,6 +211,16 @@ interface Bid {
211
211
  status: BidStatus;
212
212
  payout: string | null;
213
213
  slippage: number;
214
+ /**
215
+ * Refund terms frozen at cancel time. Present only on cancelled bids — absent
216
+ * (or null) on pending/won/lost bids, and on bids reconstructed from realtime
217
+ * store state which doesn't carry per-bid refund. `refundRate` is the rate
218
+ * applied (0.0–1.0); `refundAmount` is the credited amount. Prefer
219
+ * `refundAmount` for display — it's the exact value the server returned, so
220
+ * it needs no recompute from a config rate.
221
+ */
222
+ refundRate?: string | null;
223
+ refundAmount?: string | null;
214
224
  /** ISO-8601 string passthrough from the server. */
215
225
  createdAt: string | null;
216
226
  /**
@@ -982,7 +992,6 @@ interface UserPnL {
982
992
  }
983
993
 
984
994
  interface UserModuleDeps {
985
- rest: RestTransport;
986
995
  graphql: GraphQLTransport;
987
996
  graphqlUser: GraphQLTransport;
988
997
  }
@@ -1029,6 +1038,7 @@ declare class NetworkQualityMonitor {
1029
1038
  private mqttDisconnectedAt;
1030
1039
  private committedNetwork;
1031
1040
  private committedBackend;
1041
+ private lastEmitted;
1032
1042
  private tickHandle;
1033
1043
  private onlineListener;
1034
1044
  private offlineListener;
@@ -1036,7 +1046,7 @@ declare class NetworkQualityMonitor {
1036
1046
  constructor(opts: {
1037
1047
  bus: TaphubEventBus<EventMapWithNetwork>;
1038
1048
  });
1039
- addSample(sample: Sample, prevMqttConnectedOverride?: boolean): void;
1049
+ addSample(sample: Sample): void;
1040
1050
  seedFromConnection(seed: ConnectionSeed): void;
1041
1051
  notifyMqttConnect(rttMs: number): void;
1042
1052
  notifyMqttDisconnect(): void;
@@ -1052,6 +1062,7 @@ declare class NetworkQualityMonitor {
1052
1062
  private detachBrowserListeners;
1053
1063
  private pruneWindow;
1054
1064
  private recompute;
1065
+ private emitIfChanged;
1055
1066
  private deriveMetricsFrom;
1056
1067
  private isHardOffline;
1057
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;
@@ -211,6 +211,16 @@ interface Bid {
211
211
  status: BidStatus;
212
212
  payout: string | null;
213
213
  slippage: number;
214
+ /**
215
+ * Refund terms frozen at cancel time. Present only on cancelled bids — absent
216
+ * (or null) on pending/won/lost bids, and on bids reconstructed from realtime
217
+ * store state which doesn't carry per-bid refund. `refundRate` is the rate
218
+ * applied (0.0–1.0); `refundAmount` is the credited amount. Prefer
219
+ * `refundAmount` for display — it's the exact value the server returned, so
220
+ * it needs no recompute from a config rate.
221
+ */
222
+ refundRate?: string | null;
223
+ refundAmount?: string | null;
214
224
  /** ISO-8601 string passthrough from the server. */
215
225
  createdAt: string | null;
216
226
  /**
@@ -982,7 +992,6 @@ interface UserPnL {
982
992
  }
983
993
 
984
994
  interface UserModuleDeps {
985
- rest: RestTransport;
986
995
  graphql: GraphQLTransport;
987
996
  graphqlUser: GraphQLTransport;
988
997
  }
@@ -1029,6 +1038,7 @@ declare class NetworkQualityMonitor {
1029
1038
  private mqttDisconnectedAt;
1030
1039
  private committedNetwork;
1031
1040
  private committedBackend;
1041
+ private lastEmitted;
1032
1042
  private tickHandle;
1033
1043
  private onlineListener;
1034
1044
  private offlineListener;
@@ -1036,7 +1046,7 @@ declare class NetworkQualityMonitor {
1036
1046
  constructor(opts: {
1037
1047
  bus: TaphubEventBus<EventMapWithNetwork>;
1038
1048
  });
1039
- addSample(sample: Sample, prevMqttConnectedOverride?: boolean): void;
1049
+ addSample(sample: Sample): void;
1040
1050
  seedFromConnection(seed: ConnectionSeed): void;
1041
1051
  notifyMqttConnect(rttMs: number): void;
1042
1052
  notifyMqttDisconnect(): void;
@@ -1052,6 +1062,7 @@ declare class NetworkQualityMonitor {
1052
1062
  private detachBrowserListeners;
1053
1063
  private pruneWindow;
1054
1064
  private recompute;
1065
+ private emitIfChanged;
1055
1066
  private deriveMetricsFrom;
1056
1067
  private isHardOffline;
1057
1068
  private allHttpAndMqttSamples;
package/dist/index.js CHANGED
@@ -407,6 +407,8 @@ function normaliseBid(node) {
407
407
  price2: node.price2,
408
408
  status: coerceStatus(node.status),
409
409
  payout: node.payout || null,
410
+ refundRate: node.refund_rate ?? null,
411
+ refundAmount: node.refund_amount ?? null,
410
412
  slippage: node.slippage,
411
413
  createdAt: node.created_at || null,
412
414
  meta: parseMeta(node.meta)
@@ -429,12 +431,12 @@ var PLACE_BID_MUTATION = `mutation PlaceBid($input: PlaceBidInput!) {
429
431
  }`;
430
432
  var MY_BIDS_QUERY = `query MyBids($statuses: [BidStatus!], $limit: Int, $offset: Int, $pairId: ID) {
431
433
  myBids(statuses: $statuses, limit: $limit, offset: $offset, pairId: $pairId) {
432
- id user_id pair_id currency amount coefficient time1 time2 price1 price2 status payout slippage created_at meta
434
+ id user_id pair_id currency amount coefficient time1 time2 price1 price2 status payout refund_rate refund_amount slippage created_at meta
433
435
  }
434
436
  }`;
435
437
  var CANCEL_BID_MUTATION = `mutation CancelBid($input: CancelBidInput!) {
436
438
  cancelBid(input: $input) {
437
- bid { id user_id pair_id currency amount coefficient time1 time2 price1 price2 status cancelled payout slippage created_at meta }
439
+ bid { id user_id pair_id currency amount coefficient time1 time2 price1 price2 status cancelled payout refund_rate refund_amount slippage created_at meta }
438
440
  refund_amount
439
441
  new_balance
440
442
  }
@@ -1569,15 +1571,15 @@ function normaliseMeResponse(body) {
1569
1571
  };
1570
1572
  }
1571
1573
  function normaliseCurrencies(body) {
1572
- if (!Array.isArray(body)) {
1574
+ if (!body || !Array.isArray(body.listEnabledCurrencies)) {
1573
1575
  throw new TaphubServerError("Invalid response from server", {
1574
1576
  code: "InvalidResponse"
1575
1577
  });
1576
1578
  }
1577
- return body.map((entry) => ({
1579
+ return body.listEnabledCurrencies.map((entry) => ({
1578
1580
  code: entry.code,
1579
1581
  unit: entry.unit,
1580
- unitSymbol: entry.unit_symbol
1582
+ unitSymbol: entry.unitSymbol
1581
1583
  }));
1582
1584
  }
1583
1585
  function normaliseWalletNode(node) {
@@ -1633,6 +1635,9 @@ function normaliseUserPnL(node) {
1633
1635
  // src/modules/user/queries.ts
1634
1636
  var ME_QUERY = "query Me { me { id username wallet_address balance is_demo } }";
1635
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
+ }`;
1636
1641
  var MY_WALLET_BY_CURRENCY_QUERY = "query MyWalletByCurrency($currency: String!) { myWalletByCurrency(currency: $currency) { id amount currency isEnable } }";
1637
1642
  var USER_PNL_QUERY = `query UserPnL($period: String) {
1638
1643
  userPnL(period: $period) {
@@ -1642,12 +1647,10 @@ var USER_PNL_QUERY = `query UserPnL($period: String) {
1642
1647
 
1643
1648
  // src/modules/user/index.ts
1644
1649
  var UserModule = class {
1645
- #rest;
1646
1650
  #graphql;
1647
1651
  #graphqlUser;
1648
1652
  #currencies = null;
1649
1653
  constructor(deps) {
1650
- this.#rest = deps.rest;
1651
1654
  this.#graphql = deps.graphql;
1652
1655
  this.#graphqlUser = deps.graphqlUser;
1653
1656
  }
@@ -1689,7 +1692,10 @@ var UserModule = class {
1689
1692
  return this.#currencies;
1690
1693
  }
1691
1694
  async refreshCurrencies() {
1692
- const body = await this.#rest.post("/currency/list-enable");
1695
+ const body = await this.#graphqlUser.request(
1696
+ LIST_ENABLED_CURRENCIES_QUERY,
1697
+ { input: {} }
1698
+ );
1693
1699
  const currencies = normaliseCurrencies(body);
1694
1700
  this.#currencies = currencies;
1695
1701
  return currencies;
@@ -1865,23 +1871,28 @@ var NetworkQualityMonitor = class {
1865
1871
  mqttDisconnectedAt = null;
1866
1872
  committedNetwork = "good";
1867
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;
1868
1878
  tickHandle = null;
1869
1879
  onlineListener = null;
1870
1880
  offlineListener = null;
1871
1881
  disposed = false;
1872
1882
  constructor(opts) {
1873
1883
  this.bus = opts.bus;
1884
+ this.lastEmitted = this.snapshot();
1874
1885
  this.attachBrowserListeners();
1875
1886
  this.startTick();
1876
1887
  }
1877
- addSample(sample, prevMqttConnectedOverride) {
1888
+ addSample(sample) {
1878
1889
  if (this.disposed) return;
1879
1890
  const bucket = this.buckets[sample.source];
1880
1891
  bucket.push(sample);
1881
1892
  if (bucket.length > WINDOW_MAX_SAMPLES_PER_SOURCE) {
1882
1893
  bucket.shift();
1883
1894
  }
1884
- this.recompute(prevMqttConnectedOverride);
1895
+ this.recompute();
1885
1896
  }
1886
1897
  seedFromConnection(seed) {
1887
1898
  const rtt = this.resolveSeedRtt(seed);
@@ -1894,24 +1905,19 @@ var NetworkQualityMonitor = class {
1894
1905
  });
1895
1906
  }
1896
1907
  notifyMqttConnect(rttMs) {
1897
- const prevMqttConnected = this.mqttConnected;
1898
1908
  this.mqttConnected = true;
1899
1909
  this.mqttDisconnectedAt = null;
1900
- this.addSample(
1901
- {
1902
- ts: this.now(),
1903
- rtt: rttMs,
1904
- source: "mqtt",
1905
- reason: "ok"
1906
- },
1907
- prevMqttConnected
1908
- );
1910
+ this.addSample({
1911
+ ts: this.now(),
1912
+ rtt: rttMs,
1913
+ source: "mqtt",
1914
+ reason: "connect"
1915
+ });
1909
1916
  }
1910
1917
  notifyMqttDisconnect() {
1911
- const prevMqttConnected = this.mqttConnected;
1912
1918
  this.mqttConnected = false;
1913
1919
  this.mqttDisconnectedAt = this.now();
1914
- this.recompute(prevMqttConnected);
1920
+ this.recompute();
1915
1921
  }
1916
1922
  notifyMqttPing(rttMs) {
1917
1923
  this.smoother.add(rttMs);
@@ -1935,7 +1941,6 @@ var NetworkQualityMonitor = class {
1935
1941
  return this.snapshot();
1936
1942
  }
1937
1943
  reset() {
1938
- const prev = this.snapshot();
1939
1944
  for (const key of Object.keys(this.buckets)) {
1940
1945
  this.buckets[key] = [];
1941
1946
  }
@@ -1944,10 +1949,7 @@ var NetworkQualityMonitor = class {
1944
1949
  this.mqttDisconnectedAt = null;
1945
1950
  this.committedNetwork = "good";
1946
1951
  this.committedBackend = "ok";
1947
- const current = this.snapshot();
1948
- if (prev.network !== current.network || prev.backend !== current.backend) {
1949
- this.bus.emit("network:change", { previous: prev, current });
1950
- }
1952
+ this.emitIfChanged(this.snapshot());
1951
1953
  }
1952
1954
  tick() {
1953
1955
  this.pruneWindow();
@@ -2002,9 +2004,8 @@ var NetworkQualityMonitor = class {
2002
2004
  this.buckets[key] = this.buckets[key].filter((s) => s.ts >= cutoff);
2003
2005
  }
2004
2006
  }
2005
- recompute(prevMqttConnectedOverride) {
2007
+ recompute() {
2006
2008
  this.pruneWindow();
2007
- const prev = this.snapshot();
2008
2009
  const allSamples = this.allHttpAndMqttSamples();
2009
2010
  const realSamples = allSamples.filter((s) => s.source !== "connection");
2010
2011
  const useConnection = realSamples.length < COLD_START_REAL_SAMPLE_THRESHOLD;
@@ -2028,12 +2029,26 @@ var NetworkQualityMonitor = class {
2028
2029
  this.smoother.releaseOffline();
2029
2030
  nextNetwork = this.smoother.level;
2030
2031
  }
2031
- const prevMqttConnected = prevMqttConnectedOverride ?? prev.mqttConnected;
2032
- const changed = nextNetwork !== this.committedNetwork || backend !== this.committedBackend || this.mqttConnected !== prevMqttConnected;
2033
2032
  this.committedNetwork = nextNetwork;
2034
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;
2035
2051
  if (changed) {
2036
- const current = this.snapshot();
2037
2052
  this.bus.emit("network:change", { previous: prev, current });
2038
2053
  }
2039
2054
  }
@@ -2673,7 +2688,6 @@ var TaphubClient = class {
2673
2688
  onRequest: graphqlProbe
2674
2689
  });
2675
2690
  this.user = new UserModule({
2676
- rest: this.#rest,
2677
2691
  graphql: this.#graphql,
2678
2692
  graphqlUser: this.#graphqlUser
2679
2693
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taphubhq/sdk-core",
3
- "version": "0.23.1",
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",