@taphubhq/sdk-core 0.13.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +181 -10
- package/dist/index.d.mts +110 -8
- package/dist/index.d.ts +110 -8
- package/dist/index.js +180 -10
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
AgencyPairModule: () => AgencyPairModule,
|
|
33
34
|
AuthModule: () => AuthModule,
|
|
34
35
|
BidModule: () => BidModule,
|
|
35
36
|
GameModule: () => GameModule,
|
|
@@ -130,6 +131,61 @@ var TaphubEventBus = class {
|
|
|
130
131
|
}
|
|
131
132
|
};
|
|
132
133
|
|
|
134
|
+
// src/modules/agencyPair/normalise.ts
|
|
135
|
+
function normaliseAgencyPairs(rows) {
|
|
136
|
+
if (!Array.isArray(rows)) {
|
|
137
|
+
throw new TaphubServerError("Invalid response from server", {
|
|
138
|
+
code: "InvalidResponse"
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return rows.map((row) => ({
|
|
142
|
+
gamePairId: row.gamePairId,
|
|
143
|
+
pair: row.pair,
|
|
144
|
+
thumb: row.thumb,
|
|
145
|
+
ordering: Number(row.ordering),
|
|
146
|
+
maxCoef: Number(row.maxCoef),
|
|
147
|
+
currentPrice: Number(row.currentPrice),
|
|
148
|
+
currentVol24h: Number(row.currentVol24h)
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/modules/agencyPair/queries.ts
|
|
153
|
+
var FETCH_AGENCY_PAIRS_QUERY = `query FetchAgencyPairs($filter: AgencyPairFilter) {
|
|
154
|
+
fetchAgencyPairs(filter: $filter) {
|
|
155
|
+
gamePairId pair thumb ordering maxCoef currentPrice currentVol24h
|
|
156
|
+
}
|
|
157
|
+
}`;
|
|
158
|
+
|
|
159
|
+
// src/modules/agencyPair/index.ts
|
|
160
|
+
var AgencyPairModule = class {
|
|
161
|
+
#graphql;
|
|
162
|
+
constructor(deps) {
|
|
163
|
+
this.#graphql = deps.graphql;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* List the agency-in-context's pairs with live derived stats.
|
|
167
|
+
*
|
|
168
|
+
* The agency is resolved server-side from the `x-builder-code` header the SDK
|
|
169
|
+
* sends on every request (no JWT required — Tier 0 anon-readable). There is no
|
|
170
|
+
* argument to target another agency by design.
|
|
171
|
+
*/
|
|
172
|
+
async list(args) {
|
|
173
|
+
const variables = {};
|
|
174
|
+
if (args?.filter) {
|
|
175
|
+
const filter = {};
|
|
176
|
+
if (args.filter.pair !== void 0) filter.pair = args.filter.pair;
|
|
177
|
+
if (args.filter.status !== void 0) filter.status = args.filter.status;
|
|
178
|
+
variables.filter = filter;
|
|
179
|
+
}
|
|
180
|
+
const body = await this.#graphql.request(
|
|
181
|
+
FETCH_AGENCY_PAIRS_QUERY,
|
|
182
|
+
variables,
|
|
183
|
+
args?.signal ? { signal: args.signal } : void 0
|
|
184
|
+
);
|
|
185
|
+
return normaliseAgencyPairs(body.fetchAgencyPairs);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
133
189
|
// src/modules/auth/normalise.ts
|
|
134
190
|
function normaliseGoogleResponse(body) {
|
|
135
191
|
return {
|
|
@@ -818,9 +874,20 @@ var LocaleModule = class {
|
|
|
818
874
|
|
|
819
875
|
// src/transport/mqtt/index.ts
|
|
820
876
|
var import_mqtt = __toESM(require("mqtt"));
|
|
821
|
-
|
|
877
|
+
|
|
878
|
+
// src/transport/mqtt/utils.ts
|
|
879
|
+
function subKey(gameId, userId) {
|
|
880
|
+
return `${gameId}::${userId ?? ""}`;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// src/transport/mqtt/index.ts
|
|
884
|
+
var GAME_SCOPED_SUFFIXES = ["config", "ideal_config"];
|
|
822
885
|
var USER_SCOPED_SUFFIXES = ["bid_result", "balance_update"];
|
|
823
886
|
var TOPIC_PREFIX = "game";
|
|
887
|
+
var MARKET_TOPIC_PREFIX = "market";
|
|
888
|
+
function agencyPairStatsTopic(aid, gamePairId) {
|
|
889
|
+
return `public/agency/${aid}/pair/${gamePairId}/stats`;
|
|
890
|
+
}
|
|
824
891
|
function topicFor(gameId, suffix, userId) {
|
|
825
892
|
if (USER_SCOPED_SUFFIXES.includes(suffix)) {
|
|
826
893
|
return `${TOPIC_PREFIX}/${gameId}/user/${userId}/${suffix}`;
|
|
@@ -833,12 +900,11 @@ function normaliseUserId(userId) {
|
|
|
833
900
|
function userScopedTopicsFor(gameId, userId) {
|
|
834
901
|
return USER_SCOPED_SUFFIXES.map((s) => topicFor(gameId, s, userId));
|
|
835
902
|
}
|
|
836
|
-
function subKey(gameId, userId) {
|
|
837
|
-
return `${gameId}::${userId ?? ""}`;
|
|
838
|
-
}
|
|
839
903
|
function createMqttTransport(endpoint, opts = {}) {
|
|
840
904
|
let client = null;
|
|
841
905
|
const subscriptions = /* @__PURE__ */ new Map();
|
|
906
|
+
const candleSubscriptions = /* @__PURE__ */ new Map();
|
|
907
|
+
const statsSubscriptions = /* @__PURE__ */ new Map();
|
|
842
908
|
const { onLifecycle } = opts;
|
|
843
909
|
let connectStartedAt = 0;
|
|
844
910
|
function fireLifecycle(event) {
|
|
@@ -885,6 +951,28 @@ function createMqttTransport(endpoint, opts = {}) {
|
|
|
885
951
|
fireLifecycle({ kind: "error", err });
|
|
886
952
|
});
|
|
887
953
|
client.on("message", (receivedTopic, message) => {
|
|
954
|
+
const candleSub = [...candleSubscriptions.values()].find((s) => s.topic === receivedTopic);
|
|
955
|
+
if (candleSub) {
|
|
956
|
+
let payload2;
|
|
957
|
+
try {
|
|
958
|
+
payload2 = JSON.parse(message.toString());
|
|
959
|
+
} catch {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
candleSub.onCandle(receivedTopic, payload2);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
const statsSub = statsSubscriptions.get(receivedTopic);
|
|
966
|
+
if (statsSub) {
|
|
967
|
+
let payload2;
|
|
968
|
+
try {
|
|
969
|
+
payload2 = JSON.parse(message.toString());
|
|
970
|
+
} catch {
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
statsSub.onStats(receivedTopic, payload2);
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
888
976
|
let matched;
|
|
889
977
|
for (const sub of subscriptions.values()) {
|
|
890
978
|
if (sub.topics.includes(receivedTopic)) {
|
|
@@ -929,9 +1017,36 @@ function createMqttTransport(endpoint, opts = {}) {
|
|
|
929
1017
|
onError
|
|
930
1018
|
});
|
|
931
1019
|
for (const t of fullTopics) {
|
|
932
|
-
mqttClient.subscribe(t, { qos:
|
|
1020
|
+
mqttClient.subscribe(t, { qos: 1 });
|
|
933
1021
|
}
|
|
934
1022
|
},
|
|
1023
|
+
subscribeCandle(pair, onCandle) {
|
|
1024
|
+
const topic = `${MARKET_TOPIC_PREFIX}/${pair.replace(/\//g, "_")}/candle`;
|
|
1025
|
+
if (candleSubscriptions.has(pair)) return;
|
|
1026
|
+
const mqttClient = ensureConnected();
|
|
1027
|
+
candleSubscriptions.set(pair, { topic, onCandle });
|
|
1028
|
+
mqttClient.subscribe(topic, { qos: 0 });
|
|
1029
|
+
},
|
|
1030
|
+
unsubscribeCandle(pair) {
|
|
1031
|
+
const entry = candleSubscriptions.get(pair);
|
|
1032
|
+
if (!entry) return;
|
|
1033
|
+
if (client) client.unsubscribe(entry.topic);
|
|
1034
|
+
candleSubscriptions.delete(pair);
|
|
1035
|
+
},
|
|
1036
|
+
subscribeAgencyPairStats(aid, gamePairId, onStats) {
|
|
1037
|
+
const topic = agencyPairStatsTopic(aid, gamePairId);
|
|
1038
|
+
if (statsSubscriptions.has(topic)) return;
|
|
1039
|
+
const mqttClient = ensureConnected();
|
|
1040
|
+
statsSubscriptions.set(topic, { topic, onStats });
|
|
1041
|
+
mqttClient.subscribe(topic, { qos: 0 });
|
|
1042
|
+
},
|
|
1043
|
+
unsubscribeAgencyPairStats(aid, gamePairId) {
|
|
1044
|
+
const topic = agencyPairStatsTopic(aid, gamePairId);
|
|
1045
|
+
const entry = statsSubscriptions.get(topic);
|
|
1046
|
+
if (!entry) return;
|
|
1047
|
+
if (client) client.unsubscribe(entry.topic);
|
|
1048
|
+
statsSubscriptions.delete(topic);
|
|
1049
|
+
},
|
|
935
1050
|
unsubscribeAll(gameId, userId) {
|
|
936
1051
|
const matches = entriesForGame(gameId);
|
|
937
1052
|
if (matches.length === 0) return;
|
|
@@ -963,8 +1078,16 @@ function createMqttTransport(endpoint, opts = {}) {
|
|
|
963
1078
|
client.unsubscribe(t);
|
|
964
1079
|
}
|
|
965
1080
|
}
|
|
1081
|
+
for (const entry of candleSubscriptions.values()) {
|
|
1082
|
+
client.unsubscribe(entry.topic);
|
|
1083
|
+
}
|
|
1084
|
+
for (const entry of statsSubscriptions.values()) {
|
|
1085
|
+
client.unsubscribe(entry.topic);
|
|
1086
|
+
}
|
|
966
1087
|
}
|
|
967
1088
|
subscriptions.clear();
|
|
1089
|
+
candleSubscriptions.clear();
|
|
1090
|
+
statsSubscriptions.clear();
|
|
968
1091
|
if (client) {
|
|
969
1092
|
client.end(true);
|
|
970
1093
|
client = null;
|
|
@@ -1082,17 +1205,16 @@ function mapWireToEvent(topic, payload) {
|
|
|
1082
1205
|
function normaliseUserId2(userId) {
|
|
1083
1206
|
return userId && userId !== "" ? userId : null;
|
|
1084
1207
|
}
|
|
1085
|
-
function subKey2(gameId, userId) {
|
|
1086
|
-
return `${gameId}::${userId ?? ""}`;
|
|
1087
|
-
}
|
|
1088
1208
|
var RealtimeModule = class extends import_eventemitter32.default {
|
|
1089
1209
|
#transport;
|
|
1090
1210
|
#entries = /* @__PURE__ */ new Map();
|
|
1211
|
+
#agencyId;
|
|
1091
1212
|
constructor(mqttEndpointOrOptions) {
|
|
1092
1213
|
super();
|
|
1093
1214
|
if (typeof mqttEndpointOrOptions === "string") {
|
|
1094
1215
|
this.#transport = createMqttTransport(mqttEndpointOrOptions);
|
|
1095
1216
|
} else {
|
|
1217
|
+
this.#agencyId = mqttEndpointOrOptions.agencyId;
|
|
1096
1218
|
this.#transport = mqttEndpointOrOptions.transport ?? createMqttTransport(mqttEndpointOrOptions.mqttEndpoint, {
|
|
1097
1219
|
onLifecycle: mqttEndpointOrOptions.onMqttLifecycle
|
|
1098
1220
|
});
|
|
@@ -1104,7 +1226,7 @@ var RealtimeModule = class extends import_eventemitter32.default {
|
|
|
1104
1226
|
}
|
|
1105
1227
|
subscribe(gameId, userId) {
|
|
1106
1228
|
const cleanUserId = normaliseUserId2(userId);
|
|
1107
|
-
const key =
|
|
1229
|
+
const key = subKey(gameId, cleanUserId);
|
|
1108
1230
|
const existing = this.#entries.get(key);
|
|
1109
1231
|
if (existing) {
|
|
1110
1232
|
existing.refcount += 1;
|
|
@@ -1160,11 +1282,49 @@ var RealtimeModule = class extends import_eventemitter32.default {
|
|
|
1160
1282
|
}
|
|
1161
1283
|
target.refcount -= 1;
|
|
1162
1284
|
if (target.refcount > 0) return;
|
|
1163
|
-
const key =
|
|
1285
|
+
const key = subKey(target.gameId, target.userId);
|
|
1164
1286
|
this.#entries.delete(key);
|
|
1165
1287
|
target.channel.removeAllListeners();
|
|
1166
1288
|
this.#transport.unsubscribeAll(gameId, target.userId);
|
|
1167
1289
|
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Subscribe to public market candle data for a pair (e.g. "ETH/USD").
|
|
1292
|
+
* Independent of game/agency — candle is market-wide public data.
|
|
1293
|
+
*/
|
|
1294
|
+
subscribeCandle(pair, onCandle) {
|
|
1295
|
+
this.#transport.subscribeCandle(pair, onCandle);
|
|
1296
|
+
}
|
|
1297
|
+
/** Unsubscribe from candle data for a pair. */
|
|
1298
|
+
unsubscribeCandle(pair) {
|
|
1299
|
+
this.#transport.unsubscribeCandle(pair);
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Subscribe to the live per-pair stats stream for the client's own agency.
|
|
1303
|
+
* Scoped to the agency the client was constructed with — there is no argument
|
|
1304
|
+
* to target another agency by design.
|
|
1305
|
+
*/
|
|
1306
|
+
subscribeAgencyPairStats(gamePairId, handler) {
|
|
1307
|
+
if (!this.#agencyId) {
|
|
1308
|
+
throw new TaphubError("agencyId is required to subscribe to agency pair stats", {
|
|
1309
|
+
code: "AgencyIdRequired"
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
const onStats = (_topic, raw) => {
|
|
1313
|
+
const p = raw;
|
|
1314
|
+
handler({
|
|
1315
|
+
price: Number(p.price),
|
|
1316
|
+
vol24h: Number(p.vol24h),
|
|
1317
|
+
maxCoef: Number(p.maxCoef),
|
|
1318
|
+
ts: Number(p.ts)
|
|
1319
|
+
});
|
|
1320
|
+
};
|
|
1321
|
+
this.#transport.subscribeAgencyPairStats(this.#agencyId, gamePairId, onStats);
|
|
1322
|
+
}
|
|
1323
|
+
/** Unsubscribe from the per-pair stats stream for the client's own agency. */
|
|
1324
|
+
unsubscribeAgencyPairStats(gamePairId) {
|
|
1325
|
+
if (!this.#agencyId) return;
|
|
1326
|
+
this.#transport.unsubscribeAgencyPairStats(this.#agencyId, gamePairId);
|
|
1327
|
+
}
|
|
1168
1328
|
disconnect() {
|
|
1169
1329
|
for (const entry of this.#entries.values()) {
|
|
1170
1330
|
entry.channel.removeAllListeners();
|
|
@@ -2229,6 +2389,8 @@ var TaphubClient = class {
|
|
|
2229
2389
|
bid;
|
|
2230
2390
|
/** @readonly Leaderboard module — reassignment has no effect at runtime. */
|
|
2231
2391
|
leaderboard;
|
|
2392
|
+
/** @readonly Agency pair module — reassignment has no effect at runtime. */
|
|
2393
|
+
agencyPairs;
|
|
2232
2394
|
/** @readonly Locale module — reassignment has no effect at runtime. */
|
|
2233
2395
|
locale;
|
|
2234
2396
|
/** @readonly Realtime module — undefined when mqttEndpoint not configured. */
|
|
@@ -2309,9 +2471,11 @@ var TaphubClient = class {
|
|
|
2309
2471
|
this.game = new GameModule({ graphql: this.#graphql });
|
|
2310
2472
|
this.bid = new BidModule({ graphql: this.#graphql });
|
|
2311
2473
|
this.leaderboard = new LeaderboardModule({ graphql: this.#graphql });
|
|
2474
|
+
this.agencyPairs = new AgencyPairModule({ graphql: this.#graphql });
|
|
2312
2475
|
this.locale = new LocaleModule({ graphql: this.#graphql });
|
|
2313
2476
|
this.realtime = config.mqttEndpoint ? new RealtimeModule({
|
|
2314
2477
|
mqttEndpoint: config.mqttEndpoint,
|
|
2478
|
+
agencyId: this.agencyId,
|
|
2315
2479
|
onMqttLifecycle: createMqttProbe(this.network)
|
|
2316
2480
|
}) : void 0;
|
|
2317
2481
|
attachConnectionProbe(this.network);
|
|
@@ -2345,6 +2509,12 @@ var TaphubClient = class {
|
|
|
2345
2509
|
enumerable: true,
|
|
2346
2510
|
configurable: false
|
|
2347
2511
|
});
|
|
2512
|
+
Object.defineProperty(this, "agencyPairs", {
|
|
2513
|
+
value: this.agencyPairs,
|
|
2514
|
+
writable: false,
|
|
2515
|
+
enumerable: true,
|
|
2516
|
+
configurable: false
|
|
2517
|
+
});
|
|
2348
2518
|
if (this.realtime) {
|
|
2349
2519
|
Object.defineProperty(this, "realtime", {
|
|
2350
2520
|
value: this.realtime,
|
|
@@ -2481,6 +2651,7 @@ function calculateProbWin_v2(time1, time2, _price1, price2, creationTime, curren
|
|
|
2481
2651
|
}
|
|
2482
2652
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2483
2653
|
0 && (module.exports = {
|
|
2654
|
+
AgencyPairModule,
|
|
2484
2655
|
AuthModule,
|
|
2485
2656
|
BidModule,
|
|
2486
2657
|
GameModule,
|
package/dist/index.d.mts
CHANGED
|
@@ -63,6 +63,49 @@ interface GraphQLTransport {
|
|
|
63
63
|
request<T>(query: string, variables?: Record<string, unknown>, opts?: GraphQLRequestOpts): Promise<T>;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
type AgencyPairStatus = 'active' | 'paused' | 'ended';
|
|
67
|
+
interface AgencyPairFilter {
|
|
68
|
+
/** Exact match on the display pair name, e.g. "ETH/USD". */
|
|
69
|
+
pair?: string;
|
|
70
|
+
/** Exact match on the agency pair status. */
|
|
71
|
+
status?: AgencyPairStatus;
|
|
72
|
+
}
|
|
73
|
+
interface AgencyPairStats {
|
|
74
|
+
/** game_pairs.id slug, e.g. "grid-ETH-USD". Use to build the stats MQTT topic. */
|
|
75
|
+
gamePairId: string;
|
|
76
|
+
/** Display name, e.g. "ETH/USD". */
|
|
77
|
+
pair: string;
|
|
78
|
+
/** Icon URL. */
|
|
79
|
+
thumb: string;
|
|
80
|
+
/** Lower = earlier in the displayed list. */
|
|
81
|
+
ordering: number;
|
|
82
|
+
/** config.constraints.maxCoef. */
|
|
83
|
+
maxCoef: number;
|
|
84
|
+
/** Latest candle close price; 0 when no candle exists. */
|
|
85
|
+
currentPrice: number;
|
|
86
|
+
/** Sliding 24h bid-amount sum; 0 when the worker snapshot is missing or stale. */
|
|
87
|
+
currentVol24h: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface AgencyPairModuleDeps {
|
|
91
|
+
graphql: GraphQLTransport;
|
|
92
|
+
}
|
|
93
|
+
declare class AgencyPairModule {
|
|
94
|
+
#private;
|
|
95
|
+
constructor(deps: AgencyPairModuleDeps);
|
|
96
|
+
/**
|
|
97
|
+
* List the agency-in-context's pairs with live derived stats.
|
|
98
|
+
*
|
|
99
|
+
* The agency is resolved server-side from the `x-builder-code` header the SDK
|
|
100
|
+
* sends on every request (no JWT required — Tier 0 anon-readable). There is no
|
|
101
|
+
* argument to target another agency by design.
|
|
102
|
+
*/
|
|
103
|
+
list(args?: {
|
|
104
|
+
filter?: AgencyPairFilter;
|
|
105
|
+
signal?: AbortSignal;
|
|
106
|
+
}): Promise<AgencyPairStats[]>;
|
|
107
|
+
}
|
|
108
|
+
|
|
66
109
|
interface RestRequestOpts {
|
|
67
110
|
signal?: AbortSignal;
|
|
68
111
|
}
|
|
@@ -433,7 +476,13 @@ interface MqttWireConfig {
|
|
|
433
476
|
max_bid_amount: number;
|
|
434
477
|
acceptable_bids: number[];
|
|
435
478
|
}
|
|
436
|
-
|
|
479
|
+
interface MqttWireAgencyPairStats {
|
|
480
|
+
price: number;
|
|
481
|
+
vol24h: number;
|
|
482
|
+
maxCoef: number;
|
|
483
|
+
ts: number;
|
|
484
|
+
}
|
|
485
|
+
type MqttWirePayload = MqttWireCandle | MqttWireBidResult | MqttWireBalanceUpdate | MqttWireConfig | MqttWireAgencyPairStats;
|
|
437
486
|
type MqttMessageHandler = (gameId: string, topic: string, payload: MqttWirePayload) => void;
|
|
438
487
|
type MqttErrorHandler = (err: Error) => void;
|
|
439
488
|
type MqttLifecycleEvent = {
|
|
@@ -452,14 +501,14 @@ type MqttLifecycleEvent = {
|
|
|
452
501
|
rttMs: number;
|
|
453
502
|
};
|
|
454
503
|
type MqttLifecycleHook = (event: MqttLifecycleEvent) => void;
|
|
504
|
+
type MqttCandleHandler = (topic: string, payload: MqttWirePayload) => void;
|
|
505
|
+
type MqttAgencyPairStatsHandler = (topic: string, payload: MqttWirePayload) => void;
|
|
455
506
|
interface MqttTransport {
|
|
456
507
|
/**
|
|
457
|
-
* Subscribe to a game's MQTT topics
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
* tuple are idempotent; calls with the same `gameId` but a different
|
|
462
|
-
* `userId` create an independent entry with its own user-scoped topics.
|
|
508
|
+
* Subscribe to a game's MQTT topics (config, ideal_config, bid_result, balance)
|
|
509
|
+
* keyed by the tuple `(gameId, userId)`. Does NOT subscribe to candle —
|
|
510
|
+
* use `subscribeCandle` for public market candle data.
|
|
511
|
+
* Two calls with the same `(gameId, userId)` tuple are idempotent.
|
|
463
512
|
*/
|
|
464
513
|
subscribe(gameId: string, userId: string | null | undefined, onMessage: MqttMessageHandler, onError: MqttErrorHandler): void;
|
|
465
514
|
/**
|
|
@@ -470,6 +519,23 @@ interface MqttTransport {
|
|
|
470
519
|
* `code='AmbiguousUnsubscribe'`.
|
|
471
520
|
*/
|
|
472
521
|
unsubscribeAll(gameId: string, userId?: string | null): void;
|
|
522
|
+
/**
|
|
523
|
+
* Subscribe to public market candle data for a pair (e.g. "ETH/USD").
|
|
524
|
+
* Independent of game/agency context — candle is market-wide public data.
|
|
525
|
+
* Idempotent: multiple calls for the same pair are deduplicated.
|
|
526
|
+
*/
|
|
527
|
+
subscribeCandle(pair: string, onCandle: MqttCandleHandler): void;
|
|
528
|
+
/** Tear down the candle subscription for a pair. */
|
|
529
|
+
unsubscribeCandle(pair: string): void;
|
|
530
|
+
/**
|
|
531
|
+
* Subscribe to the public per-pair stats stream for an agency, topic
|
|
532
|
+
* `public/agency/{aid}/pair/{gamePairId}/stats`. `aid` and `gamePairId` are
|
|
533
|
+
* used raw (no slash escaping) to byte-match the backend publisher.
|
|
534
|
+
* Idempotent: multiple calls for the same `(aid, gamePairId)` are deduplicated.
|
|
535
|
+
*/
|
|
536
|
+
subscribeAgencyPairStats(aid: string, gamePairId: string, onStats: MqttAgencyPairStatsHandler): void;
|
|
537
|
+
/** Tear down the stats subscription for an `(aid, gamePairId)`. */
|
|
538
|
+
unsubscribeAgencyPairStats(aid: string, gamePairId: string): void;
|
|
473
539
|
close(): void;
|
|
474
540
|
}
|
|
475
541
|
|
|
@@ -542,6 +608,19 @@ interface MqttIdealConfigEvent {
|
|
|
542
608
|
currentPrice: number;
|
|
543
609
|
reason: string;
|
|
544
610
|
}
|
|
611
|
+
/**
|
|
612
|
+
* Live per-pair stats tick from the public stream
|
|
613
|
+
* `public/agency/{aid}/pair/{gamePairId}/stats`. The backend suppresses
|
|
614
|
+
* unchanged ticks, so a subscriber may not receive an event until price or
|
|
615
|
+
* vol24h actually moves — seed initial values from `agencyPairs.list()`.
|
|
616
|
+
*/
|
|
617
|
+
interface MqttAgencyPairStatsEvent {
|
|
618
|
+
price: number;
|
|
619
|
+
vol24h: number;
|
|
620
|
+
maxCoef: number;
|
|
621
|
+
/** Unix epoch **seconds** (matches the backend snapshot `ts`). */
|
|
622
|
+
ts: number;
|
|
623
|
+
}
|
|
545
624
|
|
|
546
625
|
interface GameChannelEvents {
|
|
547
626
|
candle: [MqttCandleEvent];
|
|
@@ -561,6 +640,12 @@ declare class GameChannel extends EventEmitter<GameChannelEvents> {
|
|
|
561
640
|
|
|
562
641
|
interface RealtimeModuleOptions {
|
|
563
642
|
mqttEndpoint: string;
|
|
643
|
+
/**
|
|
644
|
+
* The client's own agency id. Threaded so `subscribeAgencyPairStats` can
|
|
645
|
+
* scope the stats topic to this agency without accepting a target agency from
|
|
646
|
+
* the caller (tenant-scoping mirror of the GQL resolver).
|
|
647
|
+
*/
|
|
648
|
+
agencyId?: string;
|
|
564
649
|
/** @internal For testing only */
|
|
565
650
|
transport?: MqttTransport;
|
|
566
651
|
/**
|
|
@@ -580,6 +665,21 @@ declare class RealtimeModule extends EventEmitter<RealtimeModuleEvents> {
|
|
|
580
665
|
get _transport(): MqttTransport;
|
|
581
666
|
subscribe(gameId: string, userId?: string | null): GameChannel;
|
|
582
667
|
unsubscribe(gameId: string, userId?: string | null): void;
|
|
668
|
+
/**
|
|
669
|
+
* Subscribe to public market candle data for a pair (e.g. "ETH/USD").
|
|
670
|
+
* Independent of game/agency — candle is market-wide public data.
|
|
671
|
+
*/
|
|
672
|
+
subscribeCandle(pair: string, onCandle: MqttCandleHandler): void;
|
|
673
|
+
/** Unsubscribe from candle data for a pair. */
|
|
674
|
+
unsubscribeCandle(pair: string): void;
|
|
675
|
+
/**
|
|
676
|
+
* Subscribe to the live per-pair stats stream for the client's own agency.
|
|
677
|
+
* Scoped to the agency the client was constructed with — there is no argument
|
|
678
|
+
* to target another agency by design.
|
|
679
|
+
*/
|
|
680
|
+
subscribeAgencyPairStats(gamePairId: string, handler: (event: MqttAgencyPairStatsEvent) => void): void;
|
|
681
|
+
/** Unsubscribe from the per-pair stats stream for the client's own agency. */
|
|
682
|
+
unsubscribeAgencyPairStats(gamePairId: string): void;
|
|
583
683
|
disconnect(): void;
|
|
584
684
|
}
|
|
585
685
|
|
|
@@ -688,6 +788,8 @@ declare class TaphubClient {
|
|
|
688
788
|
bid: BidModule;
|
|
689
789
|
/** @readonly Leaderboard module — reassignment has no effect at runtime. */
|
|
690
790
|
leaderboard: LeaderboardModule;
|
|
791
|
+
/** @readonly Agency pair module — reassignment has no effect at runtime. */
|
|
792
|
+
agencyPairs: AgencyPairModule;
|
|
691
793
|
/** @readonly Locale module — reassignment has no effect at runtime. */
|
|
692
794
|
locale: LocaleModule;
|
|
693
795
|
/** @readonly Realtime module — undefined when mqttEndpoint not configured. */
|
|
@@ -777,4 +879,4 @@ declare function adaptiveSimpson(f: (x: number) => number, a: number, b: number,
|
|
|
777
879
|
declare function calculateProbWin(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
778
880
|
declare function calculateProbWin_v2(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
779
881
|
|
|
780
|
-
export { AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, type CancelBidResult, type Candle, type Constraints, type Currency, type Game, GameChannel, type GameConfig, GameModule, type GamePairInfo, type GridConfig, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, type MqttAcceptedBid, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type PlaceBidInput, RealtimeModule, type Sample, type SampleReason, type SignalSource, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserModule, type Wallet as UserWallet, type Wallet$1 as Wallet, adaptiveSimpson, autoDetectStorage, calculateProbWin, calculateProbWin_v2, errorFunction, isCancelled, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF };
|
|
882
|
+
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 Game, GameChannel, type GameConfig, GameModule, type GamePairInfo, 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 MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type PlaceBidInput, RealtimeModule, type Sample, type SampleReason, type SignalSource, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserModule, type Wallet as UserWallet, type Wallet$1 as Wallet, adaptiveSimpson, autoDetectStorage, calculateProbWin, calculateProbWin_v2, errorFunction, isCancelled, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF };
|
package/dist/index.d.ts
CHANGED
|
@@ -63,6 +63,49 @@ interface GraphQLTransport {
|
|
|
63
63
|
request<T>(query: string, variables?: Record<string, unknown>, opts?: GraphQLRequestOpts): Promise<T>;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
type AgencyPairStatus = 'active' | 'paused' | 'ended';
|
|
67
|
+
interface AgencyPairFilter {
|
|
68
|
+
/** Exact match on the display pair name, e.g. "ETH/USD". */
|
|
69
|
+
pair?: string;
|
|
70
|
+
/** Exact match on the agency pair status. */
|
|
71
|
+
status?: AgencyPairStatus;
|
|
72
|
+
}
|
|
73
|
+
interface AgencyPairStats {
|
|
74
|
+
/** game_pairs.id slug, e.g. "grid-ETH-USD". Use to build the stats MQTT topic. */
|
|
75
|
+
gamePairId: string;
|
|
76
|
+
/** Display name, e.g. "ETH/USD". */
|
|
77
|
+
pair: string;
|
|
78
|
+
/** Icon URL. */
|
|
79
|
+
thumb: string;
|
|
80
|
+
/** Lower = earlier in the displayed list. */
|
|
81
|
+
ordering: number;
|
|
82
|
+
/** config.constraints.maxCoef. */
|
|
83
|
+
maxCoef: number;
|
|
84
|
+
/** Latest candle close price; 0 when no candle exists. */
|
|
85
|
+
currentPrice: number;
|
|
86
|
+
/** Sliding 24h bid-amount sum; 0 when the worker snapshot is missing or stale. */
|
|
87
|
+
currentVol24h: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface AgencyPairModuleDeps {
|
|
91
|
+
graphql: GraphQLTransport;
|
|
92
|
+
}
|
|
93
|
+
declare class AgencyPairModule {
|
|
94
|
+
#private;
|
|
95
|
+
constructor(deps: AgencyPairModuleDeps);
|
|
96
|
+
/**
|
|
97
|
+
* List the agency-in-context's pairs with live derived stats.
|
|
98
|
+
*
|
|
99
|
+
* The agency is resolved server-side from the `x-builder-code` header the SDK
|
|
100
|
+
* sends on every request (no JWT required — Tier 0 anon-readable). There is no
|
|
101
|
+
* argument to target another agency by design.
|
|
102
|
+
*/
|
|
103
|
+
list(args?: {
|
|
104
|
+
filter?: AgencyPairFilter;
|
|
105
|
+
signal?: AbortSignal;
|
|
106
|
+
}): Promise<AgencyPairStats[]>;
|
|
107
|
+
}
|
|
108
|
+
|
|
66
109
|
interface RestRequestOpts {
|
|
67
110
|
signal?: AbortSignal;
|
|
68
111
|
}
|
|
@@ -433,7 +476,13 @@ interface MqttWireConfig {
|
|
|
433
476
|
max_bid_amount: number;
|
|
434
477
|
acceptable_bids: number[];
|
|
435
478
|
}
|
|
436
|
-
|
|
479
|
+
interface MqttWireAgencyPairStats {
|
|
480
|
+
price: number;
|
|
481
|
+
vol24h: number;
|
|
482
|
+
maxCoef: number;
|
|
483
|
+
ts: number;
|
|
484
|
+
}
|
|
485
|
+
type MqttWirePayload = MqttWireCandle | MqttWireBidResult | MqttWireBalanceUpdate | MqttWireConfig | MqttWireAgencyPairStats;
|
|
437
486
|
type MqttMessageHandler = (gameId: string, topic: string, payload: MqttWirePayload) => void;
|
|
438
487
|
type MqttErrorHandler = (err: Error) => void;
|
|
439
488
|
type MqttLifecycleEvent = {
|
|
@@ -452,14 +501,14 @@ type MqttLifecycleEvent = {
|
|
|
452
501
|
rttMs: number;
|
|
453
502
|
};
|
|
454
503
|
type MqttLifecycleHook = (event: MqttLifecycleEvent) => void;
|
|
504
|
+
type MqttCandleHandler = (topic: string, payload: MqttWirePayload) => void;
|
|
505
|
+
type MqttAgencyPairStatsHandler = (topic: string, payload: MqttWirePayload) => void;
|
|
455
506
|
interface MqttTransport {
|
|
456
507
|
/**
|
|
457
|
-
* Subscribe to a game's MQTT topics
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
* tuple are idempotent; calls with the same `gameId` but a different
|
|
462
|
-
* `userId` create an independent entry with its own user-scoped topics.
|
|
508
|
+
* Subscribe to a game's MQTT topics (config, ideal_config, bid_result, balance)
|
|
509
|
+
* keyed by the tuple `(gameId, userId)`. Does NOT subscribe to candle —
|
|
510
|
+
* use `subscribeCandle` for public market candle data.
|
|
511
|
+
* Two calls with the same `(gameId, userId)` tuple are idempotent.
|
|
463
512
|
*/
|
|
464
513
|
subscribe(gameId: string, userId: string | null | undefined, onMessage: MqttMessageHandler, onError: MqttErrorHandler): void;
|
|
465
514
|
/**
|
|
@@ -470,6 +519,23 @@ interface MqttTransport {
|
|
|
470
519
|
* `code='AmbiguousUnsubscribe'`.
|
|
471
520
|
*/
|
|
472
521
|
unsubscribeAll(gameId: string, userId?: string | null): void;
|
|
522
|
+
/**
|
|
523
|
+
* Subscribe to public market candle data for a pair (e.g. "ETH/USD").
|
|
524
|
+
* Independent of game/agency context — candle is market-wide public data.
|
|
525
|
+
* Idempotent: multiple calls for the same pair are deduplicated.
|
|
526
|
+
*/
|
|
527
|
+
subscribeCandle(pair: string, onCandle: MqttCandleHandler): void;
|
|
528
|
+
/** Tear down the candle subscription for a pair. */
|
|
529
|
+
unsubscribeCandle(pair: string): void;
|
|
530
|
+
/**
|
|
531
|
+
* Subscribe to the public per-pair stats stream for an agency, topic
|
|
532
|
+
* `public/agency/{aid}/pair/{gamePairId}/stats`. `aid` and `gamePairId` are
|
|
533
|
+
* used raw (no slash escaping) to byte-match the backend publisher.
|
|
534
|
+
* Idempotent: multiple calls for the same `(aid, gamePairId)` are deduplicated.
|
|
535
|
+
*/
|
|
536
|
+
subscribeAgencyPairStats(aid: string, gamePairId: string, onStats: MqttAgencyPairStatsHandler): void;
|
|
537
|
+
/** Tear down the stats subscription for an `(aid, gamePairId)`. */
|
|
538
|
+
unsubscribeAgencyPairStats(aid: string, gamePairId: string): void;
|
|
473
539
|
close(): void;
|
|
474
540
|
}
|
|
475
541
|
|
|
@@ -542,6 +608,19 @@ interface MqttIdealConfigEvent {
|
|
|
542
608
|
currentPrice: number;
|
|
543
609
|
reason: string;
|
|
544
610
|
}
|
|
611
|
+
/**
|
|
612
|
+
* Live per-pair stats tick from the public stream
|
|
613
|
+
* `public/agency/{aid}/pair/{gamePairId}/stats`. The backend suppresses
|
|
614
|
+
* unchanged ticks, so a subscriber may not receive an event until price or
|
|
615
|
+
* vol24h actually moves — seed initial values from `agencyPairs.list()`.
|
|
616
|
+
*/
|
|
617
|
+
interface MqttAgencyPairStatsEvent {
|
|
618
|
+
price: number;
|
|
619
|
+
vol24h: number;
|
|
620
|
+
maxCoef: number;
|
|
621
|
+
/** Unix epoch **seconds** (matches the backend snapshot `ts`). */
|
|
622
|
+
ts: number;
|
|
623
|
+
}
|
|
545
624
|
|
|
546
625
|
interface GameChannelEvents {
|
|
547
626
|
candle: [MqttCandleEvent];
|
|
@@ -561,6 +640,12 @@ declare class GameChannel extends EventEmitter<GameChannelEvents> {
|
|
|
561
640
|
|
|
562
641
|
interface RealtimeModuleOptions {
|
|
563
642
|
mqttEndpoint: string;
|
|
643
|
+
/**
|
|
644
|
+
* The client's own agency id. Threaded so `subscribeAgencyPairStats` can
|
|
645
|
+
* scope the stats topic to this agency without accepting a target agency from
|
|
646
|
+
* the caller (tenant-scoping mirror of the GQL resolver).
|
|
647
|
+
*/
|
|
648
|
+
agencyId?: string;
|
|
564
649
|
/** @internal For testing only */
|
|
565
650
|
transport?: MqttTransport;
|
|
566
651
|
/**
|
|
@@ -580,6 +665,21 @@ declare class RealtimeModule extends EventEmitter<RealtimeModuleEvents> {
|
|
|
580
665
|
get _transport(): MqttTransport;
|
|
581
666
|
subscribe(gameId: string, userId?: string | null): GameChannel;
|
|
582
667
|
unsubscribe(gameId: string, userId?: string | null): void;
|
|
668
|
+
/**
|
|
669
|
+
* Subscribe to public market candle data for a pair (e.g. "ETH/USD").
|
|
670
|
+
* Independent of game/agency — candle is market-wide public data.
|
|
671
|
+
*/
|
|
672
|
+
subscribeCandle(pair: string, onCandle: MqttCandleHandler): void;
|
|
673
|
+
/** Unsubscribe from candle data for a pair. */
|
|
674
|
+
unsubscribeCandle(pair: string): void;
|
|
675
|
+
/**
|
|
676
|
+
* Subscribe to the live per-pair stats stream for the client's own agency.
|
|
677
|
+
* Scoped to the agency the client was constructed with — there is no argument
|
|
678
|
+
* to target another agency by design.
|
|
679
|
+
*/
|
|
680
|
+
subscribeAgencyPairStats(gamePairId: string, handler: (event: MqttAgencyPairStatsEvent) => void): void;
|
|
681
|
+
/** Unsubscribe from the per-pair stats stream for the client's own agency. */
|
|
682
|
+
unsubscribeAgencyPairStats(gamePairId: string): void;
|
|
583
683
|
disconnect(): void;
|
|
584
684
|
}
|
|
585
685
|
|
|
@@ -688,6 +788,8 @@ declare class TaphubClient {
|
|
|
688
788
|
bid: BidModule;
|
|
689
789
|
/** @readonly Leaderboard module — reassignment has no effect at runtime. */
|
|
690
790
|
leaderboard: LeaderboardModule;
|
|
791
|
+
/** @readonly Agency pair module — reassignment has no effect at runtime. */
|
|
792
|
+
agencyPairs: AgencyPairModule;
|
|
691
793
|
/** @readonly Locale module — reassignment has no effect at runtime. */
|
|
692
794
|
locale: LocaleModule;
|
|
693
795
|
/** @readonly Realtime module — undefined when mqttEndpoint not configured. */
|
|
@@ -777,4 +879,4 @@ declare function adaptiveSimpson(f: (x: number) => number, a: number, b: number,
|
|
|
777
879
|
declare function calculateProbWin(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
778
880
|
declare function calculateProbWin_v2(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
779
881
|
|
|
780
|
-
export { AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, type CancelBidResult, type Candle, type Constraints, type Currency, type Game, GameChannel, type GameConfig, GameModule, type GamePairInfo, type GridConfig, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, type MqttAcceptedBid, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type PlaceBidInput, RealtimeModule, type Sample, type SampleReason, type SignalSource, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserModule, type Wallet as UserWallet, type Wallet$1 as Wallet, adaptiveSimpson, autoDetectStorage, calculateProbWin, calculateProbWin_v2, errorFunction, isCancelled, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF };
|
|
882
|
+
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 Game, GameChannel, type GameConfig, GameModule, type GamePairInfo, 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 MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type PlaceBidInput, RealtimeModule, type Sample, type SampleReason, type SignalSource, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserModule, type Wallet as UserWallet, type Wallet$1 as Wallet, adaptiveSimpson, autoDetectStorage, calculateProbWin, calculateProbWin_v2, errorFunction, isCancelled, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF };
|
package/dist/index.js
CHANGED
|
@@ -66,6 +66,61 @@ var TaphubEventBus = class {
|
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
+
// src/modules/agencyPair/normalise.ts
|
|
70
|
+
function normaliseAgencyPairs(rows) {
|
|
71
|
+
if (!Array.isArray(rows)) {
|
|
72
|
+
throw new TaphubServerError("Invalid response from server", {
|
|
73
|
+
code: "InvalidResponse"
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return rows.map((row) => ({
|
|
77
|
+
gamePairId: row.gamePairId,
|
|
78
|
+
pair: row.pair,
|
|
79
|
+
thumb: row.thumb,
|
|
80
|
+
ordering: Number(row.ordering),
|
|
81
|
+
maxCoef: Number(row.maxCoef),
|
|
82
|
+
currentPrice: Number(row.currentPrice),
|
|
83
|
+
currentVol24h: Number(row.currentVol24h)
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/modules/agencyPair/queries.ts
|
|
88
|
+
var FETCH_AGENCY_PAIRS_QUERY = `query FetchAgencyPairs($filter: AgencyPairFilter) {
|
|
89
|
+
fetchAgencyPairs(filter: $filter) {
|
|
90
|
+
gamePairId pair thumb ordering maxCoef currentPrice currentVol24h
|
|
91
|
+
}
|
|
92
|
+
}`;
|
|
93
|
+
|
|
94
|
+
// src/modules/agencyPair/index.ts
|
|
95
|
+
var AgencyPairModule = class {
|
|
96
|
+
#graphql;
|
|
97
|
+
constructor(deps) {
|
|
98
|
+
this.#graphql = deps.graphql;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* List the agency-in-context's pairs with live derived stats.
|
|
102
|
+
*
|
|
103
|
+
* The agency is resolved server-side from the `x-builder-code` header the SDK
|
|
104
|
+
* sends on every request (no JWT required — Tier 0 anon-readable). There is no
|
|
105
|
+
* argument to target another agency by design.
|
|
106
|
+
*/
|
|
107
|
+
async list(args) {
|
|
108
|
+
const variables = {};
|
|
109
|
+
if (args?.filter) {
|
|
110
|
+
const filter = {};
|
|
111
|
+
if (args.filter.pair !== void 0) filter.pair = args.filter.pair;
|
|
112
|
+
if (args.filter.status !== void 0) filter.status = args.filter.status;
|
|
113
|
+
variables.filter = filter;
|
|
114
|
+
}
|
|
115
|
+
const body = await this.#graphql.request(
|
|
116
|
+
FETCH_AGENCY_PAIRS_QUERY,
|
|
117
|
+
variables,
|
|
118
|
+
args?.signal ? { signal: args.signal } : void 0
|
|
119
|
+
);
|
|
120
|
+
return normaliseAgencyPairs(body.fetchAgencyPairs);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
69
124
|
// src/modules/auth/normalise.ts
|
|
70
125
|
function normaliseGoogleResponse(body) {
|
|
71
126
|
return {
|
|
@@ -754,9 +809,20 @@ var LocaleModule = class {
|
|
|
754
809
|
|
|
755
810
|
// src/transport/mqtt/index.ts
|
|
756
811
|
import mqtt from "mqtt";
|
|
757
|
-
|
|
812
|
+
|
|
813
|
+
// src/transport/mqtt/utils.ts
|
|
814
|
+
function subKey(gameId, userId) {
|
|
815
|
+
return `${gameId}::${userId ?? ""}`;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// src/transport/mqtt/index.ts
|
|
819
|
+
var GAME_SCOPED_SUFFIXES = ["config", "ideal_config"];
|
|
758
820
|
var USER_SCOPED_SUFFIXES = ["bid_result", "balance_update"];
|
|
759
821
|
var TOPIC_PREFIX = "game";
|
|
822
|
+
var MARKET_TOPIC_PREFIX = "market";
|
|
823
|
+
function agencyPairStatsTopic(aid, gamePairId) {
|
|
824
|
+
return `public/agency/${aid}/pair/${gamePairId}/stats`;
|
|
825
|
+
}
|
|
760
826
|
function topicFor(gameId, suffix, userId) {
|
|
761
827
|
if (USER_SCOPED_SUFFIXES.includes(suffix)) {
|
|
762
828
|
return `${TOPIC_PREFIX}/${gameId}/user/${userId}/${suffix}`;
|
|
@@ -769,12 +835,11 @@ function normaliseUserId(userId) {
|
|
|
769
835
|
function userScopedTopicsFor(gameId, userId) {
|
|
770
836
|
return USER_SCOPED_SUFFIXES.map((s) => topicFor(gameId, s, userId));
|
|
771
837
|
}
|
|
772
|
-
function subKey(gameId, userId) {
|
|
773
|
-
return `${gameId}::${userId ?? ""}`;
|
|
774
|
-
}
|
|
775
838
|
function createMqttTransport(endpoint, opts = {}) {
|
|
776
839
|
let client = null;
|
|
777
840
|
const subscriptions = /* @__PURE__ */ new Map();
|
|
841
|
+
const candleSubscriptions = /* @__PURE__ */ new Map();
|
|
842
|
+
const statsSubscriptions = /* @__PURE__ */ new Map();
|
|
778
843
|
const { onLifecycle } = opts;
|
|
779
844
|
let connectStartedAt = 0;
|
|
780
845
|
function fireLifecycle(event) {
|
|
@@ -821,6 +886,28 @@ function createMqttTransport(endpoint, opts = {}) {
|
|
|
821
886
|
fireLifecycle({ kind: "error", err });
|
|
822
887
|
});
|
|
823
888
|
client.on("message", (receivedTopic, message) => {
|
|
889
|
+
const candleSub = [...candleSubscriptions.values()].find((s) => s.topic === receivedTopic);
|
|
890
|
+
if (candleSub) {
|
|
891
|
+
let payload2;
|
|
892
|
+
try {
|
|
893
|
+
payload2 = JSON.parse(message.toString());
|
|
894
|
+
} catch {
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
candleSub.onCandle(receivedTopic, payload2);
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
const statsSub = statsSubscriptions.get(receivedTopic);
|
|
901
|
+
if (statsSub) {
|
|
902
|
+
let payload2;
|
|
903
|
+
try {
|
|
904
|
+
payload2 = JSON.parse(message.toString());
|
|
905
|
+
} catch {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
statsSub.onStats(receivedTopic, payload2);
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
824
911
|
let matched;
|
|
825
912
|
for (const sub of subscriptions.values()) {
|
|
826
913
|
if (sub.topics.includes(receivedTopic)) {
|
|
@@ -865,9 +952,36 @@ function createMqttTransport(endpoint, opts = {}) {
|
|
|
865
952
|
onError
|
|
866
953
|
});
|
|
867
954
|
for (const t of fullTopics) {
|
|
868
|
-
mqttClient.subscribe(t, { qos:
|
|
955
|
+
mqttClient.subscribe(t, { qos: 1 });
|
|
869
956
|
}
|
|
870
957
|
},
|
|
958
|
+
subscribeCandle(pair, onCandle) {
|
|
959
|
+
const topic = `${MARKET_TOPIC_PREFIX}/${pair.replace(/\//g, "_")}/candle`;
|
|
960
|
+
if (candleSubscriptions.has(pair)) return;
|
|
961
|
+
const mqttClient = ensureConnected();
|
|
962
|
+
candleSubscriptions.set(pair, { topic, onCandle });
|
|
963
|
+
mqttClient.subscribe(topic, { qos: 0 });
|
|
964
|
+
},
|
|
965
|
+
unsubscribeCandle(pair) {
|
|
966
|
+
const entry = candleSubscriptions.get(pair);
|
|
967
|
+
if (!entry) return;
|
|
968
|
+
if (client) client.unsubscribe(entry.topic);
|
|
969
|
+
candleSubscriptions.delete(pair);
|
|
970
|
+
},
|
|
971
|
+
subscribeAgencyPairStats(aid, gamePairId, onStats) {
|
|
972
|
+
const topic = agencyPairStatsTopic(aid, gamePairId);
|
|
973
|
+
if (statsSubscriptions.has(topic)) return;
|
|
974
|
+
const mqttClient = ensureConnected();
|
|
975
|
+
statsSubscriptions.set(topic, { topic, onStats });
|
|
976
|
+
mqttClient.subscribe(topic, { qos: 0 });
|
|
977
|
+
},
|
|
978
|
+
unsubscribeAgencyPairStats(aid, gamePairId) {
|
|
979
|
+
const topic = agencyPairStatsTopic(aid, gamePairId);
|
|
980
|
+
const entry = statsSubscriptions.get(topic);
|
|
981
|
+
if (!entry) return;
|
|
982
|
+
if (client) client.unsubscribe(entry.topic);
|
|
983
|
+
statsSubscriptions.delete(topic);
|
|
984
|
+
},
|
|
871
985
|
unsubscribeAll(gameId, userId) {
|
|
872
986
|
const matches = entriesForGame(gameId);
|
|
873
987
|
if (matches.length === 0) return;
|
|
@@ -899,8 +1013,16 @@ function createMqttTransport(endpoint, opts = {}) {
|
|
|
899
1013
|
client.unsubscribe(t);
|
|
900
1014
|
}
|
|
901
1015
|
}
|
|
1016
|
+
for (const entry of candleSubscriptions.values()) {
|
|
1017
|
+
client.unsubscribe(entry.topic);
|
|
1018
|
+
}
|
|
1019
|
+
for (const entry of statsSubscriptions.values()) {
|
|
1020
|
+
client.unsubscribe(entry.topic);
|
|
1021
|
+
}
|
|
902
1022
|
}
|
|
903
1023
|
subscriptions.clear();
|
|
1024
|
+
candleSubscriptions.clear();
|
|
1025
|
+
statsSubscriptions.clear();
|
|
904
1026
|
if (client) {
|
|
905
1027
|
client.end(true);
|
|
906
1028
|
client = null;
|
|
@@ -1018,17 +1140,16 @@ function mapWireToEvent(topic, payload) {
|
|
|
1018
1140
|
function normaliseUserId2(userId) {
|
|
1019
1141
|
return userId && userId !== "" ? userId : null;
|
|
1020
1142
|
}
|
|
1021
|
-
function subKey2(gameId, userId) {
|
|
1022
|
-
return `${gameId}::${userId ?? ""}`;
|
|
1023
|
-
}
|
|
1024
1143
|
var RealtimeModule = class extends EventEmitter2 {
|
|
1025
1144
|
#transport;
|
|
1026
1145
|
#entries = /* @__PURE__ */ new Map();
|
|
1146
|
+
#agencyId;
|
|
1027
1147
|
constructor(mqttEndpointOrOptions) {
|
|
1028
1148
|
super();
|
|
1029
1149
|
if (typeof mqttEndpointOrOptions === "string") {
|
|
1030
1150
|
this.#transport = createMqttTransport(mqttEndpointOrOptions);
|
|
1031
1151
|
} else {
|
|
1152
|
+
this.#agencyId = mqttEndpointOrOptions.agencyId;
|
|
1032
1153
|
this.#transport = mqttEndpointOrOptions.transport ?? createMqttTransport(mqttEndpointOrOptions.mqttEndpoint, {
|
|
1033
1154
|
onLifecycle: mqttEndpointOrOptions.onMqttLifecycle
|
|
1034
1155
|
});
|
|
@@ -1040,7 +1161,7 @@ var RealtimeModule = class extends EventEmitter2 {
|
|
|
1040
1161
|
}
|
|
1041
1162
|
subscribe(gameId, userId) {
|
|
1042
1163
|
const cleanUserId = normaliseUserId2(userId);
|
|
1043
|
-
const key =
|
|
1164
|
+
const key = subKey(gameId, cleanUserId);
|
|
1044
1165
|
const existing = this.#entries.get(key);
|
|
1045
1166
|
if (existing) {
|
|
1046
1167
|
existing.refcount += 1;
|
|
@@ -1096,11 +1217,49 @@ var RealtimeModule = class extends EventEmitter2 {
|
|
|
1096
1217
|
}
|
|
1097
1218
|
target.refcount -= 1;
|
|
1098
1219
|
if (target.refcount > 0) return;
|
|
1099
|
-
const key =
|
|
1220
|
+
const key = subKey(target.gameId, target.userId);
|
|
1100
1221
|
this.#entries.delete(key);
|
|
1101
1222
|
target.channel.removeAllListeners();
|
|
1102
1223
|
this.#transport.unsubscribeAll(gameId, target.userId);
|
|
1103
1224
|
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Subscribe to public market candle data for a pair (e.g. "ETH/USD").
|
|
1227
|
+
* Independent of game/agency — candle is market-wide public data.
|
|
1228
|
+
*/
|
|
1229
|
+
subscribeCandle(pair, onCandle) {
|
|
1230
|
+
this.#transport.subscribeCandle(pair, onCandle);
|
|
1231
|
+
}
|
|
1232
|
+
/** Unsubscribe from candle data for a pair. */
|
|
1233
|
+
unsubscribeCandle(pair) {
|
|
1234
|
+
this.#transport.unsubscribeCandle(pair);
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Subscribe to the live per-pair stats stream for the client's own agency.
|
|
1238
|
+
* Scoped to the agency the client was constructed with — there is no argument
|
|
1239
|
+
* to target another agency by design.
|
|
1240
|
+
*/
|
|
1241
|
+
subscribeAgencyPairStats(gamePairId, handler) {
|
|
1242
|
+
if (!this.#agencyId) {
|
|
1243
|
+
throw new TaphubError("agencyId is required to subscribe to agency pair stats", {
|
|
1244
|
+
code: "AgencyIdRequired"
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
const onStats = (_topic, raw) => {
|
|
1248
|
+
const p = raw;
|
|
1249
|
+
handler({
|
|
1250
|
+
price: Number(p.price),
|
|
1251
|
+
vol24h: Number(p.vol24h),
|
|
1252
|
+
maxCoef: Number(p.maxCoef),
|
|
1253
|
+
ts: Number(p.ts)
|
|
1254
|
+
});
|
|
1255
|
+
};
|
|
1256
|
+
this.#transport.subscribeAgencyPairStats(this.#agencyId, gamePairId, onStats);
|
|
1257
|
+
}
|
|
1258
|
+
/** Unsubscribe from the per-pair stats stream for the client's own agency. */
|
|
1259
|
+
unsubscribeAgencyPairStats(gamePairId) {
|
|
1260
|
+
if (!this.#agencyId) return;
|
|
1261
|
+
this.#transport.unsubscribeAgencyPairStats(this.#agencyId, gamePairId);
|
|
1262
|
+
}
|
|
1104
1263
|
disconnect() {
|
|
1105
1264
|
for (const entry of this.#entries.values()) {
|
|
1106
1265
|
entry.channel.removeAllListeners();
|
|
@@ -2165,6 +2324,8 @@ var TaphubClient = class {
|
|
|
2165
2324
|
bid;
|
|
2166
2325
|
/** @readonly Leaderboard module — reassignment has no effect at runtime. */
|
|
2167
2326
|
leaderboard;
|
|
2327
|
+
/** @readonly Agency pair module — reassignment has no effect at runtime. */
|
|
2328
|
+
agencyPairs;
|
|
2168
2329
|
/** @readonly Locale module — reassignment has no effect at runtime. */
|
|
2169
2330
|
locale;
|
|
2170
2331
|
/** @readonly Realtime module — undefined when mqttEndpoint not configured. */
|
|
@@ -2245,9 +2406,11 @@ var TaphubClient = class {
|
|
|
2245
2406
|
this.game = new GameModule({ graphql: this.#graphql });
|
|
2246
2407
|
this.bid = new BidModule({ graphql: this.#graphql });
|
|
2247
2408
|
this.leaderboard = new LeaderboardModule({ graphql: this.#graphql });
|
|
2409
|
+
this.agencyPairs = new AgencyPairModule({ graphql: this.#graphql });
|
|
2248
2410
|
this.locale = new LocaleModule({ graphql: this.#graphql });
|
|
2249
2411
|
this.realtime = config.mqttEndpoint ? new RealtimeModule({
|
|
2250
2412
|
mqttEndpoint: config.mqttEndpoint,
|
|
2413
|
+
agencyId: this.agencyId,
|
|
2251
2414
|
onMqttLifecycle: createMqttProbe(this.network)
|
|
2252
2415
|
}) : void 0;
|
|
2253
2416
|
attachConnectionProbe(this.network);
|
|
@@ -2281,6 +2444,12 @@ var TaphubClient = class {
|
|
|
2281
2444
|
enumerable: true,
|
|
2282
2445
|
configurable: false
|
|
2283
2446
|
});
|
|
2447
|
+
Object.defineProperty(this, "agencyPairs", {
|
|
2448
|
+
value: this.agencyPairs,
|
|
2449
|
+
writable: false,
|
|
2450
|
+
enumerable: true,
|
|
2451
|
+
configurable: false
|
|
2452
|
+
});
|
|
2284
2453
|
if (this.realtime) {
|
|
2285
2454
|
Object.defineProperty(this, "realtime", {
|
|
2286
2455
|
value: this.realtime,
|
|
@@ -2416,6 +2585,7 @@ function calculateProbWin_v2(time1, time2, _price1, price2, creationTime, curren
|
|
|
2416
2585
|
return Math.max(0, Math.min(1, prob));
|
|
2417
2586
|
}
|
|
2418
2587
|
export {
|
|
2588
|
+
AgencyPairModule,
|
|
2419
2589
|
AuthModule,
|
|
2420
2590
|
BidModule,
|
|
2421
2591
|
GameModule,
|