@taphubhq/sdk-core 0.25.3 → 0.25.4
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 +154 -0
- package/dist/index.d.mts +112 -1
- package/dist/index.d.ts +112 -1
- package/dist/index.js +153 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -60,6 +60,7 @@ __export(index_exports, {
|
|
|
60
60
|
isWin: () => isWin,
|
|
61
61
|
normalCDF: () => normalCDF,
|
|
62
62
|
normalPDF: () => normalPDF,
|
|
63
|
+
normaliseLang: () => normaliseLang,
|
|
63
64
|
pairIdFromBidResultTopic: () => pairIdFromBidResultTopic
|
|
64
65
|
});
|
|
65
66
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -781,6 +782,39 @@ var LeaderboardModule = class {
|
|
|
781
782
|
}
|
|
782
783
|
};
|
|
783
784
|
|
|
785
|
+
// src/modules/locale/errorMessages.ts
|
|
786
|
+
function normaliseLang(tag) {
|
|
787
|
+
const clean = (tag || "").trim().toLowerCase();
|
|
788
|
+
if (!clean) return "en";
|
|
789
|
+
if (clean === "zh" || clean.startsWith("zh-")) {
|
|
790
|
+
if (/(^|-)(hant|tw|hk|mo)(-|$)/.test(clean)) return "zh-TW";
|
|
791
|
+
return "zh-CN";
|
|
792
|
+
}
|
|
793
|
+
return clean.split("-")[0] || "en";
|
|
794
|
+
}
|
|
795
|
+
function pickStringEntries(value) {
|
|
796
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
797
|
+
const out = {};
|
|
798
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
799
|
+
if (typeof entry === "string") out[key] = entry;
|
|
800
|
+
}
|
|
801
|
+
return out;
|
|
802
|
+
}
|
|
803
|
+
function interpolate(template, meta) {
|
|
804
|
+
const filled = template.replace(/\{(\w+)\}/g, (_match, key) => {
|
|
805
|
+
const value = meta?.[key];
|
|
806
|
+
if (value === void 0 || value === null) return "";
|
|
807
|
+
return String(value);
|
|
808
|
+
});
|
|
809
|
+
return filled.replace(/\s{2,}/g, " ").trim();
|
|
810
|
+
}
|
|
811
|
+
function resolveErrorMessage(code, catalog, fallback, meta) {
|
|
812
|
+
const row = catalog?.[code];
|
|
813
|
+
if (typeof row !== "string" || row.trim() === "") return fallback;
|
|
814
|
+
const filled = interpolate(row, meta);
|
|
815
|
+
return filled === "" ? fallback : filled;
|
|
816
|
+
}
|
|
817
|
+
|
|
784
818
|
// src/modules/locale/normalise.ts
|
|
785
819
|
function normaliseLocaleResponse(node) {
|
|
786
820
|
let translations = null;
|
|
@@ -805,6 +839,14 @@ function normaliseLocaleResponse(node) {
|
|
|
805
839
|
translations
|
|
806
840
|
};
|
|
807
841
|
}
|
|
842
|
+
function normaliseErrorMessagesResponse(node) {
|
|
843
|
+
return {
|
|
844
|
+
lang: node.lang,
|
|
845
|
+
version: node.version,
|
|
846
|
+
notModified: node.notModified,
|
|
847
|
+
messages: node.messages === null ? null : pickStringEntries(node.messages)
|
|
848
|
+
};
|
|
849
|
+
}
|
|
808
850
|
function normaliseRefreshLocalesPayload(node) {
|
|
809
851
|
let versions;
|
|
810
852
|
try {
|
|
@@ -838,10 +880,33 @@ var REFRESH_LOCALES_MUTATION = `mutation RefreshLocales {
|
|
|
838
880
|
versions
|
|
839
881
|
}
|
|
840
882
|
}`;
|
|
883
|
+
var ERROR_MESSAGES_QUERY = `query ErrorMessages($input: LocalesInput!) {
|
|
884
|
+
errorMessages(input: $input) {
|
|
885
|
+
lang
|
|
886
|
+
version
|
|
887
|
+
notModified
|
|
888
|
+
messages
|
|
889
|
+
}
|
|
890
|
+
}`;
|
|
841
891
|
|
|
842
892
|
// src/modules/locale/index.ts
|
|
843
893
|
var LocaleModule = class {
|
|
844
894
|
#graphql;
|
|
895
|
+
/**
|
|
896
|
+
* The loaded error-message catalog. Instance state, not module state, so two
|
|
897
|
+
* clients in one process (tests, multi-tenant hosts) never share a catalog.
|
|
898
|
+
*
|
|
899
|
+
* Null until a load succeeds. It is NEVER reset to null afterwards — a failed
|
|
900
|
+
* refresh keeps the last-known-good copy, because losing all error copy is
|
|
901
|
+
* strictly worse than showing slightly stale copy.
|
|
902
|
+
*/
|
|
903
|
+
#errorMessages = null;
|
|
904
|
+
/**
|
|
905
|
+
* In-flight catalog loads, keyed by normalised language. Collapses concurrent
|
|
906
|
+
* callers onto one request; entries are removed as soon as a load settles, so
|
|
907
|
+
* this never becomes a cache of results (or of failures).
|
|
908
|
+
*/
|
|
909
|
+
#errorMessagesInFlight = /* @__PURE__ */ new Map();
|
|
845
910
|
constructor(deps) {
|
|
846
911
|
this.#graphql = deps.graphql;
|
|
847
912
|
}
|
|
@@ -864,6 +929,94 @@ var LocaleModule = class {
|
|
|
864
929
|
const body = await this.#graphql.request(LOCALES_QUERY, { input }, opts);
|
|
865
930
|
return normaliseLocaleResponse(body.locales);
|
|
866
931
|
}
|
|
932
|
+
/**
|
|
933
|
+
* Fetch the error-message catalog for a single language.
|
|
934
|
+
*
|
|
935
|
+
* The throwing sibling of {@link loadErrorMessages}: errors surface as
|
|
936
|
+
* TaphubError subclasses with backend `extensions.code` values
|
|
937
|
+
* (`LangNotSupported`, `FeatureDisabled`, …), because a caller doing an
|
|
938
|
+
* explicit fetch wants to see why it failed. Hosts that just want copy on
|
|
939
|
+
* screen should call `loadErrorMessages` instead.
|
|
940
|
+
*
|
|
941
|
+
* `lang` is passed through as given — normalisation is `loadErrorMessages`'
|
|
942
|
+
* job, so an explicit caller keeps full control of the tag.
|
|
943
|
+
*/
|
|
944
|
+
async getErrorMessages(lang, knownVersion, opts) {
|
|
945
|
+
const input = { lang };
|
|
946
|
+
if (knownVersion !== void 0 && knownVersion !== "") {
|
|
947
|
+
input.knownVersion = knownVersion;
|
|
948
|
+
}
|
|
949
|
+
const body = await this.#graphql.request(
|
|
950
|
+
ERROR_MESSAGES_QUERY,
|
|
951
|
+
{ input },
|
|
952
|
+
opts
|
|
953
|
+
);
|
|
954
|
+
return normaliseErrorMessagesResponse(body.errorMessages);
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Load the error-message catalog for `lang` into this client.
|
|
958
|
+
*
|
|
959
|
+
* Background hydration with nobody waiting on it, so it **never throws and
|
|
960
|
+
* never rejects**. Every failure path — network error, backend down,
|
|
961
|
+
* `FeatureDisabled` when the source sheet is not configured, an unsupported
|
|
962
|
+
* tag, a malformed body — leaves the previously loaded catalog in place.
|
|
963
|
+
* Failing to translate an error must not itself become an error.
|
|
964
|
+
*
|
|
965
|
+
* The tag is normalised first (region dropped, Chinese scripts kept apart),
|
|
966
|
+
* so a host can hand over `navigator.language` untouched.
|
|
967
|
+
*
|
|
968
|
+
* Concurrent calls for the same language share one request. The catalog lives
|
|
969
|
+
* on the client while the thing that triggers a load (a mounted provider)
|
|
970
|
+
* lives a level below, so two providers sharing one client would otherwise
|
|
971
|
+
* fetch the same bytes into the same place twice.
|
|
972
|
+
*
|
|
973
|
+
* This is dedup, not caching — it lasts only while a request is in flight. A
|
|
974
|
+
* host calling again after one settles gets a fresh fetch.
|
|
975
|
+
*
|
|
976
|
+
* Deliberately takes NO AbortSignal. A follower piggybacking on someone
|
|
977
|
+
* else's request would otherwise have its outcome decided by whether that
|
|
978
|
+
* other caller happened to unmount first. Letting a background hydration run
|
|
979
|
+
* to completion costs one small request; stranding a caller costs all the
|
|
980
|
+
* error copy on screen.
|
|
981
|
+
*
|
|
982
|
+
* No persistence and no polling: the host decides when to call this. The
|
|
983
|
+
* React provider calls it on mount and on language change.
|
|
984
|
+
*/
|
|
985
|
+
async loadErrorMessages(lang) {
|
|
986
|
+
const key = normaliseLang(lang);
|
|
987
|
+
const inFlight = this.#errorMessagesInFlight.get(key);
|
|
988
|
+
if (inFlight) return inFlight;
|
|
989
|
+
const run = (async () => {
|
|
990
|
+
try {
|
|
991
|
+
const res = await this.getErrorMessages(key);
|
|
992
|
+
if (res.notModified || res.messages === null) return;
|
|
993
|
+
if (Object.keys(res.messages).length === 0) return;
|
|
994
|
+
this.#errorMessages = res.messages;
|
|
995
|
+
} catch {
|
|
996
|
+
} finally {
|
|
997
|
+
this.#errorMessagesInFlight.delete(key);
|
|
998
|
+
}
|
|
999
|
+
})();
|
|
1000
|
+
this.#errorMessagesInFlight.set(key, run);
|
|
1001
|
+
return run;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* The sentence to show for a backend error `code`.
|
|
1005
|
+
*
|
|
1006
|
+
* **Synchronous by design** — error copy is read when a toast is built, inside
|
|
1007
|
+
* a `catch` or an error handler, where React hooks are illegal. That is why
|
|
1008
|
+
* this is a plain method and not a hook.
|
|
1009
|
+
*
|
|
1010
|
+
* Returns `fallback` whenever the catalog cannot produce a usable sentence: no
|
|
1011
|
+
* catalog loaded yet, no row for the code, or a blank row. Never returns an
|
|
1012
|
+
* empty string, and never returns the raw code.
|
|
1013
|
+
*
|
|
1014
|
+
* `meta` fills `{name}` placeholders in a catalog row; any placeholder left
|
|
1015
|
+
* unfilled is stripped, so raw braces never reach the user.
|
|
1016
|
+
*/
|
|
1017
|
+
errorMessage(code, fallback, meta) {
|
|
1018
|
+
return resolveErrorMessage(code, this.#errorMessages, fallback, meta);
|
|
1019
|
+
}
|
|
867
1020
|
/**
|
|
868
1021
|
* Trigger an admin refresh — backend re-pulls the source Sheet, invalidates
|
|
869
1022
|
* its cache, and writes fresh entries for every supported language.
|
|
@@ -3122,5 +3275,6 @@ function calculateProbWin_v2(time1, time2, _price1, price2, creationTime, curren
|
|
|
3122
3275
|
isWin,
|
|
3123
3276
|
normalCDF,
|
|
3124
3277
|
normalPDF,
|
|
3278
|
+
normaliseLang,
|
|
3125
3279
|
pairIdFromBidResultTopic
|
|
3126
3280
|
});
|
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.
|
|
@@ -1304,4 +1415,4 @@ declare function adaptiveSimpson(f: (x: number) => number, a: number, b: number,
|
|
|
1304
1415
|
declare function calculateProbWin(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1305
1416
|
declare function calculateProbWin_v2(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1306
1417
|
|
|
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 };
|
|
1418
|
+
export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, type CancelBidResult, type Candle, type Constraints, type Currency, 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.
|
|
@@ -1304,4 +1415,4 @@ declare function adaptiveSimpson(f: (x: number) => number, a: number, b: number,
|
|
|
1304
1415
|
declare function calculateProbWin(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1305
1416
|
declare function calculateProbWin_v2(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1306
1417
|
|
|
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 };
|
|
1418
|
+
export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, type CancelBidResult, type Candle, type Constraints, type Currency, 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.
|
|
@@ -3055,5 +3207,6 @@ export {
|
|
|
3055
3207
|
isWin,
|
|
3056
3208
|
normalCDF,
|
|
3057
3209
|
normalPDF,
|
|
3210
|
+
normaliseLang,
|
|
3058
3211
|
pairIdFromBidResultTopic
|
|
3059
3212
|
};
|