react-native-mosquito-transport 0.0.21 → 0.0.23

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,36 @@ 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
+ let staleData;
509
+ if (
510
+ retrieval.startsWith('sticky') &&
511
+ (staleData = await getRecordData())
512
+ ) {
513
+ finalize(staleData);
497
514
  if (retrieval !== RETRIEVAL.STICKY_RELOAD) return;
498
515
  }
499
516
  }
@@ -512,10 +529,9 @@ const findObject = async (builder, config) => {
512
529
  limit,
513
530
  random
514
531
  }),
515
- dbName,
516
- dbUrl
532
+ ...dbName ? { dbName } : undefined,
533
+ ...dbUrl ? { dbUrl } : undefined
517
534
  },
518
- accessKey,
519
535
  authToken: disableAuth ? undefined : Scoped.AuthJWTToken[projectUrl],
520
536
  serverE2E_PublicKey,
521
537
  uglify,
@@ -524,14 +540,16 @@ const findObject = async (builder, config) => {
524
540
 
525
541
  const data = await buildFetchResult(await fetch((findOne ? _readDocument : _queryCollection)(projectUrl, uglify), reqBuilder), uglify);
526
542
 
527
- const result = deserializeBSON((uglify ? await deserializeE2E(data, serverE2E_PublicKey, privateKey) : data).result)._;
543
+ const result = hydrateForeignDoc(
544
+ deserializeBSON((uglify ? await deserializeE2E(data, serverE2E_PublicKey, privateKey) : data).result)._
545
+ );
528
546
 
529
- if (shouldCache) insertRecord(builder, config, accessId, result);
530
- finalize({ liveResult: result || null });
547
+ if (shouldCache) insertRecord(builder, config, accessId, result, episode);
548
+ finalize([result]);
531
549
  } catch (e) {
532
550
  let thisRecord;
533
- const getThisRecord = async () => thisRecord ? thisRecord.value :
534
- (thisRecord = { value: await getRecordData() }).value;
551
+ const getThisRecord = async () => thisRecord ? thisRecord[0] :
552
+ (thisRecord = [await getRecordData()])[0];
535
553
 
536
554
  if (e?.simpleError) {
537
555
  finalize(undefined, e?.simpleError);
@@ -543,10 +561,14 @@ const findObject = async (builder, config) => {
543
561
  finalize(undefined, simplifyCaughtError(e).simpleError);
544
562
  } else if (
545
563
  shouldCache &&
546
- [RETRIEVAL.DEFAULT, RETRIEVAL.CACHE_NO_AWAIT].includes(retrieval) &&
564
+ [
565
+ RETRIEVAL.DEFAULT,
566
+ RETRIEVAL.CACHE_NO_AWAIT,
567
+ RETRIEVAL.CACHE_AWAIT
568
+ ].includes(retrieval) &&
547
569
  await getThisRecord()
548
570
  ) {
549
- finalize({ episode: await getThisRecord() });
571
+ finalize(await getThisRecord());
550
572
  } else if (retries > maxRetries) {
551
573
  finalize(undefined, { error: 'retry_limit_exceeded', message: `retry exceed limit(${maxRetries})` });
552
574
  } else {
@@ -563,7 +585,7 @@ const findObject = async (builder, config) => {
563
585
  }
564
586
  });
565
587
 
566
- return await readValue();
588
+ return (await readValue());
567
589
  };
568
590
 
569
591
  const transformNullRecursively = obj => Object.fromEntries(
@@ -572,6 +594,17 @@ const transformNullRecursively = obj => Object.fromEntries(
572
594
  )
573
595
  );
574
596
 
597
+ const cleanBatchWrite = (value) => cloneDeep(value).map(v => {
598
+ if (Validator.OBJECT(v?.value)) {
599
+ v.value = transformNullRecursively(v.value);
600
+ } else if (Array.isArray(v?.value)) {
601
+ v.value = v.value.map(e =>
602
+ Validator.OBJECT(e) ? transformNullRecursively(e) : e
603
+ );
604
+ }
605
+ return v;
606
+ });
607
+
575
608
  const commitData = async (builder, value, type, config) => {
576
609
  // transform undefined
577
610
  if (Validator.OBJECT(value)) {
@@ -579,28 +612,17 @@ const commitData = async (builder, value, type, config) => {
579
612
  } else if (type === 'batchWrite' && Array.isArray(value)) {
580
613
  value = deserializeBSON(
581
614
  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
- })
615
+ _: cleanBatchWrite(value)
592
616
  })
593
617
  )._;
594
618
  }
595
619
 
596
- const { projectUrl, serverE2E_PublicKey, dbUrl, dbName, accessKey, maxRetries = 7, path, find, disableCache, uglify, extraHeaders } = builder;
620
+ const { projectUrl, serverE2E_PublicKey, dbUrl, dbName, maxRetries = 1, path, find, disableCache, uglify, extraHeaders } = builder;
597
621
  const { disableAuth, delivery = DELIVERY.DEFAULT, stepping } = config || {};
598
622
  const writeId = `${Date.now() + ++Scoped.PendingIte}`;
599
623
  const isBatchWrite = type === 'batchWrite';
600
624
  const shouldCache = (delivery !== DELIVERY.DEFAULT || !disableCache) &&
601
- delivery !== DELIVERY.NO_CACHE &&
602
- delivery !== DELIVERY.NO_AWAIT_NO_CACHE &&
603
- delivery !== DELIVERY.AWAIT_NO_CACHE;
625
+ ![DELIVERY.NO_CACHE_AWAIT, DELIVERY.NO_CACHE_NO_AWAIT].includes();
604
626
 
605
627
  await awaitStore();
606
628
  if (shouldCache) {
@@ -648,10 +670,9 @@ const commitData = async (builder, value, type, config) => {
648
670
  find: find && serializeToBase64(find)
649
671
  }
650
672
  }),
651
- dbName,
652
- dbUrl
673
+ ...dbName ? { dbName } : undefined,
674
+ ...dbUrl ? { dbUrl } : undefined
653
675
  },
654
- accessKey,
655
676
  serverE2E_PublicKey,
656
677
  authToken: disableAuth ? undefined : Scoped.AuthJWTToken[projectUrl],
657
678
  uglify,
@@ -667,27 +688,16 @@ const commitData = async (builder, value, type, config) => {
667
688
  if (e?.simpleError) {
668
689
  console.error(`${type} error (${path}), ${e.simpleError?.message}`);
669
690
  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) {
691
+ } else if (delivery === DELIVERY.NO_CACHE_NO_AWAIT) {
692
+ finalize(undefined, simplifyCaughtError(e).simpleError);
693
+ } else if (retries > maxRetries) {
684
694
  finalize(
685
695
  undefined,
686
696
  { error: 'retry_limit_exceeded', message: `retry exceed limit(${maxRetries})` },
687
697
  { removeCache: true, revertCache: true }
688
698
  );
689
699
  } else {
690
- if (delivery === DELIVERY.AWAIT_NO_CACHE) {
700
+ if (delivery === DELIVERY.NO_CACHE_AWAIT) {
691
701
  const onlineListener = listenReachableServer(connected => {
692
702
  if (connected) {
693
703
  onlineListener();
@@ -703,7 +713,7 @@ const commitData = async (builder, value, type, config) => {
703
713
  }
704
714
  });
705
715
 
706
- return await sendValue();
716
+ return (await sendValue());
707
717
  };
708
718
 
709
719
  export const trySendPendingWrite = (projectUrl) => {
@@ -713,20 +723,28 @@ export const trySendPendingWrite = (projectUrl) => {
713
723
  const sortedWrite = Object.entries(CacheStore.PendingWrites[projectUrl] || {})
714
724
  .filter(([k]) => !Scoped.OutgoingWrites[k])
715
725
  .sort((a, b) => a[1].addedOn - b[1].addedOn);
726
+ let resolveCounts = 0;
716
727
 
717
728
  for (const [writeId, { snapshot, builder, attempts = 1 }] of sortedWrite) {
718
729
  try {
719
- await commitData(builder, snapshot.value, snapshot.type, { ...snapshot.config, delivery: DELIVERY.NO_AWAIT_NO_CACHE });
730
+ await commitData(builder, snapshot.value, snapshot.type, { ...snapshot.config, delivery: DELIVERY.NO_CACHE_NO_AWAIT });
720
731
  delete CacheStore.PendingWrites[projectUrl][writeId];
732
+ ++resolveCounts;
721
733
  } catch (_) {
722
734
  const { maxRetries } = builder;
723
735
  if (!maxRetries || attempts >= maxRetries) {
724
736
  delete CacheStore.PendingWrites[projectUrl][writeId];
737
+ ++resolveCounts;
738
+ } else if (CacheStore.PendingWrites[projectUrl]?.[writeId]) {
739
+ CacheStore.PendingWrites[projectUrl][writeId].attempts = attempts + 1;
725
740
  }
726
741
  }
727
742
  }
728
743
  resolve();
729
744
  Scoped.dispatchingWritesPromise = undefined;
730
- if (sortedWrite.length && await getReachableServer(projectUrl)) trySendPendingWrite(projectUrl);
745
+ if (
746
+ (sortedWrite.length - resolveCounts) &&
747
+ await getReachableServer(projectUrl)
748
+ ) trySendPendingWrite(projectUrl);
731
749
  });
732
750
  };
@@ -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