@tdengine/websocket 3.2.3 → 3.3.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 (224) hide show
  1. package/lib/package.json +1 -1
  2. package/lib/src/client/wsClient.d.ts +23 -7
  3. package/lib/src/client/wsClient.d.ts.map +1 -1
  4. package/lib/src/client/wsClient.js +154 -139
  5. package/lib/src/client/wsConnector.d.ts +55 -9
  6. package/lib/src/client/wsConnector.d.ts.map +1 -1
  7. package/lib/src/client/wsConnector.js +519 -100
  8. package/lib/src/client/wsConnectorPool.d.ts +5 -1
  9. package/lib/src/client/wsConnectorPool.d.ts.map +1 -1
  10. package/lib/src/client/wsConnectorPool.js +61 -43
  11. package/lib/src/client/wsEventCallback.d.ts +3 -0
  12. package/lib/src/client/wsEventCallback.d.ts.map +1 -1
  13. package/lib/src/client/wsEventCallback.js +67 -8
  14. package/lib/src/common/addressConnectionTracker.d.ts +11 -0
  15. package/lib/src/common/addressConnectionTracker.d.ts.map +1 -0
  16. package/lib/src/common/addressConnectionTracker.js +53 -0
  17. package/lib/src/common/dsn.d.ts +14 -2
  18. package/lib/src/common/dsn.d.ts.map +1 -1
  19. package/lib/src/common/dsn.js +91 -22
  20. package/lib/src/common/taosResult.d.ts.map +1 -1
  21. package/lib/src/common/taosResult.js +0 -5
  22. package/lib/src/common/urlParser.d.ts +32 -0
  23. package/lib/src/common/urlParser.d.ts.map +1 -0
  24. package/lib/src/common/urlParser.js +201 -0
  25. package/lib/src/common/utils.d.ts +2 -1
  26. package/lib/src/common/utils.d.ts.map +1 -1
  27. package/lib/src/common/utils.js +35 -34
  28. package/lib/src/sql/wsSql.js +2 -2
  29. package/lib/src/stmt/FieldBindParams.d.ts.map +1 -1
  30. package/lib/src/stmt/wsColumnInfo.d.ts.map +1 -1
  31. package/lib/src/stmt/wsParams1.d.ts.map +1 -1
  32. package/lib/src/stmt/wsParams1.js +26 -26
  33. package/lib/src/stmt/wsParams2.d.ts.map +1 -1
  34. package/lib/src/stmt/wsParams2.js +0 -3
  35. package/lib/src/stmt/wsParamsBase.d.ts.map +1 -1
  36. package/lib/src/stmt/wsProto.d.ts.map +1 -1
  37. package/lib/src/stmt/wsProto.js +16 -16
  38. package/lib/src/stmt/wsStmt1.d.ts.map +1 -1
  39. package/lib/src/stmt/wsStmt2.d.ts +12 -4
  40. package/lib/src/stmt/wsStmt2.d.ts.map +1 -1
  41. package/lib/src/stmt/wsStmt2.js +182 -64
  42. package/lib/src/stmt/wsTableInfo.d.ts.map +1 -1
  43. package/lib/src/tmq/config.d.ts +3 -2
  44. package/lib/src/tmq/config.d.ts.map +1 -1
  45. package/lib/src/tmq/config.js +15 -15
  46. package/lib/src/tmq/wsTmq.d.ts +4 -1
  47. package/lib/src/tmq/wsTmq.d.ts.map +1 -1
  48. package/lib/src/tmq/wsTmq.js +50 -27
  49. package/lib/test/bulkPulling/a.test.d.ts +2 -0
  50. package/lib/test/bulkPulling/a.test.d.ts.map +1 -0
  51. package/lib/test/bulkPulling/a.test.js +166 -0
  52. package/lib/test/bulkPulling/dsn.test.js +19 -0
  53. package/lib/test/bulkPulling/retryConfig.test.js +11 -11
  54. package/lib/test/bulkPulling/sql.failover.test.d.ts +2 -0
  55. package/lib/test/bulkPulling/sql.failover.test.d.ts.map +1 -0
  56. package/lib/test/bulkPulling/sql.failover.test.js +338 -0
  57. package/lib/test/bulkPulling/stmt2.failover.test.d.ts +2 -0
  58. package/lib/test/bulkPulling/stmt2.failover.test.d.ts.map +1 -0
  59. package/lib/test/bulkPulling/stmt2.failover.test.js +313 -0
  60. package/lib/test/bulkPulling/stmt2.init.failover.test.d.ts +2 -0
  61. package/lib/test/bulkPulling/stmt2.init.failover.test.d.ts.map +1 -0
  62. package/lib/test/bulkPulling/stmt2.init.failover.test.js +50 -0
  63. package/lib/test/bulkPulling/tmq.failover.test.d.ts +2 -0
  64. package/lib/test/bulkPulling/tmq.failover.test.d.ts.map +1 -0
  65. package/lib/test/bulkPulling/tmq.failover.test.js +404 -0
  66. package/lib/test/bulkPulling/urlParser.test.d.ts +2 -0
  67. package/lib/test/bulkPulling/urlParser.test.d.ts.map +1 -0
  68. package/lib/test/bulkPulling/urlParser.test.js +452 -0
  69. package/lib/test/bulkPulling/wsClient.reconnect.integration.test.js +2 -2
  70. package/lib/test/bulkPulling/wsClient.recovery.test.d.ts +2 -0
  71. package/lib/test/bulkPulling/wsClient.recovery.test.d.ts.map +1 -0
  72. package/lib/test/bulkPulling/wsClient.recovery.test.js +104 -0
  73. package/lib/test/bulkPulling/wsConfig.dsn.test.js +2 -0
  74. package/lib/test/bulkPulling/wsConnector.failover.test.js +396 -27
  75. package/lib/test/bulkPulling/wsConnectorPool.key.test.js +12 -10
  76. package/lib/test/bulkPulling/wsConnectorPool.keyAuth.test.d.ts +2 -0
  77. package/lib/test/bulkPulling/wsConnectorPool.keyAuth.test.d.ts.map +1 -0
  78. package/lib/test/bulkPulling/wsConnectorPool.keyAuth.test.js +28 -0
  79. package/lib/test/bulkPulling/wsProxy.failover.integration.test.d.ts +2 -0
  80. package/lib/test/bulkPulling/wsProxy.failover.integration.test.d.ts.map +1 -0
  81. package/lib/test/bulkPulling/wsProxy.failover.integration.test.js +120 -0
  82. package/lib/test/bulkPulling/wsProxy.failover.test.d.ts +2 -0
  83. package/lib/test/bulkPulling/wsProxy.failover.test.d.ts.map +1 -0
  84. package/lib/test/bulkPulling/wsProxy.failover.test.js +465 -0
  85. package/lib/test/client/wsClient.recovery.test.d.ts +2 -0
  86. package/lib/test/client/wsClient.recovery.test.d.ts.map +1 -0
  87. package/lib/test/client/wsClient.recovery.test.js +122 -0
  88. package/lib/test/client/wsConnectPool.test.d.ts +2 -0
  89. package/lib/test/client/wsConnectPool.test.d.ts.map +1 -0
  90. package/lib/test/client/wsConnectPool.test.js +147 -0
  91. package/lib/test/client/wsConnector.failover.test.d.ts +2 -0
  92. package/lib/test/client/wsConnector.failover.test.d.ts.map +1 -0
  93. package/lib/test/client/wsConnector.failover.test.js +681 -0
  94. package/lib/test/client/wsConnector.leastConnections.test.d.ts +2 -0
  95. package/lib/test/client/wsConnector.leastConnections.test.d.ts.map +1 -0
  96. package/lib/test/client/wsConnector.leastConnections.test.js +71 -0
  97. package/lib/test/client/wsConnectorPool.key.test.d.ts +2 -0
  98. package/lib/test/client/wsConnectorPool.key.test.d.ts.map +1 -0
  99. package/lib/test/client/wsConnectorPool.key.test.js +127 -0
  100. package/lib/test/client/wsEventCallback.test.d.ts +2 -0
  101. package/lib/test/client/wsEventCallback.test.d.ts.map +1 -0
  102. package/lib/test/client/wsEventCallback.test.js +98 -0
  103. package/lib/test/common/addressConnectionTracker.test.d.ts +2 -0
  104. package/lib/test/common/addressConnectionTracker.test.d.ts.map +1 -0
  105. package/lib/test/common/addressConnectionTracker.test.js +74 -0
  106. package/lib/test/common/dsn.test.d.ts +2 -0
  107. package/lib/test/common/dsn.test.d.ts.map +1 -0
  108. package/lib/test/common/dsn.test.js +406 -0
  109. package/lib/test/common/log.test.d.ts +2 -0
  110. package/lib/test/common/log.test.d.ts.map +1 -0
  111. package/lib/test/common/log.test.js +54 -0
  112. package/lib/test/common/utils.test.d.ts +2 -0
  113. package/lib/test/common/utils.test.d.ts.map +1 -0
  114. package/lib/test/common/utils.test.js +13 -0
  115. package/lib/test/common/wsConfig.dsn.test.d.ts +2 -0
  116. package/lib/test/common/wsConfig.dsn.test.d.ts.map +1 -0
  117. package/lib/test/common/wsConfig.dsn.test.js +39 -0
  118. package/lib/test/helpers/utils.d.ts +27 -0
  119. package/lib/test/helpers/utils.d.ts.map +1 -0
  120. package/lib/test/helpers/utils.js +341 -0
  121. package/lib/test/helpers/wsFailoverProxy.d.ts +109 -0
  122. package/lib/test/helpers/wsFailoverProxy.d.ts.map +1 -0
  123. package/lib/test/helpers/wsFailoverProxy.js +420 -0
  124. package/lib/test/helpers/wsProxy.d.ts +110 -0
  125. package/lib/test/helpers/wsProxy.d.ts.map +1 -0
  126. package/lib/test/helpers/wsProxy.js +429 -0
  127. package/lib/test/sql/core/decimal.test.d.ts +2 -0
  128. package/lib/test/sql/core/decimal.test.d.ts.map +1 -0
  129. package/lib/test/sql/core/decimal.test.js +153 -0
  130. package/lib/test/sql/core/queryTables.test.d.ts +2 -0
  131. package/lib/test/sql/core/queryTables.test.d.ts.map +1 -0
  132. package/lib/test/sql/core/queryTables.test.js +506 -0
  133. package/lib/test/sql/core/schemaless.test.d.ts +2 -0
  134. package/lib/test/sql/core/schemaless.test.d.ts.map +1 -0
  135. package/lib/test/sql/core/schemaless.test.js +102 -0
  136. package/lib/test/sql/core/sql.test.d.ts +2 -0
  137. package/lib/test/sql/core/sql.test.d.ts.map +1 -0
  138. package/lib/test/sql/core/sql.test.js +324 -0
  139. package/lib/test/sql/decimal.test.d.ts +2 -0
  140. package/lib/test/sql/decimal.test.d.ts.map +1 -0
  141. package/lib/test/sql/decimal.test.js +153 -0
  142. package/lib/test/sql/failover/sql.failover.test.d.ts +2 -0
  143. package/lib/test/sql/failover/sql.failover.test.d.ts.map +1 -0
  144. package/lib/test/sql/failover/sql.failover.test.js +341 -0
  145. package/lib/test/sql/queryTables.test.d.ts +2 -0
  146. package/lib/test/sql/queryTables.test.d.ts.map +1 -0
  147. package/lib/test/sql/queryTables.test.js +506 -0
  148. package/lib/test/sql/schemaless.test.d.ts +2 -0
  149. package/lib/test/sql/schemaless.test.d.ts.map +1 -0
  150. package/lib/test/sql/schemaless.test.js +102 -0
  151. package/lib/test/sql/sql.failover.test.d.ts +2 -0
  152. package/lib/test/sql/sql.failover.test.d.ts.map +1 -0
  153. package/lib/test/sql/sql.failover.test.js +341 -0
  154. package/lib/test/sql/sql.test.d.ts +2 -0
  155. package/lib/test/sql/sql.test.d.ts.map +1 -0
  156. package/lib/test/sql/sql.test.js +324 -0
  157. package/lib/test/stmt/failover/stmt2.failover.mock.test.d.ts +2 -0
  158. package/lib/test/stmt/failover/stmt2.failover.mock.test.d.ts.map +1 -0
  159. package/lib/test/stmt/failover/stmt2.failover.mock.test.js +341 -0
  160. package/lib/test/stmt/failover/stmt2.failover.test.d.ts +2 -0
  161. package/lib/test/stmt/failover/stmt2.failover.test.d.ts.map +1 -0
  162. package/lib/test/stmt/failover/stmt2.failover.test.js +384 -0
  163. package/lib/test/stmt/stmt1.func.test.d.ts +2 -0
  164. package/lib/test/stmt/stmt1.func.test.d.ts.map +1 -0
  165. package/lib/test/stmt/stmt1.func.test.js +418 -0
  166. package/lib/test/stmt/stmt1.type.test.d.ts +2 -0
  167. package/lib/test/stmt/stmt1.type.test.d.ts.map +1 -0
  168. package/lib/test/stmt/stmt1.type.test.js +399 -0
  169. package/lib/test/stmt/stmt2.failover.mock.test.d.ts +2 -0
  170. package/lib/test/stmt/stmt2.failover.mock.test.d.ts.map +1 -0
  171. package/lib/test/stmt/stmt2.failover.mock.test.js +341 -0
  172. package/lib/test/stmt/stmt2.failover.test.d.ts +2 -0
  173. package/lib/test/stmt/stmt2.failover.test.d.ts.map +1 -0
  174. package/lib/test/stmt/stmt2.failover.test.js +384 -0
  175. package/lib/test/stmt/stmt2.func.test.d.ts +2 -0
  176. package/lib/test/stmt/stmt2.func.test.d.ts.map +1 -0
  177. package/lib/test/stmt/stmt2.func.test.js +537 -0
  178. package/lib/test/stmt/stmt2.type.test.d.ts +2 -0
  179. package/lib/test/stmt/stmt2.type.test.d.ts.map +1 -0
  180. package/lib/test/stmt/stmt2.type.test.js +401 -0
  181. package/lib/test/stmt/v1/stmt1.func.test.d.ts +2 -0
  182. package/lib/test/stmt/v1/stmt1.func.test.d.ts.map +1 -0
  183. package/lib/test/stmt/v1/stmt1.func.test.js +418 -0
  184. package/lib/test/stmt/v1/stmt1.type.test.d.ts +2 -0
  185. package/lib/test/stmt/v1/stmt1.type.test.d.ts.map +1 -0
  186. package/lib/test/stmt/v1/stmt1.type.test.js +399 -0
  187. package/lib/test/stmt/v2/stmt2.func.test.d.ts +2 -0
  188. package/lib/test/stmt/v2/stmt2.func.test.d.ts.map +1 -0
  189. package/lib/test/stmt/v2/stmt2.func.test.js +537 -0
  190. package/lib/test/stmt/v2/stmt2.type.test.d.ts +2 -0
  191. package/lib/test/stmt/v2/stmt2.type.test.d.ts.map +1 -0
  192. package/lib/test/stmt/v2/stmt2.type.test.js +401 -0
  193. package/lib/test/tmq/cloud/cloud.tmq.test.d.ts +2 -0
  194. package/lib/test/tmq/cloud/cloud.tmq.test.d.ts.map +1 -0
  195. package/lib/test/tmq/cloud/cloud.tmq.test.js +84 -0
  196. package/lib/test/tmq/cloud/tmq.cloud.test.d.ts +2 -0
  197. package/lib/test/tmq/cloud/tmq.cloud.test.d.ts.map +1 -0
  198. package/lib/test/tmq/cloud/tmq.cloud.test.js +82 -0
  199. package/lib/test/tmq/core/tmq.config.test.d.ts +2 -0
  200. package/lib/test/tmq/core/tmq.config.test.d.ts.map +1 -0
  201. package/lib/test/tmq/core/tmq.config.test.js +83 -0
  202. package/lib/test/tmq/core/tmq.test.d.ts +2 -0
  203. package/lib/test/tmq/core/tmq.test.d.ts.map +1 -0
  204. package/lib/test/tmq/core/tmq.test.js +513 -0
  205. package/lib/test/tmq/failover/tmq.failover.test.d.ts +2 -0
  206. package/lib/test/tmq/failover/tmq.failover.test.d.ts.map +1 -0
  207. package/lib/test/tmq/failover/tmq.failover.test.js +404 -0
  208. package/lib/test/tmq/tmq.cloud.test.d.ts +2 -0
  209. package/lib/test/tmq/tmq.cloud.test.d.ts.map +1 -0
  210. package/lib/test/tmq/tmq.cloud.test.js +82 -0
  211. package/lib/test/tmq/tmq.config.test.d.ts +2 -0
  212. package/lib/test/tmq/tmq.config.test.d.ts.map +1 -0
  213. package/lib/test/tmq/tmq.config.test.js +94 -0
  214. package/lib/test/tmq/tmq.failover.test.d.ts +2 -0
  215. package/lib/test/tmq/tmq.failover.test.d.ts.map +1 -0
  216. package/lib/test/tmq/tmq.failover.test.js +404 -0
  217. package/lib/test/tmq/tmq.test.d.ts +2 -0
  218. package/lib/test/tmq/tmq.test.d.ts.map +1 -0
  219. package/lib/test/tmq/tmq.test.js +513 -0
  220. package/lib/test/unit/connectionManager.test.d.ts +2 -0
  221. package/lib/test/unit/connectionManager.test.d.ts.map +1 -0
  222. package/lib/test/unit/connectionManager.test.js +91 -0
  223. package/package.json +1 -1
  224. package/readme.md +2 -2
@@ -1,20 +1,53 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const retryConfig_1 = require("../../src/client/retryConfig");
4
3
  const wsConnector_1 = require("../../src/client/wsConnector");
4
+ const wsEventCallback_1 = require("../../src/client/wsEventCallback");
5
+ const dsn_1 = require("../../src/common/dsn");
6
+ function createInflightStore() {
7
+ let nextMsgId = 1n;
8
+ const reqIdToMsgId = new Map();
9
+ const msgIdToRequest = new Map();
10
+ return {
11
+ insert(req) {
12
+ const msgId = nextMsgId;
13
+ nextMsgId += 1n;
14
+ reqIdToMsgId.set(req.reqId, msgId);
15
+ msgIdToRequest.set(msgId, req);
16
+ },
17
+ remove(reqId) {
18
+ const msgId = reqIdToMsgId.get(reqId);
19
+ if (msgId === undefined) {
20
+ return;
21
+ }
22
+ reqIdToMsgId.delete(reqId);
23
+ msgIdToRequest.delete(msgId);
24
+ },
25
+ getRequests() {
26
+ return Array.from(msgIdToRequest.entries())
27
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
28
+ .map(([, req]) => req);
29
+ },
30
+ clear() {
31
+ nextMsgId = 1n;
32
+ reqIdToMsgId.clear();
33
+ msgIdToRequest.clear();
34
+ },
35
+ };
36
+ }
5
37
  function createBareConnector() {
6
38
  const connector = Object.create(wsConnector_1.WebSocketConnector.prototype);
7
39
  connector._timeout = 5000;
8
- connector._addresses = [
9
- { host: "host1", port: 6041 },
10
- { host: "host2", port: 6042 },
11
- ];
40
+ connector._dsn = (0, dsn_1.parse)("ws://root:taosdata@host1:6041,host2:6042");
12
41
  connector._currentAddressIndex = 0;
13
- connector._retryConfig = new retryConfig_1.RetryConfig(1, 1, 8);
42
+ connector._retryConfig = new wsConnector_1.RetryConfig(1, 1, 8);
14
43
  connector._reconnectLock = null;
15
44
  connector._isReconnecting = false;
16
- connector._inflightRequests = new Map();
17
- connector._wsConn = {
45
+ connector._allowReconnect = true;
46
+ connector._connectionReady = Promise.resolve();
47
+ connector._suppressedSockets = new WeakSet();
48
+ connector._sessionRecoveryHook = null;
49
+ connector._inflightStore = createInflightStore();
50
+ connector._conn = {
18
51
  readyState: 1,
19
52
  send: jest.fn(),
20
53
  close: jest.fn(),
@@ -30,6 +63,18 @@ function buildBinaryMessage(action) {
30
63
  function delay(ms) {
31
64
  return new Promise((resolve) => setTimeout(resolve, ms));
32
65
  }
66
+ function hasInflightRequest(connector, reqId) {
67
+ return connector
68
+ ._inflightStore
69
+ .getRequests()
70
+ .some((req) => req.reqId === reqId);
71
+ }
72
+ function listInflightReqIds(connector) {
73
+ return connector
74
+ ._inflightStore
75
+ .getRequests()
76
+ .map((req) => req.reqId);
77
+ }
33
78
  describe("WebSocketConnector failover and retry", () => {
34
79
  afterEach(() => {
35
80
  jest.restoreAllMocks();
@@ -51,8 +96,8 @@ describe("WebSocketConnector failover and retry", () => {
51
96
  const connector = createBareConnector();
52
97
  const attempts = [];
53
98
  connector.sleep = jest.fn(async () => { });
54
- connector.reconnectToCurrentAddress = jest.fn(async () => {
55
- const current = connector._addresses[connector._currentAddressIndex];
99
+ connector.reconnect = jest.fn(async () => {
100
+ const current = connector._dsn.addresses[connector._currentAddressIndex];
56
101
  const addr = `${current.host}:${current.port}`;
57
102
  attempts.push(addr);
58
103
  if (addr === "host1:6041") {
@@ -61,35 +106,141 @@ describe("WebSocketConnector failover and retry", () => {
61
106
  });
62
107
  await connector.attemptReconnect();
63
108
  expect(attempts).toEqual([
64
- "host1:6041",
65
109
  "host1:6041",
66
110
  "host2:6042",
67
111
  ]);
68
112
  expect(connector._currentAddressIndex).toBe(1);
69
113
  });
70
- test("keeps retriable string request inflight when send throws", async () => {
114
+ test("attemptReconnect throws after all addresses and retries are exhausted", async () => {
71
115
  const connector = createBareConnector();
72
- connector._wsConn.send = jest.fn(() => {
73
- throw new Error("send failed");
116
+ connector.sleep = jest.fn(async () => { });
117
+ connector.reconnect = jest.fn(async () => {
118
+ throw new Error("all down");
119
+ });
120
+ await expect(connector.attemptReconnect()).rejects.toThrow("Failed to reconnect to any available address");
121
+ expect(connector.reconnect).toHaveBeenCalledTimes(2);
122
+ expect(connector._currentAddressIndex).toBe(1);
123
+ });
124
+ test("handleDisconnect skips reconnect when reconnect is disabled", async () => {
125
+ const connector = createBareConnector();
126
+ connector._allowReconnect = false;
127
+ connector.triggerReconnect = jest.fn();
128
+ await connector.handleDisconnect(connector._conn);
129
+ expect(connector.triggerReconnect).not.toHaveBeenCalled();
130
+ });
131
+ test("handleDisconnect skips reconnect on normal close", async () => {
132
+ const connector = createBareConnector();
133
+ connector.triggerReconnect = jest.fn();
134
+ await connector.handleDisconnect(connector._conn, { code: 1000, reason: "normal close" });
135
+ expect(connector.triggerReconnect).not.toHaveBeenCalled();
136
+ });
137
+ test("handleDisconnect swallows reconnect failure", async () => {
138
+ const connector = createBareConnector();
139
+ connector.triggerReconnect = jest.fn(async () => {
140
+ throw new Error("reconnect failed");
141
+ });
142
+ await expect(connector.handleDisconnect(connector._conn, { code: 1006, reason: "abnormal close" })).resolves.toBeUndefined();
143
+ expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
144
+ });
145
+ test("_doReconnect fails all inflight requests when reconnect fails", async () => {
146
+ const connector = createBareConnector();
147
+ const rejectSpy = jest.fn();
148
+ connector.attemptReconnect = jest.fn(async () => {
149
+ throw new Error("reconnect failed");
150
+ });
151
+ connector._inflightStore.insert({
152
+ reqId: 901n,
153
+ action: "insert",
154
+ message: JSON.stringify({
155
+ action: "insert",
156
+ args: { req_id: 901 },
157
+ }),
158
+ resolve: jest.fn(),
159
+ reject: rejectSpy,
160
+ });
161
+ await expect(connector._doReconnect()).rejects.toThrow("reconnect failed");
162
+ expect(rejectSpy).toHaveBeenCalledTimes(1);
163
+ expect(hasInflightRequest(connector, 901n)).toBe(false);
164
+ });
165
+ test("close disables reconnect and closes current socket", () => {
166
+ const connector = createBareConnector();
167
+ connector.close();
168
+ expect(connector._allowReconnect).toBe(false);
169
+ expect(connector._conn.close).toHaveBeenCalledTimes(1);
170
+ expect(connector._suppressedSockets.has(connector._conn)).toBe(true);
171
+ });
172
+ test("triggers reconnect and keeps retriable string request inflight when send throws", async () => {
173
+ const connector = createBareConnector();
174
+ connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
175
+ connector._conn.send = jest.fn(() => {
176
+ throw new Error("cannot call send() while not connected");
74
177
  });
75
178
  const pending = connector.sendMsg(JSON.stringify({
76
179
  action: "insert",
77
180
  args: {
78
181
  req_id: 101,
79
182
  },
80
- }), false);
183
+ }));
184
+ void pending.catch(() => { });
185
+ const state = await Promise.race([
186
+ pending.then(() => "resolved").catch(() => "rejected"),
187
+ delay(20).then(() => "pending"),
188
+ ]);
189
+ expect(state).toBe("pending");
190
+ expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
191
+ expect(hasInflightRequest(connector, 101n)).toBe(true);
192
+ connector.failAllInflightRequests(new Error("cleanup"));
193
+ });
194
+ test("triggers reconnect and keeps retriable poll request inflight when send throws", async () => {
195
+ const connector = createBareConnector();
196
+ connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
197
+ connector._conn.send = jest.fn(() => {
198
+ throw new Error("cannot call send() while not connected");
199
+ });
200
+ const pending = connector.sendMsg(JSON.stringify({
201
+ action: "poll",
202
+ args: {
203
+ req_id: 109,
204
+ blocking_time: 500,
205
+ message_id: 0,
206
+ },
207
+ }));
81
208
  void pending.catch(() => { });
82
209
  const state = await Promise.race([
83
210
  pending.then(() => "resolved").catch(() => "rejected"),
84
211
  delay(20).then(() => "pending"),
85
212
  ]);
86
213
  expect(state).toBe("pending");
87
- expect(connector._inflightRequests.has(101n)).toBe(true);
214
+ expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
215
+ expect(hasInflightRequest(connector, 109n)).toBe(true);
216
+ connector.failAllInflightRequests(new Error("cleanup"));
217
+ });
218
+ test("triggers reconnect and keeps retriable subscribe request inflight when send throws", async () => {
219
+ const connector = createBareConnector();
220
+ connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
221
+ connector._conn.send = jest.fn(() => {
222
+ throw new Error("cannot call send() while not connected");
223
+ });
224
+ const pending = connector.sendMsg(JSON.stringify({
225
+ action: "subscribe",
226
+ args: {
227
+ req_id: 110,
228
+ topics: ["topic_a"],
229
+ },
230
+ }));
231
+ void pending.catch(() => { });
232
+ const state = await Promise.race([
233
+ pending.then(() => "resolved").catch(() => "rejected"),
234
+ delay(20).then(() => "pending"),
235
+ ]);
236
+ expect(state).toBe("pending");
237
+ expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
238
+ expect(hasInflightRequest(connector, 110n)).toBe(true);
88
239
  connector.failAllInflightRequests(new Error("cleanup"));
89
240
  });
90
241
  test("rejects non-retriable string request immediately when send throws", async () => {
91
242
  const connector = createBareConnector();
92
- connector._wsConn.send = jest.fn(() => {
243
+ connector._conn.send = jest.fn(() => {
93
244
  throw new Error("send failed");
94
245
  });
95
246
  await expect(connector.sendMsg(JSON.stringify({
@@ -97,32 +248,250 @@ describe("WebSocketConnector failover and retry", () => {
97
248
  args: {
98
249
  req_id: 102,
99
250
  },
100
- }), false)).rejects.toThrow("send failed");
101
- expect(connector._inflightRequests.has(102n)).toBe(false);
251
+ }))).rejects.toThrow("send failed");
252
+ expect(hasInflightRequest(connector, 102n)).toBe(false);
102
253
  });
103
- test("keeps retriable binary request inflight when send throws", async () => {
254
+ test("rejects retriable string request immediately when send throws with non-network error", async () => {
104
255
  const connector = createBareConnector();
105
- connector._wsConn.send = jest.fn(() => {
106
- throw new Error("send failed");
256
+ connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
257
+ connector._conn.send = jest.fn(() => {
258
+ throw new Error("serialize failed");
259
+ });
260
+ const pending = connector.sendMsg(JSON.stringify({
261
+ action: "insert",
262
+ args: {
263
+ req_id: 111,
264
+ },
265
+ }));
266
+ void pending.catch(() => { });
267
+ const state = await Promise.race([
268
+ pending.then(() => "resolved").catch(() => "rejected"),
269
+ delay(20).then(() => "pending"),
270
+ ]);
271
+ expect(state).toBe("rejected");
272
+ expect(connector.triggerReconnect).not.toHaveBeenCalled();
273
+ expect(hasInflightRequest(connector, 111n)).toBe(false);
274
+ });
275
+ test("rejects retriable subscribe request immediately when send throws with non-network error", async () => {
276
+ const connector = createBareConnector();
277
+ connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
278
+ connector._conn.send = jest.fn(() => {
279
+ throw new Error("serialize failed");
280
+ });
281
+ const pending = connector.sendMsg(JSON.stringify({
282
+ action: "subscribe",
283
+ args: {
284
+ req_id: 113,
285
+ topics: ["topic_a"],
286
+ },
287
+ }));
288
+ void pending.catch(() => { });
289
+ const state = await Promise.race([
290
+ pending.then(() => "resolved").catch(() => "rejected"),
291
+ delay(20).then(() => "pending"),
292
+ ]);
293
+ expect(state).toBe("rejected");
294
+ expect(connector.triggerReconnect).not.toHaveBeenCalled();
295
+ expect(hasInflightRequest(connector, 113n)).toBe(false);
296
+ });
297
+ test("triggers reconnect and rejects non-retriable string request on network send error", async () => {
298
+ const connector = createBareConnector();
299
+ connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
300
+ connector._conn.send = jest.fn(() => {
301
+ throw new Error("cannot call send() while not connected");
302
+ });
303
+ await expect(connector.sendMsg(JSON.stringify({
304
+ action: "query",
305
+ args: {
306
+ req_id: 112,
307
+ },
308
+ }))).rejects.toThrow("cannot call send() while not connected");
309
+ expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
310
+ expect(hasInflightRequest(connector, 112n)).toBe(false);
311
+ });
312
+ test("triggers reconnect and keeps retriable binary request inflight when send throws", async () => {
313
+ const connector = createBareConnector();
314
+ connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
315
+ connector._conn.send = jest.fn(() => {
316
+ throw new Error("cannot call send() while not connected");
107
317
  });
108
318
  const message = buildBinaryMessage(6n);
109
- const pending = connector.sendBinaryMsg(201n, "binary_query", message, false);
319
+ const pending = connector.sendBinaryMsg(201n, "binary_query", message);
110
320
  void pending.catch(() => { });
111
321
  const state = await Promise.race([
112
322
  pending.then(() => "resolved").catch(() => "rejected"),
113
323
  delay(20).then(() => "pending"),
114
324
  ]);
115
325
  expect(state).toBe("pending");
116
- expect(connector._inflightRequests.has(201n)).toBe(true);
326
+ expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
327
+ expect(hasInflightRequest(connector, 201n)).toBe(true);
117
328
  connector.failAllInflightRequests(new Error("cleanup"));
118
329
  });
119
330
  test("rejects non-retriable binary request immediately when send throws", async () => {
120
331
  const connector = createBareConnector();
121
- connector._wsConn.send = jest.fn(() => {
332
+ connector._conn.send = jest.fn(() => {
122
333
  throw new Error("send failed");
123
334
  });
124
335
  const message = buildBinaryMessage(7n);
125
- await expect(connector.sendBinaryMsg(202n, "fetch", message, false)).rejects.toThrow("send failed");
126
- expect(connector._inflightRequests.has(202n)).toBe(false);
336
+ await expect(connector.sendBinaryMsg(202n, "fetch", message)).rejects.toThrow("send failed");
337
+ expect(hasInflightRequest(connector, 202n)).toBe(false);
338
+ });
339
+ test("sendMsgDirect unregisters callback on network send error without reconnect", async () => {
340
+ const connector = createBareConnector();
341
+ connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
342
+ connector._conn.send = jest.fn(() => {
343
+ throw new Error("cannot call send() while not connected");
344
+ });
345
+ const registerSpy = jest
346
+ .spyOn(wsEventCallback_1.WsEventCallback.instance(), "registerCallback")
347
+ .mockResolvedValue();
348
+ const unregisterSpy = jest
349
+ .spyOn(wsEventCallback_1.WsEventCallback.instance(), "unregisterCallback")
350
+ .mockResolvedValue();
351
+ await expect(connector.sendMsgDirect(JSON.stringify({
352
+ action: "conn",
353
+ args: {
354
+ req_id: 3001,
355
+ },
356
+ }))).rejects.toThrow("cannot call send() while not connected");
357
+ expect(registerSpy).toHaveBeenCalledTimes(1);
358
+ expect(unregisterSpy).toHaveBeenCalledWith(3001n);
359
+ expect(connector.triggerReconnect).not.toHaveBeenCalled();
360
+ });
361
+ test("sendMsgNoResp rejects on network send error and triggers reconnect", async () => {
362
+ const connector = createBareConnector();
363
+ connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
364
+ connector._conn.send = jest.fn(() => {
365
+ throw new Error("cannot call send() while not connected");
366
+ });
367
+ await expect(connector.sendMsgNoResp(JSON.stringify({
368
+ action: "query",
369
+ args: {
370
+ req_id: 3002,
371
+ },
372
+ }))).rejects.toThrow("cannot call send() while not connected");
373
+ expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
374
+ });
375
+ test("keeps inflight store consistent when retriable requests are enqueued concurrently", async () => {
376
+ const connector = createBareConnector();
377
+ connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
378
+ connector._conn.send = jest.fn(() => {
379
+ throw new Error("cannot call send() while not connected");
380
+ });
381
+ const reqIds = [301, 302, 303, 304];
382
+ await Promise.all(reqIds.map(async (reqId) => {
383
+ const pending = connector.sendMsg(JSON.stringify({
384
+ action: "insert",
385
+ args: {
386
+ req_id: reqId,
387
+ data: `insert into t values(now, ${reqId})`,
388
+ },
389
+ }));
390
+ void pending.catch(() => { });
391
+ }));
392
+ await delay(20);
393
+ expect(listInflightReqIds(connector)).toEqual([301n, 302n, 303n, 304n]);
394
+ for (const reqId of reqIds) {
395
+ expect(hasInflightRequest(connector, BigInt(reqId))).toBe(true);
396
+ }
397
+ connector.failAllInflightRequests(new Error("cleanup"));
398
+ });
399
+ test("replays inflight requests in the same put order", async () => {
400
+ const connector = createBareConnector();
401
+ connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
402
+ connector._conn.send = jest.fn(() => {
403
+ throw new Error("cannot call send() while not connected");
404
+ });
405
+ const reqIds = [101, 102, 103, 104];
406
+ for (const reqId of reqIds) {
407
+ const pending = connector.sendMsg(JSON.stringify({
408
+ action: "insert",
409
+ args: {
410
+ req_id: reqId,
411
+ data: `insert into t values(now, ${reqId})`,
412
+ },
413
+ }));
414
+ void pending.catch(() => { });
415
+ }
416
+ await delay(20);
417
+ const replaySend = jest.fn();
418
+ connector._conn.send = replaySend;
419
+ await connector.replayRequests();
420
+ const replayedReqIds = replaySend.mock.calls.map(([payload]) => {
421
+ const parsed = JSON.parse(payload);
422
+ return parsed.args.req_id;
423
+ });
424
+ expect(replayedReqIds).toEqual([101, 102, 103, 104]);
425
+ connector.failAllInflightRequests(new Error("cleanup"));
426
+ });
427
+ test("replayRequests stops current round and keeps inflight when network send error happens", async () => {
428
+ const connector = createBareConnector();
429
+ const reject1 = jest.fn();
430
+ const reject2 = jest.fn();
431
+ connector._inflightStore.insert({
432
+ reqId: 501n,
433
+ action: "insert",
434
+ registerCallback: false,
435
+ message: JSON.stringify({
436
+ action: "insert",
437
+ args: {
438
+ req_id: 501,
439
+ },
440
+ }),
441
+ resolve: jest.fn(),
442
+ reject: reject1,
443
+ });
444
+ connector._inflightStore.insert({
445
+ reqId: 502n,
446
+ action: "insert",
447
+ registerCallback: false,
448
+ message: JSON.stringify({
449
+ action: "insert",
450
+ args: {
451
+ req_id: 502,
452
+ },
453
+ }),
454
+ resolve: jest.fn(),
455
+ reject: reject2,
456
+ });
457
+ connector._conn.readyState = 3;
458
+ connector._conn.send = jest.fn(() => {
459
+ throw new Error("cannot call send() while not connected");
460
+ });
461
+ await expect(connector.replayRequests()).rejects.toThrow("cannot call send() while not connected");
462
+ expect(hasInflightRequest(connector, 501n)).toBe(true);
463
+ expect(hasInflightRequest(connector, 502n)).toBe(true);
464
+ expect(reject1).not.toHaveBeenCalled();
465
+ expect(reject2).not.toHaveBeenCalled();
466
+ });
467
+ });
468
+ describe("RetryConfig", () => {
469
+ test("uses defaults when retry params are not provided", () => {
470
+ const dsn = (0, dsn_1.parse)("ws://root:taosdata@localhost:6041");
471
+ const config = wsConnector_1.RetryConfig.fromDsn(dsn);
472
+ expect(config.retries).toBe(5);
473
+ expect(config.retryBackoffMs).toBe(200);
474
+ expect(config.retryBackoffMaxMs).toBe(2000);
475
+ });
476
+ test("reads retry params from dsn", () => {
477
+ const dsn = (0, dsn_1.parse)("ws://root:taosdata@localhost:6041?retries=7&retry_backoff_ms=150&retry_backoff_max_ms=3200");
478
+ const config = wsConnector_1.RetryConfig.fromDsn(dsn);
479
+ expect(config.retries).toBe(7);
480
+ expect(config.retryBackoffMs).toBe(150);
481
+ expect(config.retryBackoffMaxMs).toBe(3200);
482
+ });
483
+ test("normalizes invalid params to safe defaults", () => {
484
+ const dsn = (0, dsn_1.parse)("ws://root:taosdata@localhost:6041?retries=-1&retry_backoff_ms=abc&retry_backoff_max_ms=0");
485
+ const config = wsConnector_1.RetryConfig.fromDsn(dsn);
486
+ expect(config.retries).toBe(5);
487
+ expect(config.retryBackoffMs).toBe(200);
488
+ expect(config.retryBackoffMaxMs).toBe(2000);
489
+ });
490
+ test("computes exponential backoff and caps at max", () => {
491
+ const config = new wsConnector_1.RetryConfig(3, 100, 350);
492
+ expect(config.getBackoffDelay(0)).toBe(100);
493
+ expect(config.getBackoffDelay(1)).toBe(200);
494
+ expect(config.getBackoffDelay(2)).toBe(350);
495
+ expect(config.getBackoffDelay(10)).toBe(350);
127
496
  });
128
497
  });
@@ -20,31 +20,33 @@ describe("WebSocketConnectionPool key generation", () => {
20
20
  const pool = wsConnectorPool_1.WebSocketConnectionPool.instance();
21
21
  const dsnA = (0, dsn_1.parse)("ws://root:taosdata@host2:6042,host1:6041/mydb?timezone=UTC&token=abc");
22
22
  const dsnB = (0, dsn_1.parse)("ws://root:taosdata@host1:6041,host2:6042/mydb?token=abc&timezone=UTC");
23
- const keyA = pool.getPoolKey(dsnA, "ws");
24
- const keyB = pool.getPoolKey(dsnB, "ws");
23
+ const keyA = pool.getPoolKey(dsnA);
24
+ const keyB = pool.getPoolKey(dsnB);
25
25
  expect(keyA).toBe(keyB);
26
26
  });
27
27
  test("isolates connections for different auth identities", () => {
28
28
  const pool = wsConnectorPool_1.WebSocketConnectionPool.instance();
29
29
  const dsnUserA = (0, dsn_1.parse)("ws://root:taosdata@host1:6041/mydb");
30
30
  const dsnUserB = (0, dsn_1.parse)("ws://admin:taosdata@host1:6041/mydb");
31
- const keyA = pool.getPoolKey(dsnUserA, "ws");
32
- const keyB = pool.getPoolKey(dsnUserB, "ws");
31
+ const keyA = pool.getPoolKey(dsnUserA);
32
+ const keyB = pool.getPoolKey(dsnUserB);
33
33
  expect(keyA).not.toBe(keyB);
34
34
  });
35
35
  test("isolates connections for different token values", () => {
36
36
  const pool = wsConnectorPool_1.WebSocketConnectionPool.instance();
37
37
  const dsnA = (0, dsn_1.parse)("ws://host1:6041/mydb?token=token-a");
38
38
  const dsnB = (0, dsn_1.parse)("ws://host1:6041/mydb?token=token-b");
39
- const keyA = pool.getPoolKey(dsnA, "ws");
40
- const keyB = pool.getPoolKey(dsnB, "ws");
39
+ const keyA = pool.getPoolKey(dsnA);
40
+ const keyB = pool.getPoolKey(dsnB);
41
41
  expect(keyA).not.toBe(keyB);
42
42
  });
43
- test("includes websocket path in the pool key scope", () => {
43
+ test("includes endpoint-derived websocket path in the pool key scope", () => {
44
44
  const pool = wsConnectorPool_1.WebSocketConnectionPool.instance();
45
- const dsn = (0, dsn_1.parse)("ws://root:taosdata@host1:6041/mydb");
46
- const sqlKey = pool.getPoolKey(dsn, "ws");
47
- const tmqKey = pool.getPoolKey(dsn, "rest/tmq");
45
+ const sqlDsn = (0, dsn_1.parse)("ws://root:taosdata@host1:6041/mydb");
46
+ const tmqDsn = (0, dsn_1.parse)("ws://root:taosdata@host1:6041/mydb");
47
+ tmqDsn.endpoint = dsn_1.WS_TMQ_ENDPOINT;
48
+ const sqlKey = pool.getPoolKey(sqlDsn);
49
+ const tmqKey = pool.getPoolKey(tmqDsn);
48
50
  expect(sqlKey).not.toBe(tmqKey);
49
51
  });
50
52
  });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=wsConnectorPool.keyAuth.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wsConnectorPool.keyAuth.test.d.ts","sourceRoot":"","sources":["../../../test/bulkPulling/wsConnectorPool.keyAuth.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const wsConnectorPool_1 = require("../../src/client/wsConnectorPool");
4
+ const config_1 = require("../../src/common/config");
5
+ const wsSql_1 = require("../../src/sql/wsSql");
6
+ const utils_1 = require("../helpers/utils");
7
+ afterAll(() => {
8
+ wsConnectorPool_1.WebSocketConnectionPool.instance().destroyed();
9
+ });
10
+ describe("Security: pool key must include auth identity", () => {
11
+ test("username/password: different credentials must not share a pool entry", async () => {
12
+ const validDsn = `ws://${(0, utils_1.testUsername)()}:${(0, utils_1.testPassword)()}@localhost:6041`;
13
+ const wrongPasswordDsn = `ws://${(0, utils_1.testUsername)()}:WRONG_PASSWORD_SENTINEL@localhost:6041`;
14
+ const validConn = await wsSql_1.WsSql.open(new config_1.WSConfig(validDsn));
15
+ expect(validConn.state()).toBeGreaterThan(0);
16
+ await validConn.close();
17
+ await expect(wsSql_1.WsSql.open(new config_1.WSConfig(wrongPasswordDsn))).rejects.toThrow();
18
+ });
19
+ test("same credentials should share a pool entry (sanity check)", async () => {
20
+ const dsn = `ws://${(0, utils_1.testUsername)()}:${(0, utils_1.testPassword)()}@localhost:6041`;
21
+ const conn1 = await wsSql_1.WsSql.open(new config_1.WSConfig(dsn));
22
+ expect(conn1.state()).toBeGreaterThan(0);
23
+ await conn1.close();
24
+ const conn2 = await wsSql_1.WsSql.open(new config_1.WSConfig(dsn));
25
+ expect(conn2.state()).toBeGreaterThan(0);
26
+ await conn2.close();
27
+ });
28
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=wsProxy.failover.integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wsProxy.failover.integration.test.d.ts","sourceRoot":"","sources":["../../../test/bulkPulling/wsProxy.failover.integration.test.ts"],"names":[],"mappings":""}