@imbingox/acex 0.1.0 → 0.2.0

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 (85) hide show
  1. package/README.md +92 -285
  2. package/index.ts +1 -0
  3. package/package.json +40 -23
  4. package/src/adapters/binance/adapter.ts +80 -0
  5. package/src/adapters/binance/book-ticker.ts +123 -0
  6. package/src/adapters/binance/mark-price.ts +126 -0
  7. package/src/adapters/binance/market-catalog.ts +258 -0
  8. package/src/adapters/binance/private-adapter.ts +833 -0
  9. package/src/adapters/types.ts +219 -0
  10. package/src/client/context.ts +123 -0
  11. package/src/client/create-client.ts +6 -0
  12. package/src/client/private-subscription-coordinator.ts +512 -0
  13. package/src/client/runtime.ts +410 -0
  14. package/src/errors.ts +27 -0
  15. package/src/index.ts +5 -0
  16. package/src/internal/async-event-bus.ts +100 -0
  17. package/src/internal/filters.ts +117 -0
  18. package/src/internal/managed-websocket.ts +280 -0
  19. package/src/managers/account-manager.ts +609 -0
  20. package/src/managers/market-manager.ts +889 -0
  21. package/src/managers/order-manager.ts +685 -0
  22. package/src/types/account.ts +157 -0
  23. package/src/types/client.ts +79 -0
  24. package/src/types/index.ts +5 -0
  25. package/src/types/market.ts +150 -0
  26. package/src/types/order.ts +177 -0
  27. package/src/types/shared.ts +93 -0
  28. package/dist/adapters/binance/composite-adapter.d.ts +0 -116
  29. package/dist/adapters/binance/composite-adapter.js +0 -121
  30. package/dist/adapters/binance/market-types.d.ts +0 -63
  31. package/dist/adapters/binance/market-types.js +0 -1
  32. package/dist/adapters/binance/native-market-adapter.d.ts +0 -102
  33. package/dist/adapters/binance/native-market-adapter.js +0 -455
  34. package/dist/adapters/binance/normalizers.d.ts +0 -8
  35. package/dist/adapters/binance/normalizers.js +0 -123
  36. package/dist/adapters/binance/rest-client.d.ts +0 -17
  37. package/dist/adapters/binance/rest-client.js +0 -66
  38. package/dist/adapters/binance/symbol-router.d.ts +0 -9
  39. package/dist/adapters/binance/symbol-router.js +0 -174
  40. package/dist/adapters/binance/ws-client.d.ts +0 -24
  41. package/dist/adapters/binance/ws-client.js +0 -261
  42. package/dist/adapters/ccxt/aster-ccxt-adapter.d.ts +0 -157
  43. package/dist/adapters/ccxt/aster-ccxt-adapter.js +0 -272
  44. package/dist/adapters/ccxt/binance-usdm-ccxt-adapter.d.ts +0 -180
  45. package/dist/adapters/ccxt/binance-usdm-ccxt-adapter.js +0 -539
  46. package/dist/adapters/ccxt/binance-usdm-exchange.d.ts +0 -22
  47. package/dist/adapters/ccxt/binance-usdm-exchange.js +0 -23
  48. package/dist/adapters/fake/fake-aster-adapter.d.ts +0 -130
  49. package/dist/adapters/fake/fake-aster-adapter.js +0 -283
  50. package/dist/adapters/types.d.ts +0 -210
  51. package/dist/adapters/types.js +0 -1
  52. package/dist/core/client.d.ts +0 -50
  53. package/dist/core/client.js +0 -403
  54. package/dist/core/recovery.d.ts +0 -22
  55. package/dist/core/recovery.js +0 -18
  56. package/dist/core/runtime.d.ts +0 -26
  57. package/dist/core/runtime.js +0 -150
  58. package/dist/errors/acex-error.d.ts +0 -25
  59. package/dist/errors/acex-error.js +0 -54
  60. package/dist/index.d.ts +0 -6
  61. package/dist/index.js +0 -3
  62. package/dist/managers/account-manager.d.ts +0 -41
  63. package/dist/managers/account-manager.js +0 -80
  64. package/dist/managers/market-manager.d.ts +0 -16
  65. package/dist/managers/market-manager.js +0 -28
  66. package/dist/managers/order-manager.d.ts +0 -87
  67. package/dist/managers/order-manager.js +0 -122
  68. package/dist/runtime/async-queue.d.ts +0 -8
  69. package/dist/runtime/async-queue.js +0 -88
  70. package/dist/runtime/request-id.d.ts +0 -1
  71. package/dist/runtime/request-id.js +0 -5
  72. package/dist/runtime/ws-connection-supervisor.d.ts +0 -76
  73. package/dist/runtime/ws-connection-supervisor.js +0 -522
  74. package/dist/store/account-store.d.ts +0 -52
  75. package/dist/store/account-store.js +0 -18
  76. package/dist/store/health-store.d.ts +0 -16
  77. package/dist/store/health-store.js +0 -29
  78. package/dist/store/market-store.d.ts +0 -42
  79. package/dist/store/market-store.js +0 -51
  80. package/dist/store/order-store.d.ts +0 -38
  81. package/dist/store/order-store.js +0 -49
  82. package/dist/testing/create-fake-runtime.d.ts +0 -5
  83. package/dist/testing/create-fake-runtime.js +0 -7
  84. package/dist/types/public.d.ts +0 -5
  85. package/dist/types/public.js +0 -1
@@ -0,0 +1,280 @@
1
+ type TimerHandle = ReturnType<typeof setTimeout>;
2
+
3
+ export type WebSocketFactory = (url: string) => WebSocket;
4
+
5
+ export interface ManagedWebSocketWatchdogOptions {
6
+ staleAfterMs: number;
7
+ onStale(staleAt: number): void;
8
+ }
9
+
10
+ export interface ManagedWebSocketReconnectOptions {
11
+ initialDelayMs: number;
12
+ maxDelayMs: number;
13
+ backoffMultiplier?: number;
14
+ reconnectWithoutMessages?: boolean;
15
+ }
16
+
17
+ export interface ManagedWebSocketOptions<TMessage> {
18
+ url: string;
19
+ initialMessageTimeoutMs: number;
20
+ readyWhen?: "message" | "open";
21
+ parseMessage(data: string): TMessage | undefined;
22
+ onMessage(message: TMessage, receivedAt: number): void;
23
+ onUnexpectedClose(event: CloseEvent): void;
24
+ onOpen?(): void;
25
+ onError?(event: Event): void;
26
+ messageWatchdog?: ManagedWebSocketWatchdogOptions;
27
+ reconnect?: ManagedWebSocketReconnectOptions;
28
+ now?: () => number;
29
+ createWebSocket?: WebSocketFactory;
30
+ setTimer?: typeof setTimeout;
31
+ clearTimer?: typeof clearTimeout;
32
+ }
33
+
34
+ export interface ManagedWebSocketSession {
35
+ readonly ready: Promise<void>;
36
+ close(): void;
37
+ }
38
+
39
+ function toError(value: unknown, fallback: string): Error {
40
+ if (value instanceof Error) {
41
+ return value;
42
+ }
43
+
44
+ return new Error(fallback);
45
+ }
46
+
47
+ export function createManagedWebSocket<TMessage>(
48
+ options: ManagedWebSocketOptions<TMessage>,
49
+ ): ManagedWebSocketSession {
50
+ const now = options.now ?? Date.now;
51
+ const setTimer = options.setTimer ?? setTimeout;
52
+ const clearTimer = options.clearTimer ?? clearTimeout;
53
+ const createWebSocket =
54
+ options.createWebSocket ?? ((url: string) => new WebSocket(url));
55
+ const messageWatchdog = options.messageWatchdog;
56
+ const reconnect = options.reconnect;
57
+ const reconnectMultiplier = reconnect?.backoffMultiplier ?? 2;
58
+ const readyWhen = options.readyWhen ?? "message";
59
+
60
+ let closed = false;
61
+ let staleNotified = false;
62
+ let hasMessage = false;
63
+ let lastMessageAt = now();
64
+ let initialTimeout: TimerHandle | undefined;
65
+ let staleTimeout: TimerHandle | undefined;
66
+ let reconnectTimeout: TimerHandle | undefined;
67
+ let reconnectAttempts = 0;
68
+ let resolveReady: (() => void) | undefined;
69
+ let rejectReady: ((error: Error) => void) | undefined;
70
+ let activeSocket: WebSocket | undefined;
71
+
72
+ const ready = new Promise<void>((resolve, reject) => {
73
+ resolveReady = resolve;
74
+ rejectReady = reject;
75
+ });
76
+
77
+ const clearTimers = () => {
78
+ if (initialTimeout) {
79
+ clearTimer(initialTimeout);
80
+ initialTimeout = undefined;
81
+ }
82
+
83
+ if (staleTimeout) {
84
+ clearTimer(staleTimeout);
85
+ staleTimeout = undefined;
86
+ }
87
+ };
88
+
89
+ const clearReconnectTimer = () => {
90
+ if (reconnectTimeout) {
91
+ clearTimer(reconnectTimeout);
92
+ reconnectTimeout = undefined;
93
+ }
94
+ };
95
+
96
+ const rejectIfPending = (error: Error) => {
97
+ if (!resolveReady || !rejectReady) {
98
+ return;
99
+ }
100
+
101
+ const reject = rejectReady;
102
+ resolveReady = undefined;
103
+ rejectReady = undefined;
104
+ reject(error);
105
+ };
106
+
107
+ const resolveIfPending = () => {
108
+ if (!resolveReady || !rejectReady) {
109
+ return;
110
+ }
111
+
112
+ const resolve = resolveReady;
113
+ resolveReady = undefined;
114
+ rejectReady = undefined;
115
+ resolve();
116
+ };
117
+
118
+ const scheduleStaleTimeout = () => {
119
+ if (closed || !messageWatchdog) {
120
+ return;
121
+ }
122
+
123
+ if (staleTimeout) {
124
+ clearTimer(staleTimeout);
125
+ }
126
+
127
+ staleTimeout = setTimer(() => {
128
+ if (closed || staleNotified) {
129
+ return;
130
+ }
131
+
132
+ staleNotified = true;
133
+ messageWatchdog.onStale(now());
134
+ }, messageWatchdog.staleAfterMs);
135
+ };
136
+
137
+ const scheduleReconnect = () => {
138
+ if (
139
+ closed ||
140
+ !reconnect ||
141
+ reconnectTimeout ||
142
+ (!hasMessage && !reconnect.reconnectWithoutMessages)
143
+ ) {
144
+ return;
145
+ }
146
+
147
+ const delay = Math.min(
148
+ reconnect.initialDelayMs * reconnectMultiplier ** reconnectAttempts,
149
+ reconnect.maxDelayMs,
150
+ );
151
+ reconnectAttempts += 1;
152
+ reconnectTimeout = setTimer(() => {
153
+ reconnectTimeout = undefined;
154
+ connect();
155
+ }, delay);
156
+ };
157
+
158
+ const connect = () => {
159
+ if (closed) {
160
+ return;
161
+ }
162
+
163
+ const socket = createWebSocket(options.url);
164
+ activeSocket = socket;
165
+
166
+ if (!hasMessage) {
167
+ initialTimeout = setTimer(() => {
168
+ if (closed || hasMessage || activeSocket !== socket) {
169
+ return;
170
+ }
171
+
172
+ rejectIfPending(
173
+ new Error("Timed out waiting for the first websocket message"),
174
+ );
175
+ socket.close(1000, "initial message timeout");
176
+ }, options.initialMessageTimeoutMs);
177
+ }
178
+
179
+ if (messageWatchdog) {
180
+ scheduleStaleTimeout();
181
+ }
182
+
183
+ socket.addEventListener("open", () => {
184
+ if (closed || activeSocket !== socket) {
185
+ return;
186
+ }
187
+
188
+ options.onOpen?.();
189
+ if (readyWhen === "open") {
190
+ resolveIfPending();
191
+ }
192
+ });
193
+
194
+ socket.addEventListener("message", (event) => {
195
+ if (closed || activeSocket !== socket || typeof event.data !== "string") {
196
+ return;
197
+ }
198
+
199
+ let parsed: TMessage | undefined;
200
+ try {
201
+ parsed = options.parseMessage(event.data);
202
+ } catch (error) {
203
+ options.onError?.(
204
+ new ErrorEvent("error", {
205
+ error: toError(error, "Failed to parse websocket message"),
206
+ }),
207
+ );
208
+ return;
209
+ }
210
+
211
+ if (!parsed) {
212
+ return;
213
+ }
214
+
215
+ hasMessage = true;
216
+ staleNotified = false;
217
+ lastMessageAt = now();
218
+ reconnectAttempts = 0;
219
+
220
+ if (initialTimeout) {
221
+ clearTimer(initialTimeout);
222
+ initialTimeout = undefined;
223
+ }
224
+
225
+ if (messageWatchdog) {
226
+ scheduleStaleTimeout();
227
+ }
228
+ options.onMessage(parsed, lastMessageAt);
229
+ if (readyWhen === "message") {
230
+ resolveIfPending();
231
+ }
232
+ });
233
+
234
+ socket.addEventListener("error", (event) => {
235
+ if (closed || activeSocket !== socket) {
236
+ return;
237
+ }
238
+
239
+ options.onError?.(event);
240
+ });
241
+
242
+ socket.addEventListener("close", (event) => {
243
+ if (closed || activeSocket !== socket) {
244
+ return;
245
+ }
246
+
247
+ activeSocket = undefined;
248
+ clearTimers();
249
+
250
+ if (!hasMessage) {
251
+ rejectIfPending(
252
+ toError(
253
+ event.reason || undefined,
254
+ "WebSocket closed before the first market update arrived",
255
+ ),
256
+ );
257
+ }
258
+
259
+ options.onUnexpectedClose(event);
260
+ scheduleReconnect();
261
+ });
262
+ };
263
+
264
+ connect();
265
+
266
+ return {
267
+ ready,
268
+ close() {
269
+ if (closed) {
270
+ return;
271
+ }
272
+
273
+ closed = true;
274
+ clearTimers();
275
+ clearReconnectTimer();
276
+ activeSocket?.close(1000, "manual close");
277
+ activeSocket = undefined;
278
+ },
279
+ };
280
+ }