@cloudbase/database 1.4.1 → 1.4.2

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 (195) hide show
  1. package/.trunk/configs/.markdownlint.yaml +10 -0
  2. package/.trunk/trunk.yaml +26 -0
  3. package/dist/commonjs/ObjectId/index.d.ts +11 -11
  4. package/dist/commonjs/ObjectId/index.js +21 -21
  5. package/dist/commonjs/aggregate.d.ts +29 -29
  6. package/dist/commonjs/aggregate.js +124 -124
  7. package/dist/commonjs/collection.d.ts +17 -17
  8. package/dist/commonjs/collection.js +72 -72
  9. package/dist/commonjs/command.d.ts +175 -175
  10. package/dist/commonjs/command.js +285 -285
  11. package/dist/commonjs/commands/logic.d.ts +24 -24
  12. package/dist/commonjs/commands/logic.js +79 -79
  13. package/dist/commonjs/commands/query.d.ts +73 -73
  14. package/dist/commonjs/commands/query.js +147 -147
  15. package/dist/commonjs/commands/update.d.ts +29 -29
  16. package/dist/commonjs/commands/update.js +49 -49
  17. package/dist/commonjs/config/app.config.d.ts +6 -6
  18. package/dist/commonjs/config/app.config.js +7 -7
  19. package/dist/commonjs/config/error.config.d.ts +3 -3
  20. package/dist/commonjs/config/error.config.js +15 -15
  21. package/dist/commonjs/const/code.d.ts +34 -34
  22. package/dist/commonjs/const/code.js +36 -36
  23. package/dist/commonjs/constant.d.ts +54 -54
  24. package/dist/commonjs/constant.js +76 -76
  25. package/dist/commonjs/document.d.ts +14 -14
  26. package/dist/commonjs/document.js +217 -219
  27. package/dist/commonjs/geo/index.d.ts +6 -6
  28. package/dist/commonjs/geo/index.js +11 -11
  29. package/dist/commonjs/geo/interface.d.ts +24 -24
  30. package/dist/commonjs/geo/interface.js +2 -2
  31. package/dist/commonjs/geo/lineString.d.ts +19 -19
  32. package/dist/commonjs/geo/lineString.js +57 -57
  33. package/dist/commonjs/geo/multiLineString.d.ts +18 -18
  34. package/dist/commonjs/geo/multiLineString.js +56 -56
  35. package/dist/commonjs/geo/multiPoint.d.ts +18 -18
  36. package/dist/commonjs/geo/multiPoint.js +50 -50
  37. package/dist/commonjs/geo/multiPolygon.d.ts +18 -18
  38. package/dist/commonjs/geo/multiPolygon.js +62 -62
  39. package/dist/commonjs/geo/point.d.ts +19 -19
  40. package/dist/commonjs/geo/point.js +43 -43
  41. package/dist/commonjs/geo/polygon.d.ts +19 -19
  42. package/dist/commonjs/geo/polygon.js +70 -70
  43. package/dist/commonjs/helper/symbol.d.ts +15 -15
  44. package/dist/commonjs/helper/symbol.js +20 -20
  45. package/dist/commonjs/index.d.ts +50 -50
  46. package/dist/commonjs/index.js +41 -41
  47. package/dist/commonjs/operator-map.d.ts +4 -4
  48. package/dist/commonjs/operator-map.js +23 -23
  49. package/dist/commonjs/query.d.ts +38 -38
  50. package/dist/commonjs/query.js +249 -247
  51. package/dist/commonjs/realtime/error.d.ts +7 -7
  52. package/dist/commonjs/realtime/error.js +11 -11
  53. package/dist/commonjs/realtime/listener.d.ts +14 -14
  54. package/dist/commonjs/realtime/listener.js +17 -17
  55. package/dist/commonjs/realtime/message.d.ts +3 -3
  56. package/dist/commonjs/realtime/message.js +10 -10
  57. package/dist/commonjs/realtime/snapshot.d.ts +16 -16
  58. package/dist/commonjs/realtime/snapshot.js +42 -42
  59. package/dist/commonjs/realtime/virtual-websocket-client.d.ts +63 -63
  60. package/dist/commonjs/realtime/virtual-websocket-client.js +736 -736
  61. package/dist/commonjs/realtime/websocket-client.d.ts +78 -78
  62. package/dist/commonjs/realtime/websocket-client.js +567 -567
  63. package/dist/commonjs/realtime/ws-event.d.ts +126 -126
  64. package/dist/commonjs/realtime/ws-event.js +139 -139
  65. package/dist/commonjs/regexp/index.d.ts +18 -18
  66. package/dist/commonjs/regexp/index.js +30 -30
  67. package/dist/commonjs/serializer/common.d.ts +11 -11
  68. package/dist/commonjs/serializer/common.js +90 -90
  69. package/dist/commonjs/serializer/datatype.d.ts +7 -7
  70. package/dist/commonjs/serializer/datatype.js +99 -99
  71. package/dist/commonjs/serializer/query.d.ts +8 -8
  72. package/dist/commonjs/serializer/query.js +254 -254
  73. package/dist/commonjs/serializer/update.d.ts +16 -16
  74. package/dist/commonjs/serializer/update.js +142 -142
  75. package/dist/commonjs/serverDate/index.d.ts +13 -13
  76. package/dist/commonjs/serverDate/index.js +23 -23
  77. package/dist/commonjs/transaction/index.d.ts +30 -30
  78. package/dist/commonjs/transaction/index.js +105 -105
  79. package/dist/commonjs/transaction.d.ts +42 -42
  80. package/dist/commonjs/transaction.js +126 -126
  81. package/dist/commonjs/typings/index.d.ts +45 -45
  82. package/dist/commonjs/typings/index.js +2 -2
  83. package/dist/commonjs/typings/realtime.d.ts +219 -219
  84. package/dist/commonjs/typings/realtime.js +2 -2
  85. package/dist/commonjs/util.d.ts +9 -9
  86. package/dist/commonjs/util.js +101 -101
  87. package/dist/commonjs/utils/error.d.ts +37 -37
  88. package/dist/commonjs/utils/error.js +55 -55
  89. package/dist/commonjs/utils/symbol.d.ts +8 -8
  90. package/dist/commonjs/utils/symbol.js +39 -39
  91. package/dist/commonjs/utils/type.d.ts +13 -13
  92. package/dist/commonjs/utils/type.js +22 -22
  93. package/dist/commonjs/utils/utils.d.ts +20 -19
  94. package/dist/commonjs/utils/utils.js +59 -59
  95. package/dist/commonjs/validate.d.ts +1 -1
  96. package/dist/commonjs/validate.js +155 -155
  97. package/package.json +2 -3
  98. package/src/document.ts +1 -3
  99. package/src/query.ts +1 -0
  100. package/dist/esm/ObjectId/index.d.ts +0 -11
  101. package/dist/esm/ObjectId/index.js +0 -17
  102. package/dist/esm/aggregate.d.ts +0 -29
  103. package/dist/esm/aggregate.js +0 -121
  104. package/dist/esm/collection.d.ts +0 -17
  105. package/dist/esm/collection.js +0 -69
  106. package/dist/esm/command.d.ts +0 -175
  107. package/dist/esm/command.js +0 -281
  108. package/dist/esm/commands/logic.d.ts +0 -24
  109. package/dist/esm/commands/logic.js +0 -74
  110. package/dist/esm/commands/query.d.ts +0 -73
  111. package/dist/esm/commands/query.js +0 -141
  112. package/dist/esm/commands/update.d.ts +0 -29
  113. package/dist/esm/commands/update.js +0 -44
  114. package/dist/esm/config/app.config.d.ts +0 -6
  115. package/dist/esm/config/app.config.js +0 -5
  116. package/dist/esm/config/error.config.d.ts +0 -3
  117. package/dist/esm/config/error.config.js +0 -13
  118. package/dist/esm/const/code.d.ts +0 -34
  119. package/dist/esm/const/code.js +0 -34
  120. package/dist/esm/constant.d.ts +0 -54
  121. package/dist/esm/constant.js +0 -67
  122. package/dist/esm/document.d.ts +0 -14
  123. package/dist/esm/document.js +0 -216
  124. package/dist/esm/geo/index.d.ts +0 -6
  125. package/dist/esm/geo/index.js +0 -6
  126. package/dist/esm/geo/interface.d.ts +0 -24
  127. package/dist/esm/geo/interface.js +0 -0
  128. package/dist/esm/geo/lineString.d.ts +0 -19
  129. package/dist/esm/geo/lineString.js +0 -54
  130. package/dist/esm/geo/multiLineString.d.ts +0 -18
  131. package/dist/esm/geo/multiLineString.js +0 -53
  132. package/dist/esm/geo/multiPoint.d.ts +0 -18
  133. package/dist/esm/geo/multiPoint.js +0 -47
  134. package/dist/esm/geo/multiPolygon.d.ts +0 -18
  135. package/dist/esm/geo/multiPolygon.js +0 -59
  136. package/dist/esm/geo/point.d.ts +0 -19
  137. package/dist/esm/geo/point.js +0 -40
  138. package/dist/esm/geo/polygon.d.ts +0 -19
  139. package/dist/esm/geo/polygon.js +0 -67
  140. package/dist/esm/helper/symbol.d.ts +0 -15
  141. package/dist/esm/helper/symbol.js +0 -15
  142. package/dist/esm/index.d.ts +0 -50
  143. package/dist/esm/index.js +0 -35
  144. package/dist/esm/operator-map.d.ts +0 -4
  145. package/dist/esm/operator-map.js +0 -20
  146. package/dist/esm/query.d.ts +0 -38
  147. package/dist/esm/query.js +0 -244
  148. package/dist/esm/realtime/error.d.ts +0 -7
  149. package/dist/esm/realtime/error.js +0 -8
  150. package/dist/esm/realtime/listener.d.ts +0 -14
  151. package/dist/esm/realtime/listener.js +0 -14
  152. package/dist/esm/realtime/message.d.ts +0 -3
  153. package/dist/esm/realtime/message.js +0 -6
  154. package/dist/esm/realtime/snapshot.d.ts +0 -16
  155. package/dist/esm/realtime/snapshot.js +0 -39
  156. package/dist/esm/realtime/virtual-websocket-client.d.ts +0 -63
  157. package/dist/esm/realtime/virtual-websocket-client.js +0 -733
  158. package/dist/esm/realtime/websocket-client.d.ts +0 -78
  159. package/dist/esm/realtime/websocket-client.js +0 -564
  160. package/dist/esm/realtime/ws-event.d.ts +0 -126
  161. package/dist/esm/realtime/ws-event.js +0 -137
  162. package/dist/esm/regexp/index.d.ts +0 -18
  163. package/dist/esm/regexp/index.js +0 -26
  164. package/dist/esm/serializer/common.d.ts +0 -11
  165. package/dist/esm/serializer/common.js +0 -82
  166. package/dist/esm/serializer/datatype.d.ts +0 -7
  167. package/dist/esm/serializer/datatype.js +0 -95
  168. package/dist/esm/serializer/query.d.ts +0 -8
  169. package/dist/esm/serializer/query.js +0 -251
  170. package/dist/esm/serializer/update.d.ts +0 -16
  171. package/dist/esm/serializer/update.js +0 -139
  172. package/dist/esm/serverDate/index.d.ts +0 -13
  173. package/dist/esm/serverDate/index.js +0 -19
  174. package/dist/esm/transaction/index.d.ts +0 -30
  175. package/dist/esm/transaction/index.js +0 -100
  176. package/dist/esm/transaction.d.ts +0 -42
  177. package/dist/esm/transaction.js +0 -121
  178. package/dist/esm/typings/index.d.ts +0 -45
  179. package/dist/esm/typings/index.js +0 -0
  180. package/dist/esm/typings/realtime.d.ts +0 -219
  181. package/dist/esm/typings/realtime.js +0 -0
  182. package/dist/esm/util.d.ts +0 -9
  183. package/dist/esm/util.js +0 -98
  184. package/dist/esm/utils/error.d.ts +0 -37
  185. package/dist/esm/utils/error.js +0 -49
  186. package/dist/esm/utils/msg.d.ts +0 -0
  187. package/dist/esm/utils/msg.js +0 -0
  188. package/dist/esm/utils/symbol.d.ts +0 -8
  189. package/dist/esm/utils/symbol.js +0 -36
  190. package/dist/esm/utils/type.d.ts +0 -13
  191. package/dist/esm/utils/type.js +0 -20
  192. package/dist/esm/utils/utils.d.ts +0 -19
  193. package/dist/esm/utils/utils.js +0 -55
  194. package/dist/esm/validate.d.ts +0 -1
  195. package/dist/esm/validate.js +0 -152
@@ -1,736 +1,736 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const lodash_set_1 = require("lodash.set");
4
- const lodash_unset_1 = require("lodash.unset");
5
- const lodash_clonedeep_1 = require("lodash.clonedeep");
6
- const message_1 = require("./message");
7
- const error_1 = require("../utils/error");
8
- const error_config_1 = require("../config/error.config");
9
- const utils_1 = require("../utils/utils");
10
- const listener_1 = require("./listener");
11
- const snapshot_1 = require("./snapshot");
12
- const error_2 = require("./error");
13
- var WATCH_STATUS;
14
- (function (WATCH_STATUS) {
15
- WATCH_STATUS["LOGGINGIN"] = "LOGGINGIN";
16
- WATCH_STATUS["INITING"] = "INITING";
17
- WATCH_STATUS["REBUILDING"] = "REBUILDING";
18
- WATCH_STATUS["ACTIVE"] = "ACTIVE";
19
- WATCH_STATUS["ERRORED"] = "ERRORED";
20
- WATCH_STATUS["CLOSING"] = "CLOSING";
21
- WATCH_STATUS["CLOSED"] = "CLOSED";
22
- WATCH_STATUS["PAUSED"] = "PAUSED";
23
- WATCH_STATUS["RESUMING"] = "RESUMING";
24
- })(WATCH_STATUS || (WATCH_STATUS = {}));
25
- const DEFAULT_WAIT_TIME_ON_UNKNOWN_ERROR = 100;
26
- const DEFAULT_MAX_AUTO_RETRY_ON_ERROR = 2;
27
- const DEFAULT_MAX_SEND_ACK_AUTO_RETRY_ON_ERROR = 2;
28
- const DEFAULT_SEND_ACK_DEBOUNCE_TIMEOUT = 10 * 1000;
29
- const DEFAULT_INIT_WATCH_TIMEOUT = 10 * 1000;
30
- const DEFAULT_REBUILD_WATCH_TIMEOUT = 10 * 1000;
31
- class VirtualWebSocketClient {
32
- constructor(options) {
33
- this.watchStatus = WATCH_STATUS.INITING;
34
- this._login = async (envId, refresh) => {
35
- this.watchStatus = WATCH_STATUS.LOGGINGIN;
36
- const loginResult = await this.login(envId, refresh);
37
- if (!this.envId) {
38
- this.envId = loginResult.envId;
39
- }
40
- return loginResult;
41
- };
42
- this.initWatch = async (forceRefreshLogin) => {
43
- if (this._initWatchPromise) {
44
- return this._initWatchPromise;
45
- }
46
- this._initWatchPromise = new Promise(async (resolve, reject) => {
47
- try {
48
- if (this.watchStatus === WATCH_STATUS.PAUSED) {
49
- console.log('[realtime] initWatch cancelled on pause');
50
- return resolve();
51
- }
52
- const { envId } = await this._login(this.envId, forceRefreshLogin);
53
- if (this.watchStatus === WATCH_STATUS.PAUSED) {
54
- console.log('[realtime] initWatch cancelled on pause');
55
- return resolve();
56
- }
57
- this.watchStatus = WATCH_STATUS.INITING;
58
- const initWatchMsg = {
59
- watchId: this.watchId,
60
- requestId: message_1.genRequestId(),
61
- msgType: 'INIT_WATCH',
62
- msgData: {
63
- envId,
64
- collName: this.collectionName,
65
- query: this.query,
66
- limit: this.limit,
67
- orderBy: this.orderBy
68
- }
69
- };
70
- const initEventMsg = await this.send({
71
- msg: initWatchMsg,
72
- waitResponse: true,
73
- skipOnMessage: true,
74
- timeout: DEFAULT_INIT_WATCH_TIMEOUT
75
- });
76
- const { events, currEvent } = initEventMsg.msgData;
77
- this.sessionInfo = {
78
- queryID: initEventMsg.msgData.queryID,
79
- currentEventId: currEvent - 1,
80
- currentDocs: []
81
- };
82
- if (events.length > 0) {
83
- for (const e of events) {
84
- e.ID = currEvent;
85
- }
86
- this.handleServerEvents(initEventMsg);
87
- }
88
- else {
89
- this.sessionInfo.currentEventId = currEvent;
90
- const snapshot = new snapshot_1.Snapshot({
91
- id: currEvent,
92
- docChanges: [],
93
- docs: [],
94
- type: 'init'
95
- });
96
- this.listener.onChange(snapshot);
97
- this.scheduleSendACK();
98
- }
99
- this.onWatchStart(this, this.sessionInfo.queryID);
100
- this.watchStatus = WATCH_STATUS.ACTIVE;
101
- this._availableRetries.INIT_WATCH = DEFAULT_MAX_AUTO_RETRY_ON_ERROR;
102
- resolve();
103
- }
104
- catch (e) {
105
- this.handleWatchEstablishmentError(e, {
106
- operationName: 'INIT_WATCH',
107
- resolve,
108
- reject
109
- });
110
- }
111
- });
112
- let success = false;
113
- try {
114
- await this._initWatchPromise;
115
- success = true;
116
- }
117
- finally {
118
- this._initWatchPromise = undefined;
119
- }
120
- console.log(`[realtime] initWatch ${success ? 'success' : 'fail'}`);
121
- };
122
- this.rebuildWatch = async (forceRefreshLogin) => {
123
- if (this._rebuildWatchPromise) {
124
- return this._rebuildWatchPromise;
125
- }
126
- this._rebuildWatchPromise = new Promise(async (resolve, reject) => {
127
- try {
128
- if (this.watchStatus === WATCH_STATUS.PAUSED) {
129
- console.log('[realtime] rebuildWatch cancelled on pause');
130
- return resolve();
131
- }
132
- const { envId } = await this._login(this.envId, forceRefreshLogin);
133
- if (!this.sessionInfo) {
134
- throw new Error('can not rebuildWatch without a successful initWatch (lack of sessionInfo)');
135
- }
136
- if (this.watchStatus === WATCH_STATUS.PAUSED) {
137
- console.log('[realtime] rebuildWatch cancelled on pause');
138
- return resolve();
139
- }
140
- this.watchStatus = WATCH_STATUS.REBUILDING;
141
- const rebuildWatchMsg = {
142
- watchId: this.watchId,
143
- requestId: message_1.genRequestId(),
144
- msgType: 'REBUILD_WATCH',
145
- msgData: {
146
- envId,
147
- collName: this.collectionName,
148
- queryID: this.sessionInfo.queryID,
149
- eventID: this.sessionInfo.currentEventId
150
- }
151
- };
152
- const nextEventMsg = await this.send({
153
- msg: rebuildWatchMsg,
154
- waitResponse: true,
155
- skipOnMessage: false,
156
- timeout: DEFAULT_REBUILD_WATCH_TIMEOUT
157
- });
158
- this.handleServerEvents(nextEventMsg);
159
- this.watchStatus = WATCH_STATUS.ACTIVE;
160
- this._availableRetries.REBUILD_WATCH = DEFAULT_MAX_AUTO_RETRY_ON_ERROR;
161
- resolve();
162
- }
163
- catch (e) {
164
- this.handleWatchEstablishmentError(e, {
165
- operationName: 'REBUILD_WATCH',
166
- resolve,
167
- reject
168
- });
169
- }
170
- });
171
- let success = false;
172
- try {
173
- await this._rebuildWatchPromise;
174
- success = true;
175
- }
176
- finally {
177
- this._rebuildWatchPromise = undefined;
178
- }
179
- console.log(`[realtime] rebuildWatch ${success ? 'success' : 'fail'}`);
180
- };
181
- this.handleWatchEstablishmentError = async (e, options) => {
182
- const isInitWatch = options.operationName === 'INIT_WATCH';
183
- const abortWatch = () => {
184
- this.closeWithError(new error_1.CloudSDKError({
185
- errCode: isInitWatch
186
- ? error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_INIT_WATCH_FAIL
187
- : error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_REBUILD_WATCH_FAIL,
188
- errMsg: e
189
- }));
190
- options.reject(e);
191
- };
192
- const retry = (refreshLogin) => {
193
- if (this.useRetryTicket(options.operationName)) {
194
- if (isInitWatch) {
195
- this._initWatchPromise = undefined;
196
- options.resolve(this.initWatch(refreshLogin));
197
- }
198
- else {
199
- this._rebuildWatchPromise = undefined;
200
- options.resolve(this.rebuildWatch(refreshLogin));
201
- }
202
- }
203
- else {
204
- abortWatch();
205
- }
206
- };
207
- this.handleCommonError(e, {
208
- onSignError: () => retry(true),
209
- onTimeoutError: () => retry(false),
210
- onNotRetryableError: abortWatch,
211
- onCancelledError: options.reject,
212
- onUnknownError: async () => {
213
- try {
214
- const onWSDisconnected = async () => {
215
- this.pause();
216
- await this.onceWSConnected();
217
- retry(true);
218
- };
219
- if (!this.isWSConnected()) {
220
- await onWSDisconnected();
221
- }
222
- else {
223
- await utils_1.sleep(DEFAULT_WAIT_TIME_ON_UNKNOWN_ERROR);
224
- if (this.watchStatus === WATCH_STATUS.PAUSED) {
225
- options.reject(new error_1.CancelledError(`${options.operationName} cancelled due to pause after unknownError`));
226
- }
227
- else if (!this.isWSConnected()) {
228
- await onWSDisconnected();
229
- }
230
- else {
231
- retry(false);
232
- }
233
- }
234
- }
235
- catch (e) {
236
- retry(true);
237
- }
238
- }
239
- });
240
- };
241
- this.closeWatch = async () => {
242
- const queryId = this.sessionInfo ? this.sessionInfo.queryID : '';
243
- if (this.watchStatus !== WATCH_STATUS.ACTIVE) {
244
- this.watchStatus = WATCH_STATUS.CLOSED;
245
- this.onWatchClose(this, queryId);
246
- return;
247
- }
248
- try {
249
- this.watchStatus = WATCH_STATUS.CLOSING;
250
- const closeWatchMsg = {
251
- watchId: this.watchId,
252
- requestId: message_1.genRequestId(),
253
- msgType: 'CLOSE_WATCH',
254
- msgData: null
255
- };
256
- await this.send({
257
- msg: closeWatchMsg
258
- });
259
- this.sessionInfo = undefined;
260
- this.watchStatus = WATCH_STATUS.CLOSED;
261
- }
262
- catch (e) {
263
- this.closeWithError(new error_1.CloudSDKError({
264
- errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CLOSE_WATCH_FAIL,
265
- errMsg: e
266
- }));
267
- }
268
- finally {
269
- this.onWatchClose(this, queryId);
270
- }
271
- };
272
- this.scheduleSendACK = () => {
273
- this.clearACKSchedule();
274
- this._ackTimeoutId = setTimeout(() => {
275
- if (this._waitExpectedTimeoutId) {
276
- this.scheduleSendACK();
277
- }
278
- else {
279
- this.sendACK();
280
- }
281
- }, DEFAULT_SEND_ACK_DEBOUNCE_TIMEOUT);
282
- };
283
- this.clearACKSchedule = () => {
284
- if (this._ackTimeoutId) {
285
- clearTimeout(this._ackTimeoutId);
286
- }
287
- };
288
- this.sendACK = async () => {
289
- try {
290
- if (this.watchStatus !== WATCH_STATUS.ACTIVE) {
291
- this.scheduleSendACK();
292
- return;
293
- }
294
- if (!this.sessionInfo) {
295
- console.warn('[realtime listener] can not send ack without a successful initWatch (lack of sessionInfo)');
296
- return;
297
- }
298
- const ackMsg = {
299
- watchId: this.watchId,
300
- requestId: message_1.genRequestId(),
301
- msgType: 'CHECK_LAST',
302
- msgData: {
303
- queryID: this.sessionInfo.queryID,
304
- eventID: this.sessionInfo.currentEventId
305
- }
306
- };
307
- await this.send({
308
- msg: ackMsg
309
- });
310
- this.scheduleSendACK();
311
- }
312
- catch (e) {
313
- if (error_2.isRealtimeErrorMessageError(e)) {
314
- const msg = e.payload;
315
- switch (msg.msgData.code) {
316
- case 'CHECK_LOGIN_FAILED':
317
- case 'SIGN_EXPIRED_ERROR':
318
- case 'SIGN_INVALID_ERROR':
319
- case 'SIGN_PARAM_INVALID': {
320
- this.rebuildWatch();
321
- return;
322
- }
323
- case 'QUERYID_INVALID_ERROR':
324
- case 'SYS_ERR':
325
- case 'INVALIID_ENV':
326
- case 'COLLECTION_PERMISSION_DENIED': {
327
- this.closeWithError(new error_1.CloudSDKError({
328
- errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL,
329
- errMsg: msg.msgData.code
330
- }));
331
- return;
332
- }
333
- default: {
334
- break;
335
- }
336
- }
337
- }
338
- if (this._availableRetries.CHECK_LAST &&
339
- this._availableRetries.CHECK_LAST > 0) {
340
- this._availableRetries.CHECK_LAST--;
341
- this.scheduleSendACK();
342
- }
343
- else {
344
- this.closeWithError(new error_1.CloudSDKError({
345
- errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL,
346
- errMsg: e
347
- }));
348
- }
349
- }
350
- };
351
- this.handleCommonError = (e, options) => {
352
- if (error_2.isRealtimeErrorMessageError(e)) {
353
- const msg = e.payload;
354
- switch (msg.msgData.code) {
355
- case 'CHECK_LOGIN_FAILED':
356
- case 'SIGN_EXPIRED_ERROR':
357
- case 'SIGN_INVALID_ERROR':
358
- case 'SIGN_PARAM_INVALID': {
359
- options.onSignError(e);
360
- return;
361
- }
362
- case 'QUERYID_INVALID_ERROR':
363
- case 'SYS_ERR':
364
- case 'INVALIID_ENV':
365
- case 'COLLECTION_PERMISSION_DENIED': {
366
- options.onNotRetryableError(e);
367
- return;
368
- }
369
- default: {
370
- options.onNotRetryableError(e);
371
- return;
372
- }
373
- }
374
- }
375
- else if (error_1.isTimeoutError(e)) {
376
- options.onTimeoutError(e);
377
- return;
378
- }
379
- else if (error_1.isCancelledError(e)) {
380
- options.onCancelledError(e);
381
- return;
382
- }
383
- options.onUnknownError(e);
384
- };
385
- this.watchId = `watchid_${+new Date()}_${Math.random()}`;
386
- this.envId = options.envId;
387
- this.collectionName = options.collectionName;
388
- this.query = options.query;
389
- this.limit = options.limit;
390
- this.orderBy = options.orderBy;
391
- this.send = options.send;
392
- this.login = options.login;
393
- this.isWSConnected = options.isWSConnected;
394
- this.onceWSConnected = options.onceWSConnected;
395
- this.getWaitExpectedTimeoutLength = options.getWaitExpectedTimeoutLength;
396
- this.onWatchStart = options.onWatchStart;
397
- this.onWatchClose = options.onWatchClose;
398
- this.debug = options.debug;
399
- this._availableRetries = {
400
- INIT_WATCH: DEFAULT_MAX_AUTO_RETRY_ON_ERROR,
401
- REBUILD_WATCH: DEFAULT_MAX_AUTO_RETRY_ON_ERROR,
402
- CHECK_LAST: DEFAULT_MAX_SEND_ACK_AUTO_RETRY_ON_ERROR
403
- };
404
- this.listener = new listener_1.RealtimeListener({
405
- close: this.closeWatch,
406
- onChange: options.onChange,
407
- onError: options.onError,
408
- debug: this.debug,
409
- virtualClient: this
410
- });
411
- this.initWatch();
412
- }
413
- useRetryTicket(operationName) {
414
- if (this._availableRetries[operationName] &&
415
- this._availableRetries[operationName] > 0) {
416
- this._availableRetries[operationName]--;
417
- console.log(`[realtime] ${operationName} use a retry ticket, now only ${this._availableRetries[operationName]} retry left`);
418
- return true;
419
- }
420
- return false;
421
- }
422
- async handleServerEvents(msg) {
423
- try {
424
- this.scheduleSendACK();
425
- await this._handleServerEvents(msg);
426
- this._postHandleServerEventsValidityCheck(msg);
427
- }
428
- catch (e) {
429
- console.error('[realtime listener] internal non-fatal error: handle server events failed with error: ', e);
430
- throw e;
431
- }
432
- }
433
- async _handleServerEvents(msg) {
434
- const { requestId } = msg;
435
- const { events } = msg.msgData;
436
- const { msgType } = msg;
437
- if (!events.length || !this.sessionInfo) {
438
- return;
439
- }
440
- const sessionInfo = this.sessionInfo;
441
- let allChangeEvents;
442
- try {
443
- allChangeEvents = events.map(getPublicEvent);
444
- }
445
- catch (e) {
446
- this.closeWithError(new error_1.CloudSDKError({
447
- errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_RECEIVE_INVALID_SERVER_DATA,
448
- errMsg: e
449
- }));
450
- return;
451
- }
452
- let docs = [...sessionInfo.currentDocs];
453
- let initEncountered = false;
454
- for (let i = 0, len = allChangeEvents.length; i < len; i++) {
455
- const change = allChangeEvents[i];
456
- if (sessionInfo.currentEventId >= change.id) {
457
- if (!allChangeEvents[i - 1] || change.id > allChangeEvents[i - 1].id) {
458
- console.warn(`[realtime] duplicate event received, cur ${sessionInfo.currentEventId} but got ${change.id}`);
459
- }
460
- else {
461
- console.error(`[realtime listener] server non-fatal error: events out of order (the latter event's id is smaller than that of the former) (requestId ${requestId})`);
462
- }
463
- continue;
464
- }
465
- else if (sessionInfo.currentEventId === change.id - 1) {
466
- switch (change.dataType) {
467
- case 'update': {
468
- if (!change.doc) {
469
- switch (change.queueType) {
470
- case 'update':
471
- case 'dequeue': {
472
- const localDoc = docs.find(doc => doc._id === change.docId);
473
- if (localDoc) {
474
- const doc = lodash_clonedeep_1.default(localDoc);
475
- if (change.updatedFields) {
476
- for (const fieldPath in change.updatedFields) {
477
- lodash_set_1.default(doc, fieldPath, change.updatedFields[fieldPath]);
478
- }
479
- }
480
- if (change.removedFields) {
481
- for (const fieldPath of change.removedFields) {
482
- lodash_unset_1.default(doc, fieldPath);
483
- }
484
- }
485
- change.doc = doc;
486
- }
487
- else {
488
- console.error('[realtime listener] internal non-fatal server error: unexpected update dataType event where no doc is associated.');
489
- }
490
- break;
491
- }
492
- case 'enqueue': {
493
- const err = new error_1.CloudSDKError({
494
- errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR,
495
- errMsg: `HandleServerEvents: full doc is not provided with dataType="update" and queueType="enqueue" (requestId ${msg.requestId})`
496
- });
497
- this.closeWithError(err);
498
- throw err;
499
- }
500
- default: {
501
- break;
502
- }
503
- }
504
- }
505
- break;
506
- }
507
- case 'replace': {
508
- if (!change.doc) {
509
- const err = new error_1.CloudSDKError({
510
- errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR,
511
- errMsg: `HandleServerEvents: full doc is not provided with dataType="replace" (requestId ${msg.requestId})`
512
- });
513
- this.closeWithError(err);
514
- throw err;
515
- }
516
- break;
517
- }
518
- case 'remove': {
519
- const doc = docs.find(doc => doc._id === change.docId);
520
- if (doc) {
521
- change.doc = doc;
522
- }
523
- else {
524
- console.error('[realtime listener] internal non-fatal server error: unexpected remove event where no doc is associated.');
525
- }
526
- break;
527
- }
528
- case 'limit': {
529
- if (!change.doc) {
530
- switch (change.queueType) {
531
- case 'dequeue': {
532
- const doc = docs.find(doc => doc._id === change.docId);
533
- if (doc) {
534
- change.doc = doc;
535
- }
536
- else {
537
- console.error('[realtime listener] internal non-fatal server error: unexpected limit dataType event where no doc is associated.');
538
- }
539
- break;
540
- }
541
- case 'enqueue': {
542
- const err = new error_1.CloudSDKError({
543
- errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR,
544
- errMsg: `HandleServerEvents: full doc is not provided with dataType="limit" and queueType="enqueue" (requestId ${msg.requestId})`
545
- });
546
- this.closeWithError(err);
547
- throw err;
548
- }
549
- default: {
550
- break;
551
- }
552
- }
553
- }
554
- break;
555
- }
556
- }
557
- switch (change.queueType) {
558
- case 'init': {
559
- if (!initEncountered) {
560
- initEncountered = true;
561
- docs = [change.doc];
562
- }
563
- else {
564
- docs.push(change.doc);
565
- }
566
- break;
567
- }
568
- case 'enqueue': {
569
- docs.push(change.doc);
570
- break;
571
- }
572
- case 'dequeue': {
573
- const ind = docs.findIndex(doc => doc._id === change.docId);
574
- if (ind > -1) {
575
- docs.splice(ind, 1);
576
- }
577
- else {
578
- console.error('[realtime listener] internal non-fatal server error: unexpected dequeue event where no doc is associated.');
579
- }
580
- break;
581
- }
582
- case 'update': {
583
- const ind = docs.findIndex(doc => doc._id === change.docId);
584
- if (ind > -1) {
585
- docs[ind] = change.doc;
586
- }
587
- else {
588
- console.error('[realtime listener] internal non-fatal server error: unexpected queueType update event where no doc is associated.');
589
- }
590
- break;
591
- }
592
- }
593
- if (i === len - 1 ||
594
- (allChangeEvents[i + 1] && allChangeEvents[i + 1].id !== change.id)) {
595
- const docsSnapshot = [...docs];
596
- const docChanges = allChangeEvents
597
- .slice(0, i + 1)
598
- .filter(c => c.id === change.id);
599
- this.sessionInfo.currentEventId = change.id;
600
- this.sessionInfo.currentDocs = docs;
601
- const snapshot = new snapshot_1.Snapshot({
602
- id: change.id,
603
- docChanges,
604
- docs: docsSnapshot,
605
- msgType
606
- });
607
- this.listener.onChange(snapshot);
608
- }
609
- }
610
- else {
611
- console.warn(`[realtime listener] event received is out of order, cur ${this.sessionInfo.currentEventId} but got ${change.id}`);
612
- await this.rebuildWatch();
613
- return;
614
- }
615
- }
616
- }
617
- _postHandleServerEventsValidityCheck(msg) {
618
- if (!this.sessionInfo) {
619
- console.error('[realtime listener] internal non-fatal error: sessionInfo lost after server event handling, this should never occur');
620
- return;
621
- }
622
- if (this.sessionInfo.expectEventId &&
623
- this.sessionInfo.currentEventId >= this.sessionInfo.expectEventId) {
624
- this.clearWaitExpectedEvent();
625
- }
626
- if (this.sessionInfo.currentEventId < msg.msgData.currEvent) {
627
- console.warn('[realtime listener] internal non-fatal error: client eventId does not match with server event id after server event handling');
628
- return;
629
- }
630
- }
631
- clearWaitExpectedEvent() {
632
- if (this._waitExpectedTimeoutId) {
633
- clearTimeout(this._waitExpectedTimeoutId);
634
- this._waitExpectedTimeoutId = undefined;
635
- }
636
- }
637
- onMessage(msg) {
638
- switch (this.watchStatus) {
639
- case WATCH_STATUS.PAUSED: {
640
- if (msg.msgType !== 'ERROR') {
641
- return;
642
- }
643
- break;
644
- }
645
- case WATCH_STATUS.LOGGINGIN:
646
- case WATCH_STATUS.INITING:
647
- case WATCH_STATUS.REBUILDING: {
648
- console.warn(`[realtime listener] internal non-fatal error: unexpected message received while ${this.watchStatus}`);
649
- return;
650
- }
651
- case WATCH_STATUS.CLOSED: {
652
- console.warn('[realtime listener] internal non-fatal error: unexpected message received when the watch has closed');
653
- return;
654
- }
655
- case WATCH_STATUS.ERRORED: {
656
- console.warn('[realtime listener] internal non-fatal error: unexpected message received when the watch has ended with error');
657
- return;
658
- }
659
- }
660
- if (!this.sessionInfo) {
661
- console.warn('[realtime listener] internal non-fatal error: sessionInfo not found while message is received.');
662
- return;
663
- }
664
- this.scheduleSendACK();
665
- switch (msg.msgType) {
666
- case 'NEXT_EVENT': {
667
- console.warn(`nextevent ${msg.msgData.currEvent} ignored`, msg);
668
- this.handleServerEvents(msg);
669
- break;
670
- }
671
- case 'CHECK_EVENT': {
672
- if (this.sessionInfo.currentEventId < msg.msgData.currEvent) {
673
- this.sessionInfo.expectEventId = msg.msgData.currEvent;
674
- this.clearWaitExpectedEvent();
675
- this._waitExpectedTimeoutId = setTimeout(() => {
676
- this.rebuildWatch();
677
- }, this.getWaitExpectedTimeoutLength());
678
- console.log(`[realtime] waitExpectedTimeoutLength ${this.getWaitExpectedTimeoutLength()}`);
679
- }
680
- break;
681
- }
682
- case 'ERROR': {
683
- this.closeWithError(new error_1.CloudSDKError({
684
- errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_SERVER_ERROR_MSG,
685
- errMsg: `${msg.msgData.code} - ${msg.msgData.message}`
686
- }));
687
- break;
688
- }
689
- default: {
690
- console.warn(`[realtime listener] virtual client receive unexpected msg ${msg.msgType}: `, msg);
691
- break;
692
- }
693
- }
694
- }
695
- closeWithError(error) {
696
- this.watchStatus = WATCH_STATUS.ERRORED;
697
- this.clearACKSchedule();
698
- this.listener.onError(error);
699
- this.onWatchClose(this, (this.sessionInfo && this.sessionInfo.queryID) || '');
700
- console.log(`[realtime] client closed (${this.collectionName} ${this.query}) (watchId ${this.watchId})`);
701
- }
702
- pause() {
703
- this.watchStatus = WATCH_STATUS.PAUSED;
704
- console.log(`[realtime] client paused (${this.collectionName} ${this.query}) (watchId ${this.watchId})`);
705
- }
706
- async resume() {
707
- this.watchStatus = WATCH_STATUS.RESUMING;
708
- console.log(`[realtime] client resuming with ${this.sessionInfo ? 'REBUILD_WATCH' : 'INIT_WATCH'} (${this.collectionName} ${this.query}) (${this.watchId})`);
709
- try {
710
- await (this.sessionInfo ? this.rebuildWatch() : this.initWatch());
711
- console.log(`[realtime] client successfully resumed (${this.collectionName} ${this.query}) (${this.watchId})`);
712
- }
713
- catch (e) {
714
- console.error(`[realtime] client resume failed (${this.collectionName} ${this.query}) (${this.watchId})`, e);
715
- }
716
- }
717
- }
718
- exports.VirtualWebSocketClient = VirtualWebSocketClient;
719
- function getPublicEvent(event) {
720
- const e = {
721
- id: event.ID,
722
- dataType: event.DataType,
723
- queueType: event.QueueType,
724
- docId: event.DocID,
725
- doc: event.Doc && event.Doc !== '{}' ? JSON.parse(event.Doc) : undefined
726
- };
727
- if (event.DataType === 'update') {
728
- if (event.UpdatedFields) {
729
- e.updatedFields = JSON.parse(event.UpdatedFields);
730
- }
731
- if (event.removedFields || event.RemovedFields) {
732
- e.removedFields = JSON.parse(event.removedFields);
733
- }
734
- }
735
- return e;
736
- }
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const lodash_set_1 = require("lodash.set");
4
+ const lodash_unset_1 = require("lodash.unset");
5
+ const lodash_clonedeep_1 = require("lodash.clonedeep");
6
+ const message_1 = require("./message");
7
+ const error_1 = require("../utils/error");
8
+ const error_config_1 = require("../config/error.config");
9
+ const utils_1 = require("../utils/utils");
10
+ const listener_1 = require("./listener");
11
+ const snapshot_1 = require("./snapshot");
12
+ const error_2 = require("./error");
13
+ var WATCH_STATUS;
14
+ (function (WATCH_STATUS) {
15
+ WATCH_STATUS["LOGGINGIN"] = "LOGGINGIN";
16
+ WATCH_STATUS["INITING"] = "INITING";
17
+ WATCH_STATUS["REBUILDING"] = "REBUILDING";
18
+ WATCH_STATUS["ACTIVE"] = "ACTIVE";
19
+ WATCH_STATUS["ERRORED"] = "ERRORED";
20
+ WATCH_STATUS["CLOSING"] = "CLOSING";
21
+ WATCH_STATUS["CLOSED"] = "CLOSED";
22
+ WATCH_STATUS["PAUSED"] = "PAUSED";
23
+ WATCH_STATUS["RESUMING"] = "RESUMING";
24
+ })(WATCH_STATUS || (WATCH_STATUS = {}));
25
+ const DEFAULT_WAIT_TIME_ON_UNKNOWN_ERROR = 100;
26
+ const DEFAULT_MAX_AUTO_RETRY_ON_ERROR = 2;
27
+ const DEFAULT_MAX_SEND_ACK_AUTO_RETRY_ON_ERROR = 2;
28
+ const DEFAULT_SEND_ACK_DEBOUNCE_TIMEOUT = 10 * 1000;
29
+ const DEFAULT_INIT_WATCH_TIMEOUT = 10 * 1000;
30
+ const DEFAULT_REBUILD_WATCH_TIMEOUT = 10 * 1000;
31
+ class VirtualWebSocketClient {
32
+ constructor(options) {
33
+ this.watchStatus = WATCH_STATUS.INITING;
34
+ this._login = async (envId, refresh) => {
35
+ this.watchStatus = WATCH_STATUS.LOGGINGIN;
36
+ const loginResult = await this.login(envId, refresh);
37
+ if (!this.envId) {
38
+ this.envId = loginResult.envId;
39
+ }
40
+ return loginResult;
41
+ };
42
+ this.initWatch = async (forceRefreshLogin) => {
43
+ if (this._initWatchPromise) {
44
+ return this._initWatchPromise;
45
+ }
46
+ this._initWatchPromise = new Promise(async (resolve, reject) => {
47
+ try {
48
+ if (this.watchStatus === WATCH_STATUS.PAUSED) {
49
+ console.log('[realtime] initWatch cancelled on pause');
50
+ return resolve();
51
+ }
52
+ const { envId } = await this._login(this.envId, forceRefreshLogin);
53
+ if (this.watchStatus === WATCH_STATUS.PAUSED) {
54
+ console.log('[realtime] initWatch cancelled on pause');
55
+ return resolve();
56
+ }
57
+ this.watchStatus = WATCH_STATUS.INITING;
58
+ const initWatchMsg = {
59
+ watchId: this.watchId,
60
+ requestId: message_1.genRequestId(),
61
+ msgType: 'INIT_WATCH',
62
+ msgData: {
63
+ envId,
64
+ collName: this.collectionName,
65
+ query: this.query,
66
+ limit: this.limit,
67
+ orderBy: this.orderBy
68
+ }
69
+ };
70
+ const initEventMsg = await this.send({
71
+ msg: initWatchMsg,
72
+ waitResponse: true,
73
+ skipOnMessage: true,
74
+ timeout: DEFAULT_INIT_WATCH_TIMEOUT
75
+ });
76
+ const { events, currEvent } = initEventMsg.msgData;
77
+ this.sessionInfo = {
78
+ queryID: initEventMsg.msgData.queryID,
79
+ currentEventId: currEvent - 1,
80
+ currentDocs: []
81
+ };
82
+ if (events.length > 0) {
83
+ for (const e of events) {
84
+ e.ID = currEvent;
85
+ }
86
+ this.handleServerEvents(initEventMsg);
87
+ }
88
+ else {
89
+ this.sessionInfo.currentEventId = currEvent;
90
+ const snapshot = new snapshot_1.Snapshot({
91
+ id: currEvent,
92
+ docChanges: [],
93
+ docs: [],
94
+ type: 'init'
95
+ });
96
+ this.listener.onChange(snapshot);
97
+ this.scheduleSendACK();
98
+ }
99
+ this.onWatchStart(this, this.sessionInfo.queryID);
100
+ this.watchStatus = WATCH_STATUS.ACTIVE;
101
+ this._availableRetries.INIT_WATCH = DEFAULT_MAX_AUTO_RETRY_ON_ERROR;
102
+ resolve();
103
+ }
104
+ catch (e) {
105
+ this.handleWatchEstablishmentError(e, {
106
+ operationName: 'INIT_WATCH',
107
+ resolve,
108
+ reject
109
+ });
110
+ }
111
+ });
112
+ let success = false;
113
+ try {
114
+ await this._initWatchPromise;
115
+ success = true;
116
+ }
117
+ finally {
118
+ this._initWatchPromise = undefined;
119
+ }
120
+ console.log(`[realtime] initWatch ${success ? 'success' : 'fail'}`);
121
+ };
122
+ this.rebuildWatch = async (forceRefreshLogin) => {
123
+ if (this._rebuildWatchPromise) {
124
+ return this._rebuildWatchPromise;
125
+ }
126
+ this._rebuildWatchPromise = new Promise(async (resolve, reject) => {
127
+ try {
128
+ if (this.watchStatus === WATCH_STATUS.PAUSED) {
129
+ console.log('[realtime] rebuildWatch cancelled on pause');
130
+ return resolve();
131
+ }
132
+ const { envId } = await this._login(this.envId, forceRefreshLogin);
133
+ if (!this.sessionInfo) {
134
+ throw new Error('can not rebuildWatch without a successful initWatch (lack of sessionInfo)');
135
+ }
136
+ if (this.watchStatus === WATCH_STATUS.PAUSED) {
137
+ console.log('[realtime] rebuildWatch cancelled on pause');
138
+ return resolve();
139
+ }
140
+ this.watchStatus = WATCH_STATUS.REBUILDING;
141
+ const rebuildWatchMsg = {
142
+ watchId: this.watchId,
143
+ requestId: message_1.genRequestId(),
144
+ msgType: 'REBUILD_WATCH',
145
+ msgData: {
146
+ envId,
147
+ collName: this.collectionName,
148
+ queryID: this.sessionInfo.queryID,
149
+ eventID: this.sessionInfo.currentEventId
150
+ }
151
+ };
152
+ const nextEventMsg = await this.send({
153
+ msg: rebuildWatchMsg,
154
+ waitResponse: true,
155
+ skipOnMessage: false,
156
+ timeout: DEFAULT_REBUILD_WATCH_TIMEOUT
157
+ });
158
+ this.handleServerEvents(nextEventMsg);
159
+ this.watchStatus = WATCH_STATUS.ACTIVE;
160
+ this._availableRetries.REBUILD_WATCH = DEFAULT_MAX_AUTO_RETRY_ON_ERROR;
161
+ resolve();
162
+ }
163
+ catch (e) {
164
+ this.handleWatchEstablishmentError(e, {
165
+ operationName: 'REBUILD_WATCH',
166
+ resolve,
167
+ reject
168
+ });
169
+ }
170
+ });
171
+ let success = false;
172
+ try {
173
+ await this._rebuildWatchPromise;
174
+ success = true;
175
+ }
176
+ finally {
177
+ this._rebuildWatchPromise = undefined;
178
+ }
179
+ console.log(`[realtime] rebuildWatch ${success ? 'success' : 'fail'}`);
180
+ };
181
+ this.handleWatchEstablishmentError = async (e, options) => {
182
+ const isInitWatch = options.operationName === 'INIT_WATCH';
183
+ const abortWatch = () => {
184
+ this.closeWithError(new error_1.CloudSDKError({
185
+ errCode: isInitWatch
186
+ ? error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_INIT_WATCH_FAIL
187
+ : error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_REBUILD_WATCH_FAIL,
188
+ errMsg: e
189
+ }));
190
+ options.reject(e);
191
+ };
192
+ const retry = (refreshLogin) => {
193
+ if (this.useRetryTicket(options.operationName)) {
194
+ if (isInitWatch) {
195
+ this._initWatchPromise = undefined;
196
+ options.resolve(this.initWatch(refreshLogin));
197
+ }
198
+ else {
199
+ this._rebuildWatchPromise = undefined;
200
+ options.resolve(this.rebuildWatch(refreshLogin));
201
+ }
202
+ }
203
+ else {
204
+ abortWatch();
205
+ }
206
+ };
207
+ this.handleCommonError(e, {
208
+ onSignError: () => retry(true),
209
+ onTimeoutError: () => retry(false),
210
+ onNotRetryableError: abortWatch,
211
+ onCancelledError: options.reject,
212
+ onUnknownError: async () => {
213
+ try {
214
+ const onWSDisconnected = async () => {
215
+ this.pause();
216
+ await this.onceWSConnected();
217
+ retry(true);
218
+ };
219
+ if (!this.isWSConnected()) {
220
+ await onWSDisconnected();
221
+ }
222
+ else {
223
+ await utils_1.sleep(DEFAULT_WAIT_TIME_ON_UNKNOWN_ERROR);
224
+ if (this.watchStatus === WATCH_STATUS.PAUSED) {
225
+ options.reject(new error_1.CancelledError(`${options.operationName} cancelled due to pause after unknownError`));
226
+ }
227
+ else if (!this.isWSConnected()) {
228
+ await onWSDisconnected();
229
+ }
230
+ else {
231
+ retry(false);
232
+ }
233
+ }
234
+ }
235
+ catch (e) {
236
+ retry(true);
237
+ }
238
+ }
239
+ });
240
+ };
241
+ this.closeWatch = async () => {
242
+ const queryId = this.sessionInfo ? this.sessionInfo.queryID : '';
243
+ if (this.watchStatus !== WATCH_STATUS.ACTIVE) {
244
+ this.watchStatus = WATCH_STATUS.CLOSED;
245
+ this.onWatchClose(this, queryId);
246
+ return;
247
+ }
248
+ try {
249
+ this.watchStatus = WATCH_STATUS.CLOSING;
250
+ const closeWatchMsg = {
251
+ watchId: this.watchId,
252
+ requestId: message_1.genRequestId(),
253
+ msgType: 'CLOSE_WATCH',
254
+ msgData: null
255
+ };
256
+ await this.send({
257
+ msg: closeWatchMsg
258
+ });
259
+ this.sessionInfo = undefined;
260
+ this.watchStatus = WATCH_STATUS.CLOSED;
261
+ }
262
+ catch (e) {
263
+ this.closeWithError(new error_1.CloudSDKError({
264
+ errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CLOSE_WATCH_FAIL,
265
+ errMsg: e
266
+ }));
267
+ }
268
+ finally {
269
+ this.onWatchClose(this, queryId);
270
+ }
271
+ };
272
+ this.scheduleSendACK = () => {
273
+ this.clearACKSchedule();
274
+ this._ackTimeoutId = setTimeout(() => {
275
+ if (this._waitExpectedTimeoutId) {
276
+ this.scheduleSendACK();
277
+ }
278
+ else {
279
+ this.sendACK();
280
+ }
281
+ }, DEFAULT_SEND_ACK_DEBOUNCE_TIMEOUT);
282
+ };
283
+ this.clearACKSchedule = () => {
284
+ if (this._ackTimeoutId) {
285
+ clearTimeout(this._ackTimeoutId);
286
+ }
287
+ };
288
+ this.sendACK = async () => {
289
+ try {
290
+ if (this.watchStatus !== WATCH_STATUS.ACTIVE) {
291
+ this.scheduleSendACK();
292
+ return;
293
+ }
294
+ if (!this.sessionInfo) {
295
+ console.warn('[realtime listener] can not send ack without a successful initWatch (lack of sessionInfo)');
296
+ return;
297
+ }
298
+ const ackMsg = {
299
+ watchId: this.watchId,
300
+ requestId: message_1.genRequestId(),
301
+ msgType: 'CHECK_LAST',
302
+ msgData: {
303
+ queryID: this.sessionInfo.queryID,
304
+ eventID: this.sessionInfo.currentEventId
305
+ }
306
+ };
307
+ await this.send({
308
+ msg: ackMsg
309
+ });
310
+ this.scheduleSendACK();
311
+ }
312
+ catch (e) {
313
+ if (error_2.isRealtimeErrorMessageError(e)) {
314
+ const msg = e.payload;
315
+ switch (msg.msgData.code) {
316
+ case 'CHECK_LOGIN_FAILED':
317
+ case 'SIGN_EXPIRED_ERROR':
318
+ case 'SIGN_INVALID_ERROR':
319
+ case 'SIGN_PARAM_INVALID': {
320
+ this.rebuildWatch();
321
+ return;
322
+ }
323
+ case 'QUERYID_INVALID_ERROR':
324
+ case 'SYS_ERR':
325
+ case 'INVALIID_ENV':
326
+ case 'COLLECTION_PERMISSION_DENIED': {
327
+ this.closeWithError(new error_1.CloudSDKError({
328
+ errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL,
329
+ errMsg: msg.msgData.code
330
+ }));
331
+ return;
332
+ }
333
+ default: {
334
+ break;
335
+ }
336
+ }
337
+ }
338
+ if (this._availableRetries.CHECK_LAST &&
339
+ this._availableRetries.CHECK_LAST > 0) {
340
+ this._availableRetries.CHECK_LAST--;
341
+ this.scheduleSendACK();
342
+ }
343
+ else {
344
+ this.closeWithError(new error_1.CloudSDKError({
345
+ errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL,
346
+ errMsg: e
347
+ }));
348
+ }
349
+ }
350
+ };
351
+ this.handleCommonError = (e, options) => {
352
+ if (error_2.isRealtimeErrorMessageError(e)) {
353
+ const msg = e.payload;
354
+ switch (msg.msgData.code) {
355
+ case 'CHECK_LOGIN_FAILED':
356
+ case 'SIGN_EXPIRED_ERROR':
357
+ case 'SIGN_INVALID_ERROR':
358
+ case 'SIGN_PARAM_INVALID': {
359
+ options.onSignError(e);
360
+ return;
361
+ }
362
+ case 'QUERYID_INVALID_ERROR':
363
+ case 'SYS_ERR':
364
+ case 'INVALIID_ENV':
365
+ case 'COLLECTION_PERMISSION_DENIED': {
366
+ options.onNotRetryableError(e);
367
+ return;
368
+ }
369
+ default: {
370
+ options.onNotRetryableError(e);
371
+ return;
372
+ }
373
+ }
374
+ }
375
+ else if (error_1.isTimeoutError(e)) {
376
+ options.onTimeoutError(e);
377
+ return;
378
+ }
379
+ else if (error_1.isCancelledError(e)) {
380
+ options.onCancelledError(e);
381
+ return;
382
+ }
383
+ options.onUnknownError(e);
384
+ };
385
+ this.watchId = `watchid_${+new Date()}_${Math.random()}`;
386
+ this.envId = options.envId;
387
+ this.collectionName = options.collectionName;
388
+ this.query = options.query;
389
+ this.limit = options.limit;
390
+ this.orderBy = options.orderBy;
391
+ this.send = options.send;
392
+ this.login = options.login;
393
+ this.isWSConnected = options.isWSConnected;
394
+ this.onceWSConnected = options.onceWSConnected;
395
+ this.getWaitExpectedTimeoutLength = options.getWaitExpectedTimeoutLength;
396
+ this.onWatchStart = options.onWatchStart;
397
+ this.onWatchClose = options.onWatchClose;
398
+ this.debug = options.debug;
399
+ this._availableRetries = {
400
+ INIT_WATCH: DEFAULT_MAX_AUTO_RETRY_ON_ERROR,
401
+ REBUILD_WATCH: DEFAULT_MAX_AUTO_RETRY_ON_ERROR,
402
+ CHECK_LAST: DEFAULT_MAX_SEND_ACK_AUTO_RETRY_ON_ERROR
403
+ };
404
+ this.listener = new listener_1.RealtimeListener({
405
+ close: this.closeWatch,
406
+ onChange: options.onChange,
407
+ onError: options.onError,
408
+ debug: this.debug,
409
+ virtualClient: this
410
+ });
411
+ this.initWatch();
412
+ }
413
+ useRetryTicket(operationName) {
414
+ if (this._availableRetries[operationName] &&
415
+ this._availableRetries[operationName] > 0) {
416
+ this._availableRetries[operationName]--;
417
+ console.log(`[realtime] ${operationName} use a retry ticket, now only ${this._availableRetries[operationName]} retry left`);
418
+ return true;
419
+ }
420
+ return false;
421
+ }
422
+ async handleServerEvents(msg) {
423
+ try {
424
+ this.scheduleSendACK();
425
+ await this._handleServerEvents(msg);
426
+ this._postHandleServerEventsValidityCheck(msg);
427
+ }
428
+ catch (e) {
429
+ console.error('[realtime listener] internal non-fatal error: handle server events failed with error: ', e);
430
+ throw e;
431
+ }
432
+ }
433
+ async _handleServerEvents(msg) {
434
+ const { requestId } = msg;
435
+ const { events } = msg.msgData;
436
+ const { msgType } = msg;
437
+ if (!events.length || !this.sessionInfo) {
438
+ return;
439
+ }
440
+ const sessionInfo = this.sessionInfo;
441
+ let allChangeEvents;
442
+ try {
443
+ allChangeEvents = events.map(getPublicEvent);
444
+ }
445
+ catch (e) {
446
+ this.closeWithError(new error_1.CloudSDKError({
447
+ errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_RECEIVE_INVALID_SERVER_DATA,
448
+ errMsg: e
449
+ }));
450
+ return;
451
+ }
452
+ let docs = [...sessionInfo.currentDocs];
453
+ let initEncountered = false;
454
+ for (let i = 0, len = allChangeEvents.length; i < len; i++) {
455
+ const change = allChangeEvents[i];
456
+ if (sessionInfo.currentEventId >= change.id) {
457
+ if (!allChangeEvents[i - 1] || change.id > allChangeEvents[i - 1].id) {
458
+ console.warn(`[realtime] duplicate event received, cur ${sessionInfo.currentEventId} but got ${change.id}`);
459
+ }
460
+ else {
461
+ console.error(`[realtime listener] server non-fatal error: events out of order (the latter event's id is smaller than that of the former) (requestId ${requestId})`);
462
+ }
463
+ continue;
464
+ }
465
+ else if (sessionInfo.currentEventId === change.id - 1) {
466
+ switch (change.dataType) {
467
+ case 'update': {
468
+ if (!change.doc) {
469
+ switch (change.queueType) {
470
+ case 'update':
471
+ case 'dequeue': {
472
+ const localDoc = docs.find(doc => doc._id === change.docId);
473
+ if (localDoc) {
474
+ const doc = lodash_clonedeep_1.default(localDoc);
475
+ if (change.updatedFields) {
476
+ for (const fieldPath in change.updatedFields) {
477
+ lodash_set_1.default(doc, fieldPath, change.updatedFields[fieldPath]);
478
+ }
479
+ }
480
+ if (change.removedFields) {
481
+ for (const fieldPath of change.removedFields) {
482
+ lodash_unset_1.default(doc, fieldPath);
483
+ }
484
+ }
485
+ change.doc = doc;
486
+ }
487
+ else {
488
+ console.error('[realtime listener] internal non-fatal server error: unexpected update dataType event where no doc is associated.');
489
+ }
490
+ break;
491
+ }
492
+ case 'enqueue': {
493
+ const err = new error_1.CloudSDKError({
494
+ errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR,
495
+ errMsg: `HandleServerEvents: full doc is not provided with dataType="update" and queueType="enqueue" (requestId ${msg.requestId})`
496
+ });
497
+ this.closeWithError(err);
498
+ throw err;
499
+ }
500
+ default: {
501
+ break;
502
+ }
503
+ }
504
+ }
505
+ break;
506
+ }
507
+ case 'replace': {
508
+ if (!change.doc) {
509
+ const err = new error_1.CloudSDKError({
510
+ errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR,
511
+ errMsg: `HandleServerEvents: full doc is not provided with dataType="replace" (requestId ${msg.requestId})`
512
+ });
513
+ this.closeWithError(err);
514
+ throw err;
515
+ }
516
+ break;
517
+ }
518
+ case 'remove': {
519
+ const doc = docs.find(doc => doc._id === change.docId);
520
+ if (doc) {
521
+ change.doc = doc;
522
+ }
523
+ else {
524
+ console.error('[realtime listener] internal non-fatal server error: unexpected remove event where no doc is associated.');
525
+ }
526
+ break;
527
+ }
528
+ case 'limit': {
529
+ if (!change.doc) {
530
+ switch (change.queueType) {
531
+ case 'dequeue': {
532
+ const doc = docs.find(doc => doc._id === change.docId);
533
+ if (doc) {
534
+ change.doc = doc;
535
+ }
536
+ else {
537
+ console.error('[realtime listener] internal non-fatal server error: unexpected limit dataType event where no doc is associated.');
538
+ }
539
+ break;
540
+ }
541
+ case 'enqueue': {
542
+ const err = new error_1.CloudSDKError({
543
+ errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR,
544
+ errMsg: `HandleServerEvents: full doc is not provided with dataType="limit" and queueType="enqueue" (requestId ${msg.requestId})`
545
+ });
546
+ this.closeWithError(err);
547
+ throw err;
548
+ }
549
+ default: {
550
+ break;
551
+ }
552
+ }
553
+ }
554
+ break;
555
+ }
556
+ }
557
+ switch (change.queueType) {
558
+ case 'init': {
559
+ if (!initEncountered) {
560
+ initEncountered = true;
561
+ docs = [change.doc];
562
+ }
563
+ else {
564
+ docs.push(change.doc);
565
+ }
566
+ break;
567
+ }
568
+ case 'enqueue': {
569
+ docs.push(change.doc);
570
+ break;
571
+ }
572
+ case 'dequeue': {
573
+ const ind = docs.findIndex(doc => doc._id === change.docId);
574
+ if (ind > -1) {
575
+ docs.splice(ind, 1);
576
+ }
577
+ else {
578
+ console.error('[realtime listener] internal non-fatal server error: unexpected dequeue event where no doc is associated.');
579
+ }
580
+ break;
581
+ }
582
+ case 'update': {
583
+ const ind = docs.findIndex(doc => doc._id === change.docId);
584
+ if (ind > -1) {
585
+ docs[ind] = change.doc;
586
+ }
587
+ else {
588
+ console.error('[realtime listener] internal non-fatal server error: unexpected queueType update event where no doc is associated.');
589
+ }
590
+ break;
591
+ }
592
+ }
593
+ if (i === len - 1 ||
594
+ (allChangeEvents[i + 1] && allChangeEvents[i + 1].id !== change.id)) {
595
+ const docsSnapshot = [...docs];
596
+ const docChanges = allChangeEvents
597
+ .slice(0, i + 1)
598
+ .filter(c => c.id === change.id);
599
+ this.sessionInfo.currentEventId = change.id;
600
+ this.sessionInfo.currentDocs = docs;
601
+ const snapshot = new snapshot_1.Snapshot({
602
+ id: change.id,
603
+ docChanges,
604
+ docs: docsSnapshot,
605
+ msgType
606
+ });
607
+ this.listener.onChange(snapshot);
608
+ }
609
+ }
610
+ else {
611
+ console.warn(`[realtime listener] event received is out of order, cur ${this.sessionInfo.currentEventId} but got ${change.id}`);
612
+ await this.rebuildWatch();
613
+ return;
614
+ }
615
+ }
616
+ }
617
+ _postHandleServerEventsValidityCheck(msg) {
618
+ if (!this.sessionInfo) {
619
+ console.error('[realtime listener] internal non-fatal error: sessionInfo lost after server event handling, this should never occur');
620
+ return;
621
+ }
622
+ if (this.sessionInfo.expectEventId &&
623
+ this.sessionInfo.currentEventId >= this.sessionInfo.expectEventId) {
624
+ this.clearWaitExpectedEvent();
625
+ }
626
+ if (this.sessionInfo.currentEventId < msg.msgData.currEvent) {
627
+ console.warn('[realtime listener] internal non-fatal error: client eventId does not match with server event id after server event handling');
628
+ return;
629
+ }
630
+ }
631
+ clearWaitExpectedEvent() {
632
+ if (this._waitExpectedTimeoutId) {
633
+ clearTimeout(this._waitExpectedTimeoutId);
634
+ this._waitExpectedTimeoutId = undefined;
635
+ }
636
+ }
637
+ onMessage(msg) {
638
+ switch (this.watchStatus) {
639
+ case WATCH_STATUS.PAUSED: {
640
+ if (msg.msgType !== 'ERROR') {
641
+ return;
642
+ }
643
+ break;
644
+ }
645
+ case WATCH_STATUS.LOGGINGIN:
646
+ case WATCH_STATUS.INITING:
647
+ case WATCH_STATUS.REBUILDING: {
648
+ console.warn(`[realtime listener] internal non-fatal error: unexpected message received while ${this.watchStatus}`);
649
+ return;
650
+ }
651
+ case WATCH_STATUS.CLOSED: {
652
+ console.warn('[realtime listener] internal non-fatal error: unexpected message received when the watch has closed');
653
+ return;
654
+ }
655
+ case WATCH_STATUS.ERRORED: {
656
+ console.warn('[realtime listener] internal non-fatal error: unexpected message received when the watch has ended with error');
657
+ return;
658
+ }
659
+ }
660
+ if (!this.sessionInfo) {
661
+ console.warn('[realtime listener] internal non-fatal error: sessionInfo not found while message is received.');
662
+ return;
663
+ }
664
+ this.scheduleSendACK();
665
+ switch (msg.msgType) {
666
+ case 'NEXT_EVENT': {
667
+ console.warn(`nextevent ${msg.msgData.currEvent} ignored`, msg);
668
+ this.handleServerEvents(msg);
669
+ break;
670
+ }
671
+ case 'CHECK_EVENT': {
672
+ if (this.sessionInfo.currentEventId < msg.msgData.currEvent) {
673
+ this.sessionInfo.expectEventId = msg.msgData.currEvent;
674
+ this.clearWaitExpectedEvent();
675
+ this._waitExpectedTimeoutId = setTimeout(() => {
676
+ this.rebuildWatch();
677
+ }, this.getWaitExpectedTimeoutLength());
678
+ console.log(`[realtime] waitExpectedTimeoutLength ${this.getWaitExpectedTimeoutLength()}`);
679
+ }
680
+ break;
681
+ }
682
+ case 'ERROR': {
683
+ this.closeWithError(new error_1.CloudSDKError({
684
+ errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_SERVER_ERROR_MSG,
685
+ errMsg: `${msg.msgData.code} - ${msg.msgData.message}`
686
+ }));
687
+ break;
688
+ }
689
+ default: {
690
+ console.warn(`[realtime listener] virtual client receive unexpected msg ${msg.msgType}: `, msg);
691
+ break;
692
+ }
693
+ }
694
+ }
695
+ closeWithError(error) {
696
+ this.watchStatus = WATCH_STATUS.ERRORED;
697
+ this.clearACKSchedule();
698
+ this.listener.onError(error);
699
+ this.onWatchClose(this, (this.sessionInfo && this.sessionInfo.queryID) || '');
700
+ console.log(`[realtime] client closed (${this.collectionName} ${this.query}) (watchId ${this.watchId})`);
701
+ }
702
+ pause() {
703
+ this.watchStatus = WATCH_STATUS.PAUSED;
704
+ console.log(`[realtime] client paused (${this.collectionName} ${this.query}) (watchId ${this.watchId})`);
705
+ }
706
+ async resume() {
707
+ this.watchStatus = WATCH_STATUS.RESUMING;
708
+ console.log(`[realtime] client resuming with ${this.sessionInfo ? 'REBUILD_WATCH' : 'INIT_WATCH'} (${this.collectionName} ${this.query}) (${this.watchId})`);
709
+ try {
710
+ await (this.sessionInfo ? this.rebuildWatch() : this.initWatch());
711
+ console.log(`[realtime] client successfully resumed (${this.collectionName} ${this.query}) (${this.watchId})`);
712
+ }
713
+ catch (e) {
714
+ console.error(`[realtime] client resume failed (${this.collectionName} ${this.query}) (${this.watchId})`, e);
715
+ }
716
+ }
717
+ }
718
+ exports.VirtualWebSocketClient = VirtualWebSocketClient;
719
+ function getPublicEvent(event) {
720
+ const e = {
721
+ id: event.ID,
722
+ dataType: event.DataType,
723
+ queueType: event.QueueType,
724
+ docId: event.DocID,
725
+ doc: event.Doc && event.Doc !== '{}' ? JSON.parse(event.Doc) : undefined
726
+ };
727
+ if (event.DataType === 'update') {
728
+ if (event.UpdatedFields) {
729
+ e.updatedFields = JSON.parse(event.UpdatedFields);
730
+ }
731
+ if (event.removedFields || event.RemovedFields) {
732
+ e.removedFields = JSON.parse(event.removedFields);
733
+ }
734
+ }
735
+ return e;
736
+ }