tardis-machine 18.0.0 → 18.1.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 (228) hide show
  1. package/benchmark.js +35 -14
  2. package/bin/tardis-machine.js +0 -1
  3. package/dist/tardismachine.d.ts.map +1 -1
  4. package/dist/tardismachine.js +20 -55
  5. package/dist/tardismachine.js.map +1 -1
  6. package/dist/ws/replay.d.ts +2 -2
  7. package/dist/ws/replay.d.ts.map +1 -1
  8. package/dist/ws/replay.js +11 -5
  9. package/dist/ws/replay.js.map +1 -1
  10. package/dist/ws/replaynormalized.d.ts +2 -2
  11. package/dist/ws/replaynormalized.d.ts.map +1 -1
  12. package/dist/ws/replaynormalized.js +7 -5
  13. package/dist/ws/replaynormalized.js.map +1 -1
  14. package/dist/ws/server.d.ts +20 -0
  15. package/dist/ws/server.d.ts.map +1 -0
  16. package/dist/ws/server.js +154 -0
  17. package/dist/ws/server.js.map +1 -0
  18. package/dist/ws/streamnormalized.d.ts +2 -2
  19. package/dist/ws/streamnormalized.d.ts.map +1 -1
  20. package/dist/ws/streamnormalized.js +7 -5
  21. package/dist/ws/streamnormalized.js.map +1 -1
  22. package/dist/ws/subscriptionsmappers.d.ts +1 -1
  23. package/dist/ws/subscriptionsmappers.d.ts.map +1 -1
  24. package/node_modules/tardis-dev/README.md +1 -1
  25. package/node_modules/tardis-dev/dist/debug.d.ts +12 -2
  26. package/node_modules/tardis-dev/dist/debug.d.ts.map +1 -1
  27. package/node_modules/tardis-dev/dist/debug.js +2 -1
  28. package/node_modules/tardis-dev/dist/debug.js.map +1 -1
  29. package/node_modules/tardis-dev/dist/downloaddatasets.d.ts.map +1 -1
  30. package/node_modules/tardis-dev/dist/downloaddatasets.js +3 -1
  31. package/node_modules/tardis-dev/dist/downloaddatasets.js.map +1 -1
  32. package/node_modules/tardis-dev/dist/handy.d.ts +5 -2
  33. package/node_modules/tardis-dev/dist/handy.d.ts.map +1 -1
  34. package/node_modules/tardis-dev/dist/handy.js +77 -90
  35. package/node_modules/tardis-dev/dist/handy.js.map +1 -1
  36. package/node_modules/tardis-dev/dist/mappers/hyperliquid.d.ts +1 -1
  37. package/node_modules/tardis-dev/dist/mappers/hyperliquid.d.ts.map +1 -1
  38. package/node_modules/tardis-dev/dist/mappers/hyperliquid.js +15 -4
  39. package/node_modules/tardis-dev/dist/mappers/hyperliquid.js.map +1 -1
  40. package/node_modules/tardis-dev/dist/mappers/index.d.ts +1 -1
  41. package/node_modules/tardis-dev/dist/realtimefeeds/mexc.d.ts +0 -7
  42. package/node_modules/tardis-dev/dist/realtimefeeds/mexc.d.ts.map +1 -1
  43. package/node_modules/tardis-dev/dist/realtimefeeds/mexc.js +2 -70
  44. package/node_modules/tardis-dev/dist/realtimefeeds/mexc.js.map +1 -1
  45. package/node_modules/tardis-dev/dist/realtimefeeds/mexcprotobuf.d.ts +43 -0
  46. package/node_modules/tardis-dev/dist/realtimefeeds/mexcprotobuf.d.ts.map +1 -0
  47. package/node_modules/tardis-dev/dist/realtimefeeds/mexcprotobuf.js +294 -0
  48. package/node_modules/tardis-dev/dist/realtimefeeds/mexcprotobuf.js.map +1 -0
  49. package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.d.ts +3 -3
  50. package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.d.ts.map +1 -1
  51. package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.js +3 -3
  52. package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.js.map +1 -1
  53. package/node_modules/tardis-dev/dist/replay.d.ts.map +1 -1
  54. package/node_modules/tardis-dev/dist/replay.js +6 -4
  55. package/node_modules/tardis-dev/dist/replay.js.map +1 -1
  56. package/node_modules/tardis-dev/dist/worker.d.ts.map +1 -1
  57. package/node_modules/tardis-dev/dist/worker.js +7 -4
  58. package/node_modules/tardis-dev/dist/worker.js.map +1 -1
  59. package/node_modules/tardis-dev/dist/zstd.d.ts +3 -0
  60. package/node_modules/tardis-dev/dist/zstd.d.ts.map +1 -0
  61. package/node_modules/tardis-dev/dist/zstd.js +122 -0
  62. package/node_modules/tardis-dev/dist/zstd.js.map +1 -0
  63. package/node_modules/tardis-dev/package.json +3 -7
  64. package/node_modules/tardis-dev/src/debug.ts +16 -1
  65. package/node_modules/tardis-dev/src/downloaddatasets.ts +3 -1
  66. package/node_modules/tardis-dev/src/external-modules.d.ts +5 -0
  67. package/node_modules/tardis-dev/src/handy.ts +100 -93
  68. package/node_modules/tardis-dev/src/mappers/hyperliquid.ts +19 -5
  69. package/node_modules/tardis-dev/src/realtimefeeds/mexc.ts +2 -72
  70. package/node_modules/tardis-dev/src/realtimefeeds/mexcprotobuf.ts +364 -0
  71. package/node_modules/tardis-dev/src/realtimefeeds/realtimefeed.ts +5 -5
  72. package/node_modules/tardis-dev/src/replay.ts +6 -4
  73. package/node_modules/tardis-dev/src/worker.ts +7 -4
  74. package/node_modules/tardis-dev/src/zstd.ts +139 -0
  75. package/package.json +9 -35
  76. package/src/tardismachine.ts +25 -66
  77. package/src/ws/replay.ts +13 -8
  78. package/src/ws/replaynormalized.ts +8 -6
  79. package/src/ws/server.ts +173 -0
  80. package/src/ws/streamnormalized.ts +8 -6
  81. package/src/ws/subscriptionsmappers.ts +1 -1
  82. package/node_modules/follow-redirects/LICENSE +0 -18
  83. package/node_modules/follow-redirects/README.md +0 -157
  84. package/node_modules/follow-redirects/debug.js +0 -15
  85. package/node_modules/follow-redirects/http.js +0 -1
  86. package/node_modules/follow-redirects/https.js +0 -1
  87. package/node_modules/follow-redirects/index.js +0 -709
  88. package/node_modules/follow-redirects/package.json +0 -58
  89. package/node_modules/long/LICENSE +0 -202
  90. package/node_modules/long/README.md +0 -286
  91. package/node_modules/long/index.d.ts +0 -2
  92. package/node_modules/long/index.js +0 -1581
  93. package/node_modules/long/package.json +0 -58
  94. package/node_modules/long/types.d.ts +0 -474
  95. package/node_modules/long/umd/index.d.ts +0 -3
  96. package/node_modules/long/umd/index.js +0 -1622
  97. package/node_modules/long/umd/package.json +0 -3
  98. package/node_modules/long/umd/types.d.ts +0 -474
  99. package/node_modules/protobufjs/LICENSE +0 -39
  100. package/node_modules/protobufjs/README.md +0 -440
  101. package/node_modules/protobufjs/dist/light/protobuf.js +0 -9276
  102. package/node_modules/protobufjs/dist/light/protobuf.js.map +0 -1
  103. package/node_modules/protobufjs/dist/light/protobuf.min.js +0 -8
  104. package/node_modules/protobufjs/dist/light/protobuf.min.js.map +0 -1
  105. package/node_modules/protobufjs/dist/minimal/protobuf.js +0 -3556
  106. package/node_modules/protobufjs/dist/minimal/protobuf.js.map +0 -1
  107. package/node_modules/protobufjs/dist/minimal/protobuf.min.js +0 -8
  108. package/node_modules/protobufjs/dist/minimal/protobuf.min.js.map +0 -1
  109. package/node_modules/protobufjs/dist/protobuf.js +0 -11233
  110. package/node_modules/protobufjs/dist/protobuf.js.map +0 -1
  111. package/node_modules/protobufjs/dist/protobuf.min.js +0 -8
  112. package/node_modules/protobufjs/dist/protobuf.min.js.map +0 -1
  113. package/node_modules/protobufjs/ext/README.md +0 -70
  114. package/node_modules/protobufjs/ext/debug/README.md +0 -4
  115. package/node_modules/protobufjs/ext/debug/index.js +0 -71
  116. package/node_modules/protobufjs/ext/descriptor/README.md +0 -5
  117. package/node_modules/protobufjs/ext/descriptor/index.d.ts +0 -2
  118. package/node_modules/protobufjs/ext/descriptor/index.js +0 -2
  119. package/node_modules/protobufjs/ext/descriptor.d.ts +0 -87
  120. package/node_modules/protobufjs/ext/descriptor.generated.d.ts +0 -417
  121. package/node_modules/protobufjs/ext/descriptor.js +0 -1356
  122. package/node_modules/protobufjs/ext/protojson.LICENSE +0 -201
  123. package/node_modules/protobufjs/ext/protojson.d.ts +0 -20
  124. package/node_modules/protobufjs/ext/protojson.generated.d.ts +0 -49
  125. package/node_modules/protobufjs/ext/protojson.js +0 -951
  126. package/node_modules/protobufjs/ext/textformat.d.ts +0 -19
  127. package/node_modules/protobufjs/ext/textformat.generated.d.ts +0 -33
  128. package/node_modules/protobufjs/ext/textformat.js +0 -1276
  129. package/node_modules/protobufjs/google/LICENSE +0 -27
  130. package/node_modules/protobufjs/google/README.md +0 -1
  131. package/node_modules/protobufjs/google/api/annotations.json +0 -85
  132. package/node_modules/protobufjs/google/api/annotations.proto +0 -11
  133. package/node_modules/protobufjs/google/api/http.json +0 -87
  134. package/node_modules/protobufjs/google/api/http.proto +0 -31
  135. package/node_modules/protobufjs/google/protobuf/api.json +0 -124
  136. package/node_modules/protobufjs/google/protobuf/api.proto +0 -34
  137. package/node_modules/protobufjs/google/protobuf/compiler/plugin.json +0 -126
  138. package/node_modules/protobufjs/google/protobuf/compiler/plugin.proto +0 -47
  139. package/node_modules/protobufjs/google/protobuf/descriptor.json +0 -1465
  140. package/node_modules/protobufjs/google/protobuf/descriptor.proto +0 -527
  141. package/node_modules/protobufjs/google/protobuf/source_context.json +0 -21
  142. package/node_modules/protobufjs/google/protobuf/source_context.proto +0 -7
  143. package/node_modules/protobufjs/google/protobuf/type.json +0 -209
  144. package/node_modules/protobufjs/google/protobuf/type.proto +0 -89
  145. package/node_modules/protobufjs/index.d.ts +0 -3203
  146. package/node_modules/protobufjs/index.js +0 -4
  147. package/node_modules/protobufjs/light.d.ts +0 -2
  148. package/node_modules/protobufjs/light.js +0 -4
  149. package/node_modules/protobufjs/minimal.d.ts +0 -2
  150. package/node_modules/protobufjs/minimal.js +0 -4
  151. package/node_modules/protobufjs/package.json +0 -93
  152. package/node_modules/protobufjs/src/common.js +0 -405
  153. package/node_modules/protobufjs/src/converter.js +0 -352
  154. package/node_modules/protobufjs/src/decoder.js +0 -279
  155. package/node_modules/protobufjs/src/encoder.js +0 -123
  156. package/node_modules/protobufjs/src/enum.js +0 -239
  157. package/node_modules/protobufjs/src/field.js +0 -505
  158. package/node_modules/protobufjs/src/index-light.js +0 -104
  159. package/node_modules/protobufjs/src/index-minimal.js +0 -35
  160. package/node_modules/protobufjs/src/index.js +0 -12
  161. package/node_modules/protobufjs/src/mapfield.js +0 -144
  162. package/node_modules/protobufjs/src/message.js +0 -137
  163. package/node_modules/protobufjs/src/method.js +0 -183
  164. package/node_modules/protobufjs/src/namespace.js +0 -574
  165. package/node_modules/protobufjs/src/object.js +0 -392
  166. package/node_modules/protobufjs/src/oneof.js +0 -233
  167. package/node_modules/protobufjs/src/parse.js +0 -1111
  168. package/node_modules/protobufjs/src/reader.js +0 -788
  169. package/node_modules/protobufjs/src/reader_buffer.js +0 -77
  170. package/node_modules/protobufjs/src/root.js +0 -424
  171. package/node_modules/protobufjs/src/roots.js +0 -18
  172. package/node_modules/protobufjs/src/rpc/service.js +0 -155
  173. package/node_modules/protobufjs/src/rpc.js +0 -36
  174. package/node_modules/protobufjs/src/service.js +0 -206
  175. package/node_modules/protobufjs/src/tokenize.js +0 -421
  176. package/node_modules/protobufjs/src/type.js +0 -676
  177. package/node_modules/protobufjs/src/types.js +0 -196
  178. package/node_modules/protobufjs/src/typescript.js +0 -25
  179. package/node_modules/protobufjs/src/util/aspromise.d.ts +0 -13
  180. package/node_modules/protobufjs/src/util/aspromise.js +0 -52
  181. package/node_modules/protobufjs/src/util/base64.d.ts +0 -32
  182. package/node_modules/protobufjs/src/util/base64.js +0 -146
  183. package/node_modules/protobufjs/src/util/codegen.d.ts +0 -31
  184. package/node_modules/protobufjs/src/util/codegen.js +0 -118
  185. package/node_modules/protobufjs/src/util/eventemitter.d.ts +0 -45
  186. package/node_modules/protobufjs/src/util/eventemitter.js +0 -86
  187. package/node_modules/protobufjs/src/util/fetch.d.ts +0 -56
  188. package/node_modules/protobufjs/src/util/fetch.js +0 -112
  189. package/node_modules/protobufjs/src/util/float.d.ts +0 -83
  190. package/node_modules/protobufjs/src/util/float.js +0 -335
  191. package/node_modules/protobufjs/src/util/fs.js +0 -11
  192. package/node_modules/protobufjs/src/util/longbits.js +0 -204
  193. package/node_modules/protobufjs/src/util/minimal.js +0 -511
  194. package/node_modules/protobufjs/src/util/path.d.ts +0 -22
  195. package/node_modules/protobufjs/src/util/path.js +0 -100
  196. package/node_modules/protobufjs/src/util/patterns.js +0 -7
  197. package/node_modules/protobufjs/src/util/pool.d.ts +0 -32
  198. package/node_modules/protobufjs/src/util/pool.js +0 -48
  199. package/node_modules/protobufjs/src/util/utf8.d.ts +0 -24
  200. package/node_modules/protobufjs/src/util/utf8.js +0 -190
  201. package/node_modules/protobufjs/src/util.js +0 -264
  202. package/node_modules/protobufjs/src/verifier.js +0 -185
  203. package/node_modules/protobufjs/src/wrappers.js +0 -106
  204. package/node_modules/protobufjs/src/writer.js +0 -700
  205. package/node_modules/protobufjs/src/writer_buffer.js +0 -100
  206. package/node_modules/protobufjs/tsconfig.json +0 -6
  207. package/node_modules/uWebSockets.js/ESM_wrapper.mjs +0 -53
  208. package/node_modules/uWebSockets.js/LICENSE +0 -201
  209. package/node_modules/uWebSockets.js/README.md +0 -6
  210. package/node_modules/uWebSockets.js/index.d.ts +0 -468
  211. package/node_modules/uWebSockets.js/package.json +0 -12
  212. package/node_modules/uWebSockets.js/source_commit +0 -1
  213. package/node_modules/uWebSockets.js/uws.js +0 -76
  214. package/node_modules/uWebSockets.js/uws_darwin_arm64_127.node +0 -0
  215. package/node_modules/uWebSockets.js/uws_darwin_arm64_137.node +0 -0
  216. package/node_modules/uWebSockets.js/uws_darwin_arm64_147.node +0 -0
  217. package/node_modules/uWebSockets.js/uws_darwin_x64_127.node +0 -0
  218. package/node_modules/uWebSockets.js/uws_darwin_x64_137.node +0 -0
  219. package/node_modules/uWebSockets.js/uws_darwin_x64_147.node +0 -0
  220. package/node_modules/uWebSockets.js/uws_linux_arm64_127.node +0 -0
  221. package/node_modules/uWebSockets.js/uws_linux_arm64_137.node +0 -0
  222. package/node_modules/uWebSockets.js/uws_linux_arm64_147.node +0 -0
  223. package/node_modules/uWebSockets.js/uws_linux_x64_127.node +0 -0
  224. package/node_modules/uWebSockets.js/uws_linux_x64_137.node +0 -0
  225. package/node_modules/uWebSockets.js/uws_linux_x64_147.node +0 -0
  226. package/node_modules/uWebSockets.js/uws_win32_x64_127.node +0 -0
  227. package/node_modules/uWebSockets.js/uws_win32_x64_137.node +0 -0
  228. package/node_modules/uWebSockets.js/uws_win32_x64_147.node +0 -0
@@ -1,14 +1,13 @@
1
1
  import crypto, { createHash } from 'crypto'
2
2
  import { createWriteStream, mkdirSync, rmSync } from 'node:fs'
3
3
  import { rename } from 'node:fs/promises'
4
- import { Agent as HttpAgent } from 'node:http'
5
- import { Agent as HttpsAgent, type RequestOptions } from 'node:https'
6
- import followRedirects from 'follow-redirects'
4
+ import { Agent as HttpAgent, request as httpRequest, type IncomingMessage } from 'node:http'
5
+ import { Agent as HttpsAgent, request as httpsRequest, type RequestOptions } from 'node:https'
6
+ import { pipeline } from 'node:stream/promises'
7
7
  import path from 'path'
8
8
  import { debug } from './debug.ts'
9
9
  import { Mapper } from './mappers/index.ts'
10
10
  import { Disconnect, Exchange, Filter, FilterForExchange } from './types.ts'
11
- const { http: followRedirectsHttp, https: followRedirectsHttps } = followRedirects
12
11
 
13
12
  export function parseAsUTCDate(val: string) {
14
13
  // Treat date-only and minute-level strings as UTC instead of local time.
@@ -456,44 +455,75 @@ async function requestViaFetch(method: string, url: string, options: HttpRequest
456
455
  }
457
456
  }
458
457
 
459
- async function requestViaProxy(method: string, url: string, options: HttpRequestOptions): Promise<HttpResponse> {
460
- const requestClient = new URL(url).protocol === 'http:' ? followRedirectsHttp : followRedirectsHttps
461
- const preparedRequest = prepareRequest(method, options)
458
+ type DownloadRequestOptions = {
459
+ headers: Record<string, string>
460
+ timeout: number
461
+ }
462
462
 
463
- return await new Promise<HttpResponse>((resolve, reject) => {
464
- const request = requestClient
465
- .request(
466
- url,
467
- {
468
- method,
469
- agent: getProxyAgent(url),
470
- headers: preparedRequest.headers,
471
- timeout: options.timeout
472
- },
473
- (response) => {
474
- response.setEncoding('utf8')
475
- let body = ''
476
- response.on('error', reject)
477
- response.on('data', (chunk) => (body += chunk))
478
- response.on('end', () => {
479
- resolve(createHttpResponse(response.statusCode ?? 0, parseNodeResponseHeaders(response.headers), body))
480
- })
481
- }
482
- )
483
- .on('error', reject)
484
- .on('timeout', () => {
485
- reject(new Error('Request timed out'))
486
- request.destroy()
487
- })
488
-
489
- if (preparedRequest.body !== undefined) {
490
- request.write(preparedRequest.body)
491
- }
463
+ function sendHttpRequest(url: string | URL, options: RequestOptions, body?: string): Promise<IncomingMessage> {
464
+ const requestUrl = new URL(url)
465
+ const requestClient = requestUrl.protocol === 'http:' ? httpRequest : httpsRequest
466
+ const agent = getProxyAgent(requestUrl) ?? (requestUrl.protocol === 'https:' ? httpsAgent : undefined)
467
+
468
+ return new Promise((resolve, reject) => {
469
+ const request = requestClient(requestUrl, { ...options, agent }, resolve)
492
470
 
471
+ request.once('error', reject)
472
+ request.once('timeout', () => request.destroy(new Error('Request timed out')))
473
+ if (body !== undefined) {
474
+ request.write(body)
475
+ }
493
476
  request.end()
494
477
  })
495
478
  }
496
479
 
480
+ async function openDownloadResponse(url: string, options: DownloadRequestOptions) {
481
+ const response = await sendHttpRequest(url, options)
482
+ const statusCode = response.statusCode ?? 0
483
+ const location = response.headers.location
484
+ if (location === undefined || statusCode < 300 || statusCode >= 400) {
485
+ return response
486
+ }
487
+
488
+ response.destroy()
489
+ const sourceUrl = new URL(url)
490
+ const redirectUrl = new URL(location, sourceUrl)
491
+ const headers: Record<string, string> = { ...options.headers }
492
+ if (sourceUrl.origin !== redirectUrl.origin) {
493
+ delete headers.Authorization
494
+ delete headers.authorization
495
+ }
496
+
497
+ return sendHttpRequest(redirectUrl, {
498
+ ...options,
499
+ headers
500
+ })
501
+ }
502
+
503
+ async function readResponseText(response: IncomingMessage) {
504
+ response.setEncoding('utf8')
505
+ let body = ''
506
+ for await (const chunk of response) {
507
+ body += chunk
508
+ }
509
+ return body
510
+ }
511
+
512
+ async function requestViaProxy(method: string, url: string, options: HttpRequestOptions): Promise<HttpResponse> {
513
+ const preparedRequest = prepareRequest(method, options)
514
+ const response = await sendHttpRequest(
515
+ url,
516
+ {
517
+ method,
518
+ headers: preparedRequest.headers,
519
+ timeout: options.timeout
520
+ },
521
+ preparedRequest.body
522
+ )
523
+
524
+ return createHttpResponse(response.statusCode ?? 0, parseNodeResponseHeaders(response.headers), await readResponseText(response))
525
+ }
526
+
497
527
  async function request(method: string, url: string, options: HttpRequestOptions = {}) {
498
528
  const retrySettings = getRetrySettings(method, options.retry)
499
529
 
@@ -551,7 +581,8 @@ export async function download({
551
581
  url,
552
582
  userAgent,
553
583
  appendContentEncodingExtension = false,
554
- acceptEncoding = 'gzip'
584
+ acceptEncoding = 'gzip',
585
+ attemptTimeoutMS = 90 * ONE_SEC_IN_MS
555
586
  }: {
556
587
  url: string
557
588
  downloadPath: string
@@ -559,9 +590,10 @@ export async function download({
559
590
  apiKey: string
560
591
  appendContentEncodingExtension?: boolean
561
592
  acceptEncoding?: string
593
+ attemptTimeoutMS?: number
562
594
  }) {
563
595
  const httpRequestOptions = {
564
- timeout: 90 * ONE_SEC_IN_MS,
596
+ timeout: attemptTimeoutMS,
565
597
  headers: {
566
598
  'Accept-Encoding': acceptEncoding,
567
599
  'User-Agent': userAgent,
@@ -622,17 +654,22 @@ export function cleanTempFiles() {
622
654
  tmpFileCleanups.forEach((cleanup) => cleanup())
623
655
  }
624
656
 
625
- async function _downloadFile(requestOptions: RequestOptions, url: string, downloadPath: string, appendContentEncodingExtension: boolean) {
657
+ async function _downloadFile(
658
+ requestOptions: DownloadRequestOptions,
659
+ url: string,
660
+ downloadPath: string,
661
+ appendContentEncodingExtension: boolean
662
+ ) {
626
663
  // first ensure that directory where we want to download file exists
627
664
  mkdirSync(path.dirname(downloadPath), { recursive: true })
628
665
 
629
666
  // create write file stream that we'll write data into - first as unconfirmed temp file
630
667
 
631
668
  const tmpFilePath = `${downloadPath}${crypto.randomBytes(8).toString('hex')}.unconfirmed`
632
- const fileWriteStream = createWriteStream(tmpFilePath)
669
+ let fileWriteStream: ReturnType<typeof createWriteStream> | undefined
633
670
  const cleanup = () => {
634
671
  try {
635
- fileWriteStream.destroy()
672
+ fileWriteStream?.destroy()
636
673
  rmSync(tmpFilePath, { force: true })
637
674
  } catch {}
638
675
  }
@@ -642,59 +679,29 @@ async function _downloadFile(requestOptions: RequestOptions, url: string, downlo
642
679
 
643
680
  try {
644
681
  // based on https://github.com/nodejs/node/issues/28172 - only reliable way to consume response stream and avoiding all the 'gotchas'
645
- let responseHeaders: Record<string, string> = {}
646
- await new Promise<void>((resolve, reject) => {
647
- const protocol = new URL(url).protocol
648
- const requestClient = protocol === 'http:' ? followRedirectsHttp : followRedirectsHttps
649
- const agent = getProxyAgent(url) ?? (protocol === 'https:' ? httpsAgent : undefined)
650
- const req = requestClient
651
- .get(url, { ...requestOptions, agent }, (res) => {
652
- const { statusCode } = res
653
- if (statusCode !== 200) {
654
- // read the error response text and throw it as an HttpError
655
- res.setEncoding('utf8')
656
- let body = ''
657
- res.on('error', reject)
658
- res.on('data', (chunk) => (body += chunk))
659
- res.on('end', () => {
660
- reject(new HttpError(statusCode!, body, url))
661
- })
662
- } else {
663
- responseHeaders = parseNodeResponseHeaders(res.headers)
664
- if (appendContentEncodingExtension) {
665
- const contentEncoding = asSingleHeaderValue(res.headers['content-encoding'])
666
- if (contentEncoding === 'zstd') {
667
- finalDownloadPath = `${downloadPath}.zst`
668
- } else if (contentEncoding === undefined || contentEncoding === 'gzip') {
669
- finalDownloadPath = `${downloadPath}.gz`
670
- } else {
671
- reject(new Error(`Unsupported data feed content encoding: ${contentEncoding}`))
672
- return
673
- }
674
- }
682
+ const response = await openDownloadResponse(url, requestOptions)
683
+ const { statusCode } = response
684
+ if (statusCode !== 200) {
685
+ throw new HttpError(statusCode ?? 0, await readResponseText(response), url)
686
+ }
675
687
 
676
- // consume the response stream by writing it to the file
677
- res
678
- .on('error', reject)
679
- .on('aborted', () => reject(new Error('Request aborted')))
680
- .pipe(fileWriteStream)
681
- .on('error', reject)
682
- .on('finish', () => {
683
- if (res.complete) {
684
- resolve()
685
- } else {
686
- reject(new Error('The connection was terminated while the message was still being sent'))
687
- }
688
- })
689
- }
690
- })
691
- .on('error', reject)
692
- .on('timeout', () => {
693
- debug('download file request timeout, %s', url)
694
- reject(new Error('Request timed out'))
695
- req.destroy()
696
- })
697
- })
688
+ const responseHeaders = parseNodeResponseHeaders(response.headers)
689
+ if (appendContentEncodingExtension) {
690
+ const contentEncoding = asSingleHeaderValue(response.headers['content-encoding'])
691
+ if (contentEncoding === 'zstd') {
692
+ finalDownloadPath = `${downloadPath}.zst`
693
+ } else if (contentEncoding === undefined || contentEncoding === 'gzip') {
694
+ finalDownloadPath = `${downloadPath}.gz`
695
+ } else {
696
+ throw new Error(`Unsupported data feed content encoding: ${contentEncoding}`)
697
+ }
698
+ }
699
+
700
+ fileWriteStream = createWriteStream(tmpFilePath)
701
+ await pipeline(response, fileWriteStream)
702
+ if (!response.complete) {
703
+ throw new Error('The connection was terminated while the message was still being sent')
704
+ }
698
705
 
699
706
  // finally when saving from the network to file has succeded, rename tmp file to normal name
700
707
  // then we're sure that responses is 100% saved and also even if different process was doing the same we're good
@@ -1,11 +1,16 @@
1
1
  import { BookChange, BookTicker, DerivativeTicker, Trade } from '../types.ts'
2
2
  import { Mapper, PendingTickerInfoHelper } from './mapper.ts'
3
- import { exchangeMappers } from './registry.ts'
3
+ import { exchangeMappers, mapper } from './registry.ts'
4
+
5
+ const HYPERLIQUID_FAST_BOOK_SWITCH_DATE = new Date('2026-06-17T00:00:00.000Z')
4
6
 
5
7
  export const hyperliquidMappers = exchangeMappers({
6
8
  hyperliquid: {
7
9
  trades: () => new HyperliquidTradesMapper(),
8
- bookChanges: () => new HyperliquidBookChangeMapper(),
10
+ bookChanges: mapper([
11
+ { until: HYPERLIQUID_FAST_BOOK_SWITCH_DATE, use: () => new HyperliquidBookChangeMapper('l2Book') },
12
+ { use: () => new HyperliquidBookChangeMapper('fastBook') }
13
+ ]),
9
14
  derivativeTickers: () => new HyperliquidDerivativeTickerMapper(),
10
15
  bookTickers: () => new HyperliquidBookTickerMapper()
11
16
  }
@@ -78,8 +83,14 @@ function mapHyperliquidLevel(level: HyperliquidWsLevel) {
78
83
  }
79
84
  }
80
85
  class HyperliquidBookChangeMapper implements Mapper<'hyperliquid', BookChange> {
86
+ constructor(private readonly filterChannel: HyperliquidBookChannel) {}
87
+
81
88
  canHandle(message: HyperliquidWsBookMessage) {
82
- return message.channel === 'l2Book'
89
+ // Hyperliquid returns channel=l2Book for both live feeds and marks only the fast feed with data.fast=true.
90
+ // The recorder rewrites that channel to fastBook so historical data can distinguish the two feeds.
91
+ const isFastBook = message.channel === 'fastBook' || (message.channel === 'l2Book' && message.data.fast === true)
92
+
93
+ return this.filterChannel === 'fastBook' ? isFastBook : message.channel === 'l2Book' && isFastBook === false
83
94
  }
84
95
 
85
96
  getFilters(symbols?: string[]) {
@@ -87,7 +98,7 @@ class HyperliquidBookChangeMapper implements Mapper<'hyperliquid', BookChange> {
87
98
 
88
99
  return [
89
100
  {
90
- channel: 'l2Book',
101
+ channel: this.filterChannel,
91
102
  symbols
92
103
  }
93
104
  ]
@@ -206,14 +217,17 @@ type HyperliquidTradeMessage = {
206
217
  }
207
218
 
208
219
  type HyperliquidWsBookMessage = {
209
- channel: 'l2Book'
220
+ channel: HyperliquidBookChannel
210
221
  data: {
211
222
  coin: 'ATOM'
212
223
  time: 1730160007687
213
224
  levels: [HyperliquidWsLevel[], HyperliquidWsLevel[]]
225
+ fast?: boolean
214
226
  }
215
227
  }
216
228
 
229
+ type HyperliquidBookChannel = 'l2Book' | 'fastBook'
230
+
217
231
  type HyperliquidWsLevel = {
218
232
  px: string // price
219
233
  sz: string // size
@@ -1,71 +1,13 @@
1
- import protobuf from 'protobufjs'
2
1
  import { CircularBuffer, getJSON, wait } from '../handy.ts'
3
2
  import type { MexcDepthSnapshotMessage } from '../mappers/mexc.ts'
4
3
  import { Filter } from '../types.ts'
5
4
  import { RealTimeFeedBase } from './realtimefeed.ts'
5
+ import { decodeMexcProtobufMessage } from './mexcprotobuf.ts'
6
6
 
7
7
  export class MexcRealTimeFeed extends RealTimeFeedBase {
8
8
  private static readonly depthChannel = 'spot@public.aggre.depth.v3.api.pb@10ms'
9
9
  private static readonly depthSnapshotChannel = 'spot@public.aggre.depth.snapshot.v3.api.pb@10ms'
10
10
  private static readonly jsonObjectStart = '{'.charCodeAt(0)
11
- private static pushDataV3ApiWrapper: protobuf.Type | undefined
12
- /**
13
- * MEXC protobuf docs at @see https://www.mexc.com/api-docs/spot-v3/websocket-market-streams/protocol-buffers-integration
14
- * and schemas from @see https://github.com/mexcdevelop/websocket-proto, mirrored in tardis-recorder/datafeeds/src/mexc/proto.
15
- */
16
- private static readonly pushDataV3ApiWrapperSchema = `
17
- syntax = "proto3";
18
-
19
- message PushDataV3ApiWrapper {
20
- string channel = 1;
21
- oneof body {
22
- PublicAggreDepthsV3Api publicAggreDepths = 313;
23
- PublicAggreDealsV3Api publicAggreDeals = 314;
24
- PublicAggreBookTickerV3Api publicAggreBookTicker = 315;
25
- }
26
-
27
- optional string symbol = 3;
28
- optional string symbolId = 4;
29
- optional int64 createTime = 5;
30
- optional int64 sendTime = 6;
31
- }
32
-
33
- message PublicAggreDealsV3Api {
34
- repeated PublicAggreDealsV3ApiItem deals = 1;
35
- string eventType = 2;
36
- }
37
-
38
- message PublicAggreDealsV3ApiItem {
39
- string price = 1;
40
- string quantity = 2;
41
- int32 tradeType = 3;
42
- int64 time = 4;
43
- string tradeId = 5;
44
- }
45
-
46
- message PublicAggreDepthsV3Api {
47
- repeated PublicAggreDepthV3ApiItem asks = 1;
48
- repeated PublicAggreDepthV3ApiItem bids = 2;
49
- string eventType = 3;
50
- string fromVersion = 4;
51
- string toVersion = 5;
52
- int64 lastOrderCreateTime = 6;
53
- }
54
-
55
- message PublicAggreDepthV3ApiItem {
56
- string price = 1;
57
- string quantity = 2;
58
- }
59
-
60
- message PublicAggreBookTickerV3Api {
61
- string bidPrice = 1;
62
- string bidQuantity = 2;
63
- string askPrice = 3;
64
- string askQuantity = 4;
65
- string version = 5;
66
- int64 lastOrderCreateTime = 6;
67
- }
68
- `
69
11
 
70
12
  private readonly pendingDepthSnapshotSymbols = new Set<string>()
71
13
  private readonly bufferedDepthUpdates = new Map<string, CircularBuffer<MexcDepthUpdateData>>()
@@ -114,11 +56,7 @@ export class MexcRealTimeFeed extends RealTimeFeedBase {
114
56
  return JSON.parse(buffer.toString())
115
57
  }
116
58
 
117
- const pushDataV3ApiWrapper = this.getPushDataV3ApiWrapper()
118
- return pushDataV3ApiWrapper.toObject(pushDataV3ApiWrapper.decode(buffer), {
119
- longs: String,
120
- arrays: true
121
- })
59
+ return decodeMexcProtobufMessage(buffer)
122
60
  }
123
61
 
124
62
  protected override messageIsError(message: any): boolean {
@@ -168,14 +106,6 @@ export class MexcRealTimeFeed extends RealTimeFeedBase {
168
106
  this.send({ method: 'PING' })
169
107
  }
170
108
 
171
- private getPushDataV3ApiWrapper() {
172
- MexcRealTimeFeed.pushDataV3ApiWrapper ??= protobuf
173
- .parse(MexcRealTimeFeed.pushDataV3ApiWrapperSchema)
174
- .root.lookupType('PushDataV3ApiWrapper')
175
-
176
- return MexcRealTimeFeed.pushDataV3ApiWrapper
177
- }
178
-
179
109
  private resetDepthSnapshotTracking(filters: Required<Filter<string>>[]) {
180
110
  this.pendingDepthSnapshotSymbols.clear()
181
111
  this.bufferedDepthUpdates.clear()