@taphubhq/sdk-core 0.15.3 → 0.16.1

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
@@ -33,10 +33,10 @@ __export(index_exports, {
33
33
  AgencyPairModule: () => AgencyPairModule,
34
34
  AuthModule: () => AuthModule,
35
35
  BidModule: () => BidModule,
36
- GameModule: () => GameModule,
37
36
  LeaderboardModule: () => LeaderboardModule,
38
37
  LocaleModule: () => LocaleModule,
39
38
  NetworkQualityMonitor: () => NetworkQualityMonitor,
39
+ PairModule: () => PairModule,
40
40
  RealtimeModule: () => RealtimeModule,
41
41
  TaphubAuthError: () => TaphubAuthError,
42
42
  TaphubClient: () => TaphubClient,
@@ -574,8 +574,151 @@ var BidModule = class {
574
574
  }
575
575
  };
576
576
 
577
- // src/modules/game/normalise.ts
578
- function normaliseGame(node) {
577
+ // src/modules/leaderboard/normalise.ts
578
+ function normaliseLeaderboard(rows) {
579
+ if (!Array.isArray(rows)) {
580
+ throw new TaphubServerError("Invalid response from server", {
581
+ code: "InvalidResponse"
582
+ });
583
+ }
584
+ return rows.map((row) => ({
585
+ userId: row.user_id,
586
+ username: row.username,
587
+ rank: row.rank,
588
+ totalBids: row.total_bids,
589
+ totalWins: row.total_wins,
590
+ totalWagered: row.total_wagered,
591
+ totalPayout: row.total_payout,
592
+ gain: row.gain
593
+ }));
594
+ }
595
+
596
+ // src/modules/leaderboard/queries.ts
597
+ var LEADERBOARD_QUERY = `query Leaderboard($period: String!, $sort_by: String!) {
598
+ leaderboard(period: $period, sort_by: $sort_by) {
599
+ user_id username rank total_bids total_wins total_wagered total_payout gain
600
+ }
601
+ }`;
602
+
603
+ // src/modules/leaderboard/index.ts
604
+ var LeaderboardModule = class {
605
+ #graphql;
606
+ constructor(deps) {
607
+ this.#graphql = deps.graphql;
608
+ }
609
+ async list(args) {
610
+ const body = await this.#graphql.request(
611
+ LEADERBOARD_QUERY,
612
+ { period: args.period, sort_by: args.sortBy },
613
+ args.signal ? { signal: args.signal } : void 0
614
+ );
615
+ return normaliseLeaderboard(body.leaderboard);
616
+ }
617
+ };
618
+
619
+ // src/modules/locale/normalise.ts
620
+ function normaliseLocaleResponse(node) {
621
+ let translations = null;
622
+ if (node.translations !== null) {
623
+ try {
624
+ translations = JSON.parse(node.translations);
625
+ } catch (err) {
626
+ throw new TaphubValidationError("Malformed translations JSON string from backend", {
627
+ code: "InvalidLocaleResponse",
628
+ details: {
629
+ lang: node.lang,
630
+ version: node.version,
631
+ parseError: err instanceof Error ? err.message : String(err)
632
+ }
633
+ });
634
+ }
635
+ }
636
+ return {
637
+ lang: node.lang,
638
+ version: node.version,
639
+ notModified: node.notModified,
640
+ translations
641
+ };
642
+ }
643
+ function normaliseRefreshLocalesPayload(node) {
644
+ let versions;
645
+ try {
646
+ versions = JSON.parse(node.versions);
647
+ } catch (err) {
648
+ throw new TaphubValidationError("Malformed versions JSON string from backend", {
649
+ code: "InvalidRefreshLocalesPayload",
650
+ details: {
651
+ parseError: err instanceof Error ? err.message : String(err)
652
+ }
653
+ });
654
+ }
655
+ return {
656
+ refreshed: node.refreshed,
657
+ versions
658
+ };
659
+ }
660
+
661
+ // src/modules/locale/queries.ts
662
+ var LOCALES_QUERY = `query Locales($input: LocalesInput!) {
663
+ locales(input: $input) {
664
+ lang
665
+ version
666
+ notModified
667
+ translations
668
+ }
669
+ }`;
670
+ var REFRESH_LOCALES_MUTATION = `mutation RefreshLocales {
671
+ refreshLocales {
672
+ refreshed
673
+ versions
674
+ }
675
+ }`;
676
+
677
+ // src/modules/locale/index.ts
678
+ var LocaleModule = class {
679
+ #graphql;
680
+ constructor(deps) {
681
+ this.#graphql = deps.graphql;
682
+ }
683
+ /**
684
+ * Fetch translations for a single language.
685
+ *
686
+ * Pass the last-seen `version` as `knownVersion` to opt into not-modified
687
+ * short-circuit semantics: backend returns `{notModified: true, translations: null}`
688
+ * when the cached body still matches, and the caller keeps prior state.
689
+ *
690
+ * Errors surface as TaphubError subclasses with `extensions.code` codes from
691
+ * the backend (e.g. `LangNotSupported`, `InsufficientUpstream`, `FeatureDisabled`).
692
+ * Caller decides the fallback strategy.
693
+ */
694
+ async get(lang, knownVersion, opts) {
695
+ const input = { lang };
696
+ if (knownVersion !== void 0 && knownVersion !== "") {
697
+ input.knownVersion = knownVersion;
698
+ }
699
+ const body = await this.#graphql.request(LOCALES_QUERY, { input }, opts);
700
+ return normaliseLocaleResponse(body.locales);
701
+ }
702
+ /**
703
+ * Trigger an admin refresh — backend re-pulls the source Sheet, invalidates
704
+ * its cache, and writes fresh entries for every supported language.
705
+ *
706
+ * Requires `X-API-Key` header equal to backend `InternalApiKey`. The transport
707
+ * layer is responsible for attaching the header; this method does not handle
708
+ * auth concerns directly.
709
+ */
710
+ async refresh(opts) {
711
+ const body = await this.#graphql.request(
712
+ REFRESH_LOCALES_MUTATION,
713
+ {},
714
+ opts
715
+ );
716
+ return normaliseRefreshLocalesPayload(body.refreshLocales);
717
+ }
718
+ };
719
+
720
+ // src/modules/pair/normalise.ts
721
+ function normalisePair(node) {
579
722
  if (typeof node.id !== "string" || node.id === "" || typeof node.pair !== "string" || node.pair === "" || typeof node.status !== "string" || node.status === "" || !node.config) {
580
723
  throw new TaphubServerError("Invalid response from server", {
581
724
  code: "InvalidResponse"
@@ -634,7 +777,7 @@ function normaliseCandles(list) {
634
777
  coefMults: c.coefMults
635
778
  }));
636
779
  }
637
- function normaliseGamePairInfo(node) {
780
+ function normalisePairInfo(node) {
638
781
  if (typeof node.id !== "string" || node.id === "" || typeof node.pair !== "string" || node.pair === "") {
639
782
  throw new TaphubServerError("Invalid response from server", { code: "InvalidResponse" });
640
783
  }
@@ -644,13 +787,14 @@ function normaliseGamePairInfo(node) {
644
787
  gameplayId: node.gameplayId,
645
788
  gameplayName: node.gameplayName,
646
789
  source: node.source,
647
- gameId: node.gameId ?? null
790
+ // REVIEW[bid-260602]: maps node.agencyPairId (was: node.gameId)
791
+ agencyPairId: node.agencyPairId ?? null
648
792
  };
649
793
  }
650
794
 
651
- // src/modules/game/queries.ts
652
- var GAME_QUERY = `query Game($pair: String!, $gameplaySlug: String) {
653
- game(pair: $pair, gameplaySlug: $gameplaySlug) {
795
+ // src/modules/pair/queries.ts
796
+ var PAIR_QUERY = `query Pair($pair: String!, $gameplaySlug: String) {
797
+ pair(pair: $pair, gameplaySlug: $gameplaySlug) {
654
798
  id pair status created_at
655
799
  config {
656
800
  gridConfig { cellSizeTime cellSizeValue candleSize baseline baselineTime }
@@ -659,8 +803,8 @@ var GAME_QUERY = `query Game($pair: String!, $gameplaySlug: String) {
659
803
  }
660
804
  }
661
805
  }`;
662
- var CHART_HISTORY_QUERY = `query ChartHistory($gameId: ID!, $limit: Int) {
663
- chartHistory(gameId: $gameId, limit: $limit) {
806
+ var CHART_HISTORY_QUERY = `query ChartHistory($agencyPairId: ID!, $limit: Int) {
807
+ chartHistory(agencyPairId: $agencyPairId, limit: $limit) {
664
808
  time o h l c volatility coefMults
665
809
  }
666
810
  }`;
@@ -671,12 +815,12 @@ var BUILDER_AVAILABLE_GAME_PAIRS_QUERY = `query BuilderAvailableGamePairs($gamep
671
815
  gameplayId
672
816
  gameplayName
673
817
  source
674
- gameId
818
+ agencyPairId
675
819
  }
676
820
  }`;
677
821
 
678
- // src/modules/game/index.ts
679
- var GameModule = class {
822
+ // src/modules/pair/index.ts
823
+ var PairModule = class {
680
824
  #graphql;
681
825
  constructor(deps) {
682
826
  this.#graphql = deps.graphql;
@@ -686,7 +830,7 @@ var GameModule = class {
686
830
  if (opts?.gameplaySlug) variables.gameplaySlug = opts.gameplaySlug;
687
831
  let body;
688
832
  try {
689
- body = await this.#graphql.request(GAME_QUERY, variables, opts);
833
+ body = await this.#graphql.request(PAIR_QUERY, variables, opts);
690
834
  } catch (err) {
691
835
  if (err instanceof TaphubValidationError && err.code === "NotExist") {
692
836
  throw new TaphubValidationError("Game not found for pair", {
@@ -696,13 +840,13 @@ var GameModule = class {
696
840
  }
697
841
  throw err;
698
842
  }
699
- if (body.game === null) {
843
+ if (body.pair === null) {
700
844
  throw new TaphubValidationError("Game not found for pair", {
701
845
  code: "GameNotFound",
702
846
  details: { pair }
703
847
  });
704
848
  }
705
- return normaliseGame(body.game);
849
+ return normalisePair(body.pair);
706
850
  }
707
851
  /**
708
852
  * Returns available game pairs, optionally filtered by gameplay.
@@ -711,9 +855,9 @@ var GameModule = class {
711
855
  * `gameId` — use it directly as the MQTT topic `game/{gameId}/candle`.
712
856
  *
713
857
  * @example
714
- * const pairs = await client.game.availableGamePairs({ gameplayId: 'taptrading' });
715
- * const game = await client.game.get(pairs[0].pair, { gameplaySlug: pairs[0].gameplayId });
716
- * const ch = client.realtime?.subscribe(pairs[0].gameId ?? game.id, userId);
858
+ * const pairs = await client.pair.availableGamePairs({ gameplayId: 'taptrading' });
859
+ * const game = await client.pair.get(pairs[0].pair, { gameplaySlug: pairs[0].gameplayId });
860
+ * const ch = client.realtime?.subscribe(pairs[0].agencyPairId ?? game.id, userId);
717
861
  */
718
862
  async availableGamePairs(opts) {
719
863
  const variables = {};
@@ -723,10 +867,11 @@ var GameModule = class {
723
867
  variables,
724
868
  opts
725
869
  );
726
- return body.builderAvailableGamePairs.map(normaliseGamePairInfo);
870
+ return body.builderAvailableGamePairs.map(normalisePairInfo);
727
871
  }
728
- async chartHistory(gameId, limit, opts) {
729
- const variables = { gameId };
872
+ // REVIEW[bid-260602]: chartHistory param/variable renamed agencyPairId (was: gameId)
873
+ async chartHistory(agencyPairId, limit, opts) {
874
+ const variables = { agencyPairId };
730
875
  if (limit !== void 0) {
731
876
  variables.limit = limit;
732
877
  }
@@ -739,149 +884,6 @@ var GameModule = class {
739
884
  }
740
885
  };
741
886
 
742
- // src/modules/leaderboard/normalise.ts
743
- function normaliseLeaderboard(rows) {
744
- if (!Array.isArray(rows)) {
745
- throw new TaphubServerError("Invalid response from server", {
746
- code: "InvalidResponse"
747
- });
748
- }
749
- return rows.map((row) => ({
750
- userId: row.user_id,
751
- username: row.username,
752
- rank: row.rank,
753
- totalBids: row.total_bids,
754
- totalWins: row.total_wins,
755
- totalWagered: row.total_wagered,
756
- totalPayout: row.total_payout,
757
- gain: row.gain
758
- }));
759
- }
760
-
761
- // src/modules/leaderboard/queries.ts
762
- var LEADERBOARD_QUERY = `query Leaderboard($period: String!, $sort_by: String!) {
763
- leaderboard(period: $period, sort_by: $sort_by) {
764
- user_id username rank total_bids total_wins total_wagered total_payout gain
765
- }
766
- }`;
767
-
768
- // src/modules/leaderboard/index.ts
769
- var LeaderboardModule = class {
770
- #graphql;
771
- constructor(deps) {
772
- this.#graphql = deps.graphql;
773
- }
774
- async list(args) {
775
- const body = await this.#graphql.request(
776
- LEADERBOARD_QUERY,
777
- { period: args.period, sort_by: args.sortBy },
778
- args.signal ? { signal: args.signal } : void 0
779
- );
780
- return normaliseLeaderboard(body.leaderboard);
781
- }
782
- };
783
-
784
- // src/modules/locale/normalise.ts
785
- function normaliseLocaleResponse(node) {
786
- let translations = null;
787
- if (node.translations !== null) {
788
- try {
789
- translations = JSON.parse(node.translations);
790
- } catch (err) {
791
- throw new TaphubValidationError("Malformed translations JSON string from backend", {
792
- code: "InvalidLocaleResponse",
793
- details: {
794
- lang: node.lang,
795
- version: node.version,
796
- parseError: err instanceof Error ? err.message : String(err)
797
- }
798
- });
799
- }
800
- }
801
- return {
802
- lang: node.lang,
803
- version: node.version,
804
- notModified: node.notModified,
805
- translations
806
- };
807
- }
808
- function normaliseRefreshLocalesPayload(node) {
809
- let versions;
810
- try {
811
- versions = JSON.parse(node.versions);
812
- } catch (err) {
813
- throw new TaphubValidationError("Malformed versions JSON string from backend", {
814
- code: "InvalidRefreshLocalesPayload",
815
- details: {
816
- parseError: err instanceof Error ? err.message : String(err)
817
- }
818
- });
819
- }
820
- return {
821
- refreshed: node.refreshed,
822
- versions
823
- };
824
- }
825
-
826
- // src/modules/locale/queries.ts
827
- var LOCALES_QUERY = `query Locales($input: LocalesInput!) {
828
- locales(input: $input) {
829
- lang
830
- version
831
- notModified
832
- translations
833
- }
834
- }`;
835
- var REFRESH_LOCALES_MUTATION = `mutation RefreshLocales {
836
- refreshLocales {
837
- refreshed
838
- versions
839
- }
840
- }`;
841
-
842
- // src/modules/locale/index.ts
843
- var LocaleModule = class {
844
- #graphql;
845
- constructor(deps) {
846
- this.#graphql = deps.graphql;
847
- }
848
- /**
849
- * Fetch translations for a single language.
850
- *
851
- * Pass the last-seen `version` as `knownVersion` to opt into not-modified
852
- * short-circuit semantics: backend returns `{notModified: true, translations: null}`
853
- * when the cached body still matches, and the caller keeps prior state.
854
- *
855
- * Errors surface as TaphubError subclasses with `extensions.code` codes from
856
- * the backend (e.g. `LangNotSupported`, `InsufficientUpstream`, `FeatureDisabled`).
857
- * Caller decides the fallback strategy.
858
- */
859
- async get(lang, knownVersion, opts) {
860
- const input = { lang };
861
- if (knownVersion !== void 0 && knownVersion !== "") {
862
- input.knownVersion = knownVersion;
863
- }
864
- const body = await this.#graphql.request(LOCALES_QUERY, { input }, opts);
865
- return normaliseLocaleResponse(body.locales);
866
- }
867
- /**
868
- * Trigger an admin refresh — backend re-pulls the source Sheet, invalidates
869
- * its cache, and writes fresh entries for every supported language.
870
- *
871
- * Requires `X-API-Key` header equal to backend `InternalApiKey`. The transport
872
- * layer is responsible for attaching the header; this method does not handle
873
- * auth concerns directly.
874
- */
875
- async refresh(opts) {
876
- const body = await this.#graphql.request(
877
- REFRESH_LOCALES_MUTATION,
878
- {},
879
- opts
880
- );
881
- return normaliseRefreshLocalesPayload(body.refreshLocales);
882
- }
883
- };
884
-
885
887
  // src/transport/mqtt/index.ts
886
888
  var import_mqtt = __toESM(require("mqtt"));
887
889
 
@@ -952,6 +954,19 @@ function createMqttTransport(endpoint, opts = {}) {
952
954
  }
953
955
  });
954
956
  client.on("connect", () => {
957
+ const c = client;
958
+ for (const sub of subscriptions.values()) {
959
+ for (const t of sub.topics) c.subscribe(t, { qos: 1 });
960
+ }
961
+ for (const entry of candleSubscriptions.values()) {
962
+ c.subscribe(entry.topic, { qos: 0 });
963
+ }
964
+ for (const entry of statsSubscriptions.values()) {
965
+ c.subscribe(entry.topic, { qos: 0 });
966
+ }
967
+ for (const entry of walletSubscriptions.values()) {
968
+ c.subscribe(entry.topic, { qos: 1 });
969
+ }
955
970
  fireLifecycle({ kind: "connect", rttMs: Date.now() - connectStartedAt });
956
971
  });
957
972
  client.on("reconnect", () => {
@@ -1666,10 +1681,18 @@ function classifyBackendHealth(samples) {
1666
1681
  }
1667
1682
  return "ok";
1668
1683
  }
1669
- var isPoor = (m) => m.rtt > 450 || m.jitter > 150 || m.lossRate > 0.05;
1670
- var canLeavePoor = (m) => m.rtt < 350 && m.jitter < 120 && m.lossRate < 0.03;
1671
- var isFairUpper = (rtt) => rtt > 180;
1672
- var canReachGood = (rtt) => rtt < 120;
1684
+ var RTT_GOOD_TO_FAIR = 180;
1685
+ var RTT_FAIR_TO_GOOD = 120;
1686
+ var RTT_FAIR_TO_POOR = 450;
1687
+ var RTT_POOR_TO_FAIR = 350;
1688
+ var JITTER_POOR = 150;
1689
+ var JITTER_LEAVE_POOR = 120;
1690
+ var LOSS_POOR = 0.05;
1691
+ var LOSS_LEAVE_POOR = 0.03;
1692
+ var isPoor = (m) => m.rtt > RTT_FAIR_TO_POOR || m.jitter > JITTER_POOR || m.lossRate > LOSS_POOR;
1693
+ var canLeavePoor = (m) => m.rtt < RTT_POOR_TO_FAIR && m.jitter < JITTER_LEAVE_POOR && m.lossRate < LOSS_LEAVE_POOR;
1694
+ var isFairUpper = (rtt) => rtt > RTT_GOOD_TO_FAIR;
1695
+ var canReachGood = (rtt) => rtt < RTT_FAIR_TO_GOOD;
1673
1696
  function classifyNetworkLevel(metrics, current) {
1674
1697
  if (current === "offline") {
1675
1698
  return isPoor(metrics) ? "poor" : "fair";
@@ -1796,13 +1819,11 @@ var NetworkQualityMonitor = class {
1796
1819
  if (bucket.length > WINDOW_MAX_SAMPLES_PER_SOURCE) {
1797
1820
  bucket.shift();
1798
1821
  }
1799
- if (sample.source !== "connection" && sample.source !== "browser") {
1800
- if (sample.reason === "ok") {
1801
- this.lastSuccessfulHttpAt = sample.ts;
1802
- this.smoother.add(sample.rtt, this.deriveMetrics());
1803
- } else if (sample.reason === "network" || sample.reason === "timeout") {
1804
- this.smoother.add(sample.rtt, this.deriveMetrics());
1805
- }
1822
+ if (sample.reason === "ok" && sample.source !== "connection" && sample.source !== "browser") {
1823
+ this.lastSuccessfulHttpAt = sample.ts;
1824
+ }
1825
+ if (sample.source === "mqtt" && sample.reason === "ok") {
1826
+ this.smoother.add(sample.rtt, this.deriveMetrics());
1806
1827
  }
1807
1828
  this.recompute(prevMqttConnectedOverride);
1808
1829
  }
@@ -1973,17 +1994,15 @@ var NetworkQualityMonitor = class {
1973
1994
  (s) => s.reason === "network" || s.reason === "timeout" || s.reason === "offline"
1974
1995
  ).length;
1975
1996
  const lossRate = total > 0 ? lossCount / total : 0;
1976
- const okRtts = httpSamples.filter(
1977
- (s) => s.reason === "ok" || s.reason === "backend5xx" || s.reason === "backend4xx" || s.reason === "gqlError"
1978
- ).map((s) => s.rtt).filter((r) => Number.isFinite(r));
1997
+ const mqttRtts = samples.filter((s) => s.source === "mqtt" && s.reason === "ok").map((s) => s.rtt).filter((r) => Number.isFinite(r));
1979
1998
  let jitter = 0;
1980
- if (okRtts.length > 1) {
1981
- const mean = okRtts.reduce((a, b) => a + b, 0) / okRtts.length;
1982
- const variance = okRtts.reduce((a, b) => a + (b - mean) ** 2, 0) / okRtts.length;
1999
+ if (mqttRtts.length > 1) {
2000
+ const mean = mqttRtts.reduce((a, b) => a + b, 0) / mqttRtts.length;
2001
+ const variance = mqttRtts.reduce((a, b) => a + (b - mean) ** 2, 0) / mqttRtts.length;
1983
2002
  jitter = Math.sqrt(variance);
1984
2003
  }
1985
2004
  const connectionFallback = samples.filter((s) => s.source === "connection" && Number.isFinite(s.rtt)).slice(-1)[0]?.rtt;
1986
- const emaForLevel = this.smoother.ema > 0 ? this.smoother.ema : okRtts.length > 0 ? okRtts[okRtts.length - 1] ?? 0 : connectionFallback ?? 0;
2005
+ const emaForLevel = this.smoother.ema > 0 ? this.smoother.ema : mqttRtts.length > 0 ? mqttRtts[mqttRtts.length - 1] ?? 0 : connectionFallback ?? 0;
1987
2006
  return { jitter, lossRate, emaForLevel };
1988
2007
  }
1989
2008
  isHardOffline() {
@@ -2536,8 +2555,9 @@ var TaphubClient = class {
2536
2555
  user;
2537
2556
  /** @readonly Auth module — reassignment has no effect at runtime. */
2538
2557
  auth;
2539
- /** @readonly Game module — reassignment has no effect at runtime. */
2540
- game;
2558
+ /** @readonly Pair module — reassignment has no effect at runtime. */
2559
+ // REVIEW[bid-260602]: field pair: PairModule (was: game: GameModule)
2560
+ pair;
2541
2561
  /** @readonly Bid module — reassignment has no effect at runtime. */
2542
2562
  bid;
2543
2563
  /** @readonly Leaderboard module — reassignment has no effect at runtime. */
@@ -2621,7 +2641,7 @@ var TaphubClient = class {
2621
2641
  this.user.clearCurrencies();
2622
2642
  }
2623
2643
  });
2624
- this.game = new GameModule({ graphql: this.#graphql });
2644
+ this.pair = new PairModule({ graphql: this.#graphql });
2625
2645
  this.bid = new BidModule({ graphql: this.#graphql });
2626
2646
  this.leaderboard = new LeaderboardModule({ graphql: this.#graphql });
2627
2647
  this.agencyPairs = new AgencyPairModule({ graphql: this.#graphql });
@@ -2644,8 +2664,8 @@ var TaphubClient = class {
2644
2664
  enumerable: true,
2645
2665
  configurable: false
2646
2666
  });
2647
- Object.defineProperty(this, "game", {
2648
- value: this.game,
2667
+ Object.defineProperty(this, "pair", {
2668
+ value: this.pair,
2649
2669
  writable: false,
2650
2670
  enumerable: true,
2651
2671
  configurable: false
@@ -2807,10 +2827,10 @@ function calculateProbWin_v2(time1, time2, _price1, price2, creationTime, curren
2807
2827
  AgencyPairModule,
2808
2828
  AuthModule,
2809
2829
  BidModule,
2810
- GameModule,
2811
2830
  LeaderboardModule,
2812
2831
  LocaleModule,
2813
2832
  NetworkQualityMonitor,
2833
+ PairModule,
2814
2834
  RealtimeModule,
2815
2835
  TaphubAuthError,
2816
2836
  TaphubClient,