@medievalrain/binance-ts 0.2.0 → 0.3.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +25 -42
  2. package/dist/index.js +134 -102
  3. package/package.json +6 -5
package/dist/index.d.ts CHANGED
@@ -1481,43 +1481,16 @@ declare class MalformedParamError extends ResponseError {
1481
1481
  }
1482
1482
  //#endregion
1483
1483
  //#region src/websocket/base/types.d.ts
1484
- type ConnectionSuccessEvent = {
1485
- result: null;
1486
- id: number;
1487
- };
1488
- type ConnectionErrorEvent = {
1489
- result: null;
1490
- id: number;
1491
- status: number;
1492
- error: {
1493
- code: number;
1494
- msg: string;
1495
- };
1496
- };
1497
- type ConnectionEvent = ConnectionSuccessEvent | ConnectionErrorEvent;
1498
- //#endregion
1499
- //#region src/websocket/base/client.d.ts
1500
- type WebsocketClientEventMap<MarketEvent extends object> = {
1501
- marketMessage: (data: MarketEvent) => void;
1502
- connectionMessage: (data: ConnectionEvent) => void;
1503
- error: (error: Error | ConnectionErrorEvent) => void;
1504
- };
1505
- declare class BinanceWebsocketClient<MarketEvent extends object> {
1506
- private socket;
1507
- private baseUrl;
1508
- private emitter;
1509
- private subscriptionId;
1510
- private subscriptions;
1511
- constructor(baseUrl: string);
1512
- private createSocket;
1513
- private sendMessage;
1514
- private parseEvent;
1515
- subscribe(...channels: string[]): Promise<void>;
1516
- unsubscribe(...channels: string[]): Promise<void>;
1517
- reconnect(): Promise<void>;
1518
- connect(): Promise<void>;
1519
- addEventListener<E extends keyof WebsocketClientEventMap<MarketEvent>>(event: E, callback: WebsocketClientEventMap<MarketEvent>[E], options?: AddEventListenerOptions): void;
1520
- }
1484
+ type ChannelsMap = Record<string, {
1485
+ messageSchema: object;
1486
+ subscriptionOptions?: object;
1487
+ }>;
1488
+ type OptArgs<CM extends ChannelsMap, K extends keyof CM> = CM[K]["subscriptionOptions"] extends never | undefined ? [] : [options: CM[K]["subscriptionOptions"]];
1489
+ type WebsocketClient<CM extends ChannelsMap> = { [K in keyof CM]: {
1490
+ subscribe: (symbols: string[], ...args: OptArgs<CM, K>) => Promise<void>;
1491
+ unsubscribe: (symbols: string[], ...args: OptArgs<CM, K>) => Promise<void>;
1492
+ addEventListener: (cb: (data: CM[K]["messageSchema"]) => void, options?: AddEventListenerOptions) => void;
1493
+ } };
1521
1494
  //#endregion
1522
1495
  //#region src/websocket/futures/types.d.ts
1523
1496
  type FuturesBookTickerEvent = {
@@ -1538,11 +1511,21 @@ type FuturesBookDepthEvent = {
1538
1511
  b: [string, string][];
1539
1512
  a: [string, string][];
1540
1513
  };
1541
- type FuturesMarketEvent = FuturesBookDepthEvent | FuturesBookTickerEvent;
1514
+ type FuturesChannels = {
1515
+ bookTicker: {
1516
+ messageSchema: FuturesBookTickerEvent;
1517
+ subscriptionOptions: never;
1518
+ };
1519
+ partialBookDepth: {
1520
+ messageSchema: FuturesBookDepthEvent;
1521
+ subscriptionOptions: {
1522
+ levels: number;
1523
+ updateSpeedMs?: number;
1524
+ };
1525
+ };
1526
+ };
1542
1527
  //#endregion
1543
1528
  //#region src/websocket/futures/client.d.ts
1544
- declare class FuturesWebsocketClient extends BinanceWebsocketClient<FuturesMarketEvent> {
1545
- constructor(baseUrl?: string);
1546
- }
1529
+ declare const createFuturesWebsocketClient: (baseUrl?: string) => WebsocketClient<FuturesChannels>;
1547
1530
  //#endregion
1548
- export { ApiError, BinanceRestClient, ConnectionErrorEvent, ConnectionEvent, ConnectionSuccessEvent, ErrorMessageParsingError, FundingInfo, FundingInfoEntry, FundingRate, FundingRateEntry, FuturesAccountAsset, FuturesAccountBalance, FuturesAccountConfig, FuturesAccountInfo, FuturesAccountPosition, FuturesAssetIndex, FuturesBasis, FuturesBookDepthEvent, FuturesBookTicker, FuturesBookTickerEvent, FuturesCheckServerTime, FuturesCommissionRate, FuturesCompositeIndex, FuturesCompositeIndexAsset, FuturesContractType, FuturesDeliveryPrice, FuturesExchangeInfo, FuturesExchangeInfoAsset, FuturesExchangeInfoFilter, FuturesExchangeInfoRateLimit, FuturesExchangeInfoSymbol, FuturesIncomeHistory, FuturesIncomeType, FuturesIndexPriceConstituent, FuturesIndexPriceConstituents, FuturesInsuranceBalanceAsset, FuturesInsuranceBalanceEntry, FuturesKlineInterval, FuturesLeverageBracket, FuturesLeverageBracketEntry, FuturesLongShortRatio, FuturesMarketEvent, FuturesNewOrder, FuturesNewOrderRespType, FuturesOldTradesLookup, FuturesOpenInterest, FuturesOpenInterestPeriod, FuturesOpenInterestStats, FuturesOrderBook, FuturesOrderSide, FuturesOrderType, FuturesPositionMode, FuturesPositionSide, FuturesPriceMatch, FuturesRecentTrades, FuturesSelfTradePrevention, FuturesSymbolConfig, FuturesSymbolPrice, FuturesTakerBuySellRatio, FuturesTakerBuySellRatioItem, FuturesTestConnectivity, FuturesTicker24h, FuturesTimeInForce, FuturesUnderlyingType, FuturesUserRateLimit, FuturesWebsocketClient, FuturesWorkingType, FuturesggregateTrades, KlineData, MalformedParamError, MarkPrice, ResponseError, ValidationError, WeightError };
1531
+ export { ApiError, BinanceRestClient, ErrorMessageParsingError, FundingInfo, FundingInfoEntry, FundingRate, FundingRateEntry, FuturesAccountAsset, FuturesAccountBalance, FuturesAccountConfig, FuturesAccountInfo, FuturesAccountPosition, FuturesAssetIndex, FuturesBasis, FuturesBookDepthEvent, FuturesBookTicker, FuturesBookTickerEvent, FuturesCheckServerTime, FuturesCommissionRate, FuturesCompositeIndex, FuturesCompositeIndexAsset, FuturesContractType, FuturesDeliveryPrice, FuturesExchangeInfo, FuturesExchangeInfoAsset, FuturesExchangeInfoFilter, FuturesExchangeInfoRateLimit, FuturesExchangeInfoSymbol, FuturesIncomeHistory, FuturesIncomeType, FuturesIndexPriceConstituent, FuturesIndexPriceConstituents, FuturesInsuranceBalanceAsset, FuturesInsuranceBalanceEntry, FuturesKlineInterval, FuturesLeverageBracket, FuturesLeverageBracketEntry, FuturesLongShortRatio, FuturesNewOrder, FuturesNewOrderRespType, FuturesOldTradesLookup, FuturesOpenInterest, FuturesOpenInterestPeriod, FuturesOpenInterestStats, FuturesOrderBook, FuturesOrderSide, FuturesOrderType, FuturesPositionMode, FuturesPositionSide, FuturesPriceMatch, FuturesRecentTrades, FuturesSelfTradePrevention, FuturesSymbolConfig, FuturesSymbolPrice, FuturesTakerBuySellRatio, FuturesTakerBuySellRatioItem, FuturesTestConnectivity, FuturesTicker24h, FuturesTimeInForce, FuturesUnderlyingType, FuturesUserRateLimit, FuturesWorkingType, FuturesggregateTrades, KlineData, MalformedParamError, MarkPrice, ResponseError, ValidationError, WeightError, createFuturesWebsocketClient };
package/dist/index.js CHANGED
@@ -1033,7 +1033,7 @@ var BinanceRestClient = class {
1033
1033
  };
1034
1034
 
1035
1035
  //#endregion
1036
- //#region src/websocket/typed-event-emitter.ts
1036
+ //#region src/websocket/base/typed-event-emitter.ts
1037
1037
  var Emitter = class {
1038
1038
  eventTarget = new EventTarget();
1039
1039
  addEventListener(event, callback, options) {
@@ -1046,129 +1046,161 @@ var Emitter = class {
1046
1046
 
1047
1047
  //#endregion
1048
1048
  //#region src/websocket/base/client.ts
1049
- var BinanceWebsocketClient = class {
1050
- socket;
1051
- baseUrl;
1052
- emitter = new Emitter();
1053
- subscriptionId = 1;
1054
- subscriptions = /* @__PURE__ */ new Map();
1055
- constructor(baseUrl) {
1056
- this.parseEvent = this.parseEvent.bind(this);
1057
- this.baseUrl = baseUrl;
1058
- this.socket = this.createSocket();
1059
- }
1060
- createSocket() {
1061
- if (this.socket) {
1062
- this.socket.removeEventListener("message", this.parseEvent);
1063
- this.socket.close();
1064
- }
1065
- const socket = new WebSocket(this.baseUrl);
1066
- socket.addEventListener("message", this.parseEvent);
1067
- return socket;
1068
- }
1069
- sendMessage(data) {
1070
- this.socket.send(JSON.stringify(data));
1071
- }
1072
- parseEvent(e) {
1049
+ const makeSection = (baseUrl) => {
1050
+ let socket = new WebSocket(baseUrl);
1051
+ let connectionController = new AbortController();
1052
+ const subscriptions = /* @__PURE__ */ new Map();
1053
+ const emitter = new Emitter();
1054
+ let connectionId = 1;
1055
+ const parseMessageEvent = (e) => {
1073
1056
  if (typeof e.data !== "string") {
1074
- this.emitter.emit("error", new Error("Message event is not a string", { cause: e.data }));
1057
+ emitter.emit("error", new Error("Message event is not a string", { cause: e.data }));
1075
1058
  return;
1076
1059
  }
1077
1060
  const data = JSON.parse(e.data);
1078
- if ("id" in data) this.emitter.emit("connectionMessage", data);
1079
- else this.emitter.emit("marketMessage", data);
1080
- }
1081
- async subscribe(...channels) {
1082
- await this.connect();
1083
- const pendingChannels = [];
1084
- for (const channel of channels) if (!this.subscriptions.get(channel)) {
1085
- pendingChannels.push(channel);
1086
- this.subscriptions.set(channel, "PENDING_SUBSCRIPTION");
1087
- }
1088
- const id = this.subscriptionId;
1089
- const data = {
1061
+ if ("id" in data) emitter.emit("connectionMessage", data);
1062
+ else emitter.emit("marketMessage", data);
1063
+ };
1064
+ socket.addEventListener("message", parseMessageEvent, { signal: connectionController.signal });
1065
+ const sendMessage = (data) => socket.send(JSON.stringify(data));
1066
+ const connect = async () => {
1067
+ return new Promise((resolve) => {
1068
+ if (socket.readyState === WebSocket.OPEN) return resolve();
1069
+ if (socket.readyState === WebSocket.CLOSED) {
1070
+ connectionController.abort();
1071
+ socket = new WebSocket(baseUrl);
1072
+ connectionController = new AbortController();
1073
+ socket.addEventListener("message", parseMessageEvent, { signal: connectionController.signal });
1074
+ connectionId = 1;
1075
+ const toSubscribe = Array.from(subscriptions.entries()).filter(([_, state]) => state !== "PENDING_UNSUBSCRIPTION").map(([key]) => key);
1076
+ return subscribe(toSubscribe);
1077
+ }
1078
+ if (socket.readyState === WebSocket.CONNECTING) socket.addEventListener("open", () => resolve(), { once: true });
1079
+ if (socket.readyState === WebSocket.CLOSING) socket.addEventListener("close", async () => {
1080
+ await connect();
1081
+ resolve();
1082
+ }, { once: true });
1083
+ });
1084
+ };
1085
+ const subscribe = async (symbols) => {
1086
+ await connect();
1087
+ const toSubscribe = [];
1088
+ const delayed = [];
1089
+ connectionId += 1;
1090
+ const currentId = connectionId;
1091
+ symbols.forEach((symbol) => {
1092
+ const existing = subscriptions.get(symbol);
1093
+ if (!existing) {
1094
+ toSubscribe.push(symbol);
1095
+ subscriptions.set(symbol, "PENDING_SUBSCRIPTION");
1096
+ return;
1097
+ }
1098
+ if (existing === "SUBSCRIBED") return;
1099
+ delayed.push(symbol);
1100
+ });
1101
+ sendMessage({
1090
1102
  method: "SUBSCRIBE",
1091
- params: pendingChannels,
1092
- id
1093
- };
1094
- this.subscriptionId += 1;
1095
- this.sendMessage(data);
1096
- return new Promise((resolve, reject) => {
1103
+ params: toSubscribe,
1104
+ id: currentId
1105
+ });
1106
+ await new Promise((resolve, reject) => {
1097
1107
  const controller = new AbortController();
1098
- const handler = (data$1) => {
1099
- if (data$1.id !== id) return;
1108
+ const handleSubscription = (data) => {
1109
+ if (data.id !== currentId) return;
1100
1110
  controller.abort();
1101
- if ("error" in data$1) {
1102
- for (const channel of pendingChannels) if (this.subscriptions.get(channel) === "PENDING_SUBSCRIPTION") this.subscriptions.delete(channel);
1103
- this.emitter.emit("error", data$1);
1104
- reject(/* @__PURE__ */ new Error(`Subscription ${id} failed`));
1105
- } else {
1106
- pendingChannels.forEach((channel) => this.subscriptions.set(channel, "SUBSCRIBED"));
1107
- resolve();
1111
+ if ("error" in data) return reject();
1112
+ else {
1113
+ toSubscribe.forEach((symbol) => subscriptions.set(symbol, "SUBSCRIBED"));
1114
+ return resolve();
1108
1115
  }
1109
1116
  };
1110
- this.emitter.addEventListener("connectionMessage", handler, { signal: controller.signal });
1117
+ emitter.addEventListener("connectionMessage", handleSubscription, { signal: controller.signal });
1111
1118
  });
1112
- }
1113
- async unsubscribe(...channels) {
1114
- await this.connect();
1115
- const id = this.subscriptionId;
1116
- const pendingChannels = [];
1117
- for (const channel of channels) if (this.subscriptions.get(channel) !== "PENDING_UNSUBSCRIPTION") {
1118
- pendingChannels.push(channel);
1119
- this.subscriptions.set(channel, "PENDING_UNSUBSCRIPTION");
1120
- }
1121
- const data = {
1119
+ if (delayed.length) return subscribe(delayed);
1120
+ };
1121
+ const unsubscribe = async (symbols) => {
1122
+ await connect();
1123
+ const toUnsubscribe = [];
1124
+ const delayed = [];
1125
+ connectionId += 1;
1126
+ const currentId = connectionId;
1127
+ symbols.forEach((symbol) => {
1128
+ if (subscriptions.get(symbol) === "SUBSCRIBED") {
1129
+ toUnsubscribe.push(symbol);
1130
+ subscriptions.set(symbol, "PENDING_UNSUBSCRIPTION");
1131
+ return;
1132
+ }
1133
+ delayed.push(symbol);
1134
+ });
1135
+ sendMessage({
1122
1136
  method: "UNSUBSCRIBE",
1123
- params: pendingChannels,
1124
- id
1125
- };
1126
- this.subscriptionId += 1;
1127
- this.sendMessage(data);
1128
- return new Promise((resolve, reject) => {
1137
+ params: toUnsubscribe,
1138
+ id: currentId
1139
+ });
1140
+ await new Promise((resolve, reject) => {
1129
1141
  const controller = new AbortController();
1130
- const handler = (data$1) => {
1131
- if (data$1.id !== id) return;
1142
+ const handleSubscription = (data) => {
1143
+ if (data.id !== currentId) return;
1132
1144
  controller.abort();
1133
- if ("error" in data$1) {
1134
- for (const channel of pendingChannels) if (this.subscriptions.get(channel) === "PENDING_UNSUBSCRIPTION") this.subscriptions.set(channel, "SUBSCRIBED");
1135
- this.emitter.emit("error", data$1);
1136
- reject(/* @__PURE__ */ new Error(`Unsubscription ${id} failed`));
1137
- } else {
1138
- pendingChannels.forEach((channel) => this.subscriptions.delete(channel));
1139
- resolve();
1145
+ if ("error" in data) return reject();
1146
+ else {
1147
+ toUnsubscribe.forEach((symbol) => subscriptions.delete(symbol));
1148
+ return resolve();
1140
1149
  }
1141
1150
  };
1142
- this.emitter.addEventListener("connectionMessage", handler, { signal: controller.signal });
1151
+ emitter.addEventListener("connectionMessage", handleSubscription, { signal: controller.signal });
1143
1152
  });
1144
- }
1145
- async reconnect() {
1146
- this.socket = this.createSocket();
1147
- await this.connect();
1148
- const toRestore = [];
1149
- for (const [channel, state] of this.subscriptions) if (state !== "PENDING_UNSUBSCRIPTION") toRestore.push(channel);
1150
- if (toRestore.length) await this.subscribe(...toRestore);
1151
- }
1152
- async connect() {
1153
- if (this.socket.readyState === WebSocket.OPEN) return;
1154
- if (this.socket.readyState === WebSocket.CONNECTING) return new Promise((resolve, reject) => {
1155
- this.socket.addEventListener("open", () => resolve(), { once: true });
1156
- this.socket.addEventListener("close", () => reject(), { once: true });
1153
+ if (delayed.length) return unsubscribe(delayed);
1154
+ };
1155
+ return {
1156
+ socket,
1157
+ subscriptions,
1158
+ connectionId,
1159
+ addEventListener: emitter.addEventListener.bind(emitter),
1160
+ subscribe,
1161
+ unsubscribe
1162
+ };
1163
+ };
1164
+ const createWebsocketClient = (baseUrl, symbolConverter$1) => {
1165
+ const sections = /* @__PURE__ */ new Map();
1166
+ const getSection = (channel) => {
1167
+ const existingSection = sections.get(channel);
1168
+ if (existingSection) return existingSection;
1169
+ const section = makeSection(baseUrl);
1170
+ sections.set(channel, section);
1171
+ return section;
1172
+ };
1173
+ return new Proxy({}, { get(_target, channel) {
1174
+ const section = getSection(channel);
1175
+ const converter = symbolConverter$1[channel];
1176
+ return Object.freeze({
1177
+ subscribe: (symbols, ...args) => {
1178
+ return section.subscribe(symbols.map((s) => converter(s, ...args)));
1179
+ },
1180
+ unsubscribe: (symbols, ...args) => {
1181
+ return section.unsubscribe(symbols.map((s) => converter(s, ...args)));
1182
+ },
1183
+ addEventListener: (callback, options) => {
1184
+ section.addEventListener("marketMessage", callback, options);
1185
+ }
1157
1186
  });
1158
- return this.reconnect();
1159
- }
1160
- addEventListener(event, callback, options) {
1161
- this.emitter.addEventListener(event, callback, options);
1162
- }
1187
+ } });
1163
1188
  };
1164
1189
 
1165
1190
  //#endregion
1166
1191
  //#region src/websocket/futures/client.ts
1167
- var FuturesWebsocketClient = class extends BinanceWebsocketClient {
1168
- constructor(baseUrl = "wss://stream.binance.com:9443/ws") {
1169
- super(baseUrl);
1192
+ const symbolConverter = {
1193
+ bookTicker: (symbol) => {
1194
+ return `${symbol.toLowerCase()}@bookTicker`;
1195
+ },
1196
+ partialBookDepth: (symbol, { levels, updateSpeedMs }) => {
1197
+ if (updateSpeedMs) return `${symbol.toLowerCase()}@depth${levels}@${updateSpeedMs}ms`;
1198
+ return `${symbol.toLowerCase()}@depth${levels}`;
1170
1199
  }
1171
1200
  };
1201
+ const createFuturesWebsocketClient = (baseUrl = "wss://stream.binance.com:9443/ws") => {
1202
+ return createWebsocketClient(baseUrl, symbolConverter);
1203
+ };
1172
1204
 
1173
1205
  //#endregion
1174
- export { ApiError, BinanceRestClient, ErrorMessageParsingError, FuturesWebsocketClient, MalformedParamError, ResponseError, ValidationError, WeightError };
1206
+ export { ApiError, BinanceRestClient, ErrorMessageParsingError, MalformedParamError, ResponseError, ValidationError, WeightError, createFuturesWebsocketClient };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@medievalrain/binance-ts",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Binance API SDK",
5
5
  "access": "public",
6
6
  "type": "module",
@@ -23,20 +23,21 @@
23
23
  "devDependencies": {
24
24
  "date-fns": "4.1.0",
25
25
  "dotenv": "17.2.2",
26
- "oxlint": "1.14.0",
26
+ "oxlint": "1.15.0",
27
27
  "prettier": "3.6.2",
28
- "tsdown": "0.15.0",
28
+ "tsdown": "0.15.1",
29
29
  "typescript": "5.9.2",
30
30
  "vitest": "3.2.4",
31
31
  "@types/node": "24.3.1"
32
32
  },
33
33
  "peerDependencies": {
34
- "zod": "4"
34
+ "zod": "^4"
35
35
  },
36
36
  "dependencies": {
37
37
  "undici": "7.16.0"
38
38
  },
39
39
  "engines": {
40
40
  "node": ">=22.19.0"
41
- }
41
+ },
42
+ "packageManager": "pnpm@10.16.0"
42
43
  }