@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.js CHANGED
@@ -509,8 +509,151 @@ var BidModule = class {
509
509
  }
510
510
  };
511
511
 
512
- // src/modules/game/normalise.ts
513
- function normaliseGame(node) {
512
+ // src/modules/leaderboard/normalise.ts
513
+ function normaliseLeaderboard(rows) {
514
+ if (!Array.isArray(rows)) {
515
+ throw new TaphubServerError("Invalid response from server", {
516
+ code: "InvalidResponse"
517
+ });
518
+ }
519
+ return rows.map((row) => ({
520
+ userId: row.user_id,
521
+ username: row.username,
522
+ rank: row.rank,
523
+ totalBids: row.total_bids,
524
+ totalWins: row.total_wins,
525
+ totalWagered: row.total_wagered,
526
+ totalPayout: row.total_payout,
527
+ gain: row.gain
528
+ }));
529
+ }
530
+
531
+ // src/modules/leaderboard/queries.ts
532
+ var LEADERBOARD_QUERY = `query Leaderboard($period: String!, $sort_by: String!) {
533
+ leaderboard(period: $period, sort_by: $sort_by) {
534
+ user_id username rank total_bids total_wins total_wagered total_payout gain
535
+ }
536
+ }`;
537
+
538
+ // src/modules/leaderboard/index.ts
539
+ var LeaderboardModule = class {
540
+ #graphql;
541
+ constructor(deps) {
542
+ this.#graphql = deps.graphql;
543
+ }
544
+ async list(args) {
545
+ const body = await this.#graphql.request(
546
+ LEADERBOARD_QUERY,
547
+ { period: args.period, sort_by: args.sortBy },
548
+ args.signal ? { signal: args.signal } : void 0
549
+ );
550
+ return normaliseLeaderboard(body.leaderboard);
551
+ }
552
+ };
553
+
554
+ // src/modules/locale/normalise.ts
555
+ function normaliseLocaleResponse(node) {
556
+ let translations = null;
557
+ if (node.translations !== null) {
558
+ try {
559
+ translations = JSON.parse(node.translations);
560
+ } catch (err) {
561
+ throw new TaphubValidationError("Malformed translations JSON string from backend", {
562
+ code: "InvalidLocaleResponse",
563
+ details: {
564
+ lang: node.lang,
565
+ version: node.version,
566
+ parseError: err instanceof Error ? err.message : String(err)
567
+ }
568
+ });
569
+ }
570
+ }
571
+ return {
572
+ lang: node.lang,
573
+ version: node.version,
574
+ notModified: node.notModified,
575
+ translations
576
+ };
577
+ }
578
+ function normaliseRefreshLocalesPayload(node) {
579
+ let versions;
580
+ try {
581
+ versions = JSON.parse(node.versions);
582
+ } catch (err) {
583
+ throw new TaphubValidationError("Malformed versions JSON string from backend", {
584
+ code: "InvalidRefreshLocalesPayload",
585
+ details: {
586
+ parseError: err instanceof Error ? err.message : String(err)
587
+ }
588
+ });
589
+ }
590
+ return {
591
+ refreshed: node.refreshed,
592
+ versions
593
+ };
594
+ }
595
+
596
+ // src/modules/locale/queries.ts
597
+ var LOCALES_QUERY = `query Locales($input: LocalesInput!) {
598
+ locales(input: $input) {
599
+ lang
600
+ version
601
+ notModified
602
+ translations
603
+ }
604
+ }`;
605
+ var REFRESH_LOCALES_MUTATION = `mutation RefreshLocales {
606
+ refreshLocales {
607
+ refreshed
608
+ versions
609
+ }
610
+ }`;
611
+
612
+ // src/modules/locale/index.ts
613
+ var LocaleModule = class {
614
+ #graphql;
615
+ constructor(deps) {
616
+ this.#graphql = deps.graphql;
617
+ }
618
+ /**
619
+ * Fetch translations for a single language.
620
+ *
621
+ * Pass the last-seen `version` as `knownVersion` to opt into not-modified
622
+ * short-circuit semantics: backend returns `{notModified: true, translations: null}`
623
+ * when the cached body still matches, and the caller keeps prior state.
624
+ *
625
+ * Errors surface as TaphubError subclasses with `extensions.code` codes from
626
+ * the backend (e.g. `LangNotSupported`, `InsufficientUpstream`, `FeatureDisabled`).
627
+ * Caller decides the fallback strategy.
628
+ */
629
+ async get(lang, knownVersion, opts) {
630
+ const input = { lang };
631
+ if (knownVersion !== void 0 && knownVersion !== "") {
632
+ input.knownVersion = knownVersion;
633
+ }
634
+ const body = await this.#graphql.request(LOCALES_QUERY, { input }, opts);
635
+ return normaliseLocaleResponse(body.locales);
636
+ }
637
+ /**
638
+ * Trigger an admin refresh — backend re-pulls the source Sheet, invalidates
639
+ * its cache, and writes fresh entries for every supported language.
640
+ *
641
+ * Requires `X-API-Key` header equal to backend `InternalApiKey`. The transport
642
+ * layer is responsible for attaching the header; this method does not handle
643
+ * auth concerns directly.
644
+ */
645
+ async refresh(opts) {
646
+ const body = await this.#graphql.request(
647
+ REFRESH_LOCALES_MUTATION,
648
+ {},
649
+ opts
650
+ );
651
+ return normaliseRefreshLocalesPayload(body.refreshLocales);
652
+ }
653
+ };
654
+
655
+ // src/modules/pair/normalise.ts
656
+ function normalisePair(node) {
514
657
  if (typeof node.id !== "string" || node.id === "" || typeof node.pair !== "string" || node.pair === "" || typeof node.status !== "string" || node.status === "" || !node.config) {
515
658
  throw new TaphubServerError("Invalid response from server", {
516
659
  code: "InvalidResponse"
@@ -569,7 +712,7 @@ function normaliseCandles(list) {
569
712
  coefMults: c.coefMults
570
713
  }));
571
714
  }
572
- function normaliseGamePairInfo(node) {
715
+ function normalisePairInfo(node) {
573
716
  if (typeof node.id !== "string" || node.id === "" || typeof node.pair !== "string" || node.pair === "") {
574
717
  throw new TaphubServerError("Invalid response from server", { code: "InvalidResponse" });
575
718
  }
@@ -579,13 +722,14 @@ function normaliseGamePairInfo(node) {
579
722
  gameplayId: node.gameplayId,
580
723
  gameplayName: node.gameplayName,
581
724
  source: node.source,
582
- gameId: node.gameId ?? null
725
+ // REVIEW[bid-260602]: maps node.agencyPairId (was: node.gameId)
726
+ agencyPairId: node.agencyPairId ?? null
583
727
  };
584
728
  }
585
729
 
586
- // src/modules/game/queries.ts
587
- var GAME_QUERY = `query Game($pair: String!, $gameplaySlug: String) {
588
- game(pair: $pair, gameplaySlug: $gameplaySlug) {
730
+ // src/modules/pair/queries.ts
731
+ var PAIR_QUERY = `query Pair($pair: String!, $gameplaySlug: String) {
732
+ pair(pair: $pair, gameplaySlug: $gameplaySlug) {
589
733
  id pair status created_at
590
734
  config {
591
735
  gridConfig { cellSizeTime cellSizeValue candleSize baseline baselineTime }
@@ -594,8 +738,8 @@ var GAME_QUERY = `query Game($pair: String!, $gameplaySlug: String) {
594
738
  }
595
739
  }
596
740
  }`;
597
- var CHART_HISTORY_QUERY = `query ChartHistory($gameId: ID!, $limit: Int) {
598
- chartHistory(gameId: $gameId, limit: $limit) {
741
+ var CHART_HISTORY_QUERY = `query ChartHistory($agencyPairId: ID!, $limit: Int) {
742
+ chartHistory(agencyPairId: $agencyPairId, limit: $limit) {
599
743
  time o h l c volatility coefMults
600
744
  }
601
745
  }`;
@@ -606,12 +750,12 @@ var BUILDER_AVAILABLE_GAME_PAIRS_QUERY = `query BuilderAvailableGamePairs($gamep
606
750
  gameplayId
607
751
  gameplayName
608
752
  source
609
- gameId
753
+ agencyPairId
610
754
  }
611
755
  }`;
612
756
 
613
- // src/modules/game/index.ts
614
- var GameModule = class {
757
+ // src/modules/pair/index.ts
758
+ var PairModule = class {
615
759
  #graphql;
616
760
  constructor(deps) {
617
761
  this.#graphql = deps.graphql;
@@ -621,7 +765,7 @@ var GameModule = class {
621
765
  if (opts?.gameplaySlug) variables.gameplaySlug = opts.gameplaySlug;
622
766
  let body;
623
767
  try {
624
- body = await this.#graphql.request(GAME_QUERY, variables, opts);
768
+ body = await this.#graphql.request(PAIR_QUERY, variables, opts);
625
769
  } catch (err) {
626
770
  if (err instanceof TaphubValidationError && err.code === "NotExist") {
627
771
  throw new TaphubValidationError("Game not found for pair", {
@@ -631,13 +775,13 @@ var GameModule = class {
631
775
  }
632
776
  throw err;
633
777
  }
634
- if (body.game === null) {
778
+ if (body.pair === null) {
635
779
  throw new TaphubValidationError("Game not found for pair", {
636
780
  code: "GameNotFound",
637
781
  details: { pair }
638
782
  });
639
783
  }
640
- return normaliseGame(body.game);
784
+ return normalisePair(body.pair);
641
785
  }
642
786
  /**
643
787
  * Returns available game pairs, optionally filtered by gameplay.
@@ -646,9 +790,9 @@ var GameModule = class {
646
790
  * `gameId` — use it directly as the MQTT topic `game/{gameId}/candle`.
647
791
  *
648
792
  * @example
649
- * const pairs = await client.game.availableGamePairs({ gameplayId: 'taptrading' });
650
- * const game = await client.game.get(pairs[0].pair, { gameplaySlug: pairs[0].gameplayId });
651
- * const ch = client.realtime?.subscribe(pairs[0].gameId ?? game.id, userId);
793
+ * const pairs = await client.pair.availableGamePairs({ gameplayId: 'taptrading' });
794
+ * const game = await client.pair.get(pairs[0].pair, { gameplaySlug: pairs[0].gameplayId });
795
+ * const ch = client.realtime?.subscribe(pairs[0].agencyPairId ?? game.id, userId);
652
796
  */
653
797
  async availableGamePairs(opts) {
654
798
  const variables = {};
@@ -658,10 +802,11 @@ var GameModule = class {
658
802
  variables,
659
803
  opts
660
804
  );
661
- return body.builderAvailableGamePairs.map(normaliseGamePairInfo);
805
+ return body.builderAvailableGamePairs.map(normalisePairInfo);
662
806
  }
663
- async chartHistory(gameId, limit, opts) {
664
- const variables = { gameId };
807
+ // REVIEW[bid-260602]: chartHistory param/variable renamed agencyPairId (was: gameId)
808
+ async chartHistory(agencyPairId, limit, opts) {
809
+ const variables = { agencyPairId };
665
810
  if (limit !== void 0) {
666
811
  variables.limit = limit;
667
812
  }
@@ -674,149 +819,6 @@ var GameModule = class {
674
819
  }
675
820
  };
676
821
 
677
- // src/modules/leaderboard/normalise.ts
678
- function normaliseLeaderboard(rows) {
679
- if (!Array.isArray(rows)) {
680
- throw new TaphubServerError("Invalid response from server", {
681
- code: "InvalidResponse"
682
- });
683
- }
684
- return rows.map((row) => ({
685
- userId: row.user_id,
686
- username: row.username,
687
- rank: row.rank,
688
- totalBids: row.total_bids,
689
- totalWins: row.total_wins,
690
- totalWagered: row.total_wagered,
691
- totalPayout: row.total_payout,
692
- gain: row.gain
693
- }));
694
- }
695
-
696
- // src/modules/leaderboard/queries.ts
697
- var LEADERBOARD_QUERY = `query Leaderboard($period: String!, $sort_by: String!) {
698
- leaderboard(period: $period, sort_by: $sort_by) {
699
- user_id username rank total_bids total_wins total_wagered total_payout gain
700
- }
701
- }`;
702
-
703
- // src/modules/leaderboard/index.ts
704
- var LeaderboardModule = class {
705
- #graphql;
706
- constructor(deps) {
707
- this.#graphql = deps.graphql;
708
- }
709
- async list(args) {
710
- const body = await this.#graphql.request(
711
- LEADERBOARD_QUERY,
712
- { period: args.period, sort_by: args.sortBy },
713
- args.signal ? { signal: args.signal } : void 0
714
- );
715
- return normaliseLeaderboard(body.leaderboard);
716
- }
717
- };
718
-
719
- // src/modules/locale/normalise.ts
720
- function normaliseLocaleResponse(node) {
721
- let translations = null;
722
- if (node.translations !== null) {
723
- try {
724
- translations = JSON.parse(node.translations);
725
- } catch (err) {
726
- throw new TaphubValidationError("Malformed translations JSON string from backend", {
727
- code: "InvalidLocaleResponse",
728
- details: {
729
- lang: node.lang,
730
- version: node.version,
731
- parseError: err instanceof Error ? err.message : String(err)
732
- }
733
- });
734
- }
735
- }
736
- return {
737
- lang: node.lang,
738
- version: node.version,
739
- notModified: node.notModified,
740
- translations
741
- };
742
- }
743
- function normaliseRefreshLocalesPayload(node) {
744
- let versions;
745
- try {
746
- versions = JSON.parse(node.versions);
747
- } catch (err) {
748
- throw new TaphubValidationError("Malformed versions JSON string from backend", {
749
- code: "InvalidRefreshLocalesPayload",
750
- details: {
751
- parseError: err instanceof Error ? err.message : String(err)
752
- }
753
- });
754
- }
755
- return {
756
- refreshed: node.refreshed,
757
- versions
758
- };
759
- }
760
-
761
- // src/modules/locale/queries.ts
762
- var LOCALES_QUERY = `query Locales($input: LocalesInput!) {
763
- locales(input: $input) {
764
- lang
765
- version
766
- notModified
767
- translations
768
- }
769
- }`;
770
- var REFRESH_LOCALES_MUTATION = `mutation RefreshLocales {
771
- refreshLocales {
772
- refreshed
773
- versions
774
- }
775
- }`;
776
-
777
- // src/modules/locale/index.ts
778
- var LocaleModule = class {
779
- #graphql;
780
- constructor(deps) {
781
- this.#graphql = deps.graphql;
782
- }
783
- /**
784
- * Fetch translations for a single language.
785
- *
786
- * Pass the last-seen `version` as `knownVersion` to opt into not-modified
787
- * short-circuit semantics: backend returns `{notModified: true, translations: null}`
788
- * when the cached body still matches, and the caller keeps prior state.
789
- *
790
- * Errors surface as TaphubError subclasses with `extensions.code` codes from
791
- * the backend (e.g. `LangNotSupported`, `InsufficientUpstream`, `FeatureDisabled`).
792
- * Caller decides the fallback strategy.
793
- */
794
- async get(lang, knownVersion, opts) {
795
- const input = { lang };
796
- if (knownVersion !== void 0 && knownVersion !== "") {
797
- input.knownVersion = knownVersion;
798
- }
799
- const body = await this.#graphql.request(LOCALES_QUERY, { input }, opts);
800
- return normaliseLocaleResponse(body.locales);
801
- }
802
- /**
803
- * Trigger an admin refresh — backend re-pulls the source Sheet, invalidates
804
- * its cache, and writes fresh entries for every supported language.
805
- *
806
- * Requires `X-API-Key` header equal to backend `InternalApiKey`. The transport
807
- * layer is responsible for attaching the header; this method does not handle
808
- * auth concerns directly.
809
- */
810
- async refresh(opts) {
811
- const body = await this.#graphql.request(
812
- REFRESH_LOCALES_MUTATION,
813
- {},
814
- opts
815
- );
816
- return normaliseRefreshLocalesPayload(body.refreshLocales);
817
- }
818
- };
819
-
820
822
  // src/transport/mqtt/index.ts
821
823
  import mqtt from "mqtt";
822
824
 
@@ -887,6 +889,19 @@ function createMqttTransport(endpoint, opts = {}) {
887
889
  }
888
890
  });
889
891
  client.on("connect", () => {
892
+ const c = client;
893
+ for (const sub of subscriptions.values()) {
894
+ for (const t of sub.topics) c.subscribe(t, { qos: 1 });
895
+ }
896
+ for (const entry of candleSubscriptions.values()) {
897
+ c.subscribe(entry.topic, { qos: 0 });
898
+ }
899
+ for (const entry of statsSubscriptions.values()) {
900
+ c.subscribe(entry.topic, { qos: 0 });
901
+ }
902
+ for (const entry of walletSubscriptions.values()) {
903
+ c.subscribe(entry.topic, { qos: 1 });
904
+ }
890
905
  fireLifecycle({ kind: "connect", rttMs: Date.now() - connectStartedAt });
891
906
  });
892
907
  client.on("reconnect", () => {
@@ -1601,10 +1616,18 @@ function classifyBackendHealth(samples) {
1601
1616
  }
1602
1617
  return "ok";
1603
1618
  }
1604
- var isPoor = (m) => m.rtt > 450 || m.jitter > 150 || m.lossRate > 0.05;
1605
- var canLeavePoor = (m) => m.rtt < 350 && m.jitter < 120 && m.lossRate < 0.03;
1606
- var isFairUpper = (rtt) => rtt > 180;
1607
- var canReachGood = (rtt) => rtt < 120;
1619
+ var RTT_GOOD_TO_FAIR = 180;
1620
+ var RTT_FAIR_TO_GOOD = 120;
1621
+ var RTT_FAIR_TO_POOR = 450;
1622
+ var RTT_POOR_TO_FAIR = 350;
1623
+ var JITTER_POOR = 150;
1624
+ var JITTER_LEAVE_POOR = 120;
1625
+ var LOSS_POOR = 0.05;
1626
+ var LOSS_LEAVE_POOR = 0.03;
1627
+ var isPoor = (m) => m.rtt > RTT_FAIR_TO_POOR || m.jitter > JITTER_POOR || m.lossRate > LOSS_POOR;
1628
+ var canLeavePoor = (m) => m.rtt < RTT_POOR_TO_FAIR && m.jitter < JITTER_LEAVE_POOR && m.lossRate < LOSS_LEAVE_POOR;
1629
+ var isFairUpper = (rtt) => rtt > RTT_GOOD_TO_FAIR;
1630
+ var canReachGood = (rtt) => rtt < RTT_FAIR_TO_GOOD;
1608
1631
  function classifyNetworkLevel(metrics, current) {
1609
1632
  if (current === "offline") {
1610
1633
  return isPoor(metrics) ? "poor" : "fair";
@@ -1731,13 +1754,11 @@ var NetworkQualityMonitor = class {
1731
1754
  if (bucket.length > WINDOW_MAX_SAMPLES_PER_SOURCE) {
1732
1755
  bucket.shift();
1733
1756
  }
1734
- if (sample.source !== "connection" && sample.source !== "browser") {
1735
- if (sample.reason === "ok") {
1736
- this.lastSuccessfulHttpAt = sample.ts;
1737
- this.smoother.add(sample.rtt, this.deriveMetrics());
1738
- } else if (sample.reason === "network" || sample.reason === "timeout") {
1739
- this.smoother.add(sample.rtt, this.deriveMetrics());
1740
- }
1757
+ if (sample.reason === "ok" && sample.source !== "connection" && sample.source !== "browser") {
1758
+ this.lastSuccessfulHttpAt = sample.ts;
1759
+ }
1760
+ if (sample.source === "mqtt" && sample.reason === "ok") {
1761
+ this.smoother.add(sample.rtt, this.deriveMetrics());
1741
1762
  }
1742
1763
  this.recompute(prevMqttConnectedOverride);
1743
1764
  }
@@ -1908,17 +1929,15 @@ var NetworkQualityMonitor = class {
1908
1929
  (s) => s.reason === "network" || s.reason === "timeout" || s.reason === "offline"
1909
1930
  ).length;
1910
1931
  const lossRate = total > 0 ? lossCount / total : 0;
1911
- const okRtts = httpSamples.filter(
1912
- (s) => s.reason === "ok" || s.reason === "backend5xx" || s.reason === "backend4xx" || s.reason === "gqlError"
1913
- ).map((s) => s.rtt).filter((r) => Number.isFinite(r));
1932
+ const mqttRtts = samples.filter((s) => s.source === "mqtt" && s.reason === "ok").map((s) => s.rtt).filter((r) => Number.isFinite(r));
1914
1933
  let jitter = 0;
1915
- if (okRtts.length > 1) {
1916
- const mean = okRtts.reduce((a, b) => a + b, 0) / okRtts.length;
1917
- const variance = okRtts.reduce((a, b) => a + (b - mean) ** 2, 0) / okRtts.length;
1934
+ if (mqttRtts.length > 1) {
1935
+ const mean = mqttRtts.reduce((a, b) => a + b, 0) / mqttRtts.length;
1936
+ const variance = mqttRtts.reduce((a, b) => a + (b - mean) ** 2, 0) / mqttRtts.length;
1918
1937
  jitter = Math.sqrt(variance);
1919
1938
  }
1920
1939
  const connectionFallback = samples.filter((s) => s.source === "connection" && Number.isFinite(s.rtt)).slice(-1)[0]?.rtt;
1921
- const emaForLevel = this.smoother.ema > 0 ? this.smoother.ema : okRtts.length > 0 ? okRtts[okRtts.length - 1] ?? 0 : connectionFallback ?? 0;
1940
+ const emaForLevel = this.smoother.ema > 0 ? this.smoother.ema : mqttRtts.length > 0 ? mqttRtts[mqttRtts.length - 1] ?? 0 : connectionFallback ?? 0;
1922
1941
  return { jitter, lossRate, emaForLevel };
1923
1942
  }
1924
1943
  isHardOffline() {
@@ -2471,8 +2490,9 @@ var TaphubClient = class {
2471
2490
  user;
2472
2491
  /** @readonly Auth module — reassignment has no effect at runtime. */
2473
2492
  auth;
2474
- /** @readonly Game module — reassignment has no effect at runtime. */
2475
- game;
2493
+ /** @readonly Pair module — reassignment has no effect at runtime. */
2494
+ // REVIEW[bid-260602]: field pair: PairModule (was: game: GameModule)
2495
+ pair;
2476
2496
  /** @readonly Bid module — reassignment has no effect at runtime. */
2477
2497
  bid;
2478
2498
  /** @readonly Leaderboard module — reassignment has no effect at runtime. */
@@ -2556,7 +2576,7 @@ var TaphubClient = class {
2556
2576
  this.user.clearCurrencies();
2557
2577
  }
2558
2578
  });
2559
- this.game = new GameModule({ graphql: this.#graphql });
2579
+ this.pair = new PairModule({ graphql: this.#graphql });
2560
2580
  this.bid = new BidModule({ graphql: this.#graphql });
2561
2581
  this.leaderboard = new LeaderboardModule({ graphql: this.#graphql });
2562
2582
  this.agencyPairs = new AgencyPairModule({ graphql: this.#graphql });
@@ -2579,8 +2599,8 @@ var TaphubClient = class {
2579
2599
  enumerable: true,
2580
2600
  configurable: false
2581
2601
  });
2582
- Object.defineProperty(this, "game", {
2583
- value: this.game,
2602
+ Object.defineProperty(this, "pair", {
2603
+ value: this.pair,
2584
2604
  writable: false,
2585
2605
  enumerable: true,
2586
2606
  configurable: false
@@ -2741,10 +2761,10 @@ export {
2741
2761
  AgencyPairModule,
2742
2762
  AuthModule,
2743
2763
  BidModule,
2744
- GameModule,
2745
2764
  LeaderboardModule,
2746
2765
  LocaleModule,
2747
2766
  NetworkQualityMonitor,
2767
+ PairModule,
2748
2768
  RealtimeModule,
2749
2769
  TaphubAuthError,
2750
2770
  TaphubClient,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taphubhq/sdk-core",
3
- "version": "0.15.3",
3
+ "version": "0.16.1",
4
4
  "description": "Core SDK for building on the TabHub platform",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.cjs",