react-native-mosquito-transport 0.0.21 → 0.0.22

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.
@@ -1,16 +1,18 @@
1
+ import getLodash from "lodash/get";
2
+ import setLodash from 'lodash/set';
1
3
  import { CacheStore } from "../../helpers/variables";
4
+ import { serializeToBase64 } from "./bson";
2
5
 
3
- // TODO: fix this shit
4
- export const incrementDatabaseSize = (projectUrl, doc) => {
6
+ export const incrementDatabaseSize = (builder, path, size) => incrementDatabaseSizeCore(CacheStore.DatabaseStats, builder, path, size);
5
7
 
6
- };
8
+ export const incrementDatabaseSizeCore = (baseObj, builder, path, size = 0) => {
9
+ const { projectUrl, dbUrl, dbName } = builder;
10
+ baseObj._db_size += size;
7
11
 
8
- export const decrementDatabaseSize = (projectUrl, doc) => {
12
+ const node = [projectUrl, dbUrl, dbName, path];
9
13
 
10
- };
14
+ const b4 = getLodash(baseObj.database, node, 0);
15
+ setLodash(baseObj.database, node, b4 + size);
16
+ }
11
17
 
12
- const opDatabaseSize = (projectUrl, doc) => {
13
- // if (!CacheStore.DatabaseStats[projectUrl])
14
- // CacheStore.DatabaseStats[projectUrl] = 0;
15
- // CacheStore.DatabaseStats[projectUrl] += size;
16
- };
18
+ export const docSize = doc => doc ? serializeToBase64({ _: doc }).length : 0;
@@ -4,15 +4,14 @@ import { DatabaseRecordsListener } from "../../helpers/listeners";
4
4
  import { deserializeE2E, listenReachableServer, niceTry, serializeE2E } from "../../helpers/peripherals";
5
5
  import { awaitStore, buildFetchInterface, buildFetchResult, getReachableServer } from "../../helpers/utils";
6
6
  import { CacheStore, Scoped } from "../../helpers/variables";
7
- import { addPendingWrites, generateRecordID, getRecord, insertRecord, listenQueryEntry, removePendingWrite, validateWriteValue } from "./accessor";
7
+ import { addPendingWrites, generateRecordID, getCountQuery, getRecord, insertCountQuery, insertRecord, listenQueryEntry, removePendingWrite, validateWriteValue } from "./accessor";
8
8
  import { validateCollectionName, validateFilter, validateFindConfig, validateFindObject, validateListenFindConfig } from "./validator";
9
9
  import { awaitRefreshToken, listenToken } from "../auth/accessor";
10
10
  import { DELIVERY, RETRIEVAL } from "../../helpers/values";
11
- import setLodash from 'lodash.set';
12
11
  import { ObjectId } from "bson";
13
12
  import { guardObject, Validator } from "guard-object";
14
13
  import { simplifyCaughtError } from "simplify-error";
15
- import cloneDeep from "lodash.clonedeep";
14
+ import cloneDeep from "lodash/cloneDeep";
16
15
  import { deserializeBSON, serializeToBase64 } from "./bson";
17
16
 
18
17
  export class MTCollection {
@@ -67,19 +66,6 @@ export class MTCollection {
67
66
  get: (config) => findObject({ ...this.builder, command: { findOne } }, config)
68
67
  });
69
68
 
70
- onDisconnect = () => ({
71
- setOne: (value) => initOnDisconnectionTask({ ...this.builder }, value, 'setOne'),
72
- setMany: (value) => initOnDisconnectionTask({ ...this.builder }, value, 'setMany'),
73
- updateOne: (find = {}, value) => initOnDisconnectionTask({ ...this.builder, command: { find } }, value, 'updateOne'),
74
- updateMany: (find = {}, value) => initOnDisconnectionTask({ ...this.builder, command: { find } }, value, 'updateMany'),
75
- mergeOne: (find = {}, value) => initOnDisconnectionTask({ ...this.builder, command: { find } }, value, 'mergeOne'),
76
- mergeMany: (find = {}, value) => initOnDisconnectionTask({ ...this.builder, command: { find } }, value, 'mergeMany'),
77
- deleteOne: (find = {}) => initOnDisconnectionTask({ ...this.builder, command: { find } }, undefined, 'deleteOne'),
78
- deleteMany: (find = {}) => initOnDisconnectionTask({ ...this.builder, command: { find } }, undefined, 'deleteMany'),
79
- replaceOne: (find = {}, value) => initOnDisconnectionTask({ ...this.builder, command: { find } }, value, 'replaceOne'),
80
- putOne: (find = {}, value) => initOnDisconnectionTask({ ...this.builder, command: { find } }, value, 'putOne')
81
- });
82
-
83
69
  setOne = (value, config) => commitData(this.builder, value, 'setOne', config);
84
70
 
85
71
  setMany = (value, config) => commitData(this.builder, value, 'setMany', config);
@@ -115,6 +101,26 @@ export class MTCollection {
115
101
  deleteMany = (find = {}, config) => commitData({ ...this.builder, find }, undefined, 'deleteMany', config);
116
102
  };
117
103
 
104
+ export const onCollectionConnect = (builder) => ({
105
+ ...collectionIO(data => ({
106
+ ...initCollectionIO({ connectData: data, builder }),
107
+ onDisconnect: () => collectionIO(data2 =>
108
+ initCollectionIO({ connectData: data, disconnectData: data2, builder })
109
+ )
110
+ })),
111
+ onDisconnect: () => collectionIO(data =>
112
+ initCollectionIO({ disconnectData: data, builder })
113
+ )
114
+ });
115
+
116
+ const collectionIO = (caller) => ({
117
+ batchWrite: (map, config) => caller({ value: map, config })
118
+ });
119
+
120
+ const initCollectionIO = (data) => ({
121
+ start: () => initOnDisconnectionTask(data)
122
+ });
123
+
118
124
  export const batchWrite = (builder, map, config) => commitData({ ...builder }, map, 'batchWrite', config);
119
125
 
120
126
  const {
@@ -130,9 +136,9 @@ const {
130
136
  } = EngineApi;
131
137
 
132
138
  const listenDocument = (callback, onError, builder, config) => {
133
- const { projectUrl, wsPrefix, serverE2E_PublicKey, baseUrl, dbUrl, dbName, accessKey, path, disableCache, command, uglify, extraHeaders, castBSON } = builder;
139
+ const { projectUrl, wsPrefix, serverE2E_PublicKey, baseUrl, dbUrl, dbName, path, disableCache, command, uglify, extraHeaders, castBSON } = builder;
134
140
  const { find, findOne, sort, direction, limit } = command;
135
- const { disableAuth } = config || {};
141
+ const { disableAuth, episode } = config || {};
136
142
  const shouldCache = !disableCache;
137
143
  const processId = `${++Scoped.AnyProcessIte}`;
138
144
  let accessId;
@@ -145,7 +151,6 @@ const listenDocument = (callback, onError, builder, config) => {
145
151
  hasRespond,
146
152
  cacheListener,
147
153
  socket,
148
- wasDisconnected,
149
154
  lastToken = Scoped.AuthJWTToken[projectUrl] || null,
150
155
  lastInitRef = 0,
151
156
  connectedListener,
@@ -159,7 +164,7 @@ const listenDocument = (callback, onError, builder, config) => {
159
164
  };
160
165
 
161
166
  if (shouldCache) {
162
- accessId = generateRecordID(builder, config).then(hash => {
167
+ accessId = generateRecordID(builder, config, true).then(hash => {
163
168
  if (hasCancelled) return hash;
164
169
  cacheListener = listenQueryEntry(snapshot => {
165
170
  if (!Scoped.IS_CONNECTED[projectUrl]) dispatchSnapshot(snapshot);
@@ -193,24 +198,25 @@ const listenDocument = (callback, onError, builder, config) => {
193
198
  direction,
194
199
  limit
195
200
  }),
196
- dbName,
197
- dbUrl
201
+ ...dbName ? { dbName } : undefined,
202
+ ...dbUrl ? { dbUrl } : undefined
198
203
  };
199
204
 
200
- const [encPlate, [privateKey]] = uglify ? await serializeE2E({ accessKey, _body: authObj }, mtoken, serverE2E_PublicKey) : ['', []];
205
+ const [encPlate, [privateKey]] = uglify ? await serializeE2E({ _body: authObj }, mtoken, serverE2E_PublicKey) : ['', []];
201
206
 
202
207
  socket = io(`${wsPrefix}://${baseUrl}`, {
203
208
  transports: ['websocket', 'polling', 'flashsocket'],
204
209
  extraHeaders,
205
- auth: uglify ? { e2e: encPlate.toString('base64'), _m_internal: true } : {
206
- accessKey,
207
- _body: authObj,
208
- ...mtoken ? { mtoken } : {},
209
- _m_internal: true
210
+ auth: {
211
+ ...uglify ? { e2e: encPlate.toString('base64') } : {
212
+ _body: authObj,
213
+ ...mtoken ? { mtoken } : {}
214
+ },
215
+ _m_internal: true,
216
+ _m_route: (findOne ? _listenDocument : _listenCollection)(uglify)
210
217
  }
211
218
  });
212
219
 
213
- socket.emit((findOne ? _listenDocument : _listenCollection)(uglify));
214
220
  socket.on('mSnapshot', async ([err, snapshot]) => {
215
221
  hasRespond = true;
216
222
  if (err) {
@@ -219,20 +225,12 @@ const listenDocument = (callback, onError, builder, config) => {
219
225
  } else console.error('unhandled listen for:', { path, find }, ' error:', err);
220
226
  } else {
221
227
  if (uglify) snapshot = await deserializeE2E(snapshot, serverE2E_PublicKey, privateKey);
222
- snapshot = deserializeBSON(snapshot)._;
228
+ snapshot = hydrateForeignDoc(deserializeBSON(snapshot)._);
223
229
  dispatchSnapshot(snapshot);
224
230
 
225
- if (shouldCache) insertRecord(builder, config, await accessId, snapshot);
231
+ if (shouldCache) insertRecord(builder, config, await accessId, snapshot, episode);
226
232
  }
227
233
  });
228
-
229
- socket.on('connect', () => {
230
- if (wasDisconnected) socket.emit((findOne ? _listenDocument : _listenCollection)(uglify));
231
- });
232
-
233
- socket.on('disconnect', () => {
234
- wasDisconnected = true;
235
- });
236
234
  };
237
235
 
238
236
  init();
@@ -240,10 +238,10 @@ const listenDocument = (callback, onError, builder, config) => {
240
238
  const tokenListener = listenToken(t => {
241
239
  if ((t || null) !== lastToken) {
242
240
  socket?.close?.();
243
- wasDisconnected = undefined;
241
+ socket = undefined;
244
242
  init();
245
243
  }
246
- lastToken = t;
244
+ lastToken = t || null;
247
245
  }, projectUrl);
248
246
 
249
247
  return () => {
@@ -256,17 +254,30 @@ const listenDocument = (callback, onError, builder, config) => {
256
254
  }
257
255
  };
258
256
 
259
- const initOnDisconnectionTask = (builder, value, type) => {
260
- const { projectUrl, wsPrefix, baseUrl, serverE2E_PublicKey, dbUrl, dbName, accessKey, path, extraHeaders, command, uglify } = builder;
261
- const { find } = command || {};
257
+ const initOnDisconnectionTask = ({ builder, connectData, disconnectData }) => {
258
+ const { projectUrl, wsPrefix, baseUrl, serverE2E_PublicKey, dbUrl, dbName, extraHeaders, uglify } = builder;
262
259
  const disableAuth = false;
263
260
 
264
- validateCollectionName(path);
265
- validateWriteValue({ type, find, value });
261
+ [connectData, disconnectData].forEach((e) => {
262
+ if (e) {
263
+ if (e.config !== undefined)
264
+ guardObject({
265
+ stepping: t => t === undefined || Validator.BOOLEAN(t)
266
+ }).validate(e.config);
267
+
268
+ cleanBatchWrite(e.value).forEach(e => {
269
+ const { scope, find, value, path } = e;
270
+ validateCollectionName(path);
271
+ validateWriteValue({ find, value, type: scope });
272
+ });
273
+ }
274
+ });
266
275
 
267
276
  let hasCancelled,
277
+ /**
278
+ * @type {import('socket.io-client').Socket}
279
+ */
268
280
  socket,
269
- wasDisconnected,
270
281
  lastToken = Scoped.AuthJWTToken[projectUrl] || null,
271
282
  lastInitRef = 0;
272
283
 
@@ -276,68 +287,64 @@ const initOnDisconnectionTask = (builder, value, type) => {
276
287
  if (hasCancelled || processID !== lastInitRef) return;
277
288
 
278
289
  const mtoken = disableAuth ? undefined : Scoped.AuthJWTToken[projectUrl];
290
+ const makeObj = (d) => ({
291
+ ...d?.config,
292
+ value: serializeToBase64({ _: cleanBatchWrite(d.value) })
293
+ });
294
+
279
295
  const authObj = {
280
- commands: stripUndefined({
281
- path,
282
- find: find && serializeToBase64(find),
283
- value: value && serializeToBase64({ _: value }),
284
- scope: type
285
- }),
286
- dbName,
287
- dbUrl
296
+ commands: {
297
+ ...connectData ? { connectTask: makeObj(connectData) } : {},
298
+ ...disconnectData ? { disconnectTask: makeObj(disconnectData) } : {}
299
+ },
300
+ ...dbName ? { dbName } : undefined,
301
+ ...dbUrl ? { dbUrl } : undefined
288
302
  };
289
303
 
290
304
  socket = io(`${wsPrefix}://${baseUrl}`, {
291
305
  transports: ['websocket', 'polling', 'flashsocket'],
292
306
  extraHeaders,
293
- auth: uglify ? {
294
- e2e: (await serializeE2E({ accessKey, _body: authObj }, mtoken, serverE2E_PublicKey))[0].toString('base64'),
295
- _m_internal: true
296
- } : {
297
- ...mtoken ? { mtoken } : {},
298
- accessKey,
299
- _body: authObj,
300
- _m_internal: true
307
+ auth: {
308
+ ...uglify ? {
309
+ e2e: (await serializeE2E({ _body: authObj }, mtoken, serverE2E_PublicKey))[0].toString('base64')
310
+ } : {
311
+ ...mtoken ? { mtoken } : {},
312
+ _body: authObj
313
+ },
314
+ _m_internal: true,
315
+ _m_route: _startDisconnectWriteTask(uglify)
301
316
  }
302
317
  });
303
- socket.emit(_startDisconnectWriteTask(uglify));
304
-
305
- socket.on('connect', () => {
306
- if (wasDisconnected) socket.emit(_startDisconnectWriteTask(uglify));
307
- });
308
-
309
- socket.on('disconnect', () => {
310
- wasDisconnected = true;
311
- });
312
318
  };
313
319
 
314
320
  init();
315
321
 
316
- const tokenListener = listenToken(async t => {
322
+ const tokenListener = disableAuth ? undefined : listenToken(async t => {
317
323
  if ((t || null) !== lastToken) {
318
324
  if (socket) {
319
- await niceTry(() => socket.timeout(7000).emitWithAck(_cancelDisconnectWriteTask(uglify)));
320
325
  socket.close();
321
- }
322
- wasDisconnected = undefined;
323
- init();
326
+ socket = undefined;
327
+ setTimeout(init, 500);
328
+ } else init();
324
329
  }
325
330
  lastToken = t;
326
331
  }, projectUrl);
327
332
 
328
333
  return () => {
329
334
  if (hasCancelled) return;
330
- tokenListener();
331
- if (socket)
332
- niceTry(() => socket.timeout(7000).emitWithAck(_cancelDisconnectWriteTask(uglify))).then(() => {
333
- socket.close();
334
- });
335
335
  hasCancelled = true;
336
+ tokenListener?.();
337
+ if (socket) {
338
+ const thisSocket = socket;
339
+ return niceTry(() => thisSocket.timeout(5000).emitWithAck(_cancelDisconnectWriteTask(uglify))).finally(() => {
340
+ thisSocket.close();
341
+ });
342
+ }
336
343
  };
337
344
  };
338
345
 
339
346
  const countCollection = async (builder, config) => {
340
- const { projectUrl, serverE2E_PublicKey, dbUrl, dbName, accessKey, maxRetries = 7, uglify, extraHeaders, path, disableCache, command = {} } = builder;
347
+ const { projectUrl, serverE2E_PublicKey, dbUrl, dbName, maxRetries = 1, uglify, extraHeaders, path, disableCache, command = {} } = builder;
341
348
  const { find } = command;
342
349
  const { disableAuth } = config || {};
343
350
  const accessId = await generateRecordID({ ...builder, countDoc: true }, config);
@@ -368,10 +375,9 @@ const countCollection = async (builder, config) => {
368
375
  const [reqBuilder, [privateKey]] = await buildFetchInterface({
369
376
  body: {
370
377
  commands: { path, find: serializeToBase64(find) },
371
- dbName,
372
- dbUrl
378
+ ...dbName ? { dbName } : undefined,
379
+ ...dbUrl ? { dbUrl } : undefined
373
380
  },
374
- accessKey,
375
381
  ...disableAuth ? {} : { authToken: Scoped.AuthJWTToken[projectUrl] },
376
382
  serverE2E_PublicKey,
377
383
  uglify,
@@ -382,12 +388,11 @@ const countCollection = async (builder, config) => {
382
388
 
383
389
  const f = uglify ? await deserializeE2E(data, serverE2E_PublicKey, privateKey) : data;
384
390
 
385
- if (!disableCache)
386
- setLodash(CacheStore.DatabaseCountResult, [projectUrl, dbUrl, dbName, accessId], f.result);
387
-
388
391
  finalize(f.result);
392
+
393
+ if (!disableCache) insertCountQuery(builder, accessId, f.result);
389
394
  } catch (e) {
390
- const b4Data = setLodash(CacheStore.DatabaseCountResult, [projectUrl, dbUrl, dbName, accessId]);
395
+ const b4Data = await getCountQuery(builder, accessId).catch(() => null);
391
396
 
392
397
  if (e?.simpleError) {
393
398
  finalize(undefined, e.simpleError);
@@ -424,26 +429,40 @@ const stripUndefined = o => Object.fromEntries(
424
429
  Object.entries(o).filter(v => v[1] !== undefined)
425
430
  );
426
431
 
432
+ const hydrateForeignDoc = ({ data, doc_holder }) => {
433
+ const isList = Array.isArray(data);
434
+ const filled = (isList ? data : [data]).map(v => {
435
+ if (v?._foreign_doc) {
436
+ v._foreign_doc = Array.isArray(v._foreign_doc)
437
+ ? v._foreign_doc.map(k => doc_holder[k])
438
+ : doc_holder[v._foreign_doc];
439
+ }
440
+ return v;
441
+ });
442
+ return isList ? filled : filled[0];
443
+ }
444
+
427
445
  const transformBSON = (d, castBSON) => {
428
446
  if (castBSON) return d && deserializeBSON(serializeToBase64({ _: d }), true)._;
429
447
  return cloneDeep(d);
430
448
  };
431
449
 
432
450
  const findObject = async (builder, config) => {
433
- const { projectUrl, serverE2E_PublicKey, dbUrl, dbName, accessKey, maxRetries = 7, path, disableCache, uglify, extraHeaders, command, castBSON } = builder;
451
+ const { projectUrl, serverE2E_PublicKey, dbUrl, dbName, maxRetries = 1, path, disableCache = false, uglify, extraHeaders, command, castBSON } = builder;
452
+ const pureConfig = stripRequestConfig(config);
453
+ validateFindObject(command);
454
+ validateFindConfig(config);
455
+ validateCollectionName(path);
456
+
434
457
  const { find, findOne, sort, direction, limit, random } = command;
435
458
  const { retrieval = RETRIEVAL.DEFAULT, episode = 0, disableAuth, disableMinimizer } = config || {};
436
459
  const enableMinimizer = !disableMinimizer;
437
- const accessId = await generateRecordID(builder, config);
438
- const processAccessId = `${accessId}${projectUrl}${dbUrl}${dbName}${retrieval}`;
439
- const getRecordData = () => getRecord(builder, config, accessId);
460
+ const accessId = await generateRecordID(builder, config, true);
461
+ const processAccessId = `${accessId}_${limit}_${episode}_${projectUrl}_${dbUrl}_${dbName}_${retrieval}_${disableCache}`;
462
+ const getRecordData = () => getRecord(builder, accessId, episode);
440
463
  const shouldCache = (retrieval !== RETRIEVAL.DEFAULT || !disableCache) &&
441
464
  ![RETRIEVAL.NO_CACHE_NO_AWAIT, RETRIEVAL.NO_CACHE_AWAIT].includes(retrieval);
442
465
 
443
- const pureConfig = stripRequestConfig(config);
444
- validateFindObject(command);
445
- validateFindConfig(config);
446
- validateCollectionName(path);
447
466
  await awaitStore();
448
467
 
449
468
  let retries = 0, hasFinalize;
@@ -453,10 +472,7 @@ const findObject = async (builder, config) => {
453
472
  instantProcess = retryProcess === 1;
454
473
 
455
474
  const finalize = (a, b) => {
456
- const res = (instantProcess && a) ?
457
- (a.liveResult || a.liveResult === null) ?
458
- transformBSON(a.liveResult || undefined, castBSON) :
459
- transformBSON(a.episode[episode], castBSON) : a;
475
+ const res = (instantProcess && a) ? transformBSON(a[0] || undefined, castBSON) : a;
460
476
 
461
477
  if (a) {
462
478
  resolve(instantProcess ? cloneDeep(res) : a);
@@ -465,35 +481,33 @@ const findObject = async (builder, config) => {
465
481
  hasFinalize = true;
466
482
 
467
483
  if (enableMinimizer) {
468
- (Scoped.PendingDbReadCollective.pendingResolution[processAccessId] || []).forEach(e => {
484
+ const resolutionList = (Scoped.PendingDbReadCollective[processAccessId] || []).slice(0);
485
+
486
+ if (Scoped.PendingDbReadCollective[processAccessId])
487
+ delete Scoped.PendingDbReadCollective[processAccessId];
488
+
489
+ resolutionList.forEach(e => {
469
490
  e(a ? { result: res } : undefined, b);
470
491
  });
471
- if (Scoped.PendingDbReadCollective.pendingResolution[processAccessId])
472
- delete Scoped.PendingDbReadCollective.pendingResolution[processAccessId];
473
-
474
- if (Scoped.PendingDbReadCollective.pendingProcess[processAccessId])
475
- delete Scoped.PendingDbReadCollective.pendingProcess[processAccessId];
476
492
  }
477
493
  };
478
494
 
479
495
  try {
480
496
  if (instantProcess) {
481
497
  if (enableMinimizer) {
482
- if (Scoped.PendingDbReadCollective.pendingProcess[processAccessId]) {
483
- if (!Scoped.PendingDbReadCollective.pendingResolution[processAccessId])
484
- Scoped.PendingDbReadCollective.pendingResolution[processAccessId] = [];
485
-
486
- Scoped.PendingDbReadCollective.pendingResolution[processAccessId].push((a, b) => {
498
+ if (Scoped.PendingDbReadCollective[processAccessId]) {
499
+ Scoped.PendingDbReadCollective[processAccessId].push((a, b) => {
487
500
  if (a) resolve(cloneDeep(a.result));
488
501
  else reject(cloneDeep(b));
489
502
  });
490
503
  return;
491
504
  }
492
- Scoped.PendingDbReadCollective.pendingProcess[processAccessId] = true;
505
+ Scoped.PendingDbReadCollective[processAccessId] = [];
493
506
  }
494
507
 
495
- if (retrieval.startsWith('sticky') && await getRecordData()) {
496
- finalize({ episode: await getRecordData() });
508
+ const staleData = await getRecordData();
509
+ if (retrieval.startsWith('sticky') && staleData) {
510
+ finalize(staleData);
497
511
  if (retrieval !== RETRIEVAL.STICKY_RELOAD) return;
498
512
  }
499
513
  }
@@ -512,10 +526,9 @@ const findObject = async (builder, config) => {
512
526
  limit,
513
527
  random
514
528
  }),
515
- dbName,
516
- dbUrl
529
+ ...dbName ? { dbName } : undefined,
530
+ ...dbUrl ? { dbUrl } : undefined
517
531
  },
518
- accessKey,
519
532
  authToken: disableAuth ? undefined : Scoped.AuthJWTToken[projectUrl],
520
533
  serverE2E_PublicKey,
521
534
  uglify,
@@ -524,14 +537,16 @@ const findObject = async (builder, config) => {
524
537
 
525
538
  const data = await buildFetchResult(await fetch((findOne ? _readDocument : _queryCollection)(projectUrl, uglify), reqBuilder), uglify);
526
539
 
527
- const result = deserializeBSON((uglify ? await deserializeE2E(data, serverE2E_PublicKey, privateKey) : data).result)._;
540
+ const result = hydrateForeignDoc(
541
+ deserializeBSON((uglify ? await deserializeE2E(data, serverE2E_PublicKey, privateKey) : data).result)._
542
+ );
528
543
 
529
- if (shouldCache) insertRecord(builder, config, accessId, result);
530
- finalize({ liveResult: result || null });
544
+ if (shouldCache) insertRecord(builder, config, accessId, result, episode);
545
+ finalize([result]);
531
546
  } catch (e) {
532
547
  let thisRecord;
533
- const getThisRecord = async () => thisRecord ? thisRecord.value :
534
- (thisRecord = { value: await getRecordData() }).value;
548
+ const getThisRecord = async () => thisRecord ? thisRecord[0] :
549
+ (thisRecord = [await getRecordData()])[0];
535
550
 
536
551
  if (e?.simpleError) {
537
552
  finalize(undefined, e?.simpleError);
@@ -543,10 +558,14 @@ const findObject = async (builder, config) => {
543
558
  finalize(undefined, simplifyCaughtError(e).simpleError);
544
559
  } else if (
545
560
  shouldCache &&
546
- [RETRIEVAL.DEFAULT, RETRIEVAL.CACHE_NO_AWAIT].includes(retrieval) &&
561
+ [
562
+ RETRIEVAL.DEFAULT,
563
+ RETRIEVAL.CACHE_NO_AWAIT,
564
+ RETRIEVAL.CACHE_AWAIT
565
+ ].includes(retrieval) &&
547
566
  await getThisRecord()
548
567
  ) {
549
- finalize({ episode: await getThisRecord() });
568
+ finalize(await getThisRecord());
550
569
  } else if (retries > maxRetries) {
551
570
  finalize(undefined, { error: 'retry_limit_exceeded', message: `retry exceed limit(${maxRetries})` });
552
571
  } else {
@@ -563,7 +582,7 @@ const findObject = async (builder, config) => {
563
582
  }
564
583
  });
565
584
 
566
- return await readValue();
585
+ return (await readValue());
567
586
  };
568
587
 
569
588
  const transformNullRecursively = obj => Object.fromEntries(
@@ -572,6 +591,17 @@ const transformNullRecursively = obj => Object.fromEntries(
572
591
  )
573
592
  );
574
593
 
594
+ const cleanBatchWrite = (value) => cloneDeep(value).map(v => {
595
+ if (Validator.OBJECT(v?.value)) {
596
+ v.value = transformNullRecursively(v.value);
597
+ } else if (Array.isArray(v?.value)) {
598
+ v.value = v.value.map(e =>
599
+ Validator.OBJECT(e) ? transformNullRecursively(e) : e
600
+ );
601
+ }
602
+ return v;
603
+ });
604
+
575
605
  const commitData = async (builder, value, type, config) => {
576
606
  // transform undefined
577
607
  if (Validator.OBJECT(value)) {
@@ -579,28 +609,17 @@ const commitData = async (builder, value, type, config) => {
579
609
  } else if (type === 'batchWrite' && Array.isArray(value)) {
580
610
  value = deserializeBSON(
581
611
  serializeToBase64({
582
- _: value.map(v => {
583
- if (Validator.OBJECT(v?.value)) {
584
- v.value = transformNullRecursively(v.value);
585
- } else if (Array.isArray(v?.value)) {
586
- v.value = v.value.map(e =>
587
- Validator.OBJECT(e) ? transformNullRecursively(e) : e
588
- );
589
- }
590
- return v;
591
- })
612
+ _: cleanBatchWrite(value)
592
613
  })
593
614
  )._;
594
615
  }
595
616
 
596
- const { projectUrl, serverE2E_PublicKey, dbUrl, dbName, accessKey, maxRetries = 7, path, find, disableCache, uglify, extraHeaders } = builder;
617
+ const { projectUrl, serverE2E_PublicKey, dbUrl, dbName, maxRetries = 1, path, find, disableCache, uglify, extraHeaders } = builder;
597
618
  const { disableAuth, delivery = DELIVERY.DEFAULT, stepping } = config || {};
598
619
  const writeId = `${Date.now() + ++Scoped.PendingIte}`;
599
620
  const isBatchWrite = type === 'batchWrite';
600
621
  const shouldCache = (delivery !== DELIVERY.DEFAULT || !disableCache) &&
601
- delivery !== DELIVERY.NO_CACHE &&
602
- delivery !== DELIVERY.NO_AWAIT_NO_CACHE &&
603
- delivery !== DELIVERY.AWAIT_NO_CACHE;
622
+ ![DELIVERY.NO_CACHE_AWAIT, DELIVERY.NO_CACHE_NO_AWAIT].includes();
604
623
 
605
624
  await awaitStore();
606
625
  if (shouldCache) {
@@ -648,10 +667,9 @@ const commitData = async (builder, value, type, config) => {
648
667
  find: find && serializeToBase64(find)
649
668
  }
650
669
  }),
651
- dbName,
652
- dbUrl
670
+ ...dbName ? { dbName } : undefined,
671
+ ...dbUrl ? { dbUrl } : undefined
653
672
  },
654
- accessKey,
655
673
  serverE2E_PublicKey,
656
674
  authToken: disableAuth ? undefined : Scoped.AuthJWTToken[projectUrl],
657
675
  uglify,
@@ -667,27 +685,16 @@ const commitData = async (builder, value, type, config) => {
667
685
  if (e?.simpleError) {
668
686
  console.error(`${type} error (${path}), ${e.simpleError?.message}`);
669
687
  finalize(undefined, e?.simpleError, { removeCache: true, revertCache: true });
670
- } else if (
671
- [
672
- DELIVERY.NO_AWAIT,
673
- DELIVERY.CACHE_NO_AWAIT,
674
- DELIVERY.NO_AWAIT_NO_CACHE,
675
- DELIVERY.NO_CACHE
676
- ].includes(delivery)
677
- ) {
678
- finalize(
679
- undefined,
680
- simplifyCaughtError(e).simpleError,
681
- await getReachableServer(projectUrl) ? { removeCache: true } : undefined
682
- );
683
- } else if (retries >= maxRetries) {
688
+ } else if (delivery === DELIVERY.NO_CACHE_NO_AWAIT) {
689
+ finalize(undefined, simplifyCaughtError(e).simpleError);
690
+ } else if (retries > maxRetries) {
684
691
  finalize(
685
692
  undefined,
686
693
  { error: 'retry_limit_exceeded', message: `retry exceed limit(${maxRetries})` },
687
694
  { removeCache: true, revertCache: true }
688
695
  );
689
696
  } else {
690
- if (delivery === DELIVERY.AWAIT_NO_CACHE) {
697
+ if (delivery === DELIVERY.NO_CACHE_AWAIT) {
691
698
  const onlineListener = listenReachableServer(connected => {
692
699
  if (connected) {
693
700
  onlineListener();
@@ -703,7 +710,7 @@ const commitData = async (builder, value, type, config) => {
703
710
  }
704
711
  });
705
712
 
706
- return await sendValue();
713
+ return (await sendValue());
707
714
  };
708
715
 
709
716
  export const trySendPendingWrite = (projectUrl) => {
@@ -713,20 +720,28 @@ export const trySendPendingWrite = (projectUrl) => {
713
720
  const sortedWrite = Object.entries(CacheStore.PendingWrites[projectUrl] || {})
714
721
  .filter(([k]) => !Scoped.OutgoingWrites[k])
715
722
  .sort((a, b) => a[1].addedOn - b[1].addedOn);
723
+ let resolveCounts = 0;
716
724
 
717
725
  for (const [writeId, { snapshot, builder, attempts = 1 }] of sortedWrite) {
718
726
  try {
719
- await commitData(builder, snapshot.value, snapshot.type, { ...snapshot.config, delivery: DELIVERY.NO_AWAIT_NO_CACHE });
727
+ await commitData(builder, snapshot.value, snapshot.type, { ...snapshot.config, delivery: DELIVERY.NO_CACHE_NO_AWAIT });
720
728
  delete CacheStore.PendingWrites[projectUrl][writeId];
729
+ ++resolveCounts;
721
730
  } catch (_) {
722
731
  const { maxRetries } = builder;
723
732
  if (!maxRetries || attempts >= maxRetries) {
724
733
  delete CacheStore.PendingWrites[projectUrl][writeId];
734
+ ++resolveCounts;
735
+ } else if (CacheStore.PendingWrites[projectUrl]?.[writeId]) {
736
+ CacheStore.PendingWrites[projectUrl][writeId].attempts = attempts + 1;
725
737
  }
726
738
  }
727
739
  }
728
740
  resolve();
729
741
  Scoped.dispatchingWritesPromise = undefined;
730
- if (sortedWrite.length && await getReachableServer(projectUrl)) trySendPendingWrite(projectUrl);
742
+ if (
743
+ (sortedWrite.length - resolveCounts) &&
744
+ await getReachableServer(projectUrl)
745
+ ) trySendPendingWrite(projectUrl);
731
746
  });
732
747
  };
@@ -1,5 +1,6 @@
1
1
 
2
2
  export const TIMESTAMP = { $timestamp: "now" };
3
+ export const TIMESTAMP_OFFSET = (v) => ({ $timestamp_offset: v });
3
4
 
4
5
  export const IS_TIMESTAMP = (t) => t && (typeof t.$timestamp === 'number' || t.$timestamp === 'now') && Object.keys(t).length === 1;
5
6