@taphubhq/sdk-core 0.25.3 → 0.25.5
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 +169 -5
- package/dist/index.d.mts +130 -2
- package/dist/index.d.ts +130 -2
- package/dist/index.js +166 -5
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -33,6 +33,8 @@ __export(index_exports, {
|
|
|
33
33
|
AgencyPairModule: () => AgencyPairModule,
|
|
34
34
|
AuthModule: () => AuthModule,
|
|
35
35
|
BidModule: () => BidModule,
|
|
36
|
+
CANDLE_EVENT: () => CANDLE_EVENT,
|
|
37
|
+
DEFAULT_CHART_HISTORY_LIMIT: () => DEFAULT_CHART_HISTORY_LIMIT,
|
|
36
38
|
LeaderboardModule: () => LeaderboardModule,
|
|
37
39
|
LocaleModule: () => LocaleModule,
|
|
38
40
|
NetworkQualityMonitor: () => NetworkQualityMonitor,
|
|
@@ -60,6 +62,7 @@ __export(index_exports, {
|
|
|
60
62
|
isWin: () => isWin,
|
|
61
63
|
normalCDF: () => normalCDF,
|
|
62
64
|
normalPDF: () => normalPDF,
|
|
65
|
+
normaliseLang: () => normaliseLang,
|
|
63
66
|
pairIdFromBidResultTopic: () => pairIdFromBidResultTopic
|
|
64
67
|
});
|
|
65
68
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -781,6 +784,39 @@ var LeaderboardModule = class {
|
|
|
781
784
|
}
|
|
782
785
|
};
|
|
783
786
|
|
|
787
|
+
// src/modules/locale/errorMessages.ts
|
|
788
|
+
function normaliseLang(tag) {
|
|
789
|
+
const clean = (tag || "").trim().toLowerCase();
|
|
790
|
+
if (!clean) return "en";
|
|
791
|
+
if (clean === "zh" || clean.startsWith("zh-")) {
|
|
792
|
+
if (/(^|-)(hant|tw|hk|mo)(-|$)/.test(clean)) return "zh-TW";
|
|
793
|
+
return "zh-CN";
|
|
794
|
+
}
|
|
795
|
+
return clean.split("-")[0] || "en";
|
|
796
|
+
}
|
|
797
|
+
function pickStringEntries(value) {
|
|
798
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
799
|
+
const out = {};
|
|
800
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
801
|
+
if (typeof entry === "string") out[key] = entry;
|
|
802
|
+
}
|
|
803
|
+
return out;
|
|
804
|
+
}
|
|
805
|
+
function interpolate(template, meta) {
|
|
806
|
+
const filled = template.replace(/\{(\w+)\}/g, (_match, key) => {
|
|
807
|
+
const value = meta?.[key];
|
|
808
|
+
if (value === void 0 || value === null) return "";
|
|
809
|
+
return String(value);
|
|
810
|
+
});
|
|
811
|
+
return filled.replace(/\s{2,}/g, " ").trim();
|
|
812
|
+
}
|
|
813
|
+
function resolveErrorMessage(code, catalog, fallback, meta) {
|
|
814
|
+
const row = catalog?.[code];
|
|
815
|
+
if (typeof row !== "string" || row.trim() === "") return fallback;
|
|
816
|
+
const filled = interpolate(row, meta);
|
|
817
|
+
return filled === "" ? fallback : filled;
|
|
818
|
+
}
|
|
819
|
+
|
|
784
820
|
// src/modules/locale/normalise.ts
|
|
785
821
|
function normaliseLocaleResponse(node) {
|
|
786
822
|
let translations = null;
|
|
@@ -805,6 +841,14 @@ function normaliseLocaleResponse(node) {
|
|
|
805
841
|
translations
|
|
806
842
|
};
|
|
807
843
|
}
|
|
844
|
+
function normaliseErrorMessagesResponse(node) {
|
|
845
|
+
return {
|
|
846
|
+
lang: node.lang,
|
|
847
|
+
version: node.version,
|
|
848
|
+
notModified: node.notModified,
|
|
849
|
+
messages: node.messages === null ? null : pickStringEntries(node.messages)
|
|
850
|
+
};
|
|
851
|
+
}
|
|
808
852
|
function normaliseRefreshLocalesPayload(node) {
|
|
809
853
|
let versions;
|
|
810
854
|
try {
|
|
@@ -838,10 +882,33 @@ var REFRESH_LOCALES_MUTATION = `mutation RefreshLocales {
|
|
|
838
882
|
versions
|
|
839
883
|
}
|
|
840
884
|
}`;
|
|
885
|
+
var ERROR_MESSAGES_QUERY = `query ErrorMessages($input: LocalesInput!) {
|
|
886
|
+
errorMessages(input: $input) {
|
|
887
|
+
lang
|
|
888
|
+
version
|
|
889
|
+
notModified
|
|
890
|
+
messages
|
|
891
|
+
}
|
|
892
|
+
}`;
|
|
841
893
|
|
|
842
894
|
// src/modules/locale/index.ts
|
|
843
895
|
var LocaleModule = class {
|
|
844
896
|
#graphql;
|
|
897
|
+
/**
|
|
898
|
+
* The loaded error-message catalog. Instance state, not module state, so two
|
|
899
|
+
* clients in one process (tests, multi-tenant hosts) never share a catalog.
|
|
900
|
+
*
|
|
901
|
+
* Null until a load succeeds. It is NEVER reset to null afterwards — a failed
|
|
902
|
+
* refresh keeps the last-known-good copy, because losing all error copy is
|
|
903
|
+
* strictly worse than showing slightly stale copy.
|
|
904
|
+
*/
|
|
905
|
+
#errorMessages = null;
|
|
906
|
+
/**
|
|
907
|
+
* In-flight catalog loads, keyed by normalised language. Collapses concurrent
|
|
908
|
+
* callers onto one request; entries are removed as soon as a load settles, so
|
|
909
|
+
* this never becomes a cache of results (or of failures).
|
|
910
|
+
*/
|
|
911
|
+
#errorMessagesInFlight = /* @__PURE__ */ new Map();
|
|
845
912
|
constructor(deps) {
|
|
846
913
|
this.#graphql = deps.graphql;
|
|
847
914
|
}
|
|
@@ -864,6 +931,94 @@ var LocaleModule = class {
|
|
|
864
931
|
const body = await this.#graphql.request(LOCALES_QUERY, { input }, opts);
|
|
865
932
|
return normaliseLocaleResponse(body.locales);
|
|
866
933
|
}
|
|
934
|
+
/**
|
|
935
|
+
* Fetch the error-message catalog for a single language.
|
|
936
|
+
*
|
|
937
|
+
* The throwing sibling of {@link loadErrorMessages}: errors surface as
|
|
938
|
+
* TaphubError subclasses with backend `extensions.code` values
|
|
939
|
+
* (`LangNotSupported`, `FeatureDisabled`, …), because a caller doing an
|
|
940
|
+
* explicit fetch wants to see why it failed. Hosts that just want copy on
|
|
941
|
+
* screen should call `loadErrorMessages` instead.
|
|
942
|
+
*
|
|
943
|
+
* `lang` is passed through as given — normalisation is `loadErrorMessages`'
|
|
944
|
+
* job, so an explicit caller keeps full control of the tag.
|
|
945
|
+
*/
|
|
946
|
+
async getErrorMessages(lang, knownVersion, opts) {
|
|
947
|
+
const input = { lang };
|
|
948
|
+
if (knownVersion !== void 0 && knownVersion !== "") {
|
|
949
|
+
input.knownVersion = knownVersion;
|
|
950
|
+
}
|
|
951
|
+
const body = await this.#graphql.request(
|
|
952
|
+
ERROR_MESSAGES_QUERY,
|
|
953
|
+
{ input },
|
|
954
|
+
opts
|
|
955
|
+
);
|
|
956
|
+
return normaliseErrorMessagesResponse(body.errorMessages);
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Load the error-message catalog for `lang` into this client.
|
|
960
|
+
*
|
|
961
|
+
* Background hydration with nobody waiting on it, so it **never throws and
|
|
962
|
+
* never rejects**. Every failure path — network error, backend down,
|
|
963
|
+
* `FeatureDisabled` when the source sheet is not configured, an unsupported
|
|
964
|
+
* tag, a malformed body — leaves the previously loaded catalog in place.
|
|
965
|
+
* Failing to translate an error must not itself become an error.
|
|
966
|
+
*
|
|
967
|
+
* The tag is normalised first (region dropped, Chinese scripts kept apart),
|
|
968
|
+
* so a host can hand over `navigator.language` untouched.
|
|
969
|
+
*
|
|
970
|
+
* Concurrent calls for the same language share one request. The catalog lives
|
|
971
|
+
* on the client while the thing that triggers a load (a mounted provider)
|
|
972
|
+
* lives a level below, so two providers sharing one client would otherwise
|
|
973
|
+
* fetch the same bytes into the same place twice.
|
|
974
|
+
*
|
|
975
|
+
* This is dedup, not caching — it lasts only while a request is in flight. A
|
|
976
|
+
* host calling again after one settles gets a fresh fetch.
|
|
977
|
+
*
|
|
978
|
+
* Deliberately takes NO AbortSignal. A follower piggybacking on someone
|
|
979
|
+
* else's request would otherwise have its outcome decided by whether that
|
|
980
|
+
* other caller happened to unmount first. Letting a background hydration run
|
|
981
|
+
* to completion costs one small request; stranding a caller costs all the
|
|
982
|
+
* error copy on screen.
|
|
983
|
+
*
|
|
984
|
+
* No persistence and no polling: the host decides when to call this. The
|
|
985
|
+
* React provider calls it on mount and on language change.
|
|
986
|
+
*/
|
|
987
|
+
async loadErrorMessages(lang) {
|
|
988
|
+
const key = normaliseLang(lang);
|
|
989
|
+
const inFlight = this.#errorMessagesInFlight.get(key);
|
|
990
|
+
if (inFlight) return inFlight;
|
|
991
|
+
const run = (async () => {
|
|
992
|
+
try {
|
|
993
|
+
const res = await this.getErrorMessages(key);
|
|
994
|
+
if (res.notModified || res.messages === null) return;
|
|
995
|
+
if (Object.keys(res.messages).length === 0) return;
|
|
996
|
+
this.#errorMessages = res.messages;
|
|
997
|
+
} catch {
|
|
998
|
+
} finally {
|
|
999
|
+
this.#errorMessagesInFlight.delete(key);
|
|
1000
|
+
}
|
|
1001
|
+
})();
|
|
1002
|
+
this.#errorMessagesInFlight.set(key, run);
|
|
1003
|
+
return run;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* The sentence to show for a backend error `code`.
|
|
1007
|
+
*
|
|
1008
|
+
* **Synchronous by design** — error copy is read when a toast is built, inside
|
|
1009
|
+
* a `catch` or an error handler, where React hooks are illegal. That is why
|
|
1010
|
+
* this is a plain method and not a hook.
|
|
1011
|
+
*
|
|
1012
|
+
* Returns `fallback` whenever the catalog cannot produce a usable sentence: no
|
|
1013
|
+
* catalog loaded yet, no row for the code, or a blank row. Never returns an
|
|
1014
|
+
* empty string, and never returns the raw code.
|
|
1015
|
+
*
|
|
1016
|
+
* `meta` fills `{name}` placeholders in a catalog row; any placeholder left
|
|
1017
|
+
* unfilled is stripped, so raw braces never reach the user.
|
|
1018
|
+
*/
|
|
1019
|
+
errorMessage(code, fallback, meta) {
|
|
1020
|
+
return resolveErrorMessage(code, this.#errorMessages, fallback, meta);
|
|
1021
|
+
}
|
|
867
1022
|
/**
|
|
868
1023
|
* Trigger an admin refresh — backend re-pulls the source Sheet, invalidates
|
|
869
1024
|
* its cache, and writes fresh entries for every supported language.
|
|
@@ -994,6 +1149,9 @@ var BUILDER_AVAILABLE_GAME_PAIRS_QUERY = `query BuilderAvailableGamePairs($gamep
|
|
|
994
1149
|
}
|
|
995
1150
|
}`;
|
|
996
1151
|
|
|
1152
|
+
// src/modules/pair/types.ts
|
|
1153
|
+
var DEFAULT_CHART_HISTORY_LIMIT = 600;
|
|
1154
|
+
|
|
997
1155
|
// src/modules/pair/index.ts
|
|
998
1156
|
var PairModule = class {
|
|
999
1157
|
#graphql;
|
|
@@ -1051,11 +1209,8 @@ var PairModule = class {
|
|
|
1051
1209
|
return body.builderAvailableGamePairs.map(normalisePairInfo);
|
|
1052
1210
|
}
|
|
1053
1211
|
// REVIEW[bid-260602-v2]: chartHistory param/variable renamed pairId (was: agencyPairId/gameId)
|
|
1054
|
-
async chartHistory(pairId, limit, opts) {
|
|
1055
|
-
const variables = { pairId };
|
|
1056
|
-
if (limit !== void 0) {
|
|
1057
|
-
variables.limit = limit;
|
|
1058
|
-
}
|
|
1212
|
+
async chartHistory(pairId, limit = DEFAULT_CHART_HISTORY_LIMIT, opts) {
|
|
1213
|
+
const variables = { pairId, limit };
|
|
1059
1214
|
const body = await this.#graphql.request(
|
|
1060
1215
|
CHART_HISTORY_QUERY,
|
|
1061
1216
|
variables,
|
|
@@ -3006,6 +3161,12 @@ var TaphubClient = class {
|
|
|
3006
3161
|
}
|
|
3007
3162
|
};
|
|
3008
3163
|
|
|
3164
|
+
// src/modules/realtime/types.ts
|
|
3165
|
+
var CANDLE_EVENT = {
|
|
3166
|
+
NEW: "new",
|
|
3167
|
+
UPDATE: "update"
|
|
3168
|
+
};
|
|
3169
|
+
|
|
3009
3170
|
// src/utils/coefficient.ts
|
|
3010
3171
|
function errorFunction(x) {
|
|
3011
3172
|
const a1 = 0.254829592;
|
|
@@ -3095,6 +3256,8 @@ function calculateProbWin_v2(time1, time2, _price1, price2, creationTime, curren
|
|
|
3095
3256
|
AgencyPairModule,
|
|
3096
3257
|
AuthModule,
|
|
3097
3258
|
BidModule,
|
|
3259
|
+
CANDLE_EVENT,
|
|
3260
|
+
DEFAULT_CHART_HISTORY_LIMIT,
|
|
3098
3261
|
LeaderboardModule,
|
|
3099
3262
|
LocaleModule,
|
|
3100
3263
|
NetworkQualityMonitor,
|
|
@@ -3122,5 +3285,6 @@ function calculateProbWin_v2(time1, time2, _price1, price2, creationTime, curren
|
|
|
3122
3285
|
isWin,
|
|
3123
3286
|
normalCDF,
|
|
3124
3287
|
normalPDF,
|
|
3288
|
+
normaliseLang,
|
|
3125
3289
|
pairIdFromBidResultTopic
|
|
3126
3290
|
});
|
package/dist/index.d.mts
CHANGED
|
@@ -454,6 +454,57 @@ interface LocaleRefreshResult {
|
|
|
454
454
|
refreshed: string[];
|
|
455
455
|
versions: Record<string, string>;
|
|
456
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* Flat error-message catalog for a single language: backend error code → sentence.
|
|
459
|
+
*
|
|
460
|
+
* Keys are the `extensions.code` values the backend returns on a rejection
|
|
461
|
+
* (`Bid_RateLimited`, `Wallet_WithdrawNetworkUnavailable`, …). Values may contain
|
|
462
|
+
* `{name}` placeholders filled from the error's meta at lookup time.
|
|
463
|
+
*/
|
|
464
|
+
type ErrorMessageCatalog = Record<string, string>;
|
|
465
|
+
/**
|
|
466
|
+
* Values available to fill a catalog row's `{name}` placeholders — normally the
|
|
467
|
+
* `meta` carried by the backend error. Values are stringified as-is; a caller who
|
|
468
|
+
* needs specific formatting (decimal places, currency) should format before passing.
|
|
469
|
+
*/
|
|
470
|
+
type ErrorMessageMeta = Record<string, unknown>;
|
|
471
|
+
/**
|
|
472
|
+
* Public response shape from LocaleModule.getErrorMessages().
|
|
473
|
+
*
|
|
474
|
+
* Mirrors {@link LocaleResponse}, with one wire-level difference: `messages`
|
|
475
|
+
* arrives as a real JSON object rather than a JSON-string, so there is no parse
|
|
476
|
+
* step. Non-string values are dropped during normalisation.
|
|
477
|
+
*
|
|
478
|
+
* `messages` is null when `notModified` is true (caller keeps prior state).
|
|
479
|
+
*/
|
|
480
|
+
interface ErrorMessagesResponse {
|
|
481
|
+
lang: string;
|
|
482
|
+
version: string;
|
|
483
|
+
notModified: boolean;
|
|
484
|
+
messages: ErrorMessageCatalog | null;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Pure helpers behind the error-message catalog.
|
|
489
|
+
*
|
|
490
|
+
* Kept free of I/O and of module state so the precedence rule, the language
|
|
491
|
+
* mapping and the template filling are all testable without a transport.
|
|
492
|
+
*/
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Reduce a host language tag to the catalog's column name.
|
|
496
|
+
*
|
|
497
|
+
* The sheet's columns are en, vi, es, hi, zh-TW, zh-CN, ja, pt, fr, de, it, tr,
|
|
498
|
+
* ko, ar. Only Chinese needs more than the primary subtag: zh-TW and zh-CN are
|
|
499
|
+
* different SCRIPTS, so collapsing both to "zh" would show Simplified text to a
|
|
500
|
+
* Traditional reader. Everything else drops its region.
|
|
501
|
+
*
|
|
502
|
+
* The supported-language list is deliberately NOT duplicated here — the server
|
|
503
|
+
* owns it, and an unsupported tag simply fails the fetch and leaves prior copy
|
|
504
|
+
* in place, which is the same outcome a stale local list would produce with
|
|
505
|
+
* more code.
|
|
506
|
+
*/
|
|
507
|
+
declare function normaliseLang(tag: string): string;
|
|
457
508
|
|
|
458
509
|
interface LocaleModuleDeps {
|
|
459
510
|
graphql: GraphQLTransport;
|
|
@@ -484,6 +535,66 @@ declare class LocaleModule {
|
|
|
484
535
|
get(lang: string, knownVersion?: string, opts?: {
|
|
485
536
|
signal?: AbortSignal;
|
|
486
537
|
}): Promise<LocaleResponse>;
|
|
538
|
+
/**
|
|
539
|
+
* Fetch the error-message catalog for a single language.
|
|
540
|
+
*
|
|
541
|
+
* The throwing sibling of {@link loadErrorMessages}: errors surface as
|
|
542
|
+
* TaphubError subclasses with backend `extensions.code` values
|
|
543
|
+
* (`LangNotSupported`, `FeatureDisabled`, …), because a caller doing an
|
|
544
|
+
* explicit fetch wants to see why it failed. Hosts that just want copy on
|
|
545
|
+
* screen should call `loadErrorMessages` instead.
|
|
546
|
+
*
|
|
547
|
+
* `lang` is passed through as given — normalisation is `loadErrorMessages`'
|
|
548
|
+
* job, so an explicit caller keeps full control of the tag.
|
|
549
|
+
*/
|
|
550
|
+
getErrorMessages(lang: string, knownVersion?: string, opts?: {
|
|
551
|
+
signal?: AbortSignal;
|
|
552
|
+
}): Promise<ErrorMessagesResponse>;
|
|
553
|
+
/**
|
|
554
|
+
* Load the error-message catalog for `lang` into this client.
|
|
555
|
+
*
|
|
556
|
+
* Background hydration with nobody waiting on it, so it **never throws and
|
|
557
|
+
* never rejects**. Every failure path — network error, backend down,
|
|
558
|
+
* `FeatureDisabled` when the source sheet is not configured, an unsupported
|
|
559
|
+
* tag, a malformed body — leaves the previously loaded catalog in place.
|
|
560
|
+
* Failing to translate an error must not itself become an error.
|
|
561
|
+
*
|
|
562
|
+
* The tag is normalised first (region dropped, Chinese scripts kept apart),
|
|
563
|
+
* so a host can hand over `navigator.language` untouched.
|
|
564
|
+
*
|
|
565
|
+
* Concurrent calls for the same language share one request. The catalog lives
|
|
566
|
+
* on the client while the thing that triggers a load (a mounted provider)
|
|
567
|
+
* lives a level below, so two providers sharing one client would otherwise
|
|
568
|
+
* fetch the same bytes into the same place twice.
|
|
569
|
+
*
|
|
570
|
+
* This is dedup, not caching — it lasts only while a request is in flight. A
|
|
571
|
+
* host calling again after one settles gets a fresh fetch.
|
|
572
|
+
*
|
|
573
|
+
* Deliberately takes NO AbortSignal. A follower piggybacking on someone
|
|
574
|
+
* else's request would otherwise have its outcome decided by whether that
|
|
575
|
+
* other caller happened to unmount first. Letting a background hydration run
|
|
576
|
+
* to completion costs one small request; stranding a caller costs all the
|
|
577
|
+
* error copy on screen.
|
|
578
|
+
*
|
|
579
|
+
* No persistence and no polling: the host decides when to call this. The
|
|
580
|
+
* React provider calls it on mount and on language change.
|
|
581
|
+
*/
|
|
582
|
+
loadErrorMessages(lang: string): Promise<void>;
|
|
583
|
+
/**
|
|
584
|
+
* The sentence to show for a backend error `code`.
|
|
585
|
+
*
|
|
586
|
+
* **Synchronous by design** — error copy is read when a toast is built, inside
|
|
587
|
+
* a `catch` or an error handler, where React hooks are illegal. That is why
|
|
588
|
+
* this is a plain method and not a hook.
|
|
589
|
+
*
|
|
590
|
+
* Returns `fallback` whenever the catalog cannot produce a usable sentence: no
|
|
591
|
+
* catalog loaded yet, no row for the code, or a blank row. Never returns an
|
|
592
|
+
* empty string, and never returns the raw code.
|
|
593
|
+
*
|
|
594
|
+
* `meta` fills `{name}` placeholders in a catalog row; any placeholder left
|
|
595
|
+
* unfilled is stripped, so raw braces never reach the user.
|
|
596
|
+
*/
|
|
597
|
+
errorMessage(code: string, fallback: string, meta?: ErrorMessageMeta): string;
|
|
487
598
|
/**
|
|
488
599
|
* Trigger an admin refresh — backend re-pulls the source Sheet, invalidates
|
|
489
600
|
* its cache, and writes fresh entries for every supported language.
|
|
@@ -598,6 +709,13 @@ interface PairInfo {
|
|
|
598
709
|
*/
|
|
599
710
|
agencyPairId: string | null;
|
|
600
711
|
}
|
|
712
|
+
/**
|
|
713
|
+
* Default number of candles fetched on initial chart load when the caller does
|
|
714
|
+
* not pass an explicit limit. The server clamps any request to its cache size
|
|
715
|
+
* (grid-api services.ChartHistoryMaxLimit = 1500), so this is purely how much
|
|
716
|
+
* history the chart shows by default — kept lower than the cap for faster init.
|
|
717
|
+
*/
|
|
718
|
+
declare const DEFAULT_CHART_HISTORY_LIMIT = 600;
|
|
601
719
|
interface Candle {
|
|
602
720
|
/** Unix epoch SECONDS (not ms). Multiply by 1000 for JS Date. */
|
|
603
721
|
time: number;
|
|
@@ -795,6 +913,16 @@ interface MqttTransport {
|
|
|
795
913
|
close(): void;
|
|
796
914
|
}
|
|
797
915
|
|
|
916
|
+
/**
|
|
917
|
+
* Candle tick kind. `new` = a fresh bar opened; `update` = the current bar was
|
|
918
|
+
* revised. Exported as named constants so consumers compare against these
|
|
919
|
+
* instead of bare string literals (a typo on a literal fails silently).
|
|
920
|
+
*/
|
|
921
|
+
declare const CANDLE_EVENT: {
|
|
922
|
+
readonly NEW: "new";
|
|
923
|
+
readonly UPDATE: "update";
|
|
924
|
+
};
|
|
925
|
+
type CandleEventType = (typeof CANDLE_EVENT)[keyof typeof CANDLE_EVENT];
|
|
798
926
|
/**
|
|
799
927
|
* Candle tick from MQTT.
|
|
800
928
|
* WARNING: `time` is Unix epoch MILLISECONDS.
|
|
@@ -802,7 +930,7 @@ interface MqttTransport {
|
|
|
802
930
|
* Do NOT multiply MQTT candle time by 1000.
|
|
803
931
|
*/
|
|
804
932
|
interface MqttCandleEvent {
|
|
805
|
-
type:
|
|
933
|
+
type: CandleEventType;
|
|
806
934
|
/** Unix epoch **milliseconds** — differs from GQL chartHistory (seconds) */
|
|
807
935
|
time: number;
|
|
808
936
|
o: number;
|
|
@@ -1304,4 +1432,4 @@ declare function adaptiveSimpson(f: (x: number) => number, a: number, b: number,
|
|
|
1304
1432
|
declare function calculateProbWin(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1305
1433
|
declare function calculateProbWin_v2(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1306
1434
|
|
|
1307
|
-
export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, type CancelBidResult, type Candle, type Constraints, type Currency, GameChannel, type GameConfig, type GridConfig, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, type MqttAcceptedBid, type MqttAgencyPairStatsEvent, type MqttAuthConfig, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type MqttWalletBalanceEvent, type MyRank, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type Pair, type PairInfo, PairModule, type PlaceBidInput, type RankBoard, type RankEntry, type RankPeriod, type RankSort, RealtimeModule, type Sample, type SampleReason, type SignalSource, type SortDir, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserBidsChannel, type UserBidsChannelEvents, UserModule, type UserPnL, type UserPnLPeriod, type Wallet as UserWallet, type Wallet$1 as Wallet, type WalletBalanceReason, WalletChannel, adaptiveSimpson, autoDetectStorage, calculateProbWin, calculateProbWin_v2, errorFunction, isCancelled, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF, pairIdFromBidResultTopic };
|
|
1435
|
+
export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, CANDLE_EVENT, type CancelBidResult, type Candle, type CandleEventType, type Constraints, type Currency, DEFAULT_CHART_HISTORY_LIMIT, type ErrorMessageCatalog, type ErrorMessageMeta, type ErrorMessagesResponse, GameChannel, type GameConfig, type GridConfig, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, type MqttAcceptedBid, type MqttAgencyPairStatsEvent, type MqttAuthConfig, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type MqttWalletBalanceEvent, type MyRank, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type Pair, type PairInfo, PairModule, type PlaceBidInput, type RankBoard, type RankEntry, type RankPeriod, type RankSort, RealtimeModule, type Sample, type SampleReason, type SignalSource, type SortDir, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserBidsChannel, type UserBidsChannelEvents, UserModule, type UserPnL, type UserPnLPeriod, type Wallet as UserWallet, type Wallet$1 as Wallet, type WalletBalanceReason, WalletChannel, adaptiveSimpson, autoDetectStorage, calculateProbWin, calculateProbWin_v2, errorFunction, isCancelled, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF, normaliseLang, pairIdFromBidResultTopic };
|
package/dist/index.d.ts
CHANGED
|
@@ -454,6 +454,57 @@ interface LocaleRefreshResult {
|
|
|
454
454
|
refreshed: string[];
|
|
455
455
|
versions: Record<string, string>;
|
|
456
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* Flat error-message catalog for a single language: backend error code → sentence.
|
|
459
|
+
*
|
|
460
|
+
* Keys are the `extensions.code` values the backend returns on a rejection
|
|
461
|
+
* (`Bid_RateLimited`, `Wallet_WithdrawNetworkUnavailable`, …). Values may contain
|
|
462
|
+
* `{name}` placeholders filled from the error's meta at lookup time.
|
|
463
|
+
*/
|
|
464
|
+
type ErrorMessageCatalog = Record<string, string>;
|
|
465
|
+
/**
|
|
466
|
+
* Values available to fill a catalog row's `{name}` placeholders — normally the
|
|
467
|
+
* `meta` carried by the backend error. Values are stringified as-is; a caller who
|
|
468
|
+
* needs specific formatting (decimal places, currency) should format before passing.
|
|
469
|
+
*/
|
|
470
|
+
type ErrorMessageMeta = Record<string, unknown>;
|
|
471
|
+
/**
|
|
472
|
+
* Public response shape from LocaleModule.getErrorMessages().
|
|
473
|
+
*
|
|
474
|
+
* Mirrors {@link LocaleResponse}, with one wire-level difference: `messages`
|
|
475
|
+
* arrives as a real JSON object rather than a JSON-string, so there is no parse
|
|
476
|
+
* step. Non-string values are dropped during normalisation.
|
|
477
|
+
*
|
|
478
|
+
* `messages` is null when `notModified` is true (caller keeps prior state).
|
|
479
|
+
*/
|
|
480
|
+
interface ErrorMessagesResponse {
|
|
481
|
+
lang: string;
|
|
482
|
+
version: string;
|
|
483
|
+
notModified: boolean;
|
|
484
|
+
messages: ErrorMessageCatalog | null;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Pure helpers behind the error-message catalog.
|
|
489
|
+
*
|
|
490
|
+
* Kept free of I/O and of module state so the precedence rule, the language
|
|
491
|
+
* mapping and the template filling are all testable without a transport.
|
|
492
|
+
*/
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Reduce a host language tag to the catalog's column name.
|
|
496
|
+
*
|
|
497
|
+
* The sheet's columns are en, vi, es, hi, zh-TW, zh-CN, ja, pt, fr, de, it, tr,
|
|
498
|
+
* ko, ar. Only Chinese needs more than the primary subtag: zh-TW and zh-CN are
|
|
499
|
+
* different SCRIPTS, so collapsing both to "zh" would show Simplified text to a
|
|
500
|
+
* Traditional reader. Everything else drops its region.
|
|
501
|
+
*
|
|
502
|
+
* The supported-language list is deliberately NOT duplicated here — the server
|
|
503
|
+
* owns it, and an unsupported tag simply fails the fetch and leaves prior copy
|
|
504
|
+
* in place, which is the same outcome a stale local list would produce with
|
|
505
|
+
* more code.
|
|
506
|
+
*/
|
|
507
|
+
declare function normaliseLang(tag: string): string;
|
|
457
508
|
|
|
458
509
|
interface LocaleModuleDeps {
|
|
459
510
|
graphql: GraphQLTransport;
|
|
@@ -484,6 +535,66 @@ declare class LocaleModule {
|
|
|
484
535
|
get(lang: string, knownVersion?: string, opts?: {
|
|
485
536
|
signal?: AbortSignal;
|
|
486
537
|
}): Promise<LocaleResponse>;
|
|
538
|
+
/**
|
|
539
|
+
* Fetch the error-message catalog for a single language.
|
|
540
|
+
*
|
|
541
|
+
* The throwing sibling of {@link loadErrorMessages}: errors surface as
|
|
542
|
+
* TaphubError subclasses with backend `extensions.code` values
|
|
543
|
+
* (`LangNotSupported`, `FeatureDisabled`, …), because a caller doing an
|
|
544
|
+
* explicit fetch wants to see why it failed. Hosts that just want copy on
|
|
545
|
+
* screen should call `loadErrorMessages` instead.
|
|
546
|
+
*
|
|
547
|
+
* `lang` is passed through as given — normalisation is `loadErrorMessages`'
|
|
548
|
+
* job, so an explicit caller keeps full control of the tag.
|
|
549
|
+
*/
|
|
550
|
+
getErrorMessages(lang: string, knownVersion?: string, opts?: {
|
|
551
|
+
signal?: AbortSignal;
|
|
552
|
+
}): Promise<ErrorMessagesResponse>;
|
|
553
|
+
/**
|
|
554
|
+
* Load the error-message catalog for `lang` into this client.
|
|
555
|
+
*
|
|
556
|
+
* Background hydration with nobody waiting on it, so it **never throws and
|
|
557
|
+
* never rejects**. Every failure path — network error, backend down,
|
|
558
|
+
* `FeatureDisabled` when the source sheet is not configured, an unsupported
|
|
559
|
+
* tag, a malformed body — leaves the previously loaded catalog in place.
|
|
560
|
+
* Failing to translate an error must not itself become an error.
|
|
561
|
+
*
|
|
562
|
+
* The tag is normalised first (region dropped, Chinese scripts kept apart),
|
|
563
|
+
* so a host can hand over `navigator.language` untouched.
|
|
564
|
+
*
|
|
565
|
+
* Concurrent calls for the same language share one request. The catalog lives
|
|
566
|
+
* on the client while the thing that triggers a load (a mounted provider)
|
|
567
|
+
* lives a level below, so two providers sharing one client would otherwise
|
|
568
|
+
* fetch the same bytes into the same place twice.
|
|
569
|
+
*
|
|
570
|
+
* This is dedup, not caching — it lasts only while a request is in flight. A
|
|
571
|
+
* host calling again after one settles gets a fresh fetch.
|
|
572
|
+
*
|
|
573
|
+
* Deliberately takes NO AbortSignal. A follower piggybacking on someone
|
|
574
|
+
* else's request would otherwise have its outcome decided by whether that
|
|
575
|
+
* other caller happened to unmount first. Letting a background hydration run
|
|
576
|
+
* to completion costs one small request; stranding a caller costs all the
|
|
577
|
+
* error copy on screen.
|
|
578
|
+
*
|
|
579
|
+
* No persistence and no polling: the host decides when to call this. The
|
|
580
|
+
* React provider calls it on mount and on language change.
|
|
581
|
+
*/
|
|
582
|
+
loadErrorMessages(lang: string): Promise<void>;
|
|
583
|
+
/**
|
|
584
|
+
* The sentence to show for a backend error `code`.
|
|
585
|
+
*
|
|
586
|
+
* **Synchronous by design** — error copy is read when a toast is built, inside
|
|
587
|
+
* a `catch` or an error handler, where React hooks are illegal. That is why
|
|
588
|
+
* this is a plain method and not a hook.
|
|
589
|
+
*
|
|
590
|
+
* Returns `fallback` whenever the catalog cannot produce a usable sentence: no
|
|
591
|
+
* catalog loaded yet, no row for the code, or a blank row. Never returns an
|
|
592
|
+
* empty string, and never returns the raw code.
|
|
593
|
+
*
|
|
594
|
+
* `meta` fills `{name}` placeholders in a catalog row; any placeholder left
|
|
595
|
+
* unfilled is stripped, so raw braces never reach the user.
|
|
596
|
+
*/
|
|
597
|
+
errorMessage(code: string, fallback: string, meta?: ErrorMessageMeta): string;
|
|
487
598
|
/**
|
|
488
599
|
* Trigger an admin refresh — backend re-pulls the source Sheet, invalidates
|
|
489
600
|
* its cache, and writes fresh entries for every supported language.
|
|
@@ -598,6 +709,13 @@ interface PairInfo {
|
|
|
598
709
|
*/
|
|
599
710
|
agencyPairId: string | null;
|
|
600
711
|
}
|
|
712
|
+
/**
|
|
713
|
+
* Default number of candles fetched on initial chart load when the caller does
|
|
714
|
+
* not pass an explicit limit. The server clamps any request to its cache size
|
|
715
|
+
* (grid-api services.ChartHistoryMaxLimit = 1500), so this is purely how much
|
|
716
|
+
* history the chart shows by default — kept lower than the cap for faster init.
|
|
717
|
+
*/
|
|
718
|
+
declare const DEFAULT_CHART_HISTORY_LIMIT = 600;
|
|
601
719
|
interface Candle {
|
|
602
720
|
/** Unix epoch SECONDS (not ms). Multiply by 1000 for JS Date. */
|
|
603
721
|
time: number;
|
|
@@ -795,6 +913,16 @@ interface MqttTransport {
|
|
|
795
913
|
close(): void;
|
|
796
914
|
}
|
|
797
915
|
|
|
916
|
+
/**
|
|
917
|
+
* Candle tick kind. `new` = a fresh bar opened; `update` = the current bar was
|
|
918
|
+
* revised. Exported as named constants so consumers compare against these
|
|
919
|
+
* instead of bare string literals (a typo on a literal fails silently).
|
|
920
|
+
*/
|
|
921
|
+
declare const CANDLE_EVENT: {
|
|
922
|
+
readonly NEW: "new";
|
|
923
|
+
readonly UPDATE: "update";
|
|
924
|
+
};
|
|
925
|
+
type CandleEventType = (typeof CANDLE_EVENT)[keyof typeof CANDLE_EVENT];
|
|
798
926
|
/**
|
|
799
927
|
* Candle tick from MQTT.
|
|
800
928
|
* WARNING: `time` is Unix epoch MILLISECONDS.
|
|
@@ -802,7 +930,7 @@ interface MqttTransport {
|
|
|
802
930
|
* Do NOT multiply MQTT candle time by 1000.
|
|
803
931
|
*/
|
|
804
932
|
interface MqttCandleEvent {
|
|
805
|
-
type:
|
|
933
|
+
type: CandleEventType;
|
|
806
934
|
/** Unix epoch **milliseconds** — differs from GQL chartHistory (seconds) */
|
|
807
935
|
time: number;
|
|
808
936
|
o: number;
|
|
@@ -1304,4 +1432,4 @@ declare function adaptiveSimpson(f: (x: number) => number, a: number, b: number,
|
|
|
1304
1432
|
declare function calculateProbWin(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1305
1433
|
declare function calculateProbWin_v2(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1306
1434
|
|
|
1307
|
-
export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, type CancelBidResult, type Candle, type Constraints, type Currency, GameChannel, type GameConfig, type GridConfig, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, type MqttAcceptedBid, type MqttAgencyPairStatsEvent, type MqttAuthConfig, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type MqttWalletBalanceEvent, type MyRank, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type Pair, type PairInfo, PairModule, type PlaceBidInput, type RankBoard, type RankEntry, type RankPeriod, type RankSort, RealtimeModule, type Sample, type SampleReason, type SignalSource, type SortDir, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserBidsChannel, type UserBidsChannelEvents, UserModule, type UserPnL, type UserPnLPeriod, type Wallet as UserWallet, type Wallet$1 as Wallet, type WalletBalanceReason, WalletChannel, adaptiveSimpson, autoDetectStorage, calculateProbWin, calculateProbWin_v2, errorFunction, isCancelled, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF, pairIdFromBidResultTopic };
|
|
1435
|
+
export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, CANDLE_EVENT, type CancelBidResult, type Candle, type CandleEventType, type Constraints, type Currency, DEFAULT_CHART_HISTORY_LIMIT, type ErrorMessageCatalog, type ErrorMessageMeta, type ErrorMessagesResponse, GameChannel, type GameConfig, type GridConfig, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, type MqttAcceptedBid, type MqttAgencyPairStatsEvent, type MqttAuthConfig, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type MqttWalletBalanceEvent, type MyRank, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type Pair, type PairInfo, PairModule, type PlaceBidInput, type RankBoard, type RankEntry, type RankPeriod, type RankSort, RealtimeModule, type Sample, type SampleReason, type SignalSource, type SortDir, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserBidsChannel, type UserBidsChannelEvents, UserModule, type UserPnL, type UserPnLPeriod, type Wallet as UserWallet, type Wallet$1 as Wallet, type WalletBalanceReason, WalletChannel, adaptiveSimpson, autoDetectStorage, calculateProbWin, calculateProbWin_v2, errorFunction, isCancelled, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF, normaliseLang, pairIdFromBidResultTopic };
|
package/dist/index.js
CHANGED
|
@@ -715,6 +715,39 @@ var LeaderboardModule = class {
|
|
|
715
715
|
}
|
|
716
716
|
};
|
|
717
717
|
|
|
718
|
+
// src/modules/locale/errorMessages.ts
|
|
719
|
+
function normaliseLang(tag) {
|
|
720
|
+
const clean = (tag || "").trim().toLowerCase();
|
|
721
|
+
if (!clean) return "en";
|
|
722
|
+
if (clean === "zh" || clean.startsWith("zh-")) {
|
|
723
|
+
if (/(^|-)(hant|tw|hk|mo)(-|$)/.test(clean)) return "zh-TW";
|
|
724
|
+
return "zh-CN";
|
|
725
|
+
}
|
|
726
|
+
return clean.split("-")[0] || "en";
|
|
727
|
+
}
|
|
728
|
+
function pickStringEntries(value) {
|
|
729
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
730
|
+
const out = {};
|
|
731
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
732
|
+
if (typeof entry === "string") out[key] = entry;
|
|
733
|
+
}
|
|
734
|
+
return out;
|
|
735
|
+
}
|
|
736
|
+
function interpolate(template, meta) {
|
|
737
|
+
const filled = template.replace(/\{(\w+)\}/g, (_match, key) => {
|
|
738
|
+
const value = meta?.[key];
|
|
739
|
+
if (value === void 0 || value === null) return "";
|
|
740
|
+
return String(value);
|
|
741
|
+
});
|
|
742
|
+
return filled.replace(/\s{2,}/g, " ").trim();
|
|
743
|
+
}
|
|
744
|
+
function resolveErrorMessage(code, catalog, fallback, meta) {
|
|
745
|
+
const row = catalog?.[code];
|
|
746
|
+
if (typeof row !== "string" || row.trim() === "") return fallback;
|
|
747
|
+
const filled = interpolate(row, meta);
|
|
748
|
+
return filled === "" ? fallback : filled;
|
|
749
|
+
}
|
|
750
|
+
|
|
718
751
|
// src/modules/locale/normalise.ts
|
|
719
752
|
function normaliseLocaleResponse(node) {
|
|
720
753
|
let translations = null;
|
|
@@ -739,6 +772,14 @@ function normaliseLocaleResponse(node) {
|
|
|
739
772
|
translations
|
|
740
773
|
};
|
|
741
774
|
}
|
|
775
|
+
function normaliseErrorMessagesResponse(node) {
|
|
776
|
+
return {
|
|
777
|
+
lang: node.lang,
|
|
778
|
+
version: node.version,
|
|
779
|
+
notModified: node.notModified,
|
|
780
|
+
messages: node.messages === null ? null : pickStringEntries(node.messages)
|
|
781
|
+
};
|
|
782
|
+
}
|
|
742
783
|
function normaliseRefreshLocalesPayload(node) {
|
|
743
784
|
let versions;
|
|
744
785
|
try {
|
|
@@ -772,10 +813,33 @@ var REFRESH_LOCALES_MUTATION = `mutation RefreshLocales {
|
|
|
772
813
|
versions
|
|
773
814
|
}
|
|
774
815
|
}`;
|
|
816
|
+
var ERROR_MESSAGES_QUERY = `query ErrorMessages($input: LocalesInput!) {
|
|
817
|
+
errorMessages(input: $input) {
|
|
818
|
+
lang
|
|
819
|
+
version
|
|
820
|
+
notModified
|
|
821
|
+
messages
|
|
822
|
+
}
|
|
823
|
+
}`;
|
|
775
824
|
|
|
776
825
|
// src/modules/locale/index.ts
|
|
777
826
|
var LocaleModule = class {
|
|
778
827
|
#graphql;
|
|
828
|
+
/**
|
|
829
|
+
* The loaded error-message catalog. Instance state, not module state, so two
|
|
830
|
+
* clients in one process (tests, multi-tenant hosts) never share a catalog.
|
|
831
|
+
*
|
|
832
|
+
* Null until a load succeeds. It is NEVER reset to null afterwards — a failed
|
|
833
|
+
* refresh keeps the last-known-good copy, because losing all error copy is
|
|
834
|
+
* strictly worse than showing slightly stale copy.
|
|
835
|
+
*/
|
|
836
|
+
#errorMessages = null;
|
|
837
|
+
/**
|
|
838
|
+
* In-flight catalog loads, keyed by normalised language. Collapses concurrent
|
|
839
|
+
* callers onto one request; entries are removed as soon as a load settles, so
|
|
840
|
+
* this never becomes a cache of results (or of failures).
|
|
841
|
+
*/
|
|
842
|
+
#errorMessagesInFlight = /* @__PURE__ */ new Map();
|
|
779
843
|
constructor(deps) {
|
|
780
844
|
this.#graphql = deps.graphql;
|
|
781
845
|
}
|
|
@@ -798,6 +862,94 @@ var LocaleModule = class {
|
|
|
798
862
|
const body = await this.#graphql.request(LOCALES_QUERY, { input }, opts);
|
|
799
863
|
return normaliseLocaleResponse(body.locales);
|
|
800
864
|
}
|
|
865
|
+
/**
|
|
866
|
+
* Fetch the error-message catalog for a single language.
|
|
867
|
+
*
|
|
868
|
+
* The throwing sibling of {@link loadErrorMessages}: errors surface as
|
|
869
|
+
* TaphubError subclasses with backend `extensions.code` values
|
|
870
|
+
* (`LangNotSupported`, `FeatureDisabled`, …), because a caller doing an
|
|
871
|
+
* explicit fetch wants to see why it failed. Hosts that just want copy on
|
|
872
|
+
* screen should call `loadErrorMessages` instead.
|
|
873
|
+
*
|
|
874
|
+
* `lang` is passed through as given — normalisation is `loadErrorMessages`'
|
|
875
|
+
* job, so an explicit caller keeps full control of the tag.
|
|
876
|
+
*/
|
|
877
|
+
async getErrorMessages(lang, knownVersion, opts) {
|
|
878
|
+
const input = { lang };
|
|
879
|
+
if (knownVersion !== void 0 && knownVersion !== "") {
|
|
880
|
+
input.knownVersion = knownVersion;
|
|
881
|
+
}
|
|
882
|
+
const body = await this.#graphql.request(
|
|
883
|
+
ERROR_MESSAGES_QUERY,
|
|
884
|
+
{ input },
|
|
885
|
+
opts
|
|
886
|
+
);
|
|
887
|
+
return normaliseErrorMessagesResponse(body.errorMessages);
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Load the error-message catalog for `lang` into this client.
|
|
891
|
+
*
|
|
892
|
+
* Background hydration with nobody waiting on it, so it **never throws and
|
|
893
|
+
* never rejects**. Every failure path — network error, backend down,
|
|
894
|
+
* `FeatureDisabled` when the source sheet is not configured, an unsupported
|
|
895
|
+
* tag, a malformed body — leaves the previously loaded catalog in place.
|
|
896
|
+
* Failing to translate an error must not itself become an error.
|
|
897
|
+
*
|
|
898
|
+
* The tag is normalised first (region dropped, Chinese scripts kept apart),
|
|
899
|
+
* so a host can hand over `navigator.language` untouched.
|
|
900
|
+
*
|
|
901
|
+
* Concurrent calls for the same language share one request. The catalog lives
|
|
902
|
+
* on the client while the thing that triggers a load (a mounted provider)
|
|
903
|
+
* lives a level below, so two providers sharing one client would otherwise
|
|
904
|
+
* fetch the same bytes into the same place twice.
|
|
905
|
+
*
|
|
906
|
+
* This is dedup, not caching — it lasts only while a request is in flight. A
|
|
907
|
+
* host calling again after one settles gets a fresh fetch.
|
|
908
|
+
*
|
|
909
|
+
* Deliberately takes NO AbortSignal. A follower piggybacking on someone
|
|
910
|
+
* else's request would otherwise have its outcome decided by whether that
|
|
911
|
+
* other caller happened to unmount first. Letting a background hydration run
|
|
912
|
+
* to completion costs one small request; stranding a caller costs all the
|
|
913
|
+
* error copy on screen.
|
|
914
|
+
*
|
|
915
|
+
* No persistence and no polling: the host decides when to call this. The
|
|
916
|
+
* React provider calls it on mount and on language change.
|
|
917
|
+
*/
|
|
918
|
+
async loadErrorMessages(lang) {
|
|
919
|
+
const key = normaliseLang(lang);
|
|
920
|
+
const inFlight = this.#errorMessagesInFlight.get(key);
|
|
921
|
+
if (inFlight) return inFlight;
|
|
922
|
+
const run = (async () => {
|
|
923
|
+
try {
|
|
924
|
+
const res = await this.getErrorMessages(key);
|
|
925
|
+
if (res.notModified || res.messages === null) return;
|
|
926
|
+
if (Object.keys(res.messages).length === 0) return;
|
|
927
|
+
this.#errorMessages = res.messages;
|
|
928
|
+
} catch {
|
|
929
|
+
} finally {
|
|
930
|
+
this.#errorMessagesInFlight.delete(key);
|
|
931
|
+
}
|
|
932
|
+
})();
|
|
933
|
+
this.#errorMessagesInFlight.set(key, run);
|
|
934
|
+
return run;
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* The sentence to show for a backend error `code`.
|
|
938
|
+
*
|
|
939
|
+
* **Synchronous by design** — error copy is read when a toast is built, inside
|
|
940
|
+
* a `catch` or an error handler, where React hooks are illegal. That is why
|
|
941
|
+
* this is a plain method and not a hook.
|
|
942
|
+
*
|
|
943
|
+
* Returns `fallback` whenever the catalog cannot produce a usable sentence: no
|
|
944
|
+
* catalog loaded yet, no row for the code, or a blank row. Never returns an
|
|
945
|
+
* empty string, and never returns the raw code.
|
|
946
|
+
*
|
|
947
|
+
* `meta` fills `{name}` placeholders in a catalog row; any placeholder left
|
|
948
|
+
* unfilled is stripped, so raw braces never reach the user.
|
|
949
|
+
*/
|
|
950
|
+
errorMessage(code, fallback, meta) {
|
|
951
|
+
return resolveErrorMessage(code, this.#errorMessages, fallback, meta);
|
|
952
|
+
}
|
|
801
953
|
/**
|
|
802
954
|
* Trigger an admin refresh — backend re-pulls the source Sheet, invalidates
|
|
803
955
|
* its cache, and writes fresh entries for every supported language.
|
|
@@ -928,6 +1080,9 @@ var BUILDER_AVAILABLE_GAME_PAIRS_QUERY = `query BuilderAvailableGamePairs($gamep
|
|
|
928
1080
|
}
|
|
929
1081
|
}`;
|
|
930
1082
|
|
|
1083
|
+
// src/modules/pair/types.ts
|
|
1084
|
+
var DEFAULT_CHART_HISTORY_LIMIT = 600;
|
|
1085
|
+
|
|
931
1086
|
// src/modules/pair/index.ts
|
|
932
1087
|
var PairModule = class {
|
|
933
1088
|
#graphql;
|
|
@@ -985,11 +1140,8 @@ var PairModule = class {
|
|
|
985
1140
|
return body.builderAvailableGamePairs.map(normalisePairInfo);
|
|
986
1141
|
}
|
|
987
1142
|
// REVIEW[bid-260602-v2]: chartHistory param/variable renamed pairId (was: agencyPairId/gameId)
|
|
988
|
-
async chartHistory(pairId, limit, opts) {
|
|
989
|
-
const variables = { pairId };
|
|
990
|
-
if (limit !== void 0) {
|
|
991
|
-
variables.limit = limit;
|
|
992
|
-
}
|
|
1143
|
+
async chartHistory(pairId, limit = DEFAULT_CHART_HISTORY_LIMIT, opts) {
|
|
1144
|
+
const variables = { pairId, limit };
|
|
993
1145
|
const body = await this.#graphql.request(
|
|
994
1146
|
CHART_HISTORY_QUERY,
|
|
995
1147
|
variables,
|
|
@@ -2940,6 +3092,12 @@ var TaphubClient = class {
|
|
|
2940
3092
|
}
|
|
2941
3093
|
};
|
|
2942
3094
|
|
|
3095
|
+
// src/modules/realtime/types.ts
|
|
3096
|
+
var CANDLE_EVENT = {
|
|
3097
|
+
NEW: "new",
|
|
3098
|
+
UPDATE: "update"
|
|
3099
|
+
};
|
|
3100
|
+
|
|
2943
3101
|
// src/utils/coefficient.ts
|
|
2944
3102
|
function errorFunction(x) {
|
|
2945
3103
|
const a1 = 0.254829592;
|
|
@@ -3028,6 +3186,8 @@ export {
|
|
|
3028
3186
|
AgencyPairModule,
|
|
3029
3187
|
AuthModule,
|
|
3030
3188
|
BidModule,
|
|
3189
|
+
CANDLE_EVENT,
|
|
3190
|
+
DEFAULT_CHART_HISTORY_LIMIT,
|
|
3031
3191
|
LeaderboardModule,
|
|
3032
3192
|
LocaleModule,
|
|
3033
3193
|
NetworkQualityMonitor,
|
|
@@ -3055,5 +3215,6 @@ export {
|
|
|
3055
3215
|
isWin,
|
|
3056
3216
|
normalCDF,
|
|
3057
3217
|
normalPDF,
|
|
3218
|
+
normaliseLang,
|
|
3058
3219
|
pairIdFromBidResultTopic
|
|
3059
3220
|
};
|