spacetimedb 2.4.1 → 2.6.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/LICENSE.txt +759 -759
- package/README.md +211 -120
- package/dist/angular/index.cjs.map +1 -1
- package/dist/angular/index.mjs.map +1 -1
- package/dist/browser/angular/index.mjs.map +1 -1
- package/dist/browser/react/index.mjs +129 -57
- package/dist/browser/react/index.mjs.map +1 -1
- package/dist/browser/solid/index.mjs +1933 -0
- package/dist/browser/solid/index.mjs.map +1 -0
- package/dist/browser/svelte/index.mjs.map +1 -1
- package/dist/browser/vue/index.mjs.map +1 -1
- package/dist/index.browser.mjs +10 -2
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.cjs +10 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +10 -2
- package/dist/index.mjs.map +1 -1
- package/dist/min/index.browser.mjs +1 -1
- package/dist/min/index.browser.mjs.map +1 -1
- package/dist/min/react/index.mjs +1 -1
- package/dist/min/react/index.mjs.map +1 -1
- package/dist/min/sdk/index.browser.mjs +1 -1
- package/dist/min/sdk/index.browser.mjs.map +1 -1
- package/dist/react/index.cjs +129 -57
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.mjs +129 -57
- package/dist/react/index.mjs.map +1 -1
- package/dist/react/useTable.d.ts.map +1 -1
- package/dist/sdk/connection_manager.d.ts +8 -0
- package/dist/sdk/connection_manager.d.ts.map +1 -1
- package/dist/sdk/db_connection_impl.d.ts +7 -0
- package/dist/sdk/db_connection_impl.d.ts.map +1 -1
- package/dist/sdk/index.browser.mjs +10 -2
- package/dist/sdk/index.browser.mjs.map +1 -1
- package/dist/sdk/index.cjs +10 -2
- package/dist/sdk/index.cjs.map +1 -1
- package/dist/sdk/index.mjs +10 -2
- package/dist/sdk/index.mjs.map +1 -1
- package/dist/sdk/websocket_test_adapter.d.ts +2 -1
- package/dist/sdk/websocket_test_adapter.d.ts.map +1 -1
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/runtime.d.ts.map +1 -1
- package/dist/solid/SpacetimeDBProvider.d.ts +7 -0
- package/dist/solid/SpacetimeDBProvider.d.ts.map +1 -0
- package/dist/solid/connection_state.d.ts +6 -0
- package/dist/solid/connection_state.d.ts.map +1 -0
- package/dist/solid/index.cjs +1939 -0
- package/dist/solid/index.cjs.map +1 -0
- package/dist/solid/index.d.ts +6 -0
- package/dist/solid/index.d.ts.map +1 -0
- package/dist/solid/index.mjs +1933 -0
- package/dist/solid/index.mjs.map +1 -0
- package/dist/solid/useProcedure.d.ts +4 -0
- package/dist/solid/useProcedure.d.ts.map +1 -0
- package/dist/solid/useReducer.d.ts +4 -0
- package/dist/solid/useReducer.d.ts.map +1 -0
- package/dist/solid/useSpacetimeDB.d.ts +4 -0
- package/dist/solid/useSpacetimeDB.d.ts.map +1 -0
- package/dist/solid/useTable.d.ts +32 -0
- package/dist/solid/useTable.d.ts.map +1 -0
- package/dist/svelte/index.cjs.map +1 -1
- package/dist/svelte/index.mjs.map +1 -1
- package/dist/tanstack/index.cjs +120 -50
- package/dist/tanstack/index.cjs.map +1 -1
- package/dist/tanstack/index.mjs +120 -50
- package/dist/tanstack/index.mjs.map +1 -1
- package/dist/vue/index.cjs.map +1 -1
- package/dist/vue/index.mjs.map +1 -1
- package/package.json +13 -3
- package/src/angular/connection_state.ts +19 -19
- package/src/angular/index.ts +3 -3
- package/src/angular/injectors/index.ts +4 -4
- package/src/angular/injectors/inject-reducer.ts +62 -62
- package/src/angular/injectors/inject-spacetimedb-connected.ts +13 -13
- package/src/angular/injectors/inject-spacetimedb.ts +10 -10
- package/src/angular/injectors/inject-table.ts +234 -234
- package/src/angular/providers/index.ts +1 -1
- package/src/angular/providers/provide-spacetimedb.ts +96 -96
- package/src/index.ts +16 -16
- package/src/lib/algebraic_type.ts +819 -819
- package/src/lib/algebraic_type_variants.ts +26 -26
- package/src/lib/algebraic_value.ts +10 -10
- package/src/lib/autogen/types.ts +746 -746
- package/src/lib/binary_reader.ts +188 -188
- package/src/lib/binary_writer.ts +213 -213
- package/src/lib/connection_id.ts +102 -102
- package/src/lib/constraints.ts +48 -48
- package/src/lib/errors.ts +26 -26
- package/src/lib/filter.ts +195 -195
- package/src/lib/identity.ts +83 -83
- package/src/lib/indexes.ts +251 -251
- package/src/lib/option.ts +34 -34
- package/src/lib/query.ts +1019 -1019
- package/src/lib/reducer_schema.ts +38 -38
- package/src/lib/reducers.ts +116 -116
- package/src/lib/result.ts +36 -36
- package/src/lib/schedule_at.ts +86 -86
- package/src/lib/schema.ts +420 -420
- package/src/lib/table.ts +548 -548
- package/src/lib/table_schema.ts +64 -64
- package/src/lib/time_duration.ts +77 -77
- package/src/lib/timestamp.ts +148 -148
- package/src/lib/type_builders.test-d.ts +128 -128
- package/src/lib/type_builders.ts +4014 -4014
- package/src/lib/type_util.ts +124 -124
- package/src/lib/util.ts +196 -196
- package/src/lib/uuid.ts +337 -337
- package/src/react/SpacetimeDBProvider.ts +84 -84
- package/src/react/connection_state.ts +6 -6
- package/src/react/index.ts +5 -5
- package/src/react/useProcedure.ts +60 -60
- package/src/react/useReducer.ts +53 -53
- package/src/react/useSpacetimeDB.ts +18 -18
- package/src/react/useTable.ts +256 -251
- package/src/sdk/client_api/index.ts +114 -114
- package/src/sdk/client_api/types/procedures.ts +8 -8
- package/src/sdk/client_api/types/reducers.ts +8 -8
- package/src/sdk/client_api/types.ts +288 -288
- package/src/sdk/client_cache.ts +129 -129
- package/src/sdk/client_table.ts +179 -179
- package/src/sdk/connection_manager.ts +352 -237
- package/src/sdk/db_connection_builder.ts +290 -290
- package/src/sdk/db_connection_impl.ts +1356 -1347
- package/src/sdk/db_context.ts +28 -28
- package/src/sdk/db_view.ts +12 -12
- package/src/sdk/decompress.ts +51 -51
- package/src/sdk/event.ts +18 -18
- package/src/sdk/event_context.ts +51 -51
- package/src/sdk/event_emitter.ts +32 -32
- package/src/sdk/index.ts +14 -14
- package/src/sdk/internal.ts +2 -2
- package/src/sdk/json_api.ts +46 -46
- package/src/sdk/logger.ts +134 -134
- package/src/sdk/message_types.ts +46 -46
- package/src/sdk/procedures.ts +83 -83
- package/src/sdk/reducer_event.ts +20 -20
- package/src/sdk/reducer_handle.ts +12 -12
- package/src/sdk/reducers.ts +159 -159
- package/src/sdk/schema.ts +45 -45
- package/src/sdk/spacetime_module.ts +28 -28
- package/src/sdk/subscription_builder_impl.ts +275 -275
- package/src/sdk/table_cache.ts +581 -581
- package/src/sdk/type_utils.ts +19 -19
- package/src/sdk/version.ts +133 -133
- package/src/sdk/websocket_decompress_adapter.ts +63 -63
- package/src/sdk/websocket_protocols.ts +25 -25
- package/src/sdk/websocket_test_adapter.ts +107 -100
- package/src/sdk/websocket_v3_frames.ts +126 -126
- package/src/sdk/ws.ts +105 -105
- package/src/server/console.ts +81 -81
- package/src/server/db_view.ts +21 -21
- package/src/server/errors.ts +138 -138
- package/src/server/http.test-d.ts +80 -80
- package/src/server/http.ts +14 -14
- package/src/server/http_handlers.ts +413 -413
- package/src/server/http_internal.ts +79 -79
- package/src/server/http_shared.ts +186 -186
- package/src/server/index.ts +37 -37
- package/src/server/polyfills.ts +4 -4
- package/src/server/procedures.ts +239 -239
- package/src/server/query.ts +1 -1
- package/src/server/range.ts +53 -53
- package/src/server/reducers.ts +113 -113
- package/src/server/rng.ts +113 -113
- package/src/server/runtime.ts +1102 -1102
- package/src/server/schema.test-d.ts +99 -99
- package/src/server/schema.ts +663 -663
- package/src/server/sys.d.ts +125 -125
- package/src/server/view.test-d.ts +194 -194
- package/src/server/views.ts +340 -340
- package/src/solid/SpacetimeDBProvider.ts +97 -0
- package/src/solid/connection_state.ts +6 -0
- package/src/solid/index.ts +5 -0
- package/src/solid/useProcedure.ts +57 -0
- package/src/solid/useReducer.ts +50 -0
- package/src/solid/useSpacetimeDB.ts +18 -0
- package/src/solid/useTable.ts +203 -0
- package/src/svelte/SpacetimeDBProvider.ts +101 -101
- package/src/svelte/connection_state.ts +16 -16
- package/src/svelte/index.ts +4 -4
- package/src/svelte/useReducer.ts +61 -61
- package/src/svelte/useSpacetimeDB.ts +22 -22
- package/src/svelte/useTable.ts +218 -218
- package/src/tanstack/SpacetimeDBQueryClient.ts +330 -330
- package/src/tanstack/hooks.ts +83 -83
- package/src/tanstack/index.ts +16 -16
- package/src/util-stub.ts +1 -1
- package/src/vue/SpacetimeDBProvider.ts +157 -157
- package/src/vue/connection_state.ts +19 -19
- package/src/vue/index.ts +5 -5
- package/src/vue/useProcedure.ts +62 -62
- package/src/vue/useReducer.ts +55 -55
- package/src/vue/useSpacetimeDB.ts +18 -18
- package/src/vue/useTable.ts +229 -229
|
@@ -1,1347 +1,1356 @@
|
|
|
1
|
-
import { ConnectionId, ProductBuilder, ProductType } from '../';
|
|
2
|
-
import { AlgebraicType, type ComparablePrimitive } from '../';
|
|
3
|
-
import BinaryReader from '../lib/binary_reader.ts';
|
|
4
|
-
import BinaryWriter from '../lib/binary_writer.ts';
|
|
5
|
-
import {
|
|
6
|
-
BsatnRowList,
|
|
7
|
-
ClientMessage,
|
|
8
|
-
QueryRows,
|
|
9
|
-
QuerySetUpdate,
|
|
10
|
-
ServerMessage,
|
|
11
|
-
TableUpdateRows,
|
|
12
|
-
UnsubscribeFlags,
|
|
13
|
-
} from './client_api/types';
|
|
14
|
-
import { ClientCache } from './client_cache.ts';
|
|
15
|
-
import { DbConnectionBuilder } from './db_connection_builder.ts';
|
|
16
|
-
import { INTERNAL_REMOTE_MODULE } from './internal.ts';
|
|
17
|
-
import { type DbContext } from './db_context.ts';
|
|
18
|
-
import type { Event } from './event.ts';
|
|
19
|
-
import {
|
|
20
|
-
type ErrorContextInterface,
|
|
21
|
-
type EventContextInterface,
|
|
22
|
-
type ReducerEventContextInterface,
|
|
23
|
-
type SubscriptionEventContextInterface,
|
|
24
|
-
} from './event_context.ts';
|
|
25
|
-
import { EventEmitter } from './event_emitter.ts';
|
|
26
|
-
import type { Deserializer, Identity, InferTypeOfRow, Serializer } from '../';
|
|
27
|
-
import type {
|
|
28
|
-
ProcedureResultMessage,
|
|
29
|
-
ReducerResultMessage,
|
|
30
|
-
} from './message_types.ts';
|
|
31
|
-
import type { ReducerEvent } from './reducer_event.ts';
|
|
32
|
-
import { type UntypedRemoteModule } from './spacetime_module.ts';
|
|
33
|
-
import { makeQueryBuilder } from '../lib/query';
|
|
34
|
-
import {
|
|
35
|
-
type TableCache,
|
|
36
|
-
type Operation,
|
|
37
|
-
type PendingCallback,
|
|
38
|
-
type TableUpdate as CacheTableUpdate,
|
|
39
|
-
} from './table_cache.ts';
|
|
40
|
-
import {
|
|
41
|
-
SubscriptionBuilderImpl,
|
|
42
|
-
SubscriptionHandleImpl,
|
|
43
|
-
SubscriptionManager,
|
|
44
|
-
type SubscribeEvent,
|
|
45
|
-
} from './subscription_builder_impl.ts';
|
|
46
|
-
import { stdbLogger, stringify } from './logger.ts';
|
|
47
|
-
import { fromByteArray } from 'base64-js';
|
|
48
|
-
import type {
|
|
49
|
-
ReducerEventInfo,
|
|
50
|
-
ReducersView,
|
|
51
|
-
SubscriptionEventCallback,
|
|
52
|
-
} from './reducers.ts';
|
|
53
|
-
import type { ClientDbView } from './db_view.ts';
|
|
54
|
-
import type { RowType, UntypedTableDef } from '../lib/table.ts';
|
|
55
|
-
import type { ProceduresView } from './procedures.ts';
|
|
56
|
-
import type { Values } from '../lib/type_util.ts';
|
|
57
|
-
import type { TransactionUpdate } from './client_api/types.ts';
|
|
58
|
-
import { InternalError, SenderError } from '../lib/errors.ts';
|
|
59
|
-
import type { WebSocketAdapter, WebSocketFactory } from './ws.ts';
|
|
60
|
-
import {
|
|
61
|
-
normalizeWsProtocol,
|
|
62
|
-
PREFERRED_WS_PROTOCOLS,
|
|
63
|
-
V2_WS_PROTOCOL,
|
|
64
|
-
V3_WS_PROTOCOL,
|
|
65
|
-
type NegotiatedWsProtocol,
|
|
66
|
-
} from './websocket_protocols';
|
|
67
|
-
import {
|
|
68
|
-
countClientMessagesForV3Frame,
|
|
69
|
-
encodeClientMessagesV3,
|
|
70
|
-
forEachServerMessageV3,
|
|
71
|
-
} from './websocket_v3_frames.ts';
|
|
72
|
-
|
|
73
|
-
export {
|
|
74
|
-
DbConnectionBuilder,
|
|
75
|
-
SubscriptionBuilderImpl,
|
|
76
|
-
SubscriptionHandleImpl,
|
|
77
|
-
type TableCache,
|
|
78
|
-
type Event,
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
export type RemoteModuleOf<C> =
|
|
82
|
-
C extends DbConnectionImpl<infer RM> ? RM : never;
|
|
83
|
-
|
|
84
|
-
export type {
|
|
85
|
-
DbContext,
|
|
86
|
-
EventContextInterface,
|
|
87
|
-
ReducerEventContextInterface,
|
|
88
|
-
SubscriptionEventContextInterface,
|
|
89
|
-
ErrorContextInterface,
|
|
90
|
-
ReducerEvent,
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
export type ConnectionEvent = 'connect' | 'disconnect' | 'connectError';
|
|
94
|
-
|
|
95
|
-
export type DbConnectionConfig<RemoteModule extends UntypedRemoteModule> = {
|
|
96
|
-
uri: URL;
|
|
97
|
-
nameOrAddress: string;
|
|
98
|
-
identity?: Identity;
|
|
99
|
-
token?: string;
|
|
100
|
-
emitter: EventEmitter<ConnectionEvent>;
|
|
101
|
-
createWSFn: WebSocketFactory;
|
|
102
|
-
compression: 'gzip' | 'brotli' | 'none';
|
|
103
|
-
lightMode: boolean;
|
|
104
|
-
confirmedReads?: boolean;
|
|
105
|
-
remoteModule: RemoteModule;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
type ProcedureCallback = (result: ProcedureResultMessage['result']) => void;
|
|
109
|
-
|
|
110
|
-
const TEXT_ENCODER = new TextEncoder();
|
|
111
|
-
|
|
112
|
-
function getClientMessageVariantTag(name: string): number {
|
|
113
|
-
if (ClientMessage.algebraicType.tag !== 'Sum') {
|
|
114
|
-
throw new TypeError('ClientMessage must be a sum type');
|
|
115
|
-
}
|
|
116
|
-
const tag = ClientMessage.algebraicType.value.variants.findIndex(
|
|
117
|
-
variant => variant.name === name
|
|
118
|
-
);
|
|
119
|
-
if (tag === -1) {
|
|
120
|
-
throw new RangeError(`Unknown ClientMessage variant: ${name}`);
|
|
121
|
-
}
|
|
122
|
-
return tag;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const CLIENT_MESSAGE_CALL_REDUCER_TAG =
|
|
126
|
-
getClientMessageVariantTag('CallReducer');
|
|
127
|
-
const CLIENT_MESSAGE_CALL_PROCEDURE_TAG =
|
|
128
|
-
getClientMessageVariantTag('CallProcedure');
|
|
129
|
-
// Keep individual v3 frames bounded so one burst does not monopolize the send
|
|
130
|
-
// path or create very large websocket writes.
|
|
131
|
-
const MAX_V3_OUTBOUND_FRAME_BYTES = 256 * 1024;
|
|
132
|
-
|
|
133
|
-
export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
|
|
134
|
-
implements DbContext<RemoteModule>
|
|
135
|
-
{
|
|
136
|
-
/**
|
|
137
|
-
* Whether or not the connection is active.
|
|
138
|
-
*/
|
|
139
|
-
isActive = false;
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
*
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
#
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
#
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
>
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
#
|
|
198
|
-
#
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
#
|
|
207
|
-
string,
|
|
208
|
-
{
|
|
209
|
-
>;
|
|
210
|
-
#
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
#
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
this
|
|
258
|
-
this
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
this.#
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
this.ws.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
#
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
);
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
);
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
tableName
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
}
|
|
617
|
-
this.#
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
#
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
this.#
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
#
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
this.#
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
);
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
this.#
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
);
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
this.#
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
);
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
this.#
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
#
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
()
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
this.#
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
this.#
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
const
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
const
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
this
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
);
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
this.#inboundQueue
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
}
|
|
1116
|
-
this.#
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
});
|
|
1249
|
-
this.#
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
});
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1
|
+
import { ConnectionId, ProductBuilder, ProductType } from '../';
|
|
2
|
+
import { AlgebraicType, type ComparablePrimitive } from '../';
|
|
3
|
+
import BinaryReader from '../lib/binary_reader.ts';
|
|
4
|
+
import BinaryWriter from '../lib/binary_writer.ts';
|
|
5
|
+
import {
|
|
6
|
+
BsatnRowList,
|
|
7
|
+
ClientMessage,
|
|
8
|
+
QueryRows,
|
|
9
|
+
QuerySetUpdate,
|
|
10
|
+
ServerMessage,
|
|
11
|
+
TableUpdateRows,
|
|
12
|
+
UnsubscribeFlags,
|
|
13
|
+
} from './client_api/types';
|
|
14
|
+
import { ClientCache } from './client_cache.ts';
|
|
15
|
+
import { DbConnectionBuilder } from './db_connection_builder.ts';
|
|
16
|
+
import { INTERNAL_REMOTE_MODULE } from './internal.ts';
|
|
17
|
+
import { type DbContext } from './db_context.ts';
|
|
18
|
+
import type { Event } from './event.ts';
|
|
19
|
+
import {
|
|
20
|
+
type ErrorContextInterface,
|
|
21
|
+
type EventContextInterface,
|
|
22
|
+
type ReducerEventContextInterface,
|
|
23
|
+
type SubscriptionEventContextInterface,
|
|
24
|
+
} from './event_context.ts';
|
|
25
|
+
import { EventEmitter } from './event_emitter.ts';
|
|
26
|
+
import type { Deserializer, Identity, InferTypeOfRow, Serializer } from '../';
|
|
27
|
+
import type {
|
|
28
|
+
ProcedureResultMessage,
|
|
29
|
+
ReducerResultMessage,
|
|
30
|
+
} from './message_types.ts';
|
|
31
|
+
import type { ReducerEvent } from './reducer_event.ts';
|
|
32
|
+
import { type UntypedRemoteModule } from './spacetime_module.ts';
|
|
33
|
+
import { makeQueryBuilder } from '../lib/query';
|
|
34
|
+
import {
|
|
35
|
+
type TableCache,
|
|
36
|
+
type Operation,
|
|
37
|
+
type PendingCallback,
|
|
38
|
+
type TableUpdate as CacheTableUpdate,
|
|
39
|
+
} from './table_cache.ts';
|
|
40
|
+
import {
|
|
41
|
+
SubscriptionBuilderImpl,
|
|
42
|
+
SubscriptionHandleImpl,
|
|
43
|
+
SubscriptionManager,
|
|
44
|
+
type SubscribeEvent,
|
|
45
|
+
} from './subscription_builder_impl.ts';
|
|
46
|
+
import { stdbLogger, stringify } from './logger.ts';
|
|
47
|
+
import { fromByteArray } from 'base64-js';
|
|
48
|
+
import type {
|
|
49
|
+
ReducerEventInfo,
|
|
50
|
+
ReducersView,
|
|
51
|
+
SubscriptionEventCallback,
|
|
52
|
+
} from './reducers.ts';
|
|
53
|
+
import type { ClientDbView } from './db_view.ts';
|
|
54
|
+
import type { RowType, UntypedTableDef } from '../lib/table.ts';
|
|
55
|
+
import type { ProceduresView } from './procedures.ts';
|
|
56
|
+
import type { Values } from '../lib/type_util.ts';
|
|
57
|
+
import type { TransactionUpdate } from './client_api/types.ts';
|
|
58
|
+
import { InternalError, SenderError } from '../lib/errors.ts';
|
|
59
|
+
import type { WebSocketAdapter, WebSocketFactory } from './ws.ts';
|
|
60
|
+
import {
|
|
61
|
+
normalizeWsProtocol,
|
|
62
|
+
PREFERRED_WS_PROTOCOLS,
|
|
63
|
+
V2_WS_PROTOCOL,
|
|
64
|
+
V3_WS_PROTOCOL,
|
|
65
|
+
type NegotiatedWsProtocol,
|
|
66
|
+
} from './websocket_protocols';
|
|
67
|
+
import {
|
|
68
|
+
countClientMessagesForV3Frame,
|
|
69
|
+
encodeClientMessagesV3,
|
|
70
|
+
forEachServerMessageV3,
|
|
71
|
+
} from './websocket_v3_frames.ts';
|
|
72
|
+
|
|
73
|
+
export {
|
|
74
|
+
DbConnectionBuilder,
|
|
75
|
+
SubscriptionBuilderImpl,
|
|
76
|
+
SubscriptionHandleImpl,
|
|
77
|
+
type TableCache,
|
|
78
|
+
type Event,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type RemoteModuleOf<C> =
|
|
82
|
+
C extends DbConnectionImpl<infer RM> ? RM : never;
|
|
83
|
+
|
|
84
|
+
export type {
|
|
85
|
+
DbContext,
|
|
86
|
+
EventContextInterface,
|
|
87
|
+
ReducerEventContextInterface,
|
|
88
|
+
SubscriptionEventContextInterface,
|
|
89
|
+
ErrorContextInterface,
|
|
90
|
+
ReducerEvent,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export type ConnectionEvent = 'connect' | 'disconnect' | 'connectError';
|
|
94
|
+
|
|
95
|
+
export type DbConnectionConfig<RemoteModule extends UntypedRemoteModule> = {
|
|
96
|
+
uri: URL;
|
|
97
|
+
nameOrAddress: string;
|
|
98
|
+
identity?: Identity;
|
|
99
|
+
token?: string;
|
|
100
|
+
emitter: EventEmitter<ConnectionEvent>;
|
|
101
|
+
createWSFn: WebSocketFactory;
|
|
102
|
+
compression: 'gzip' | 'brotli' | 'none';
|
|
103
|
+
lightMode: boolean;
|
|
104
|
+
confirmedReads?: boolean;
|
|
105
|
+
remoteModule: RemoteModule;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
type ProcedureCallback = (result: ProcedureResultMessage['result']) => void;
|
|
109
|
+
|
|
110
|
+
const TEXT_ENCODER = new TextEncoder();
|
|
111
|
+
|
|
112
|
+
function getClientMessageVariantTag(name: string): number {
|
|
113
|
+
if (ClientMessage.algebraicType.tag !== 'Sum') {
|
|
114
|
+
throw new TypeError('ClientMessage must be a sum type');
|
|
115
|
+
}
|
|
116
|
+
const tag = ClientMessage.algebraicType.value.variants.findIndex(
|
|
117
|
+
variant => variant.name === name
|
|
118
|
+
);
|
|
119
|
+
if (tag === -1) {
|
|
120
|
+
throw new RangeError(`Unknown ClientMessage variant: ${name}`);
|
|
121
|
+
}
|
|
122
|
+
return tag;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const CLIENT_MESSAGE_CALL_REDUCER_TAG =
|
|
126
|
+
getClientMessageVariantTag('CallReducer');
|
|
127
|
+
const CLIENT_MESSAGE_CALL_PROCEDURE_TAG =
|
|
128
|
+
getClientMessageVariantTag('CallProcedure');
|
|
129
|
+
// Keep individual v3 frames bounded so one burst does not monopolize the send
|
|
130
|
+
// path or create very large websocket writes.
|
|
131
|
+
const MAX_V3_OUTBOUND_FRAME_BYTES = 256 * 1024;
|
|
132
|
+
|
|
133
|
+
export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
|
|
134
|
+
implements DbContext<RemoteModule>
|
|
135
|
+
{
|
|
136
|
+
/**
|
|
137
|
+
* Whether or not the connection is active.
|
|
138
|
+
*/
|
|
139
|
+
isActive = false;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Whether `disconnect()` has been called on this connection.
|
|
143
|
+
* Once requested, the connection will not be reused: managed environments
|
|
144
|
+
* (such as the React `SpacetimeDBProvider`) use this to avoid reconnecting
|
|
145
|
+
* after an intentional disconnect.
|
|
146
|
+
*/
|
|
147
|
+
isDisconnectRequested = false;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* This connection's public identity.
|
|
151
|
+
*/
|
|
152
|
+
identity?: Identity = undefined;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* This connection's private authentication token.
|
|
156
|
+
*/
|
|
157
|
+
token?: string = undefined;
|
|
158
|
+
|
|
159
|
+
/** @internal */
|
|
160
|
+
[INTERNAL_REMOTE_MODULE](): RemoteModule {
|
|
161
|
+
return this.#remoteModule;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The accessor field to access the tables in the database and associated
|
|
166
|
+
* callback functions.
|
|
167
|
+
*/
|
|
168
|
+
db: ClientDbView<RemoteModule>;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The accessor field to access the reducers in the database.
|
|
172
|
+
*/
|
|
173
|
+
reducers: ReducersView<RemoteModule>;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The accessor field to access the procedures in the database.
|
|
177
|
+
*/
|
|
178
|
+
procedures: ProceduresView<RemoteModule>;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The `ConnectionId` of the connection to to the database.
|
|
182
|
+
*/
|
|
183
|
+
connectionId: ConnectionId = ConnectionId.random();
|
|
184
|
+
#connectionIdHex = this.connectionId.toHexString();
|
|
185
|
+
|
|
186
|
+
// These fields are meant to be strictly private.
|
|
187
|
+
#queryId = 0;
|
|
188
|
+
#requestId = 0;
|
|
189
|
+
#eventId = 0;
|
|
190
|
+
#emitter: EventEmitter<ConnectionEvent>;
|
|
191
|
+
#inboundQueue: Uint8Array[] = [];
|
|
192
|
+
#inboundQueueOffset = 0;
|
|
193
|
+
#isDrainingInboundQueue = false;
|
|
194
|
+
#outboundQueue: Uint8Array<ArrayBuffer>[] = [];
|
|
195
|
+
#isOutboundFlushScheduled = false;
|
|
196
|
+
#negotiatedWsProtocol: NegotiatedWsProtocol = V2_WS_PROTOCOL;
|
|
197
|
+
#subscriptionManager = new SubscriptionManager<RemoteModule>();
|
|
198
|
+
#remoteModule: RemoteModule;
|
|
199
|
+
#reducerCallbacks = new Map<
|
|
200
|
+
number,
|
|
201
|
+
(result: ReducerResultMessage['result']) => void
|
|
202
|
+
>();
|
|
203
|
+
#reducerCallInfo = new Map<number, { name: string; args: object }>();
|
|
204
|
+
#procedureCallbacks = new Map<number, ProcedureCallback>();
|
|
205
|
+
#rowDeserializers: Record<string, Deserializer<any>>;
|
|
206
|
+
#rowIdMetadata: Record<
|
|
207
|
+
string,
|
|
208
|
+
{ primaryKeyColName?: string; primaryKeyColType?: AlgebraicType }
|
|
209
|
+
>;
|
|
210
|
+
#reducerArgsSerializers: Record<
|
|
211
|
+
string,
|
|
212
|
+
{ serialize: Serializer<any>; deserialize: Deserializer<any> }
|
|
213
|
+
>;
|
|
214
|
+
#procedureSerializers: Record<
|
|
215
|
+
string,
|
|
216
|
+
{ serializeArgs: Serializer<any>; deserializeReturn: Deserializer<any> }
|
|
217
|
+
>;
|
|
218
|
+
#reducerNameBytes: Record<string, Uint8Array>;
|
|
219
|
+
#procedureNameBytes: Record<string, Uint8Array>;
|
|
220
|
+
#sourceNameToTableDef: Record<string, Values<RemoteModule['tables']>>;
|
|
221
|
+
#messageReader = new BinaryReader(new Uint8Array());
|
|
222
|
+
#rowListReader = new BinaryReader(new Uint8Array());
|
|
223
|
+
#clientFrameEncoder = new BinaryWriter(1024);
|
|
224
|
+
#boundSubscriptionBuilder!: () => SubscriptionBuilderImpl<RemoteModule>;
|
|
225
|
+
#boundDisconnect!: () => void;
|
|
226
|
+
|
|
227
|
+
// These fields are not part of the public API, but in a pinch you
|
|
228
|
+
// could use JavaScript to access them by bypassing TypeScript's
|
|
229
|
+
// private fields.
|
|
230
|
+
// We use them in testing.
|
|
231
|
+
private clientCache: ClientCache<RemoteModule>;
|
|
232
|
+
private ws?: WebSocketAdapter;
|
|
233
|
+
private wsPromise: Promise<WebSocketAdapter | undefined>;
|
|
234
|
+
|
|
235
|
+
constructor({
|
|
236
|
+
uri,
|
|
237
|
+
nameOrAddress,
|
|
238
|
+
identity,
|
|
239
|
+
token,
|
|
240
|
+
emitter,
|
|
241
|
+
remoteModule,
|
|
242
|
+
createWSFn,
|
|
243
|
+
compression,
|
|
244
|
+
lightMode,
|
|
245
|
+
confirmedReads,
|
|
246
|
+
}: DbConnectionConfig<RemoteModule>) {
|
|
247
|
+
stdbLogger('info', 'Connecting to SpacetimeDB WS...');
|
|
248
|
+
|
|
249
|
+
// We use .toString() here because some versions of React Native contain a bug where the URL constructor
|
|
250
|
+
// incorrectly treats a URL instance as a plain string.
|
|
251
|
+
// This results in an attempt to call .endsWith() on it, leading to an error.
|
|
252
|
+
const url = new URL(uri.toString());
|
|
253
|
+
if (!/^wss?:/.test(uri.protocol)) {
|
|
254
|
+
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
this.identity = identity;
|
|
258
|
+
this.token = token;
|
|
259
|
+
|
|
260
|
+
this.#remoteModule = remoteModule;
|
|
261
|
+
this.#emitter = emitter;
|
|
262
|
+
this.#boundSubscriptionBuilder = this.subscriptionBuilder.bind(this);
|
|
263
|
+
this.#boundDisconnect = this.disconnect.bind(this);
|
|
264
|
+
|
|
265
|
+
this.#rowDeserializers = Object.create(null);
|
|
266
|
+
this.#rowIdMetadata = Object.create(null);
|
|
267
|
+
this.#sourceNameToTableDef = Object.create(null);
|
|
268
|
+
for (const table of Object.values(remoteModule.tables)) {
|
|
269
|
+
this.#rowDeserializers[table.sourceName] = ProductType.makeDeserializer(
|
|
270
|
+
table.rowType
|
|
271
|
+
);
|
|
272
|
+
this.#sourceNameToTableDef[table.sourceName] = table as Values<
|
|
273
|
+
RemoteModule['tables']
|
|
274
|
+
>;
|
|
275
|
+
const primaryKeyColumn = Object.entries(table.columns).find(
|
|
276
|
+
([, column]) => column.columnMetadata.isPrimaryKey
|
|
277
|
+
);
|
|
278
|
+
this.#rowIdMetadata[table.sourceName] = primaryKeyColumn
|
|
279
|
+
? {
|
|
280
|
+
primaryKeyColName: primaryKeyColumn[0],
|
|
281
|
+
primaryKeyColType: primaryKeyColumn[1].typeBuilder.algebraicType,
|
|
282
|
+
}
|
|
283
|
+
: {};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.#reducerArgsSerializers = Object.create(null);
|
|
287
|
+
this.#reducerNameBytes = Object.create(null);
|
|
288
|
+
for (const reducer of remoteModule.reducers) {
|
|
289
|
+
this.#reducerArgsSerializers[reducer.name] = {
|
|
290
|
+
serialize: ProductType.makeSerializer(reducer.paramsType),
|
|
291
|
+
deserialize: ProductType.makeDeserializer(reducer.paramsType),
|
|
292
|
+
};
|
|
293
|
+
this.#reducerNameBytes[reducer.name] = TEXT_ENCODER.encode(reducer.name);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
this.#procedureSerializers = Object.create(null);
|
|
297
|
+
this.#procedureNameBytes = Object.create(null);
|
|
298
|
+
for (const procedure of remoteModule.procedures) {
|
|
299
|
+
this.#procedureSerializers[procedure.name] = {
|
|
300
|
+
serializeArgs: ProductType.makeSerializer(
|
|
301
|
+
new ProductBuilder(procedure.params).algebraicType.value
|
|
302
|
+
),
|
|
303
|
+
deserializeReturn: AlgebraicType.makeDeserializer(
|
|
304
|
+
procedure.returnType.algebraicType
|
|
305
|
+
),
|
|
306
|
+
};
|
|
307
|
+
this.#procedureNameBytes[procedure.name] = TEXT_ENCODER.encode(
|
|
308
|
+
procedure.name
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
url.searchParams.set('connection_id', this.#connectionIdHex);
|
|
313
|
+
|
|
314
|
+
this.clientCache = new ClientCache<RemoteModule>();
|
|
315
|
+
this.db = this.#makeDbView();
|
|
316
|
+
this.reducers = this.#makeReducers(remoteModule);
|
|
317
|
+
this.procedures = this.#makeProcedures(remoteModule);
|
|
318
|
+
|
|
319
|
+
this.wsPromise = createWSFn({
|
|
320
|
+
url,
|
|
321
|
+
nameOrAddress,
|
|
322
|
+
wsProtocol: [...PREFERRED_WS_PROTOCOLS],
|
|
323
|
+
authToken: token,
|
|
324
|
+
compression: compression,
|
|
325
|
+
lightMode: lightMode,
|
|
326
|
+
confirmedReads: confirmedReads,
|
|
327
|
+
})
|
|
328
|
+
.then(v => {
|
|
329
|
+
this.ws = v;
|
|
330
|
+
|
|
331
|
+
this.ws.onclose = () => {
|
|
332
|
+
this.isActive = false;
|
|
333
|
+
this.#emitter.emit('disconnect', this);
|
|
334
|
+
};
|
|
335
|
+
this.ws.onerror = (e: ErrorEvent) => {
|
|
336
|
+
this.isActive = false;
|
|
337
|
+
this.#emitter.emit('connectError', this, e);
|
|
338
|
+
};
|
|
339
|
+
this.ws.onopen = this.#handleOnOpen.bind(this);
|
|
340
|
+
this.ws.onmessage = this.#handleOnMessage.bind(this);
|
|
341
|
+
return v;
|
|
342
|
+
})
|
|
343
|
+
.catch(e => {
|
|
344
|
+
stdbLogger('error', 'Error connecting to SpacetimeDB WS');
|
|
345
|
+
this.#emitter.emit('connectError', this, e);
|
|
346
|
+
|
|
347
|
+
return undefined;
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
#getNextQueryId = () => {
|
|
352
|
+
const queryId = this.#queryId;
|
|
353
|
+
this.#queryId += 1;
|
|
354
|
+
return queryId;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
#getNextRequestId = () => this.#requestId++;
|
|
358
|
+
|
|
359
|
+
#makeDbView(): ClientDbView<RemoteModule> {
|
|
360
|
+
const view = Object.create(null) as ClientDbView<RemoteModule>;
|
|
361
|
+
|
|
362
|
+
for (const tbl of Object.values(this.#sourceNameToTableDef)) {
|
|
363
|
+
// ClientDbView uses this name verbatim
|
|
364
|
+
const key = tbl.accessorName;
|
|
365
|
+
Object.defineProperty(view, key, {
|
|
366
|
+
enumerable: true,
|
|
367
|
+
configurable: false,
|
|
368
|
+
get: () => this.clientCache.getOrCreateTable(tbl),
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return view;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
#makeReducers(def: RemoteModule): ReducersView<RemoteModule> {
|
|
376
|
+
const out: Record<string, unknown> = {};
|
|
377
|
+
|
|
378
|
+
for (const reducer of def.reducers) {
|
|
379
|
+
const reducerName = reducer.name;
|
|
380
|
+
const encodedReducerName = this.#reducerNameBytes[reducerName];
|
|
381
|
+
const key = reducer.accessorName;
|
|
382
|
+
|
|
383
|
+
const { serialize: serializeArgs } =
|
|
384
|
+
this.#reducerArgsSerializers[reducerName];
|
|
385
|
+
|
|
386
|
+
(out as any)[key] = (params: InferTypeOfRow<typeof reducer.params>) => {
|
|
387
|
+
const writer = this.#reducerArgsEncoder;
|
|
388
|
+
writer.clear();
|
|
389
|
+
serializeArgs(writer, params);
|
|
390
|
+
const argsBuffer = writer.getBuffer();
|
|
391
|
+
return this.#callReducerWithEncodedName(
|
|
392
|
+
reducerName,
|
|
393
|
+
encodedReducerName,
|
|
394
|
+
argsBuffer,
|
|
395
|
+
params
|
|
396
|
+
);
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return out as ReducersView<RemoteModule>;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
#makeProcedures(def: RemoteModule): ProceduresView<RemoteModule> {
|
|
404
|
+
const out: Record<string, unknown> = {};
|
|
405
|
+
|
|
406
|
+
const writer = new BinaryWriter(1024);
|
|
407
|
+
|
|
408
|
+
for (const procedure of def.procedures) {
|
|
409
|
+
const procedureName = procedure.name;
|
|
410
|
+
const encodedProcedureName = this.#procedureNameBytes[procedureName];
|
|
411
|
+
const key = procedure.accessorName;
|
|
412
|
+
|
|
413
|
+
const { serializeArgs, deserializeReturn } =
|
|
414
|
+
this.#procedureSerializers[procedureName];
|
|
415
|
+
|
|
416
|
+
(out as any)[key] = (
|
|
417
|
+
params: InferTypeOfRow<typeof procedure.params>
|
|
418
|
+
): Promise<any> => {
|
|
419
|
+
writer.clear();
|
|
420
|
+
serializeArgs(writer, params);
|
|
421
|
+
const argsBuffer = writer.getBuffer();
|
|
422
|
+
return this.#callProcedureWithEncodedName(
|
|
423
|
+
procedureName,
|
|
424
|
+
encodedProcedureName,
|
|
425
|
+
argsBuffer
|
|
426
|
+
).then(returnBuf => {
|
|
427
|
+
return deserializeReturn(new BinaryReader(returnBuf));
|
|
428
|
+
});
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return out as ProceduresView<RemoteModule>;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
#makeEventContext(
|
|
436
|
+
event: Event<
|
|
437
|
+
ReducerEventInfo<
|
|
438
|
+
RemoteModule['reducers'][number]['name'],
|
|
439
|
+
InferTypeOfRow<RemoteModule['reducers'][number]['params']>
|
|
440
|
+
>
|
|
441
|
+
>
|
|
442
|
+
): EventContextInterface<RemoteModule> {
|
|
443
|
+
return {
|
|
444
|
+
db: this.db,
|
|
445
|
+
reducers: this.reducers,
|
|
446
|
+
isActive: this.isActive,
|
|
447
|
+
subscriptionBuilder: this.#boundSubscriptionBuilder,
|
|
448
|
+
disconnect: this.#boundDisconnect,
|
|
449
|
+
event,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// NOTE: This is very important!!! This is the actual function that
|
|
454
|
+
// gets called when you call `connection.subscriptionBuilder()`.
|
|
455
|
+
// The `subscriptionBuilder` function which is generated, just shadows
|
|
456
|
+
// this function in the type system, but not the actual implementation!
|
|
457
|
+
// Do not remove this function, or shoot yourself in the foot please.
|
|
458
|
+
// It's not clear what would be a better way to do this at this exact
|
|
459
|
+
// moment.
|
|
460
|
+
subscriptionBuilder = (): SubscriptionBuilderImpl<RemoteModule> => {
|
|
461
|
+
return new SubscriptionBuilderImpl(this);
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
getTablesMap(): any {
|
|
465
|
+
return makeQueryBuilder({ tables: this.#remoteModule.tables } as any);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
registerSubscription(
|
|
469
|
+
handle: SubscriptionHandleImpl<RemoteModule>,
|
|
470
|
+
handleEmitter: EventEmitter<
|
|
471
|
+
SubscribeEvent,
|
|
472
|
+
SubscriptionEventCallback<RemoteModule>
|
|
473
|
+
>,
|
|
474
|
+
querySql: string[]
|
|
475
|
+
): number {
|
|
476
|
+
const querySetId = this.#getNextQueryId();
|
|
477
|
+
this.#subscriptionManager.subscriptions.set(querySetId, {
|
|
478
|
+
handle,
|
|
479
|
+
emitter: handleEmitter,
|
|
480
|
+
});
|
|
481
|
+
const requestId = this.#getNextRequestId();
|
|
482
|
+
this.#sendMessage(
|
|
483
|
+
ClientMessage.Subscribe({
|
|
484
|
+
queryStrings: querySql,
|
|
485
|
+
querySetId: { id: querySetId },
|
|
486
|
+
requestId,
|
|
487
|
+
})
|
|
488
|
+
);
|
|
489
|
+
return querySetId;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
unregisterSubscription(querySetId: number): void {
|
|
493
|
+
const requestId = this.#getNextRequestId();
|
|
494
|
+
this.#sendMessage(
|
|
495
|
+
ClientMessage.Unsubscribe({
|
|
496
|
+
querySetId: { id: querySetId },
|
|
497
|
+
requestId,
|
|
498
|
+
flags: UnsubscribeFlags.SendDroppedRows,
|
|
499
|
+
})
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
#parseRowList(
|
|
504
|
+
type: 'insert' | 'delete',
|
|
505
|
+
tableName: string,
|
|
506
|
+
rowList: BsatnRowList
|
|
507
|
+
): Operation[] {
|
|
508
|
+
const buffer = rowList.rowsData;
|
|
509
|
+
const reader = this.#rowListReader;
|
|
510
|
+
reader.reset(buffer);
|
|
511
|
+
const rows: Operation[] = [];
|
|
512
|
+
|
|
513
|
+
const deserializeRow = this.#rowDeserializers[tableName];
|
|
514
|
+
const { primaryKeyColName, primaryKeyColType } =
|
|
515
|
+
this.#rowIdMetadata[tableName];
|
|
516
|
+
let previousOffset = 0;
|
|
517
|
+
while (reader.remaining > 0) {
|
|
518
|
+
const row = deserializeRow(reader);
|
|
519
|
+
let rowId: ComparablePrimitive | undefined = undefined;
|
|
520
|
+
if (primaryKeyColName !== undefined && primaryKeyColType !== undefined) {
|
|
521
|
+
rowId = AlgebraicType.intoMapKey(
|
|
522
|
+
primaryKeyColType,
|
|
523
|
+
row[primaryKeyColName]
|
|
524
|
+
);
|
|
525
|
+
} else {
|
|
526
|
+
// Get a view of the bytes for this row.
|
|
527
|
+
const rowBytes = buffer.subarray(previousOffset, reader.offset);
|
|
528
|
+
// Convert it to a base64 string, so we can use it as a map key.
|
|
529
|
+
const asBase64 = fromByteArray(rowBytes);
|
|
530
|
+
rowId = asBase64;
|
|
531
|
+
}
|
|
532
|
+
previousOffset = reader.offset;
|
|
533
|
+
|
|
534
|
+
rows.push({
|
|
535
|
+
type,
|
|
536
|
+
rowId,
|
|
537
|
+
row,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
return rows;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Take a bunch of table updates and ensure that there is at most one update per table.
|
|
544
|
+
#mergeTableUpdates(
|
|
545
|
+
updates: CacheTableUpdate<UntypedTableDef>[]
|
|
546
|
+
): CacheTableUpdate<UntypedTableDef>[] {
|
|
547
|
+
const merged = new Map<string, Operation[]>();
|
|
548
|
+
for (const update of updates) {
|
|
549
|
+
const ops = merged.get(update.tableName);
|
|
550
|
+
if (ops) {
|
|
551
|
+
for (const op of update.operations) ops.push(op);
|
|
552
|
+
} else {
|
|
553
|
+
merged.set(update.tableName, update.operations.slice());
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return Array.from(merged, ([tableName, operations]) => ({
|
|
557
|
+
tableName,
|
|
558
|
+
operations,
|
|
559
|
+
}));
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
#queryRowsToTableUpdates(
|
|
563
|
+
rows: QueryRows,
|
|
564
|
+
opType: 'insert' | 'delete'
|
|
565
|
+
): CacheTableUpdate<UntypedTableDef>[] {
|
|
566
|
+
const updates: CacheTableUpdate<UntypedTableDef>[] = [];
|
|
567
|
+
for (const tableRows of rows.tables) {
|
|
568
|
+
updates.push({
|
|
569
|
+
tableName: tableRows.table,
|
|
570
|
+
operations: this.#parseRowList(opType, tableRows.table, tableRows.rows),
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
return this.#mergeTableUpdates(updates);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
#tableUpdateRowsToOperations(
|
|
577
|
+
tableName: string,
|
|
578
|
+
rows: TableUpdateRows
|
|
579
|
+
): Operation[] {
|
|
580
|
+
if (rows.tag === 'PersistentTable') {
|
|
581
|
+
const inserts = this.#parseRowList(
|
|
582
|
+
'insert',
|
|
583
|
+
tableName,
|
|
584
|
+
rows.value.inserts
|
|
585
|
+
);
|
|
586
|
+
const deletes = this.#parseRowList(
|
|
587
|
+
'delete',
|
|
588
|
+
tableName,
|
|
589
|
+
rows.value.deletes
|
|
590
|
+
);
|
|
591
|
+
return inserts.concat(deletes);
|
|
592
|
+
}
|
|
593
|
+
if (rows.tag === 'EventTable') {
|
|
594
|
+
// Event table rows are insert-only. The table cache handles skipping
|
|
595
|
+
// storage for event tables and only firing on_insert callbacks.
|
|
596
|
+
return this.#parseRowList('insert', tableName, rows.value.events);
|
|
597
|
+
}
|
|
598
|
+
return [];
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
#querySetUpdateToTableUpdates(
|
|
602
|
+
querySetUpdate: QuerySetUpdate
|
|
603
|
+
): CacheTableUpdate<UntypedTableDef>[] {
|
|
604
|
+
const updates: CacheTableUpdate<UntypedTableDef>[] = [];
|
|
605
|
+
for (const tableUpdate of querySetUpdate.tables) {
|
|
606
|
+
let operations: Operation[] = [];
|
|
607
|
+
for (const rows of tableUpdate.rows) {
|
|
608
|
+
operations = operations.concat(
|
|
609
|
+
this.#tableUpdateRowsToOperations(tableUpdate.tableName, rows)
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
updates.push({
|
|
613
|
+
tableName: tableUpdate.tableName,
|
|
614
|
+
operations,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
return this.#mergeTableUpdates(updates);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
#flushOutboundQueue(wsResolved: WebSocketAdapter): void {
|
|
621
|
+
if (this.#negotiatedWsProtocol === V3_WS_PROTOCOL) {
|
|
622
|
+
this.#flushOutboundQueueV3(wsResolved);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
this.#flushOutboundQueueV2(wsResolved);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
#flushOutboundQueueV2(wsResolved: WebSocketAdapter): void {
|
|
629
|
+
const pending = this.#outboundQueue.splice(0);
|
|
630
|
+
for (const message of pending) {
|
|
631
|
+
wsResolved.send(message);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
#flushOutboundQueueV3(wsResolved: WebSocketAdapter): void {
|
|
636
|
+
if (this.#outboundQueue.length === 0) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Emit at most one bounded frame per flush. If more encoded v2 messages
|
|
641
|
+
// remain in the queue, they are sent by a later scheduled flush so inbound
|
|
642
|
+
// traffic and other tasks get a chance to run between websocket writes.
|
|
643
|
+
const batchSize = countClientMessagesForV3Frame(
|
|
644
|
+
this.#outboundQueue,
|
|
645
|
+
MAX_V3_OUTBOUND_FRAME_BYTES
|
|
646
|
+
);
|
|
647
|
+
wsResolved.send(
|
|
648
|
+
encodeClientMessagesV3(
|
|
649
|
+
this.#clientFrameEncoder,
|
|
650
|
+
this.#outboundQueue,
|
|
651
|
+
batchSize
|
|
652
|
+
)
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
if (batchSize === this.#outboundQueue.length) {
|
|
656
|
+
this.#outboundQueue.length = 0;
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
this.#outboundQueue.copyWithin(0, batchSize);
|
|
661
|
+
this.#outboundQueue.length -= batchSize;
|
|
662
|
+
if (this.#outboundQueue.length > 0) {
|
|
663
|
+
this.#scheduleDeferredOutboundFlush();
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
#scheduleOutboundFlush(): void {
|
|
668
|
+
this.#scheduleOutboundFlushWith('microtask');
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
#scheduleDeferredOutboundFlush(): void {
|
|
672
|
+
this.#scheduleOutboundFlushWith('next-task');
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
#scheduleOutboundFlushWith(schedule: 'microtask' | 'next-task'): void {
|
|
676
|
+
if (this.#isOutboundFlushScheduled) {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
this.#isOutboundFlushScheduled = true;
|
|
681
|
+
const flush = () => {
|
|
682
|
+
this.#isOutboundFlushScheduled = false;
|
|
683
|
+
if (this.ws && this.isActive) {
|
|
684
|
+
this.#flushOutboundQueue(this.ws);
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
// The first v3 flush stays on the current turn so same-tick sends coalesce.
|
|
689
|
+
// Follow-up flushes after a size-capped frame yield to the next task so we
|
|
690
|
+
// do not sit in a tight send loop while inbound websocket work is waiting.
|
|
691
|
+
if (schedule === 'next-task') {
|
|
692
|
+
setTimeout(flush, 0);
|
|
693
|
+
} else {
|
|
694
|
+
queueMicrotask(flush);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
#reducerArgsEncoder = new BinaryWriter(1024);
|
|
699
|
+
#clientMessageEncoder = new BinaryWriter(1024);
|
|
700
|
+
#sendEncodedMessage(
|
|
701
|
+
encoded: Uint8Array<ArrayBuffer>,
|
|
702
|
+
describe: () => string
|
|
703
|
+
): void {
|
|
704
|
+
stdbLogger('trace', describe);
|
|
705
|
+
if (this.ws && this.isActive) {
|
|
706
|
+
if (this.#negotiatedWsProtocol === V2_WS_PROTOCOL) {
|
|
707
|
+
if (this.#outboundQueue.length) this.#flushOutboundQueue(this.ws);
|
|
708
|
+
this.ws.send(encoded);
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
this.#outboundQueue.push(encoded.slice());
|
|
713
|
+
this.#scheduleOutboundFlush();
|
|
714
|
+
} else {
|
|
715
|
+
// Use slice() to copy, in case the clientMessageEncoder's buffer gets reused
|
|
716
|
+
// before the connection opens or before a v3 microbatch flush runs.
|
|
717
|
+
this.#outboundQueue.push(encoded.slice());
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
#sendMessage(message: ClientMessage): void {
|
|
722
|
+
const writer = this.#clientMessageEncoder;
|
|
723
|
+
writer.clear();
|
|
724
|
+
ClientMessage.serialize(writer, message);
|
|
725
|
+
const encoded = writer.getBuffer();
|
|
726
|
+
const isLive = !!(this.ws && this.isActive);
|
|
727
|
+
this.#sendEncodedMessage(encoded, () =>
|
|
728
|
+
isLive
|
|
729
|
+
? `Sending message to server: ${stringify(message)}`
|
|
730
|
+
: `Queuing message to server: ${stringify(message)}`
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
#sendCallReducerMessage(
|
|
735
|
+
requestId: number,
|
|
736
|
+
reducerNameBytes: Uint8Array,
|
|
737
|
+
argsBuffer: Uint8Array
|
|
738
|
+
): void {
|
|
739
|
+
const writer = this.#clientMessageEncoder;
|
|
740
|
+
writer.clear();
|
|
741
|
+
writer.writeByte(CLIENT_MESSAGE_CALL_REDUCER_TAG);
|
|
742
|
+
writer.writeU32(requestId);
|
|
743
|
+
writer.writeU8(0);
|
|
744
|
+
writer.writeUInt8Array(reducerNameBytes);
|
|
745
|
+
writer.writeUInt8Array(argsBuffer);
|
|
746
|
+
const encoded = writer.getBuffer();
|
|
747
|
+
this.#sendEncodedMessage(
|
|
748
|
+
encoded,
|
|
749
|
+
() => `Sending reducer call message to server: requestId=${requestId}`
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
#sendCallProcedureMessage(
|
|
754
|
+
requestId: number,
|
|
755
|
+
procedureNameBytes: Uint8Array,
|
|
756
|
+
argsBuffer: Uint8Array
|
|
757
|
+
): void {
|
|
758
|
+
const writer = this.#clientMessageEncoder;
|
|
759
|
+
writer.clear();
|
|
760
|
+
writer.writeByte(CLIENT_MESSAGE_CALL_PROCEDURE_TAG);
|
|
761
|
+
writer.writeU32(requestId);
|
|
762
|
+
writer.writeU8(0);
|
|
763
|
+
writer.writeUInt8Array(procedureNameBytes);
|
|
764
|
+
writer.writeUInt8Array(argsBuffer);
|
|
765
|
+
const encoded = writer.getBuffer();
|
|
766
|
+
this.#sendEncodedMessage(
|
|
767
|
+
encoded,
|
|
768
|
+
() => `Sending procedure call message to server: requestId=${requestId}`
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
#setConnectionId(connectionId: ConnectionId): void {
|
|
773
|
+
this.connectionId = connectionId;
|
|
774
|
+
this.#connectionIdHex = connectionId.toHexString();
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
#nextEventId(): string {
|
|
778
|
+
this.#eventId += 1;
|
|
779
|
+
return `${this.#connectionIdHex}:${this.#eventId}`;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Handles WebSocket onOpen event.
|
|
784
|
+
*/
|
|
785
|
+
#handleOnOpen(): void {
|
|
786
|
+
if (this.ws) {
|
|
787
|
+
this.#negotiatedWsProtocol = normalizeWsProtocol(this.ws.protocol);
|
|
788
|
+
}
|
|
789
|
+
this.isActive = true;
|
|
790
|
+
if (this.ws) {
|
|
791
|
+
this.#flushOutboundQueue(this.ws);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
#applyTableUpdates(
|
|
796
|
+
tableUpdates: CacheTableUpdate<UntypedTableDef>[],
|
|
797
|
+
eventContext: EventContextInterface<RemoteModule>
|
|
798
|
+
): PendingCallback[] {
|
|
799
|
+
const pendingCallbacks: PendingCallback[] = [];
|
|
800
|
+
for (const tableUpdate of tableUpdates) {
|
|
801
|
+
// Get table information for the table being updated
|
|
802
|
+
const tableName = tableUpdate.tableName;
|
|
803
|
+
const tableDef = this.#sourceNameToTableDef[tableName];
|
|
804
|
+
const table = this.clientCache.getOrCreateTable(tableDef);
|
|
805
|
+
const newCallbacks = table.applyOperations(
|
|
806
|
+
tableUpdate.operations as Operation<
|
|
807
|
+
RowType<Values<RemoteModule['tables']>>
|
|
808
|
+
>[],
|
|
809
|
+
eventContext
|
|
810
|
+
);
|
|
811
|
+
for (const callback of newCallbacks) {
|
|
812
|
+
pendingCallbacks.push(callback);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return pendingCallbacks;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
#applyTransactionUpdates(
|
|
819
|
+
eventContext: EventContextInterface<RemoteModule>,
|
|
820
|
+
tu: TransactionUpdate
|
|
821
|
+
): PendingCallback[] {
|
|
822
|
+
const allUpdates: CacheTableUpdate<UntypedTableDef>[] = [];
|
|
823
|
+
for (const querySetUpdate of tu.querySets) {
|
|
824
|
+
const tableUpdates = this.#querySetUpdateToTableUpdates(querySetUpdate);
|
|
825
|
+
for (const update of tableUpdates) {
|
|
826
|
+
allUpdates.push(update);
|
|
827
|
+
}
|
|
828
|
+
// TODO: When we have per-query storage, we will want to apply the per-query events here.
|
|
829
|
+
}
|
|
830
|
+
return this.#applyTableUpdates(
|
|
831
|
+
this.#mergeTableUpdates(allUpdates),
|
|
832
|
+
eventContext
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
#dispatchPendingCallbacks(callbacks: readonly PendingCallback[]): void {
|
|
837
|
+
stdbLogger(
|
|
838
|
+
'trace',
|
|
839
|
+
() => `Calling ${callbacks.length} triggered row callbacks`
|
|
840
|
+
);
|
|
841
|
+
for (const callback of callbacks) {
|
|
842
|
+
callback.cb();
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
#processServerMessage(serverMessage: ServerMessage): void {
|
|
847
|
+
stdbLogger(
|
|
848
|
+
'trace',
|
|
849
|
+
() => `Processing server message: ${stringify(serverMessage)}`
|
|
850
|
+
);
|
|
851
|
+
switch (serverMessage.tag) {
|
|
852
|
+
case 'InitialConnection': {
|
|
853
|
+
this.identity = serverMessage.value.identity;
|
|
854
|
+
if (!this.token && serverMessage.value.token) {
|
|
855
|
+
this.token = serverMessage.value.token;
|
|
856
|
+
}
|
|
857
|
+
this.#setConnectionId(serverMessage.value.connectionId);
|
|
858
|
+
this.#emitter.emit('connect', this, this.identity, this.token);
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
case 'SubscribeApplied': {
|
|
862
|
+
const querySetId = serverMessage.value.querySetId.id;
|
|
863
|
+
const subscription =
|
|
864
|
+
this.#subscriptionManager.subscriptions.get(querySetId);
|
|
865
|
+
if (!subscription) {
|
|
866
|
+
stdbLogger(
|
|
867
|
+
'error',
|
|
868
|
+
`Received SubscribeApplied for unknown querySetId ${querySetId}.`
|
|
869
|
+
);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
const event: Event<never> = {
|
|
873
|
+
id: this.#nextEventId(),
|
|
874
|
+
tag: 'SubscribeApplied',
|
|
875
|
+
};
|
|
876
|
+
const eventContext = this.#makeEventContext(event);
|
|
877
|
+
const tableUpdates = this.#queryRowsToTableUpdates(
|
|
878
|
+
serverMessage.value.rows,
|
|
879
|
+
'insert'
|
|
880
|
+
);
|
|
881
|
+
const callbacks = this.#applyTableUpdates(tableUpdates, eventContext);
|
|
882
|
+
const { event: _, ...subscriptionEventContext } = eventContext;
|
|
883
|
+
subscription.emitter.emit('applied', subscriptionEventContext);
|
|
884
|
+
this.#dispatchPendingCallbacks(callbacks);
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
case 'UnsubscribeApplied': {
|
|
888
|
+
const querySetId = serverMessage.value.querySetId.id;
|
|
889
|
+
const subscription =
|
|
890
|
+
this.#subscriptionManager.subscriptions.get(querySetId);
|
|
891
|
+
if (!subscription) {
|
|
892
|
+
stdbLogger(
|
|
893
|
+
'error',
|
|
894
|
+
`Received UnsubscribeApplied for unknown querySetId ${querySetId}.`
|
|
895
|
+
);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const event: Event<never> = {
|
|
899
|
+
id: this.#nextEventId(),
|
|
900
|
+
tag: 'UnsubscribeApplied',
|
|
901
|
+
};
|
|
902
|
+
const eventContext = this.#makeEventContext(event);
|
|
903
|
+
const tableUpdates = serverMessage.value.rows
|
|
904
|
+
? this.#queryRowsToTableUpdates(serverMessage.value.rows, 'delete')
|
|
905
|
+
: [];
|
|
906
|
+
const callbacks = this.#applyTableUpdates(tableUpdates, eventContext);
|
|
907
|
+
const { event: _, ...subscriptionEventContext } = eventContext;
|
|
908
|
+
subscription.emitter.emit('end', subscriptionEventContext);
|
|
909
|
+
this.#subscriptionManager.subscriptions.delete(querySetId);
|
|
910
|
+
this.#dispatchPendingCallbacks(callbacks);
|
|
911
|
+
break;
|
|
912
|
+
}
|
|
913
|
+
case 'SubscriptionError': {
|
|
914
|
+
const querySetId = serverMessage.value.querySetId.id;
|
|
915
|
+
const requestId = serverMessage.value.requestId;
|
|
916
|
+
const error = Error(serverMessage.value.error);
|
|
917
|
+
const event: Event<never> = {
|
|
918
|
+
id: this.#nextEventId(),
|
|
919
|
+
tag: 'Error',
|
|
920
|
+
value: error,
|
|
921
|
+
};
|
|
922
|
+
const eventContext = this.#makeEventContext(event);
|
|
923
|
+
const errorContext = {
|
|
924
|
+
...eventContext,
|
|
925
|
+
event: error,
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
// If the requestId isn't set, that means we already applied the subscription.
|
|
929
|
+
// Since we don't know how to remove the relevant rows from our table cache, we need
|
|
930
|
+
// to kill the connection. Once we have per-query storage, this won't be fatal.
|
|
931
|
+
if (requestId == null) {
|
|
932
|
+
stdbLogger(
|
|
933
|
+
'error',
|
|
934
|
+
`Disconnecting due to error for a previously applied subscription: ${serverMessage.value.error}`
|
|
935
|
+
);
|
|
936
|
+
this.disconnect();
|
|
937
|
+
break;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const subscription =
|
|
941
|
+
this.#subscriptionManager.subscriptions.get(querySetId);
|
|
942
|
+
if (subscription) {
|
|
943
|
+
subscription.emitter.emit('error', errorContext, error);
|
|
944
|
+
this.#subscriptionManager.subscriptions.delete(querySetId);
|
|
945
|
+
} else {
|
|
946
|
+
stdbLogger(
|
|
947
|
+
'error',
|
|
948
|
+
`Received SubscriptionError for unknown querySetId ${querySetId}:`,
|
|
949
|
+
error
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
case 'TransactionUpdate': {
|
|
955
|
+
const event: Event<never> = {
|
|
956
|
+
id: this.#nextEventId(),
|
|
957
|
+
tag: 'Transaction',
|
|
958
|
+
};
|
|
959
|
+
const eventContext = this.#makeEventContext(event);
|
|
960
|
+
const callbacks = this.#applyTransactionUpdates(
|
|
961
|
+
eventContext,
|
|
962
|
+
serverMessage.value
|
|
963
|
+
);
|
|
964
|
+
this.#dispatchPendingCallbacks(callbacks);
|
|
965
|
+
break;
|
|
966
|
+
}
|
|
967
|
+
case 'ReducerResult': {
|
|
968
|
+
const { requestId, result } = serverMessage.value;
|
|
969
|
+
|
|
970
|
+
if (result.tag === 'Ok') {
|
|
971
|
+
const reducerInfo = this.#reducerCallInfo.get(requestId);
|
|
972
|
+
const eventId: string = this.#nextEventId();
|
|
973
|
+
const event: Event<any> = reducerInfo
|
|
974
|
+
? {
|
|
975
|
+
id: eventId,
|
|
976
|
+
tag: 'Reducer',
|
|
977
|
+
value: {
|
|
978
|
+
timestamp: serverMessage.value.timestamp,
|
|
979
|
+
outcome: result,
|
|
980
|
+
reducer: {
|
|
981
|
+
name: reducerInfo.name,
|
|
982
|
+
args: reducerInfo.args,
|
|
983
|
+
},
|
|
984
|
+
},
|
|
985
|
+
}
|
|
986
|
+
: {
|
|
987
|
+
id: eventId,
|
|
988
|
+
tag: 'Transaction',
|
|
989
|
+
};
|
|
990
|
+
const eventContext = this.#makeEventContext(event as any);
|
|
991
|
+
|
|
992
|
+
const callbacks = this.#applyTransactionUpdates(
|
|
993
|
+
eventContext,
|
|
994
|
+
result.value.transactionUpdate
|
|
995
|
+
);
|
|
996
|
+
this.#dispatchPendingCallbacks(callbacks);
|
|
997
|
+
}
|
|
998
|
+
this.#reducerCallInfo.delete(requestId);
|
|
999
|
+
const cb = this.#reducerCallbacks.get(requestId);
|
|
1000
|
+
this.#reducerCallbacks.delete(requestId);
|
|
1001
|
+
cb?.(result);
|
|
1002
|
+
break;
|
|
1003
|
+
}
|
|
1004
|
+
case 'ProcedureResult': {
|
|
1005
|
+
const { status, requestId } = serverMessage.value;
|
|
1006
|
+
const result: ProcedureResultMessage['result'] =
|
|
1007
|
+
status.tag === 'Returned'
|
|
1008
|
+
? { tag: 'Ok', value: status.value }
|
|
1009
|
+
: { tag: 'Err', value: status.value };
|
|
1010
|
+
const cb = this.#procedureCallbacks.get(requestId);
|
|
1011
|
+
this.#procedureCallbacks.delete(requestId);
|
|
1012
|
+
cb?.(result);
|
|
1013
|
+
break;
|
|
1014
|
+
}
|
|
1015
|
+
case 'OneOffQueryResult': {
|
|
1016
|
+
stdbLogger(
|
|
1017
|
+
'warn',
|
|
1018
|
+
'Received OneOffQueryResult but SDK does not expose one-off query APIs yet.'
|
|
1019
|
+
);
|
|
1020
|
+
break;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
#processV2Message(data: Uint8Array): void {
|
|
1026
|
+
const reader = this.#messageReader;
|
|
1027
|
+
reader.reset(data);
|
|
1028
|
+
this.#processServerMessage(ServerMessage.deserialize(reader));
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
#processMessage(data: Uint8Array): void {
|
|
1032
|
+
if (this.#negotiatedWsProtocol !== V3_WS_PROTOCOL) {
|
|
1033
|
+
this.#processV2Message(data);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
const messageCount = forEachServerMessageV3(
|
|
1038
|
+
this.#messageReader,
|
|
1039
|
+
data,
|
|
1040
|
+
serverMessage => {
|
|
1041
|
+
this.#processServerMessage(serverMessage);
|
|
1042
|
+
}
|
|
1043
|
+
);
|
|
1044
|
+
stdbLogger(
|
|
1045
|
+
'trace',
|
|
1046
|
+
() => `Processing server v3 payload with ${messageCount} message(s)`
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* Handles WebSocket onMessage event.
|
|
1052
|
+
* @param wsMessage MessageEvent object.
|
|
1053
|
+
*/
|
|
1054
|
+
#handleOnMessage(wsMessage: { data: Uint8Array }): void {
|
|
1055
|
+
// Queue inbound messages so they are processed strictly in arrival order.
|
|
1056
|
+
// We deliberately drain synchronously instead of promise-chaining each
|
|
1057
|
+
// message, but this still guarantees that we do not begin processing the
|
|
1058
|
+
// next message until the current message has been fully handled.
|
|
1059
|
+
this.#inboundQueue.push(wsMessage.data);
|
|
1060
|
+
if (this.#isDrainingInboundQueue) {
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
this.#isDrainingInboundQueue = true;
|
|
1065
|
+
try {
|
|
1066
|
+
// TODO: If this loop starts monopolizing the event loop under sustained
|
|
1067
|
+
// inbound traffic, switch to a chunked drain that periodically yields.
|
|
1068
|
+
while (this.#inboundQueueOffset < this.#inboundQueue.length) {
|
|
1069
|
+
const data = this.#inboundQueue[this.#inboundQueueOffset];
|
|
1070
|
+
this.#inboundQueueOffset += 1;
|
|
1071
|
+
if (data) {
|
|
1072
|
+
this.#processMessage(data);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
} finally {
|
|
1076
|
+
if (this.#inboundQueueOffset >= this.#inboundQueue.length) {
|
|
1077
|
+
this.#inboundQueue.length = 0;
|
|
1078
|
+
} else if (this.#inboundQueueOffset > 0) {
|
|
1079
|
+
this.#inboundQueue = this.#inboundQueue.slice(this.#inboundQueueOffset);
|
|
1080
|
+
}
|
|
1081
|
+
this.#inboundQueueOffset = 0;
|
|
1082
|
+
this.#isDrainingInboundQueue = false;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* Call a reducer on your SpacetimeDB module.
|
|
1088
|
+
*
|
|
1089
|
+
* @param reducerName The name of the reducer to call
|
|
1090
|
+
* @param argsSerializer The arguments to pass to the reducer
|
|
1091
|
+
*/
|
|
1092
|
+
callReducer(
|
|
1093
|
+
reducerName: string,
|
|
1094
|
+
argsBuffer: Uint8Array,
|
|
1095
|
+
reducerArgs?: object
|
|
1096
|
+
): Promise<void> {
|
|
1097
|
+
const encodedReducerName = this.#reducerNameBytes[reducerName];
|
|
1098
|
+
if (encodedReducerName) {
|
|
1099
|
+
return this.#callReducerWithEncodedName(
|
|
1100
|
+
reducerName,
|
|
1101
|
+
encodedReducerName,
|
|
1102
|
+
argsBuffer,
|
|
1103
|
+
reducerArgs
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
return this.#callReducerGeneric(reducerName, argsBuffer, reducerArgs);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
#callReducerWithEncodedName(
|
|
1110
|
+
reducerName: string,
|
|
1111
|
+
encodedReducerName: Uint8Array,
|
|
1112
|
+
argsBuffer: Uint8Array,
|
|
1113
|
+
reducerArgs?: object
|
|
1114
|
+
): Promise<void> {
|
|
1115
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
1116
|
+
const requestId = this.#getNextRequestId();
|
|
1117
|
+
this.#sendCallReducerMessage(requestId, encodedReducerName, argsBuffer);
|
|
1118
|
+
if (reducerArgs) {
|
|
1119
|
+
this.#reducerCallInfo.set(requestId, {
|
|
1120
|
+
name: reducerName,
|
|
1121
|
+
args: reducerArgs,
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
this.#reducerCallbacks.set(requestId, result => {
|
|
1125
|
+
if (result.tag === 'Ok' || result.tag === 'OkEmpty') {
|
|
1126
|
+
resolve();
|
|
1127
|
+
} else {
|
|
1128
|
+
if (result.tag === 'Err') {
|
|
1129
|
+
/// Interpret the user-returned error as a string.
|
|
1130
|
+
const reader = new BinaryReader(result.value);
|
|
1131
|
+
const errorString = reader.readString();
|
|
1132
|
+
reject(new SenderError(errorString));
|
|
1133
|
+
} else if (result.tag === 'InternalError') {
|
|
1134
|
+
reject(new InternalError(result.value));
|
|
1135
|
+
} else {
|
|
1136
|
+
const unreachable: never = result;
|
|
1137
|
+
reject(new Error('Unexpected reducer result'));
|
|
1138
|
+
void unreachable;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
return promise;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
#callReducerGeneric(
|
|
1146
|
+
reducerName: string,
|
|
1147
|
+
argsBuffer: Uint8Array,
|
|
1148
|
+
reducerArgs?: object
|
|
1149
|
+
): Promise<void> {
|
|
1150
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
1151
|
+
const requestId = this.#getNextRequestId();
|
|
1152
|
+
const message = ClientMessage.CallReducer({
|
|
1153
|
+
reducer: reducerName,
|
|
1154
|
+
args: argsBuffer,
|
|
1155
|
+
requestId,
|
|
1156
|
+
flags: 0,
|
|
1157
|
+
});
|
|
1158
|
+
this.#sendMessage(message);
|
|
1159
|
+
if (reducerArgs) {
|
|
1160
|
+
this.#reducerCallInfo.set(requestId, {
|
|
1161
|
+
name: reducerName,
|
|
1162
|
+
args: reducerArgs,
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
this.#reducerCallbacks.set(requestId, result => {
|
|
1166
|
+
if (result.tag === 'Ok' || result.tag === 'OkEmpty') {
|
|
1167
|
+
resolve();
|
|
1168
|
+
} else {
|
|
1169
|
+
if (result.tag === 'Err') {
|
|
1170
|
+
/// Interpret the user-returned error as a string.
|
|
1171
|
+
const reader = new BinaryReader(result.value);
|
|
1172
|
+
const errorString = reader.readString();
|
|
1173
|
+
reject(new SenderError(errorString));
|
|
1174
|
+
} else if (result.tag === 'InternalError') {
|
|
1175
|
+
reject(new InternalError(result.value));
|
|
1176
|
+
} else {
|
|
1177
|
+
const unreachable: never = result;
|
|
1178
|
+
reject(new Error('Unexpected reducer result'));
|
|
1179
|
+
void unreachable;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
return promise;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* Call a reducer on your SpacetimeDB module with typed arguments.
|
|
1188
|
+
* @param reducerSchema The schema of the reducer to call
|
|
1189
|
+
* @param callReducerFlags The flags for the reducer call
|
|
1190
|
+
* @param params The arguments to pass to the reducer
|
|
1191
|
+
*/
|
|
1192
|
+
callReducerWithParams(
|
|
1193
|
+
reducerName: string,
|
|
1194
|
+
// TODO: remove
|
|
1195
|
+
_paramsType: ProductType,
|
|
1196
|
+
params: object
|
|
1197
|
+
): Promise<void> {
|
|
1198
|
+
const writer = this.#reducerArgsEncoder;
|
|
1199
|
+
writer.clear();
|
|
1200
|
+
this.#reducerArgsSerializers[reducerName].serialize(writer, params);
|
|
1201
|
+
const argsBuffer = writer.getBuffer();
|
|
1202
|
+
return this.callReducer(reducerName, argsBuffer, params);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* Call a reducer on your SpacetimeDB module.
|
|
1207
|
+
*
|
|
1208
|
+
* @param procedureName The name of the reducer to call
|
|
1209
|
+
* @param argsBuffer The arguments to pass to the reducer
|
|
1210
|
+
*/
|
|
1211
|
+
callProcedure(
|
|
1212
|
+
procedureName: string,
|
|
1213
|
+
argsBuffer: Uint8Array
|
|
1214
|
+
): Promise<Uint8Array> {
|
|
1215
|
+
const encodedProcedureName = this.#procedureNameBytes[procedureName];
|
|
1216
|
+
if (encodedProcedureName) {
|
|
1217
|
+
return this.#callProcedureWithEncodedName(
|
|
1218
|
+
procedureName,
|
|
1219
|
+
encodedProcedureName,
|
|
1220
|
+
argsBuffer
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
return this.#callProcedureGeneric(procedureName, argsBuffer);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
#callProcedureWithEncodedName(
|
|
1227
|
+
procedureName: string,
|
|
1228
|
+
encodedProcedureName: Uint8Array,
|
|
1229
|
+
argsBuffer: Uint8Array
|
|
1230
|
+
): Promise<Uint8Array> {
|
|
1231
|
+
const { promise, resolve, reject } = Promise.withResolvers<Uint8Array>();
|
|
1232
|
+
const requestId = this.#getNextRequestId();
|
|
1233
|
+
this.#sendCallProcedureMessage(requestId, encodedProcedureName, argsBuffer);
|
|
1234
|
+
this.#procedureCallbacks.set(requestId, result => {
|
|
1235
|
+
if (result.tag === 'Ok') {
|
|
1236
|
+
resolve(result.value);
|
|
1237
|
+
} else {
|
|
1238
|
+
reject(result.value);
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
return promise;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
#callProcedureGeneric(
|
|
1245
|
+
procedureName: string,
|
|
1246
|
+
argsBuffer: Uint8Array
|
|
1247
|
+
): Promise<Uint8Array> {
|
|
1248
|
+
const { promise, resolve, reject } = Promise.withResolvers<Uint8Array>();
|
|
1249
|
+
const requestId = this.#getNextRequestId();
|
|
1250
|
+
const message = ClientMessage.CallProcedure({
|
|
1251
|
+
procedure: procedureName,
|
|
1252
|
+
args: argsBuffer,
|
|
1253
|
+
requestId,
|
|
1254
|
+
// reserved for future use - 0 is the only valid value
|
|
1255
|
+
flags: 0,
|
|
1256
|
+
});
|
|
1257
|
+
this.#sendMessage(message);
|
|
1258
|
+
this.#procedureCallbacks.set(requestId, result => {
|
|
1259
|
+
if (result.tag === 'Ok') {
|
|
1260
|
+
resolve(result.value);
|
|
1261
|
+
} else {
|
|
1262
|
+
reject(result.value);
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
return promise;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
/**
|
|
1269
|
+
* Call a reducer on your SpacetimeDB module with typed arguments.
|
|
1270
|
+
* @param reducerSchema The schema of the reducer to call
|
|
1271
|
+
* @param callReducerFlags The flags for the reducer call
|
|
1272
|
+
* @param params The arguments to pass to the reducer
|
|
1273
|
+
*/
|
|
1274
|
+
callProcedureWithParams(
|
|
1275
|
+
procedureName: string,
|
|
1276
|
+
// TODO: remove
|
|
1277
|
+
_paramsType: ProductType,
|
|
1278
|
+
params: object,
|
|
1279
|
+
// TODO: remove
|
|
1280
|
+
_returnType: AlgebraicType
|
|
1281
|
+
): Promise<any> {
|
|
1282
|
+
const writer = new BinaryWriter(1024);
|
|
1283
|
+
const { serializeArgs, deserializeReturn } =
|
|
1284
|
+
this.#procedureSerializers[procedureName];
|
|
1285
|
+
serializeArgs(writer, params);
|
|
1286
|
+
const argsBuffer = writer.getBuffer();
|
|
1287
|
+
return this.callProcedure(procedureName, argsBuffer).then(returnBuf => {
|
|
1288
|
+
return deserializeReturn(new BinaryReader(returnBuf));
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/**
|
|
1293
|
+
* Close the current connection.
|
|
1294
|
+
*
|
|
1295
|
+
* @example
|
|
1296
|
+
*
|
|
1297
|
+
* ```ts
|
|
1298
|
+
* const connection = DbConnection.builder().build();
|
|
1299
|
+
* connection.disconnect()
|
|
1300
|
+
* ```
|
|
1301
|
+
*/
|
|
1302
|
+
disconnect(): void {
|
|
1303
|
+
this.isDisconnectRequested = true;
|
|
1304
|
+
this.wsPromise.then(ws => ws?.close());
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
private on(
|
|
1308
|
+
eventName: ConnectionEvent,
|
|
1309
|
+
callback: (ctx: DbConnectionImpl<RemoteModule>, ...args: any[]) => void
|
|
1310
|
+
): void {
|
|
1311
|
+
this.#emitter.on(eventName, callback);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
private off(
|
|
1315
|
+
eventName: ConnectionEvent,
|
|
1316
|
+
callback: (ctx: DbConnectionImpl<RemoteModule>, ...args: any[]) => void
|
|
1317
|
+
): void {
|
|
1318
|
+
this.#emitter.off(eventName, callback);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
private onConnect(
|
|
1322
|
+
callback: (ctx: DbConnectionImpl<RemoteModule>, ...args: any[]) => void
|
|
1323
|
+
): void {
|
|
1324
|
+
this.#emitter.on('connect', callback);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
private onDisconnect(
|
|
1328
|
+
callback: (ctx: DbConnectionImpl<RemoteModule>, ...args: any[]) => void
|
|
1329
|
+
): void {
|
|
1330
|
+
this.#emitter.on('disconnect', callback);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
private onConnectError(
|
|
1334
|
+
callback: (ctx: DbConnectionImpl<RemoteModule>, ...args: any[]) => void
|
|
1335
|
+
): void {
|
|
1336
|
+
this.#emitter.on('connectError', callback);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
removeOnConnect(
|
|
1340
|
+
callback: (ctx: DbConnectionImpl<RemoteModule>, ...args: any[]) => void
|
|
1341
|
+
): void {
|
|
1342
|
+
this.#emitter.off('connect', callback);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
removeOnDisconnect(
|
|
1346
|
+
callback: (ctx: DbConnectionImpl<RemoteModule>, ...args: any[]) => void
|
|
1347
|
+
): void {
|
|
1348
|
+
this.#emitter.off('disconnect', callback);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
removeOnConnectError(
|
|
1352
|
+
callback: (ctx: DbConnectionImpl<RemoteModule>, ...args: any[]) => void
|
|
1353
|
+
): void {
|
|
1354
|
+
this.#emitter.off('connectError', callback);
|
|
1355
|
+
}
|
|
1356
|
+
}
|