@cloudbase/database 1.4.3 → 1.5.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.
- package/dist/commonjs/ObjectId/index.d.ts +1 -1
- package/dist/commonjs/ObjectId/index.js +16 -9
- package/dist/commonjs/aggregate.js +126 -75
- package/dist/commonjs/collection.d.ts +3 -2
- package/dist/commonjs/collection.js +131 -58
- package/dist/commonjs/command.d.ts +5 -5
- package/dist/commonjs/command.js +181 -154
- package/dist/commonjs/commands/logic.js +30 -20
- package/dist/commonjs/commands/query.d.ts +1 -1
- package/dist/commonjs/commands/query.js +85 -65
- package/dist/commonjs/commands/update.js +9 -7
- package/dist/commonjs/config/error.config.js +1 -0
- package/dist/commonjs/const/code.js +1 -0
- package/dist/commonjs/constant.d.ts +7 -7
- package/dist/commonjs/constant.js +13 -11
- package/dist/commonjs/document.d.ts +10 -6
- package/dist/commonjs/document.js +305 -193
- package/dist/commonjs/geo/index.js +20 -9
- package/dist/commonjs/geo/lineString.d.ts +1 -1
- package/dist/commonjs/geo/lineString.js +62 -33
- package/dist/commonjs/geo/multiLineString.d.ts +1 -1
- package/dist/commonjs/geo/multiLineString.js +71 -32
- package/dist/commonjs/geo/multiPoint.d.ts +1 -1
- package/dist/commonjs/geo/multiPoint.js +58 -29
- package/dist/commonjs/geo/multiPolygon.d.ts +1 -1
- package/dist/commonjs/geo/multiPolygon.js +96 -36
- package/dist/commonjs/geo/point.d.ts +1 -1
- package/dist/commonjs/geo/point.js +30 -23
- package/dist/commonjs/geo/polygon.d.ts +1 -1
- package/dist/commonjs/geo/polygon.js +78 -39
- package/dist/commonjs/helper/symbol.js +17 -5
- package/dist/commonjs/index.d.ts +8 -2
- package/dist/commonjs/index.js +73 -20
- package/dist/commonjs/lib/util.d.ts +1 -0
- package/dist/commonjs/lib/util.js +26 -0
- package/dist/commonjs/operator-map.js +7 -6
- package/dist/commonjs/query.d.ts +8 -4
- package/dist/commonjs/query.js +308 -201
- package/dist/commonjs/regexp/index.d.ts +1 -1
- package/dist/commonjs/regexp/index.js +16 -9
- package/dist/commonjs/serializer/common.d.ts +2 -2
- package/dist/commonjs/serializer/common.js +59 -22
- package/dist/commonjs/serializer/datatype.d.ts +2 -2
- package/dist/commonjs/serializer/datatype.js +58 -21
- package/dist/commonjs/serializer/query.d.ts +1 -1
- package/dist/commonjs/serializer/query.js +137 -127
- package/dist/commonjs/serializer/update.d.ts +1 -1
- package/dist/commonjs/serializer/update.js +83 -78
- package/dist/commonjs/serverDate/index.d.ts +1 -1
- package/dist/commonjs/serverDate/index.js +16 -9
- package/dist/commonjs/transaction/collection.d.ts +7 -0
- package/dist/commonjs/transaction/collection.js +49 -0
- package/dist/commonjs/transaction/document.d.ts +27 -0
- package/dist/commonjs/transaction/document.js +205 -0
- package/dist/commonjs/transaction/index.js +196 -86
- package/dist/commonjs/transaction/query.d.ts +43 -0
- package/dist/commonjs/transaction/query.js +261 -0
- package/dist/commonjs/typings/index.d.ts +4 -4
- package/dist/commonjs/typings/realtime.d.ts +16 -16
- package/dist/commonjs/util.js +123 -92
- package/dist/commonjs/utils/error.d.ts +2 -1
- package/dist/commonjs/utils/error.js +74 -38
- package/dist/commonjs/utils/symbol.js +33 -14
- package/dist/commonjs/utils/type.d.ts +1 -1
- package/dist/commonjs/utils/type.js +25 -13
- package/dist/commonjs/utils/utils.d.ts +4 -1
- package/dist/commonjs/utils/utils.js +93 -22
- package/dist/commonjs/validate.js +79 -63
- package/dist/esm/ObjectId/index.d.ts +1 -1
- package/dist/esm/ObjectId/index.js +15 -8
- package/dist/esm/aggregate.js +118 -66
- package/dist/esm/collection.d.ts +3 -2
- package/dist/esm/collection.js +125 -52
- package/dist/esm/command.d.ts +5 -5
- package/dist/esm/command.js +175 -147
- package/dist/esm/commands/logic.js +30 -20
- package/dist/esm/commands/query.d.ts +1 -1
- package/dist/esm/commands/query.js +91 -71
- package/dist/esm/commands/update.js +8 -6
- package/dist/esm/config/error.config.js +1 -1
- package/dist/esm/const/code.js +1 -1
- package/dist/esm/constant.d.ts +7 -7
- package/dist/esm/constant.js +12 -11
- package/dist/esm/document.d.ts +10 -6
- package/dist/esm/document.js +297 -185
- package/dist/esm/geo/interface.js +1 -0
- package/dist/esm/geo/lineString.d.ts +1 -1
- package/dist/esm/geo/lineString.js +57 -28
- package/dist/esm/geo/multiLineString.d.ts +1 -1
- package/dist/esm/geo/multiLineString.js +66 -27
- package/dist/esm/geo/multiPoint.d.ts +1 -1
- package/dist/esm/geo/multiPoint.js +53 -24
- package/dist/esm/geo/multiPolygon.d.ts +1 -1
- package/dist/esm/geo/multiPolygon.js +91 -31
- package/dist/esm/geo/point.d.ts +1 -1
- package/dist/esm/geo/point.js +26 -19
- package/dist/esm/geo/polygon.d.ts +1 -1
- package/dist/esm/geo/polygon.js +73 -34
- package/dist/esm/helper/symbol.js +13 -13
- package/dist/esm/index.d.ts +8 -2
- package/dist/esm/index.js +62 -9
- package/dist/esm/lib/util.d.ts +1 -0
- package/dist/esm/lib/util.js +22 -0
- package/dist/esm/operator-map.js +4 -4
- package/dist/esm/query.d.ts +8 -4
- package/dist/esm/query.js +299 -192
- package/dist/esm/regexp/index.d.ts +1 -1
- package/dist/esm/regexp/index.js +15 -8
- package/dist/esm/serializer/common.d.ts +2 -2
- package/dist/esm/serializer/common.js +49 -13
- package/dist/esm/serializer/datatype.d.ts +2 -2
- package/dist/esm/serializer/datatype.js +44 -8
- package/dist/esm/serializer/query.d.ts +1 -1
- package/dist/esm/serializer/query.js +112 -102
- package/dist/esm/serializer/update.d.ts +1 -1
- package/dist/esm/serializer/update.js +69 -64
- package/dist/esm/serverDate/index.d.ts +1 -1
- package/dist/esm/serverDate/index.js +15 -8
- package/dist/esm/transaction/collection.d.ts +7 -0
- package/dist/esm/transaction/collection.js +46 -0
- package/dist/esm/transaction/document.d.ts +27 -0
- package/dist/esm/transaction/document.js +202 -0
- package/dist/esm/transaction/index.js +191 -81
- package/dist/esm/transaction/query.d.ts +43 -0
- package/dist/esm/transaction/query.js +258 -0
- package/dist/esm/typings/index.d.ts +4 -4
- package/dist/esm/typings/index.js +1 -0
- package/dist/esm/typings/realtime.d.ts +16 -16
- package/dist/esm/typings/realtime.js +1 -0
- package/dist/esm/util.js +121 -90
- package/dist/esm/utils/error.d.ts +2 -1
- package/dist/esm/utils/error.js +70 -35
- package/dist/esm/utils/symbol.js +33 -14
- package/dist/esm/utils/type.d.ts +1 -1
- package/dist/esm/utils/type.js +12 -12
- package/dist/esm/utils/utils.d.ts +4 -1
- package/dist/esm/utils/utils.js +78 -16
- package/dist/esm/validate.js +65 -49
- package/jest.config.js +14 -0
- package/package.json +14 -10
- package/src/aggregate.ts +1 -5
- package/src/collection.ts +15 -2
- package/src/document.ts +45 -34
- package/src/index.ts +51 -4
- package/src/query.ts +33 -29
- package/src/serializer/query.ts +18 -6
- package/src/transaction/index.ts +1 -1
- package/src/typings/index.ts +0 -4
- package/src/utils/utils.ts +46 -0
- package/src/validate.ts +3 -6
- package/tests/gateway-contract.test.ts +180 -0
- package/tests/index.test.ts +283 -0
- package/tests/transaction-query.test.ts +207 -0
- package/tsconfig.esm.json +4 -2
- package/tsconfig.json +16 -6
- package/tsconfig.test.json +15 -0
- package/dist/commonjs/realtime/error.d.ts +0 -7
- package/dist/commonjs/realtime/error.js +0 -11
- package/dist/commonjs/realtime/listener.d.ts +0 -14
- package/dist/commonjs/realtime/listener.js +0 -17
- package/dist/commonjs/realtime/message.d.ts +0 -3
- package/dist/commonjs/realtime/message.js +0 -10
- package/dist/commonjs/realtime/snapshot.d.ts +0 -16
- package/dist/commonjs/realtime/snapshot.js +0 -42
- package/dist/commonjs/realtime/virtual-websocket-client.d.ts +0 -63
- package/dist/commonjs/realtime/virtual-websocket-client.js +0 -736
- package/dist/commonjs/realtime/websocket-client.d.ts +0 -78
- package/dist/commonjs/realtime/websocket-client.js +0 -567
- package/dist/commonjs/realtime/ws-event.d.ts +0 -126
- package/dist/commonjs/realtime/ws-event.js +0 -139
- package/dist/commonjs/transaction.d.ts +0 -42
- package/dist/commonjs/transaction.js +0 -126
- package/dist/esm/realtime/error.d.ts +0 -7
- package/dist/esm/realtime/error.js +0 -8
- package/dist/esm/realtime/listener.d.ts +0 -14
- package/dist/esm/realtime/listener.js +0 -14
- package/dist/esm/realtime/message.d.ts +0 -3
- package/dist/esm/realtime/message.js +0 -6
- package/dist/esm/realtime/snapshot.d.ts +0 -16
- package/dist/esm/realtime/snapshot.js +0 -39
- package/dist/esm/realtime/virtual-websocket-client.d.ts +0 -63
- package/dist/esm/realtime/virtual-websocket-client.js +0 -733
- package/dist/esm/realtime/websocket-client.d.ts +0 -78
- package/dist/esm/realtime/websocket-client.js +0 -564
- package/dist/esm/realtime/ws-event.d.ts +0 -126
- package/dist/esm/realtime/ws-event.js +0 -137
- package/dist/esm/transaction.d.ts +0 -42
- package/dist/esm/transaction.js +0 -121
- package/src/realtime/error.ts +0 -19
- package/src/realtime/listener.ts +0 -35
- package/src/realtime/message.ts +0 -14
- package/src/realtime/snapshot.ts +0 -60
- package/src/realtime/virtual-websocket-client.ts +0 -1195
- package/src/realtime/websocket-client.ts +0 -995
- package/src/realtime/ws-event.ts +0 -163
- package/src/transaction.ts +0 -190
|
@@ -1,1195 +0,0 @@
|
|
|
1
|
-
import set from 'lodash.set'
|
|
2
|
-
import unset from 'lodash.unset'
|
|
3
|
-
import cloneDeep from 'lodash.clonedeep'
|
|
4
|
-
import { genRequestId } from './message'
|
|
5
|
-
import {
|
|
6
|
-
IResponseMessage,
|
|
7
|
-
IRequestMessageInitWatchMsg,
|
|
8
|
-
IResponseMessageInitEventMsg,
|
|
9
|
-
IDBEvent,
|
|
10
|
-
IRequestMessageRebuildWatchMsg,
|
|
11
|
-
IRequestMessageCloseWatchMsg,
|
|
12
|
-
IRequestMsgType,
|
|
13
|
-
IResponseMessageNextEventMsg,
|
|
14
|
-
IRequestMessageCheckLastMsg
|
|
15
|
-
} from '../typings/realtime'
|
|
16
|
-
import {
|
|
17
|
-
CloudSDKError,
|
|
18
|
-
isTimeoutError,
|
|
19
|
-
TimeoutError,
|
|
20
|
-
CancelledError,
|
|
21
|
-
isCancelledError
|
|
22
|
-
} from '../utils/error'
|
|
23
|
-
import { ERR_CODE } from '../config/error.config'
|
|
24
|
-
import { sleep } from '../utils/utils'
|
|
25
|
-
// import Reporter from "./externals/public-lib/reporter"
|
|
26
|
-
import { RealtimeListener } from './listener'
|
|
27
|
-
import { Snapshot } from './snapshot'
|
|
28
|
-
import { IWSSendOptions, ILoginResult } from './websocket-client'
|
|
29
|
-
import { isRealtimeErrorMessageError, RealtimeErrorMessageError } from './error'
|
|
30
|
-
import { IWatchOptions, ISingleDBEvent } from '../typings/index'
|
|
31
|
-
|
|
32
|
-
// =============== Realtime Virtual WebSocket Client (Internal) ====================
|
|
33
|
-
|
|
34
|
-
interface IVirtualWebSocketClientConstructorOptions extends IWatchOptions {
|
|
35
|
-
// ws: RealtimeWebSocketClient
|
|
36
|
-
envId?: string
|
|
37
|
-
collectionName: string
|
|
38
|
-
query: string
|
|
39
|
-
limit?: number
|
|
40
|
-
orderBy?: Record<string, string>
|
|
41
|
-
send: <T = any>(opts: IWSSendOptions) => Promise<T>
|
|
42
|
-
login: (envId?: string, refresh?: boolean) => Promise<any>
|
|
43
|
-
isWSConnected: () => boolean
|
|
44
|
-
onceWSConnected: () => Promise<void>
|
|
45
|
-
getWaitExpectedTimeoutLength: () => number
|
|
46
|
-
onWatchStart: (client: VirtualWebSocketClient, queryID: string) => void
|
|
47
|
-
onWatchClose: (client: VirtualWebSocketClient, queryID: string) => void
|
|
48
|
-
debug?: boolean
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface IWatchSessionInfo {
|
|
52
|
-
queryID: string
|
|
53
|
-
currentEventId: number
|
|
54
|
-
currentDocs: Record<string, any>[]
|
|
55
|
-
expectEventId?: number
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interface IHandleCommonErrorOptions {
|
|
59
|
-
onSignError: (e: RealtimeErrorMessageError) => void
|
|
60
|
-
onTimeoutError: (e: TimeoutError) => void
|
|
61
|
-
onCancelledError: (e: CancelledError) => void
|
|
62
|
-
onNotRetryableError: (e: RealtimeErrorMessageError) => void
|
|
63
|
-
onUnknownError: (e: any) => void
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
interface IHandleWatchEstablishmentErrorOptions {
|
|
67
|
-
operationName: 'INIT_WATCH' | 'REBUILD_WATCH'
|
|
68
|
-
resolve: (value?: void | PromiseLike<void> | undefined) => void
|
|
69
|
-
reject: (e: any) => void
|
|
70
|
-
// retry: (refreshLogin?: boolean) => void
|
|
71
|
-
// abortWatch: (e: any) => void
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
enum WATCH_STATUS {
|
|
75
|
-
LOGGINGIN = 'LOGGINGIN',
|
|
76
|
-
INITING = 'INITING',
|
|
77
|
-
REBUILDING = 'REBUILDING',
|
|
78
|
-
ACTIVE = 'ACTIVE',
|
|
79
|
-
ERRORED = 'ERRORED',
|
|
80
|
-
CLOSING = 'CLOSING',
|
|
81
|
-
CLOSED = 'CLOSED',
|
|
82
|
-
PAUSED = 'PAUSED',
|
|
83
|
-
RESUMING = 'RESUMING'
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const DEFAULT_WAIT_TIME_ON_UNKNOWN_ERROR = 100
|
|
87
|
-
const DEFAULT_MAX_AUTO_RETRY_ON_ERROR = 2
|
|
88
|
-
const DEFAULT_MAX_SEND_ACK_AUTO_RETRY_ON_ERROR = 2
|
|
89
|
-
const DEFAULT_SEND_ACK_DEBOUNCE_TIMEOUT = 10 * 1000
|
|
90
|
-
const DEFAULT_INIT_WATCH_TIMEOUT = 10 * 1000
|
|
91
|
-
const DEFAULT_REBUILD_WATCH_TIMEOUT = 10 * 1000
|
|
92
|
-
|
|
93
|
-
export class VirtualWebSocketClient {
|
|
94
|
-
// passed over
|
|
95
|
-
watchId: string
|
|
96
|
-
private envId?: string
|
|
97
|
-
private collectionName: string
|
|
98
|
-
private query: string
|
|
99
|
-
private limit: number
|
|
100
|
-
private orderBy: Record<string, string>
|
|
101
|
-
private send: <T = any>(opts: IWSSendOptions) => Promise<T>
|
|
102
|
-
private login: (envId?: string, refresh?: boolean) => Promise<any>
|
|
103
|
-
private isWSConnected: () => boolean
|
|
104
|
-
private onceWSConnected: () => Promise<void>
|
|
105
|
-
private getWaitExpectedTimeoutLength: () => number
|
|
106
|
-
private onWatchStart: (
|
|
107
|
-
client: VirtualWebSocketClient,
|
|
108
|
-
queryID: string
|
|
109
|
-
) => void
|
|
110
|
-
private onWatchClose: (
|
|
111
|
-
client: VirtualWebSocketClient,
|
|
112
|
-
queryID: string
|
|
113
|
-
) => void
|
|
114
|
-
private debug?: boolean
|
|
115
|
-
|
|
116
|
-
// own
|
|
117
|
-
listener: RealtimeListener
|
|
118
|
-
private watchStatus: WATCH_STATUS = WATCH_STATUS.INITING
|
|
119
|
-
private _availableRetries: Partial<Record<IRequestMsgType, number>>
|
|
120
|
-
private _ackTimeoutId?: number
|
|
121
|
-
private _initWatchPromise?: Promise<void>
|
|
122
|
-
private _rebuildWatchPromise?: Promise<void>
|
|
123
|
-
|
|
124
|
-
// obtained
|
|
125
|
-
private sessionInfo?: IWatchSessionInfo
|
|
126
|
-
|
|
127
|
-
// internal
|
|
128
|
-
private _waitExpectedTimeoutId?: number
|
|
129
|
-
|
|
130
|
-
constructor(options: IVirtualWebSocketClientConstructorOptions) {
|
|
131
|
-
this.watchId = `watchid_${+new Date()}_${Math.random()}`
|
|
132
|
-
this.envId = options.envId
|
|
133
|
-
this.collectionName = options.collectionName
|
|
134
|
-
this.query = options.query
|
|
135
|
-
this.limit = options.limit
|
|
136
|
-
this.orderBy = options.orderBy
|
|
137
|
-
this.send = options.send
|
|
138
|
-
this.login = options.login
|
|
139
|
-
this.isWSConnected = options.isWSConnected
|
|
140
|
-
this.onceWSConnected = options.onceWSConnected
|
|
141
|
-
this.getWaitExpectedTimeoutLength = options.getWaitExpectedTimeoutLength
|
|
142
|
-
this.onWatchStart = options.onWatchStart
|
|
143
|
-
this.onWatchClose = options.onWatchClose
|
|
144
|
-
this.debug = options.debug
|
|
145
|
-
|
|
146
|
-
this._availableRetries = {
|
|
147
|
-
INIT_WATCH: DEFAULT_MAX_AUTO_RETRY_ON_ERROR,
|
|
148
|
-
REBUILD_WATCH: DEFAULT_MAX_AUTO_RETRY_ON_ERROR,
|
|
149
|
-
CHECK_LAST: DEFAULT_MAX_SEND_ACK_AUTO_RETRY_ON_ERROR
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
this.listener = new RealtimeListener({
|
|
153
|
-
close: this.closeWatch,
|
|
154
|
-
onChange: options.onChange,
|
|
155
|
-
onError: options.onError,
|
|
156
|
-
debug: this.debug,
|
|
157
|
-
virtualClient: this
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
this.initWatch()
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private _login = async (
|
|
164
|
-
envId?: string,
|
|
165
|
-
refresh?: boolean
|
|
166
|
-
): Promise<ILoginResult> => {
|
|
167
|
-
this.watchStatus = WATCH_STATUS.LOGGINGIN
|
|
168
|
-
const loginResult = await this.login(envId, refresh)
|
|
169
|
-
if (!this.envId) {
|
|
170
|
-
this.envId = loginResult.envId
|
|
171
|
-
}
|
|
172
|
-
return loginResult
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
private initWatch = async (forceRefreshLogin?: boolean): Promise<void> => {
|
|
176
|
-
if (this._initWatchPromise) {
|
|
177
|
-
return this._initWatchPromise
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
this._initWatchPromise = new Promise<void>(
|
|
181
|
-
async (resolve, reject): Promise<void> => {
|
|
182
|
-
try {
|
|
183
|
-
if (this.watchStatus === WATCH_STATUS.PAUSED) {
|
|
184
|
-
// if (process.env.DEBUG) {
|
|
185
|
-
console.log('[realtime] initWatch cancelled on pause')
|
|
186
|
-
// }
|
|
187
|
-
return resolve()
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const { envId } = await this._login(this.envId, forceRefreshLogin)
|
|
191
|
-
|
|
192
|
-
// if (!this.sessionInfo) {
|
|
193
|
-
// throw new Error(`can not rebuildWatch without a successful initWatch (lack of sessionInfo)`)
|
|
194
|
-
// }
|
|
195
|
-
|
|
196
|
-
if ((this.watchStatus as WATCH_STATUS) === WATCH_STATUS.PAUSED) {
|
|
197
|
-
console.log('[realtime] initWatch cancelled on pause')
|
|
198
|
-
return resolve()
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
this.watchStatus = WATCH_STATUS.INITING
|
|
202
|
-
|
|
203
|
-
const initWatchMsg: IRequestMessageInitWatchMsg = {
|
|
204
|
-
watchId: this.watchId,
|
|
205
|
-
requestId: genRequestId(),
|
|
206
|
-
msgType: 'INIT_WATCH',
|
|
207
|
-
msgData: {
|
|
208
|
-
envId,
|
|
209
|
-
collName: this.collectionName,
|
|
210
|
-
query: this.query,
|
|
211
|
-
limit: this.limit,
|
|
212
|
-
orderBy: this.orderBy
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const initEventMsg = await this.send<IResponseMessageInitEventMsg>({
|
|
217
|
-
msg: initWatchMsg,
|
|
218
|
-
waitResponse: true,
|
|
219
|
-
skipOnMessage: true,
|
|
220
|
-
timeout: DEFAULT_INIT_WATCH_TIMEOUT
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
const { events, currEvent } = initEventMsg.msgData
|
|
224
|
-
|
|
225
|
-
this.sessionInfo = {
|
|
226
|
-
queryID: initEventMsg.msgData.queryID,
|
|
227
|
-
currentEventId: currEvent - 1,
|
|
228
|
-
currentDocs: []
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// FIX: in initEvent message, all events have id 0, which is inconsistent with currEvent
|
|
232
|
-
if (events.length > 0) {
|
|
233
|
-
for (const e of events) {
|
|
234
|
-
e.ID = currEvent
|
|
235
|
-
}
|
|
236
|
-
this.handleServerEvents(initEventMsg)
|
|
237
|
-
} else {
|
|
238
|
-
this.sessionInfo.currentEventId = currEvent
|
|
239
|
-
const snapshot = new Snapshot({
|
|
240
|
-
id: currEvent,
|
|
241
|
-
docChanges: [],
|
|
242
|
-
docs: [],
|
|
243
|
-
type: 'init'
|
|
244
|
-
})
|
|
245
|
-
this.listener.onChange(snapshot)
|
|
246
|
-
this.scheduleSendACK()
|
|
247
|
-
}
|
|
248
|
-
this.onWatchStart(this, this.sessionInfo.queryID)
|
|
249
|
-
this.watchStatus = WATCH_STATUS.ACTIVE
|
|
250
|
-
this._availableRetries.INIT_WATCH = DEFAULT_MAX_AUTO_RETRY_ON_ERROR
|
|
251
|
-
resolve()
|
|
252
|
-
} catch (e) {
|
|
253
|
-
this.handleWatchEstablishmentError(e, {
|
|
254
|
-
operationName: 'INIT_WATCH',
|
|
255
|
-
resolve,
|
|
256
|
-
reject
|
|
257
|
-
})
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
let success = false
|
|
263
|
-
|
|
264
|
-
try {
|
|
265
|
-
await this._initWatchPromise
|
|
266
|
-
success = true
|
|
267
|
-
} finally {
|
|
268
|
-
this._initWatchPromise = undefined
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// if (process.env.DEBUG) {
|
|
272
|
-
console.log(`[realtime] initWatch ${success ? 'success' : 'fail'}`)
|
|
273
|
-
// }
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
private rebuildWatch = async (forceRefreshLogin?: boolean): Promise<void> => {
|
|
277
|
-
if (this._rebuildWatchPromise) {
|
|
278
|
-
return this._rebuildWatchPromise
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
this._rebuildWatchPromise = new Promise<void>(
|
|
282
|
-
async (resolve, reject): Promise<void> => {
|
|
283
|
-
try {
|
|
284
|
-
if (this.watchStatus === WATCH_STATUS.PAUSED) {
|
|
285
|
-
// if (process.env.DEBUG) {
|
|
286
|
-
console.log('[realtime] rebuildWatch cancelled on pause')
|
|
287
|
-
// }
|
|
288
|
-
return resolve()
|
|
289
|
-
}
|
|
290
|
-
const { envId } = await this._login(this.envId, forceRefreshLogin)
|
|
291
|
-
|
|
292
|
-
if (!this.sessionInfo) {
|
|
293
|
-
throw new Error(
|
|
294
|
-
'can not rebuildWatch without a successful initWatch (lack of sessionInfo)'
|
|
295
|
-
)
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if ((this.watchStatus as WATCH_STATUS) === WATCH_STATUS.PAUSED) {
|
|
299
|
-
console.log('[realtime] rebuildWatch cancelled on pause')
|
|
300
|
-
return resolve()
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
this.watchStatus = WATCH_STATUS.REBUILDING
|
|
304
|
-
|
|
305
|
-
const rebuildWatchMsg: IRequestMessageRebuildWatchMsg = {
|
|
306
|
-
watchId: this.watchId,
|
|
307
|
-
requestId: genRequestId(),
|
|
308
|
-
msgType: 'REBUILD_WATCH',
|
|
309
|
-
msgData: {
|
|
310
|
-
envId,
|
|
311
|
-
collName: this.collectionName,
|
|
312
|
-
queryID: this.sessionInfo.queryID,
|
|
313
|
-
eventID: this.sessionInfo.currentEventId
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const nextEventMsg = await this.send<IResponseMessageNextEventMsg>({
|
|
318
|
-
msg: rebuildWatchMsg,
|
|
319
|
-
waitResponse: true,
|
|
320
|
-
skipOnMessage: false,
|
|
321
|
-
timeout: DEFAULT_REBUILD_WATCH_TIMEOUT
|
|
322
|
-
})
|
|
323
|
-
|
|
324
|
-
this.handleServerEvents(nextEventMsg)
|
|
325
|
-
|
|
326
|
-
this.watchStatus = WATCH_STATUS.ACTIVE
|
|
327
|
-
this._availableRetries.REBUILD_WATCH = DEFAULT_MAX_AUTO_RETRY_ON_ERROR
|
|
328
|
-
resolve()
|
|
329
|
-
} catch (e) {
|
|
330
|
-
this.handleWatchEstablishmentError(e, {
|
|
331
|
-
operationName: 'REBUILD_WATCH',
|
|
332
|
-
resolve,
|
|
333
|
-
reject
|
|
334
|
-
})
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
)
|
|
338
|
-
|
|
339
|
-
let success = false
|
|
340
|
-
|
|
341
|
-
try {
|
|
342
|
-
await this._rebuildWatchPromise
|
|
343
|
-
success = true
|
|
344
|
-
} finally {
|
|
345
|
-
this._rebuildWatchPromise = undefined
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// if (process.env.DEBUG) {
|
|
349
|
-
console.log(`[realtime] rebuildWatch ${success ? 'success' : 'fail'}`)
|
|
350
|
-
// }
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
private handleWatchEstablishmentError = async (
|
|
354
|
-
e: any,
|
|
355
|
-
options: IHandleWatchEstablishmentErrorOptions
|
|
356
|
-
) => {
|
|
357
|
-
const isInitWatch = options.operationName === 'INIT_WATCH'
|
|
358
|
-
|
|
359
|
-
const abortWatch = () => {
|
|
360
|
-
// mock temp comment
|
|
361
|
-
this.closeWithError(
|
|
362
|
-
new CloudSDKError({
|
|
363
|
-
errCode: isInitWatch
|
|
364
|
-
? (ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_INIT_WATCH_FAIL as string)
|
|
365
|
-
: (ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_REBUILD_WATCH_FAIL as string),
|
|
366
|
-
errMsg: e
|
|
367
|
-
})
|
|
368
|
-
)
|
|
369
|
-
options.reject(e)
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const retry = (refreshLogin?: boolean) => {
|
|
373
|
-
if (this.useRetryTicket(options.operationName)) {
|
|
374
|
-
if (isInitWatch) {
|
|
375
|
-
this._initWatchPromise = undefined
|
|
376
|
-
options.resolve(this.initWatch(refreshLogin))
|
|
377
|
-
} else {
|
|
378
|
-
this._rebuildWatchPromise = undefined
|
|
379
|
-
options.resolve(this.rebuildWatch(refreshLogin))
|
|
380
|
-
}
|
|
381
|
-
} else {
|
|
382
|
-
abortWatch()
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
this.handleCommonError(e, {
|
|
387
|
-
onSignError: () => retry(true),
|
|
388
|
-
onTimeoutError: () => retry(false),
|
|
389
|
-
onNotRetryableError: abortWatch,
|
|
390
|
-
onCancelledError: options.reject,
|
|
391
|
-
onUnknownError: async () => {
|
|
392
|
-
try {
|
|
393
|
-
const onWSDisconnected = async () => {
|
|
394
|
-
this.pause()
|
|
395
|
-
await this.onceWSConnected()
|
|
396
|
-
retry(true)
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
if (!this.isWSConnected()) {
|
|
400
|
-
await onWSDisconnected()
|
|
401
|
-
} else {
|
|
402
|
-
await sleep(DEFAULT_WAIT_TIME_ON_UNKNOWN_ERROR)
|
|
403
|
-
if (this.watchStatus === WATCH_STATUS.PAUSED) {
|
|
404
|
-
// cancel
|
|
405
|
-
options.reject(
|
|
406
|
-
new CancelledError(
|
|
407
|
-
`${options.operationName} cancelled due to pause after unknownError`
|
|
408
|
-
)
|
|
409
|
-
)
|
|
410
|
-
} else if (!this.isWSConnected()) {
|
|
411
|
-
await onWSDisconnected()
|
|
412
|
-
} else {
|
|
413
|
-
retry(false)
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
} catch (e) {
|
|
417
|
-
// unexpected error while handling error, in order to provide maximum effort on SEAMINGLESS FAULT TOLERANCE, just retry
|
|
418
|
-
retry(true)
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
})
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
private closeWatch = async () => {
|
|
425
|
-
const queryId = this.sessionInfo ? this.sessionInfo.queryID : ''
|
|
426
|
-
|
|
427
|
-
if (this.watchStatus !== WATCH_STATUS.ACTIVE) {
|
|
428
|
-
this.watchStatus = WATCH_STATUS.CLOSED
|
|
429
|
-
this.onWatchClose(this, queryId)
|
|
430
|
-
return
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
try {
|
|
434
|
-
this.watchStatus = WATCH_STATUS.CLOSING
|
|
435
|
-
|
|
436
|
-
const closeWatchMsg: IRequestMessageCloseWatchMsg = {
|
|
437
|
-
watchId: this.watchId,
|
|
438
|
-
requestId: genRequestId(),
|
|
439
|
-
msgType: 'CLOSE_WATCH',
|
|
440
|
-
msgData: null
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
await this.send<void>({
|
|
444
|
-
msg: closeWatchMsg
|
|
445
|
-
})
|
|
446
|
-
|
|
447
|
-
this.sessionInfo = undefined
|
|
448
|
-
this.watchStatus = WATCH_STATUS.CLOSED
|
|
449
|
-
} catch (e) {
|
|
450
|
-
this.closeWithError(
|
|
451
|
-
new CloudSDKError({
|
|
452
|
-
errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CLOSE_WATCH_FAIL as string,
|
|
453
|
-
errMsg: e
|
|
454
|
-
})
|
|
455
|
-
)
|
|
456
|
-
} finally {
|
|
457
|
-
this.onWatchClose(this, queryId)
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
private scheduleSendACK = () => {
|
|
462
|
-
this.clearACKSchedule()
|
|
463
|
-
|
|
464
|
-
// TODO: should we check status after timeout
|
|
465
|
-
// @ts-ignore
|
|
466
|
-
this._ackTimeoutId = setTimeout(() => {
|
|
467
|
-
if (this._waitExpectedTimeoutId) {
|
|
468
|
-
this.scheduleSendACK()
|
|
469
|
-
} else {
|
|
470
|
-
this.sendACK()
|
|
471
|
-
}
|
|
472
|
-
}, DEFAULT_SEND_ACK_DEBOUNCE_TIMEOUT)
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
private clearACKSchedule = () => {
|
|
476
|
-
if (this._ackTimeoutId) {
|
|
477
|
-
clearTimeout(this._ackTimeoutId)
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
private sendACK = async (): Promise<void> => {
|
|
482
|
-
try {
|
|
483
|
-
if (this.watchStatus !== WATCH_STATUS.ACTIVE) {
|
|
484
|
-
this.scheduleSendACK()
|
|
485
|
-
return
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
if (!this.sessionInfo) {
|
|
489
|
-
console.warn(
|
|
490
|
-
'[realtime listener] can not send ack without a successful initWatch (lack of sessionInfo)'
|
|
491
|
-
)
|
|
492
|
-
return
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
const ackMsg: IRequestMessageCheckLastMsg = {
|
|
496
|
-
watchId: this.watchId,
|
|
497
|
-
requestId: genRequestId(),
|
|
498
|
-
msgType: 'CHECK_LAST',
|
|
499
|
-
msgData: {
|
|
500
|
-
queryID: this.sessionInfo.queryID,
|
|
501
|
-
eventID: this.sessionInfo.currentEventId
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
await this.send<void>({
|
|
506
|
-
msg: ackMsg
|
|
507
|
-
})
|
|
508
|
-
|
|
509
|
-
this.scheduleSendACK()
|
|
510
|
-
} catch (e) {
|
|
511
|
-
// TODO: refactor
|
|
512
|
-
if (isRealtimeErrorMessageError(e)) {
|
|
513
|
-
const msg = e.payload
|
|
514
|
-
switch (msg.msgData.code) {
|
|
515
|
-
// signature error -> retry with refreshed signature
|
|
516
|
-
case 'CHECK_LOGIN_FAILED':
|
|
517
|
-
case 'SIGN_EXPIRED_ERROR':
|
|
518
|
-
case 'SIGN_INVALID_ERROR':
|
|
519
|
-
case 'SIGN_PARAM_INVALID': {
|
|
520
|
-
this.rebuildWatch()
|
|
521
|
-
return
|
|
522
|
-
}
|
|
523
|
-
// other -> throw
|
|
524
|
-
case 'QUERYID_INVALID_ERROR':
|
|
525
|
-
case 'SYS_ERR':
|
|
526
|
-
case 'INVALIID_ENV':
|
|
527
|
-
case 'COLLECTION_PERMISSION_DENIED': {
|
|
528
|
-
// must throw
|
|
529
|
-
this.closeWithError(
|
|
530
|
-
new CloudSDKError({
|
|
531
|
-
errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL as string,
|
|
532
|
-
errMsg: msg.msgData.code
|
|
533
|
-
})
|
|
534
|
-
)
|
|
535
|
-
return
|
|
536
|
-
}
|
|
537
|
-
default: {
|
|
538
|
-
break
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// maybe retryable
|
|
544
|
-
if (
|
|
545
|
-
this._availableRetries.CHECK_LAST &&
|
|
546
|
-
this._availableRetries.CHECK_LAST > 0
|
|
547
|
-
) {
|
|
548
|
-
this._availableRetries.CHECK_LAST--
|
|
549
|
-
this.scheduleSendACK()
|
|
550
|
-
} else {
|
|
551
|
-
this.closeWithError(
|
|
552
|
-
new CloudSDKError({
|
|
553
|
-
errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL as string,
|
|
554
|
-
errMsg: e
|
|
555
|
-
})
|
|
556
|
-
)
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
private handleCommonError = (
|
|
562
|
-
e: any,
|
|
563
|
-
options: IHandleCommonErrorOptions
|
|
564
|
-
): void => {
|
|
565
|
-
if (isRealtimeErrorMessageError(e)) {
|
|
566
|
-
const msg = e.payload
|
|
567
|
-
switch (msg.msgData.code) {
|
|
568
|
-
// signature error -> retry with refreshed signature
|
|
569
|
-
case 'CHECK_LOGIN_FAILED':
|
|
570
|
-
case 'SIGN_EXPIRED_ERROR':
|
|
571
|
-
case 'SIGN_INVALID_ERROR':
|
|
572
|
-
case 'SIGN_PARAM_INVALID': {
|
|
573
|
-
options.onSignError(e)
|
|
574
|
-
return
|
|
575
|
-
}
|
|
576
|
-
// not-retryable error -> throw
|
|
577
|
-
case 'QUERYID_INVALID_ERROR':
|
|
578
|
-
case 'SYS_ERR':
|
|
579
|
-
case 'INVALIID_ENV':
|
|
580
|
-
case 'COLLECTION_PERMISSION_DENIED': {
|
|
581
|
-
options.onNotRetryableError(e)
|
|
582
|
-
return
|
|
583
|
-
}
|
|
584
|
-
default: {
|
|
585
|
-
options.onNotRetryableError(e)
|
|
586
|
-
return
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
} else if (isTimeoutError(e)) {
|
|
590
|
-
// timeout error
|
|
591
|
-
options.onTimeoutError(e)
|
|
592
|
-
return
|
|
593
|
-
} else if (isCancelledError(e)) {
|
|
594
|
-
// cancelled error
|
|
595
|
-
options.onCancelledError(e)
|
|
596
|
-
return
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// unknown error
|
|
600
|
-
options.onUnknownError(e)
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// credit a retry chance from availableRetries
|
|
604
|
-
private useRetryTicket(operationName: IRequestMsgType): boolean {
|
|
605
|
-
if (
|
|
606
|
-
this._availableRetries[operationName] &&
|
|
607
|
-
this._availableRetries[operationName]! > 0
|
|
608
|
-
) {
|
|
609
|
-
this._availableRetries[operationName]!--
|
|
610
|
-
|
|
611
|
-
// if (process.env.DEBUG) {
|
|
612
|
-
console.log(
|
|
613
|
-
`[realtime] ${operationName} use a retry ticket, now only ${this._availableRetries[operationName]} retry left`
|
|
614
|
-
)
|
|
615
|
-
// }
|
|
616
|
-
|
|
617
|
-
return true
|
|
618
|
-
}
|
|
619
|
-
return false
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
private async handleServerEvents(
|
|
623
|
-
msg: IResponseMessageInitEventMsg | IResponseMessageNextEventMsg
|
|
624
|
-
) {
|
|
625
|
-
try {
|
|
626
|
-
this.scheduleSendACK()
|
|
627
|
-
await this._handleServerEvents(msg)
|
|
628
|
-
this._postHandleServerEventsValidityCheck(msg)
|
|
629
|
-
} catch (e) {
|
|
630
|
-
// if (process.env.DEBUG) {
|
|
631
|
-
// TODO: report
|
|
632
|
-
console.error(
|
|
633
|
-
'[realtime listener] internal non-fatal error: handle server events failed with error: ',
|
|
634
|
-
e
|
|
635
|
-
)
|
|
636
|
-
|
|
637
|
-
// writeToFile(
|
|
638
|
-
// "wserror.txt",
|
|
639
|
-
// `[realtime listener] internal non-fatal error: handle server events failed with error: ${JSON.stringify(
|
|
640
|
-
// Object.assign({}, e, {
|
|
641
|
-
// requestId: msg.requestId,
|
|
642
|
-
// watchId: msg.watchId
|
|
643
|
-
// })
|
|
644
|
-
// )} \n`
|
|
645
|
-
// )
|
|
646
|
-
// }
|
|
647
|
-
|
|
648
|
-
throw e
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
private async _handleServerEvents(
|
|
653
|
-
msg: IResponseMessageInitEventMsg | IResponseMessageNextEventMsg
|
|
654
|
-
) {
|
|
655
|
-
const { requestId } = msg
|
|
656
|
-
|
|
657
|
-
const { events } = msg.msgData
|
|
658
|
-
const { msgType } = msg
|
|
659
|
-
|
|
660
|
-
if (!events.length || !this.sessionInfo) {
|
|
661
|
-
return
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
const sessionInfo = this.sessionInfo
|
|
665
|
-
|
|
666
|
-
let allChangeEvents: ISingleDBEvent[]
|
|
667
|
-
try {
|
|
668
|
-
allChangeEvents = events.map(getPublicEvent)
|
|
669
|
-
} catch (e) {
|
|
670
|
-
this.closeWithError(
|
|
671
|
-
new CloudSDKError({
|
|
672
|
-
errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_RECEIVE_INVALID_SERVER_DATA as string,
|
|
673
|
-
errMsg: e
|
|
674
|
-
})
|
|
675
|
-
)
|
|
676
|
-
return
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
// aggregate docs
|
|
680
|
-
let docs = [...sessionInfo.currentDocs]
|
|
681
|
-
let initEncountered = false
|
|
682
|
-
for (let i = 0, len = allChangeEvents.length; i < len; i++) {
|
|
683
|
-
const change = allChangeEvents[i]
|
|
684
|
-
|
|
685
|
-
if (sessionInfo.currentEventId >= change.id) {
|
|
686
|
-
if (!allChangeEvents[i - 1] || change.id > allChangeEvents[i - 1].id) {
|
|
687
|
-
// duplicate event, dropable
|
|
688
|
-
// TODO: report
|
|
689
|
-
// if (process.env.DEBUG) {
|
|
690
|
-
console.warn(
|
|
691
|
-
`[realtime] duplicate event received, cur ${sessionInfo.currentEventId} but got ${change.id}`
|
|
692
|
-
)
|
|
693
|
-
// }
|
|
694
|
-
} else {
|
|
695
|
-
// allChangeEvents should be in ascending order according to eventId, this should never happens, must report a non-fatal error
|
|
696
|
-
console.error(
|
|
697
|
-
`[realtime listener] server non-fatal error: events out of order (the latter event's id is smaller than that of the former) (requestId ${requestId})`
|
|
698
|
-
)
|
|
699
|
-
|
|
700
|
-
// writeToFile(
|
|
701
|
-
// "wserror.txt",
|
|
702
|
-
// `[realtime listener] server non-fatal error: events out of order (the latter event's id is smaller than that of the former) ${JSON.stringify(
|
|
703
|
-
// Object.assign(
|
|
704
|
-
// {},
|
|
705
|
-
// {
|
|
706
|
-
// requestId: msg.requestId,
|
|
707
|
-
// watchId: msg.watchId
|
|
708
|
-
// }
|
|
709
|
-
// )
|
|
710
|
-
// )} \n`
|
|
711
|
-
// )
|
|
712
|
-
}
|
|
713
|
-
continue
|
|
714
|
-
} else if (sessionInfo.currentEventId === change.id - 1) {
|
|
715
|
-
// correct sequence
|
|
716
|
-
// first handle dataType then queueType:
|
|
717
|
-
// 1. dataType: we ONLY populate change.doc if neccessary
|
|
718
|
-
// 2. queueType: we build the data snapshot
|
|
719
|
-
|
|
720
|
-
switch (change.dataType) {
|
|
721
|
-
case 'update': {
|
|
722
|
-
// only need to populate change.doc when it is not provided
|
|
723
|
-
if (!change.doc) {
|
|
724
|
-
switch (change.queueType) {
|
|
725
|
-
case 'update':
|
|
726
|
-
case 'dequeue': {
|
|
727
|
-
const localDoc = docs.find(doc => doc._id === change.docId)
|
|
728
|
-
if (localDoc) {
|
|
729
|
-
// a partial update
|
|
730
|
-
const doc = cloneDeep(localDoc)
|
|
731
|
-
|
|
732
|
-
if (change.updatedFields) {
|
|
733
|
-
for (const fieldPath in change.updatedFields) {
|
|
734
|
-
set(doc, fieldPath, change.updatedFields[fieldPath])
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
if (change.removedFields) {
|
|
739
|
-
for (const fieldPath of change.removedFields) {
|
|
740
|
-
unset(doc, fieldPath)
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
change.doc = doc
|
|
745
|
-
} else {
|
|
746
|
-
// TODO report
|
|
747
|
-
console.error(
|
|
748
|
-
'[realtime listener] internal non-fatal server error: unexpected update dataType event where no doc is associated.'
|
|
749
|
-
)
|
|
750
|
-
|
|
751
|
-
// writeToFile(
|
|
752
|
-
// "wserror.txt",
|
|
753
|
-
// `[realtime listener] internal non-fatal server error: unexpected update dataType event where no doc is associated. ${JSON.stringify(
|
|
754
|
-
// Object.assign(
|
|
755
|
-
// {},
|
|
756
|
-
// {
|
|
757
|
-
// requestId: msg.requestId,
|
|
758
|
-
// watchId: msg.watchId
|
|
759
|
-
// }
|
|
760
|
-
// )
|
|
761
|
-
// )} \n`
|
|
762
|
-
// )
|
|
763
|
-
}
|
|
764
|
-
break
|
|
765
|
-
}
|
|
766
|
-
case 'enqueue': {
|
|
767
|
-
// doc is provided by server, this should never occur
|
|
768
|
-
const err = new CloudSDKError({
|
|
769
|
-
errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR as string,
|
|
770
|
-
errMsg: `HandleServerEvents: full doc is not provided with dataType="update" and queueType="enqueue" (requestId ${msg.requestId})`
|
|
771
|
-
})
|
|
772
|
-
this.closeWithError(err)
|
|
773
|
-
throw err
|
|
774
|
-
}
|
|
775
|
-
default: {
|
|
776
|
-
break
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
break
|
|
781
|
-
}
|
|
782
|
-
case 'replace': {
|
|
783
|
-
// validation
|
|
784
|
-
if (!change.doc) {
|
|
785
|
-
// doc is provided by server, this should never occur
|
|
786
|
-
const err = new CloudSDKError({
|
|
787
|
-
errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR as string,
|
|
788
|
-
errMsg: `HandleServerEvents: full doc is not provided with dataType="replace" (requestId ${msg.requestId})`
|
|
789
|
-
})
|
|
790
|
-
this.closeWithError(err)
|
|
791
|
-
throw err
|
|
792
|
-
}
|
|
793
|
-
break
|
|
794
|
-
}
|
|
795
|
-
case 'remove': {
|
|
796
|
-
const doc = docs.find(doc => doc._id === change.docId)
|
|
797
|
-
if (doc) {
|
|
798
|
-
change.doc = doc
|
|
799
|
-
} else {
|
|
800
|
-
// TODO report
|
|
801
|
-
console.error(
|
|
802
|
-
'[realtime listener] internal non-fatal server error: unexpected remove event where no doc is associated.'
|
|
803
|
-
)
|
|
804
|
-
|
|
805
|
-
// writeToFile(
|
|
806
|
-
// "wserror.txt",
|
|
807
|
-
// `[realtime listener] internal non-fatal server error: unexpected remove event where no doc is associated. ${JSON.stringify(
|
|
808
|
-
// Object.assign(
|
|
809
|
-
// {},
|
|
810
|
-
// {
|
|
811
|
-
// requestId: msg.requestId,
|
|
812
|
-
// watchId: msg.watchId
|
|
813
|
-
// }
|
|
814
|
-
// )
|
|
815
|
-
// )} \n`
|
|
816
|
-
// )
|
|
817
|
-
}
|
|
818
|
-
break
|
|
819
|
-
}
|
|
820
|
-
case 'limit': {
|
|
821
|
-
if (!change.doc) {
|
|
822
|
-
switch(change.queueType) {
|
|
823
|
-
case 'dequeue': {
|
|
824
|
-
const doc = docs.find(doc => doc._id === change.docId)
|
|
825
|
-
if (doc) {
|
|
826
|
-
change.doc = doc
|
|
827
|
-
} else {
|
|
828
|
-
console.error(
|
|
829
|
-
'[realtime listener] internal non-fatal server error: unexpected limit dataType event where no doc is associated.'
|
|
830
|
-
)
|
|
831
|
-
}
|
|
832
|
-
break
|
|
833
|
-
}
|
|
834
|
-
case 'enqueue': {
|
|
835
|
-
// doc is provided by server, this should never occur
|
|
836
|
-
const err = new CloudSDKError({
|
|
837
|
-
errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR as string,
|
|
838
|
-
errMsg: `HandleServerEvents: full doc is not provided with dataType="limit" and queueType="enqueue" (requestId ${msg.requestId})`
|
|
839
|
-
})
|
|
840
|
-
this.closeWithError(err)
|
|
841
|
-
throw err
|
|
842
|
-
}
|
|
843
|
-
default: {
|
|
844
|
-
break
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
break
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
switch (change.queueType) {
|
|
853
|
-
case 'init': {
|
|
854
|
-
if (!initEncountered) {
|
|
855
|
-
initEncountered = true
|
|
856
|
-
docs = [change.doc]
|
|
857
|
-
} else {
|
|
858
|
-
docs.push(change.doc)
|
|
859
|
-
}
|
|
860
|
-
break
|
|
861
|
-
}
|
|
862
|
-
case 'enqueue': {
|
|
863
|
-
docs.push(change.doc)
|
|
864
|
-
break
|
|
865
|
-
}
|
|
866
|
-
case 'dequeue': {
|
|
867
|
-
const ind = docs.findIndex(doc => doc._id === change.docId)
|
|
868
|
-
if (ind > -1) {
|
|
869
|
-
docs.splice(ind, 1)
|
|
870
|
-
} else {
|
|
871
|
-
// TODO report
|
|
872
|
-
console.error(
|
|
873
|
-
'[realtime listener] internal non-fatal server error: unexpected dequeue event where no doc is associated.'
|
|
874
|
-
)
|
|
875
|
-
|
|
876
|
-
// writeToFile(
|
|
877
|
-
// "wserror.txt",
|
|
878
|
-
// `[realtime listener] internal non-fatal server error: unexpected dequeue event where no doc is associated. ${JSON.stringify(
|
|
879
|
-
// Object.assign(
|
|
880
|
-
// {},
|
|
881
|
-
// {
|
|
882
|
-
// requestId: msg.requestId,
|
|
883
|
-
// watchId: msg.watchId
|
|
884
|
-
// }
|
|
885
|
-
// )
|
|
886
|
-
// )} \n`
|
|
887
|
-
// )
|
|
888
|
-
}
|
|
889
|
-
break
|
|
890
|
-
}
|
|
891
|
-
case 'update': {
|
|
892
|
-
// writeToFile(
|
|
893
|
-
// "wserror.txt",
|
|
894
|
-
// `[realtime listener] docs ${JSON.stringify(
|
|
895
|
-
// docs
|
|
896
|
-
// )} change doc ${JSON.stringify(change)} \n`
|
|
897
|
-
// )
|
|
898
|
-
const ind = docs.findIndex(doc => doc._id === change.docId)
|
|
899
|
-
if (ind > -1) {
|
|
900
|
-
docs[ind] = change.doc
|
|
901
|
-
} else {
|
|
902
|
-
// TODO report
|
|
903
|
-
console.error(
|
|
904
|
-
'[realtime listener] internal non-fatal server error: unexpected queueType update event where no doc is associated.'
|
|
905
|
-
)
|
|
906
|
-
|
|
907
|
-
// writeToFile(
|
|
908
|
-
// "wserror.txt",
|
|
909
|
-
// `[realtime listener] internal non-fatal server error: unexpected queueType update event where no doc is associated. ${JSON.stringify(
|
|
910
|
-
// Object.assign(
|
|
911
|
-
// {},
|
|
912
|
-
// {
|
|
913
|
-
// requestId: msg.requestId,
|
|
914
|
-
// watchId: msg.watchId
|
|
915
|
-
// }
|
|
916
|
-
// )
|
|
917
|
-
// )} \n`
|
|
918
|
-
// )
|
|
919
|
-
}
|
|
920
|
-
break
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
if (
|
|
925
|
-
i === len - 1 ||
|
|
926
|
-
(allChangeEvents[i + 1] && allChangeEvents[i + 1].id !== change.id)
|
|
927
|
-
) {
|
|
928
|
-
// a shallow slice creates a shallow snapshot
|
|
929
|
-
const docsSnapshot = [...docs]
|
|
930
|
-
|
|
931
|
-
// we slice first cause' if there're allChangeEvents that are of the same id after this change, we don't want to involve it for it is unexpected invalid order
|
|
932
|
-
const docChanges = allChangeEvents
|
|
933
|
-
.slice(0, i + 1)
|
|
934
|
-
.filter(c => c.id === change.id)
|
|
935
|
-
|
|
936
|
-
// all changes of this event has been handle, we could dispatch the event now
|
|
937
|
-
this.sessionInfo.currentEventId = change.id
|
|
938
|
-
this.sessionInfo.currentDocs = docs
|
|
939
|
-
|
|
940
|
-
const snapshot = new Snapshot({
|
|
941
|
-
id: change.id,
|
|
942
|
-
docChanges,
|
|
943
|
-
docs: docsSnapshot,
|
|
944
|
-
msgType
|
|
945
|
-
})
|
|
946
|
-
|
|
947
|
-
// Reporter.surroundThirdByTryCatch(() =>
|
|
948
|
-
this.listener.onChange(snapshot)
|
|
949
|
-
// )()
|
|
950
|
-
}
|
|
951
|
-
} else {
|
|
952
|
-
// out-of-order event
|
|
953
|
-
// if (process.env.DEBUG) {
|
|
954
|
-
// TODO: report
|
|
955
|
-
console.warn(
|
|
956
|
-
`[realtime listener] event received is out of order, cur ${this.sessionInfo.currentEventId} but got ${change.id}`
|
|
957
|
-
)
|
|
958
|
-
// }
|
|
959
|
-
// rebuild watch
|
|
960
|
-
await this.rebuildWatch()
|
|
961
|
-
return
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
private _postHandleServerEventsValidityCheck(
|
|
967
|
-
msg: IResponseMessageInitEventMsg | IResponseMessageNextEventMsg
|
|
968
|
-
) {
|
|
969
|
-
if (!this.sessionInfo) {
|
|
970
|
-
console.error(
|
|
971
|
-
'[realtime listener] internal non-fatal error: sessionInfo lost after server event handling, this should never occur'
|
|
972
|
-
)
|
|
973
|
-
|
|
974
|
-
// writeToFile(
|
|
975
|
-
// "wserror.txt",
|
|
976
|
-
// `[realtime listener] internal non-fatal error: sessionInfo lost after server event handling, this should never occur ${JSON.stringify(
|
|
977
|
-
// Object.assign(
|
|
978
|
-
// {},
|
|
979
|
-
// {
|
|
980
|
-
// requestId: msg.requestId,
|
|
981
|
-
// watchId: msg.watchId
|
|
982
|
-
// }
|
|
983
|
-
// )
|
|
984
|
-
// )} \n`
|
|
985
|
-
// )
|
|
986
|
-
return
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
if (
|
|
990
|
-
this.sessionInfo.expectEventId &&
|
|
991
|
-
this.sessionInfo.currentEventId >= this.sessionInfo.expectEventId
|
|
992
|
-
) {
|
|
993
|
-
this.clearWaitExpectedEvent()
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
if (this.sessionInfo.currentEventId < msg.msgData.currEvent) {
|
|
997
|
-
console.warn(
|
|
998
|
-
'[realtime listener] internal non-fatal error: client eventId does not match with server event id after server event handling'
|
|
999
|
-
)
|
|
1000
|
-
return
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
private clearWaitExpectedEvent() {
|
|
1005
|
-
if (this._waitExpectedTimeoutId) {
|
|
1006
|
-
clearTimeout(this._waitExpectedTimeoutId)
|
|
1007
|
-
this._waitExpectedTimeoutId = undefined
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
onMessage(msg: IResponseMessage) {
|
|
1012
|
-
// watchStatus sanity check
|
|
1013
|
-
switch (this.watchStatus) {
|
|
1014
|
-
case WATCH_STATUS.PAUSED: {
|
|
1015
|
-
// ignore all but error message
|
|
1016
|
-
if (msg.msgType !== 'ERROR') {
|
|
1017
|
-
return
|
|
1018
|
-
}
|
|
1019
|
-
break
|
|
1020
|
-
}
|
|
1021
|
-
case WATCH_STATUS.LOGGINGIN:
|
|
1022
|
-
case WATCH_STATUS.INITING:
|
|
1023
|
-
case WATCH_STATUS.REBUILDING: {
|
|
1024
|
-
console.warn(
|
|
1025
|
-
`[realtime listener] internal non-fatal error: unexpected message received while ${this.watchStatus}`
|
|
1026
|
-
)
|
|
1027
|
-
return
|
|
1028
|
-
}
|
|
1029
|
-
case WATCH_STATUS.CLOSED: {
|
|
1030
|
-
console.warn(
|
|
1031
|
-
'[realtime listener] internal non-fatal error: unexpected message received when the watch has closed'
|
|
1032
|
-
)
|
|
1033
|
-
return
|
|
1034
|
-
}
|
|
1035
|
-
case WATCH_STATUS.ERRORED: {
|
|
1036
|
-
console.warn(
|
|
1037
|
-
'[realtime listener] internal non-fatal error: unexpected message received when the watch has ended with error'
|
|
1038
|
-
)
|
|
1039
|
-
return
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
if (!this.sessionInfo) {
|
|
1044
|
-
console.warn(
|
|
1045
|
-
'[realtime listener] internal non-fatal error: sessionInfo not found while message is received.'
|
|
1046
|
-
)
|
|
1047
|
-
return
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
this.scheduleSendACK()
|
|
1051
|
-
|
|
1052
|
-
switch (msg.msgType) {
|
|
1053
|
-
case 'NEXT_EVENT': {
|
|
1054
|
-
// if (process.env.DEBUG) {
|
|
1055
|
-
// @ts-ignore
|
|
1056
|
-
// if (wx._ignore) {
|
|
1057
|
-
console.warn(`nextevent ${msg.msgData.currEvent} ignored`, msg)
|
|
1058
|
-
// @ts-ignore
|
|
1059
|
-
// wx._ignore = false
|
|
1060
|
-
// return
|
|
1061
|
-
// }
|
|
1062
|
-
// }
|
|
1063
|
-
|
|
1064
|
-
this.handleServerEvents(msg)
|
|
1065
|
-
break
|
|
1066
|
-
}
|
|
1067
|
-
case 'CHECK_EVENT': {
|
|
1068
|
-
if (this.sessionInfo.currentEventId < msg.msgData.currEvent) {
|
|
1069
|
-
// client eventID < server eventID:
|
|
1070
|
-
// there might be one or more pending events not yet received but sent by the server
|
|
1071
|
-
this.sessionInfo.expectEventId = msg.msgData.currEvent
|
|
1072
|
-
this.clearWaitExpectedEvent()
|
|
1073
|
-
// @ts-ignore
|
|
1074
|
-
this._waitExpectedTimeoutId = setTimeout(() => {
|
|
1075
|
-
// must rebuild watch
|
|
1076
|
-
this.rebuildWatch()
|
|
1077
|
-
}, this.getWaitExpectedTimeoutLength())
|
|
1078
|
-
|
|
1079
|
-
// if (process.env.DEBUG) {
|
|
1080
|
-
console.log(
|
|
1081
|
-
`[realtime] waitExpectedTimeoutLength ${this.getWaitExpectedTimeoutLength()}`
|
|
1082
|
-
)
|
|
1083
|
-
// }
|
|
1084
|
-
}
|
|
1085
|
-
break
|
|
1086
|
-
}
|
|
1087
|
-
case 'ERROR': {
|
|
1088
|
-
// receive server error
|
|
1089
|
-
this.closeWithError(
|
|
1090
|
-
new CloudSDKError({
|
|
1091
|
-
errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_SERVER_ERROR_MSG as string,
|
|
1092
|
-
errMsg: `${msg.msgData.code} - ${msg.msgData.message}`
|
|
1093
|
-
})
|
|
1094
|
-
)
|
|
1095
|
-
break
|
|
1096
|
-
}
|
|
1097
|
-
default: {
|
|
1098
|
-
// if (process.env.DEBUG) {
|
|
1099
|
-
console.warn(
|
|
1100
|
-
`[realtime listener] virtual client receive unexpected msg ${msg.msgType}: `,
|
|
1101
|
-
msg
|
|
1102
|
-
)
|
|
1103
|
-
// }
|
|
1104
|
-
break
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
closeWithError(error: any) {
|
|
1110
|
-
this.watchStatus = WATCH_STATUS.ERRORED
|
|
1111
|
-
this.clearACKSchedule()
|
|
1112
|
-
this.listener.onError(error)
|
|
1113
|
-
// Reporter.surroundThirdByTryCatch(() => this.listener.onError(error))
|
|
1114
|
-
this.onWatchClose(
|
|
1115
|
-
this,
|
|
1116
|
-
(this.sessionInfo && this.sessionInfo.queryID) || ''
|
|
1117
|
-
)
|
|
1118
|
-
|
|
1119
|
-
// if (process.env.DEBUG) {
|
|
1120
|
-
console.log(
|
|
1121
|
-
`[realtime] client closed (${this.collectionName} ${this.query}) (watchId ${this.watchId})`
|
|
1122
|
-
)
|
|
1123
|
-
// }
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
pause() {
|
|
1127
|
-
this.watchStatus = WATCH_STATUS.PAUSED
|
|
1128
|
-
// if (process.env.DEBUG) {
|
|
1129
|
-
console.log(
|
|
1130
|
-
`[realtime] client paused (${this.collectionName} ${this.query}) (watchId ${this.watchId})`
|
|
1131
|
-
)
|
|
1132
|
-
// }
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
// resume() {
|
|
1136
|
-
// return this.sessionInfo ? this.rebuildWatch() : this.initWatch()
|
|
1137
|
-
// }
|
|
1138
|
-
|
|
1139
|
-
async resume(): Promise<void> {
|
|
1140
|
-
this.watchStatus = WATCH_STATUS.RESUMING
|
|
1141
|
-
|
|
1142
|
-
// if (process.env.DEBUG) {
|
|
1143
|
-
console.log(
|
|
1144
|
-
`[realtime] client resuming with ${
|
|
1145
|
-
this.sessionInfo ? 'REBUILD_WATCH' : 'INIT_WATCH'
|
|
1146
|
-
} (${this.collectionName} ${this.query}) (${this.watchId})`
|
|
1147
|
-
)
|
|
1148
|
-
// }
|
|
1149
|
-
|
|
1150
|
-
try {
|
|
1151
|
-
await (this.sessionInfo ? this.rebuildWatch() : this.initWatch())
|
|
1152
|
-
|
|
1153
|
-
// if (process.env.DEBUG) {
|
|
1154
|
-
console.log(
|
|
1155
|
-
`[realtime] client successfully resumed (${this.collectionName} ${this.query}) (${this.watchId})`
|
|
1156
|
-
)
|
|
1157
|
-
// }
|
|
1158
|
-
} catch (e) {
|
|
1159
|
-
// if (process.env.DEBUG) {
|
|
1160
|
-
console.error(
|
|
1161
|
-
`[realtime] client resume failed (${this.collectionName} ${this.query}) (${this.watchId})`,
|
|
1162
|
-
e
|
|
1163
|
-
)
|
|
1164
|
-
// }
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
function getPublicEvent(event: IDBEvent): ISingleDBEvent {
|
|
1170
|
-
const e: ISingleDBEvent = {
|
|
1171
|
-
id: event.ID,
|
|
1172
|
-
dataType: event.DataType,
|
|
1173
|
-
queueType: event.QueueType,
|
|
1174
|
-
docId: event.DocID,
|
|
1175
|
-
doc: event.Doc && event.Doc !== '{}' ? JSON.parse(event.Doc) : undefined
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
if (event.DataType === 'update') {
|
|
1179
|
-
// @ts-ignore
|
|
1180
|
-
if (event.UpdatedFields) {
|
|
1181
|
-
e.updatedFields = JSON.parse(event.UpdatedFields)
|
|
1182
|
-
}
|
|
1183
|
-
// TODO: wait for tcb to change removedFields to RemovedFields
|
|
1184
|
-
// @ts-ignore
|
|
1185
|
-
if (event.removedFields || event.RemovedFields) {
|
|
1186
|
-
// @ts-ignore
|
|
1187
|
-
// e.removedFields = event.removedFields
|
|
1188
|
-
// ? JSON.parse(event.removedFields)
|
|
1189
|
-
// : JSON.parse(event.RemovedFields)
|
|
1190
|
-
e.removedFields = JSON.parse(event.removedFields)
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
return e
|
|
1195
|
-
}
|