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.
@@ -2,16 +2,19 @@ import { niceHash, shuffleArray, sortArrayByObjectKey } from "../../helpers/peri
2
2
  import { awaitStore, updateCacheStore } from "../../helpers/utils";
3
3
  import { CacheStore, Scoped } from "../../helpers/variables";
4
4
  import { assignExtractionFind, CompareBson, confirmFilterDoc, defaultBSON, downcastBSON, validateCollectionName, validateFilter } from "./validator";
5
- import getLodash from 'lodash.get';
6
- import setLodash from 'lodash.set';
7
- import unsetLodash from 'lodash.unset';
5
+ import getLodash from 'lodash/get';
6
+ import setLodash from 'lodash/set';
7
+ import unsetLodash from 'lodash/unset';
8
8
  import { DatabaseRecordsListener } from "../../helpers/listeners";
9
- import cloneDeep from "lodash.clonedeep";
9
+ import cloneDeep from "lodash/cloneDeep";
10
10
  import { BSONRegExp, ObjectId, Timestamp } from "bson";
11
11
  import { niceGuard, Validator } from "guard-object";
12
12
  import { TIMESTAMP } from "../..";
13
- import { decrementDatabaseSize, incrementDatabaseSize } from "./counter";
14
- import { serializeToBase64 } from "./bson";
13
+ import { docSize, incrementDatabaseSize } from "./counter";
14
+ import { deserializeBSON, serializeToBase64 } from "./bson";
15
+ import { openDB, SQLITE_COMMANDS, SQLITE_PATH, useSqliteLinearAccessId } from "../../helpers/sqlite_manager";
16
+
17
+ const { LIMITER_DATA, LIMITER_RESULT, DB_COUNT_QUERY } = SQLITE_PATH;
15
18
 
16
19
  export const listenQueryEntry = (callback, { accessId, builder, config, processId }) => {
17
20
  const { projectUrl, dbName, dbUrl, path } = builder;
@@ -25,8 +28,8 @@ export const listenQueryEntry = (callback, { accessId, builder, config, processI
25
28
 
26
29
  const listener = DatabaseRecordsListener.listenTo('d', async (dispatchId) => {
27
30
  if (dispatchId !== processId) return;
28
- const cache = await getRecord(builder, config, accessId);
29
- if (cache) callback(cache[episode]);
31
+ const cache = await getRecord(builder, accessId, episode);
32
+ if (cache) callback(cache[0]);
30
33
  });
31
34
 
32
35
  return () => {
@@ -40,100 +43,244 @@ export const listenQueryEntry = (callback, { accessId, builder, config, processI
40
43
  };
41
44
  };
42
45
 
43
- export const insertRecord = async (builder, config, accessId, value) => {
46
+ export const insertCountQuery = async (builder, access_id, value) => {
47
+ const { projectUrl, dbUrl, dbName, path } = builder;
48
+
49
+ const { io } = Scoped.ReleaseCacheData;
50
+ if (io) {
51
+ setLodash(CacheStore.DatabaseCountResult, [projectUrl, dbUrl, dbName, path, access_id], { value, touched: Date.now() });
52
+ } else {
53
+ const initNode = `${projectUrl}_${dbUrl}_${dbName}_${path}`;
54
+ await useSqliteLinearAccessId(builder, access_id, 'dbQueryCount')(async sqlite => {
55
+ if (!Scoped.initedSqliteInstances.dbQueryCount[initNode]) {
56
+ Scoped.initedSqliteInstances.dbQueryCount[initNode] = (async () => {
57
+ await sqlite.executeSql(`CREATE TABLE IF NOT EXISTS ${DB_COUNT_QUERY(path)} ( access_id TEXT PRIMARY KEY, value TEXT, touched INTEGER )`).catch(() => null);
58
+ await Promise.allSettled([
59
+ sqlite.executeSql(SQLITE_COMMANDS.CREATE_INDEX(DB_COUNT_QUERY(path), ['access_id'])),
60
+ // sqlite.executeSql(SQLITE_COMMANDS.CREATE_INDEX(DB_COUNT_QUERY(path), ['touched']))
61
+ ]);
62
+ })();
63
+ }
64
+
65
+ await Scoped.initedSqliteInstances.dbQueryCount[initNode];
66
+ await sqlite.executeSql(
67
+ SQLITE_COMMANDS.MERGE(DB_COUNT_QUERY(path), ['access_id', 'value', 'touched']),
68
+ [access_id, JSON.stringify(value), Date.now()]
69
+ );
70
+ setLodash(CacheStore.DatabaseStats.counters, [projectUrl, dbUrl, dbName, path], true);
71
+ });
72
+ }
73
+ updateCacheStore(undefined, ['DatabaseCountResult'])
74
+ }
75
+
76
+ export const getCountQuery = async (builder, access_id) => {
77
+ const { projectUrl, dbUrl, dbName, path } = builder;
78
+ const { io } = Scoped.ReleaseCacheData;
79
+
80
+ if (io) {
81
+ const data = getLodash(CacheStore.DatabaseCountResult, [projectUrl, dbUrl, dbName, path, access_id]);
82
+ if (data) data.touched = Date.now();
83
+ return data && data.value;
84
+ } else {
85
+ const result = await useSqliteLinearAccessId(builder, access_id, 'dbQueryCount')(sqlite =>
86
+ sqlite.executeSql(`SELECT * FROM ${DB_COUNT_QUERY(path)} WHERE access_id = ?`, [access_id]).then(async r => {
87
+ r = JSON.parse(r[0].rows.item(0).value);
88
+ await sqlite.executeSql(SQLITE_COMMANDS.UPDATE_COLUMNS(DB_COUNT_QUERY(path), ['touched'], 'access_id = ?'), [Date.now(), access_id]);
89
+ return r;
90
+ }).catch(() => undefined)
91
+ );
92
+ return result;
93
+ }
94
+ }
95
+
96
+ export const insertRecord = async (builder, config, accessIdWithoutLimit, value, episode = 0) => {
44
97
  builder = builder && cloneDeep(builder);
45
98
  config = config && cloneDeep(config);
46
99
  value = value && cloneDeep(value);
47
100
 
48
101
  await awaitStore();
102
+ const { io } = Scoped.ReleaseCacheData;
49
103
  const { projectUrl, dbUrl, dbName, path, command } = builder;
50
- const entityId = await generateRecordID({}, config);
51
- const colData = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data', entityId], { config, command, listing: [] });
52
- const trackedData = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'record', accessId]);
104
+ const { limit } = command;
105
+ const thisSize = docSize(value);
106
+
107
+ if (!io) {
108
+ await useSqliteLinearAccessId(builder, accessIdWithoutLimit, 'database')(async (sqlite) => {
109
+ const initNode = `${projectUrl}_${dbUrl}_${dbName}_${path}`;
110
+
111
+ if (!Scoped.initedSqliteInstances.database[initNode]) {
112
+ Scoped.initedSqliteInstances.database[initNode] = (async () => {
113
+ await Promise.allSettled([
114
+ sqlite.executeSql(`CREATE TABLE IF NOT EXISTS ${LIMITER_DATA(path)} ( access_id TEXT PRIMARY KEY, value TEXT, touched INTEGER, size INTEGER )`),
115
+ sqlite.executeSql(`CREATE TABLE IF NOT EXISTS ${LIMITER_RESULT(path)} ( access_id-limit TEXT PRIMARY KEY, access_id TEXT, value TEXT, touched INTEGER, size INTEGER )`)
116
+ ]);
117
+
118
+ await Promise.allSettled([
119
+ sqlite.executeSql(SQLITE_COMMANDS.CREATE_INDEX(LIMITER_DATA(path), ['access_id'])),
120
+ // sqlite.executeSql(SQLITE_COMMANDS.CREATE_INDEX(LIMITER_DATA(path), ['touched'])),
121
+ sqlite.executeSql(SQLITE_COMMANDS.CREATE_INDEX(LIMITER_RESULT(path), ['access_id-limit'])),
122
+ // sqlite.executeSql(SQLITE_COMMANDS.CREATE_INDEX(LIMITER_RESULT(path), ['touched']))
123
+ ]);
124
+ })();
125
+ }
53
126
 
54
- const newList = value ? Array.isArray(value) ? value : [value] : [];
127
+ await Scoped.initedSqliteInstances.database[initNode];
55
128
 
56
- const { tracks, ignore, registeredOn } = trackedData || {};
57
- const trackedList = [...tracks || []];
58
- const ignoreList = [...ignore || []];
129
+ const resultAccessId = `${accessIdWithoutLimit}-${limit}`;
59
130
 
60
- const addSet = (arr, _id) => {
61
- const dex = arr.findIndex(v => CompareBson.equal(v, _id));
62
- if (dex === -1) arr.push(_id);
63
- }
131
+ const [instanceData, resultData] = await Promise.all([
132
+ sqlite.executeSql(`SELECT access_id, size FROM ${LIMITER_DATA(path)} WHERE access_id = ?`, [accessIdWithoutLimit]),
133
+ sqlite.executeSql(`SELECT access_id-limit, size FROM ${LIMITER_RESULT(path)} WHERE access_id-limit = ?`, [resultAccessId])
134
+ ]).then(r =>
135
+ r.map(v => v[0].rows.item(0))
136
+ );
137
+ const isEpisode = episode === 1 || !!resultData;
138
+
139
+ const editionSizeOffset = thisSize - (instanceData?.size || 0);
140
+ const resultSizeOffset = isEpisode ? thisSize - (resultData?.size || 0) : 0;
141
+
142
+ const newData = serializeToBase64({
143
+ command,
144
+ config,
145
+ latest_limiter: limit,
146
+ size: thisSize,
147
+ data: value ? Array.isArray(value) ? value : [value] : []
148
+ });
149
+ const newResultData = isEpisode && serializeToBase64({
150
+ data: value,
151
+ size: thisSize
152
+ });
64
153
 
65
- const deleteSet = (arr, _id) => {
66
- const dex = arr.findIndex(v => CompareBson.equal(v, _id));
67
- if (dex !== -1) arr.splice(dex, 1);
154
+ await Promise.all([
155
+ sqlite.executeSql(
156
+ SQLITE_COMMANDS.MERGE(LIMITER_DATA(path), ['access_id', 'value', 'touched', 'size']),
157
+ [accessIdWithoutLimit, newData, Date.now(), thisSize]
158
+ ),
159
+ isEpisode ?
160
+ sqlite.executeSql(
161
+ SQLITE_COMMANDS.MERGE(LIMITER_RESULT(path), ['access_id-limit', 'access_id', 'value', 'touched', 'size']),
162
+ [resultAccessId, accessIdWithoutLimit, newResultData, Date.now(), thisSize]
163
+ ) : Promise.resolve()
164
+ ]);
165
+ incrementDatabaseSize(builder, path, editionSizeOffset + resultSizeOffset);
166
+ });
167
+ updateCacheStore(undefined, ['DatabaseStore', 'DatabaseStats']);
168
+ return;
68
169
  }
69
170
 
70
- newList.forEach(e => {
71
- const b4DocIndex = colData.listing.findIndex(v => CompareBson.equal(v._id, e._id));
72
- if (b4DocIndex === -1) {
73
- colData.listing.push(e);
74
- incrementDatabaseSize(projectUrl, e);
75
- } else {
76
- decrementDatabaseSize(projectUrl, colData.listing[b4DocIndex]);
77
- incrementDatabaseSize(projectUrl, e);
78
- colData.listing[b4DocIndex] = e;
79
- }
80
- addSet(trackedList, e._id);
81
- });
171
+ const instanceData = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'instance', accessIdWithoutLimit]);
172
+ const resultData = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'episode', accessIdWithoutLimit, limit]);
173
+ const isEpisode = episode === 1 || !!resultData;
82
174
 
83
- (tracks || []).forEach(e => {
84
- if (newList.findIndex(v => CompareBson.equal(v._id, e)) === -1) {
85
- if (colData.listing.findIndex(v => CompareBson.equal(v._id, e)) === -1) {
86
- deleteSet(trackedList, e);
87
- deleteSet(ignoreList, e);
88
- } else addSet(ignoreList, e);
89
- } else deleteSet(ignoreList, e);
90
- });
175
+ const editionSizeOffset = thisSize - (instanceData?.size || 0);
176
+ const resultSizeOffset = isEpisode ? thisSize - (resultData?.size || 0) : 0;
91
177
 
92
- setLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data', entityId], colData);
93
- setLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'record', accessId], {
178
+ const newData = {
94
179
  command,
95
- result: value,
96
- tracks: [...trackedList],
97
- ignore: [...ignoreList],
98
- registeredOn: registeredOn || Date.now(),
99
- updatedOn: Date.now()
100
- });
101
- updateCacheStore();
180
+ config,
181
+ latest_limiter: limit,
182
+ size: thisSize,
183
+ data: value ? Array.isArray(value) ? value : [value] : [],
184
+ touched: Date.now()
185
+ };
186
+ const newResultData = isEpisode && {
187
+ data: value,
188
+ size: thisSize,
189
+ touched: Date.now()
190
+ };
191
+
192
+ incrementDatabaseSize(builder, path, editionSizeOffset + resultSizeOffset);
193
+
194
+ setLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'instance', accessIdWithoutLimit], newData);
195
+ if (isEpisode) setLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'episode', accessIdWithoutLimit, limit], cloneDeep(newResultData));
196
+ updateCacheStore(undefined, ['DatabaseStore', 'DatabaseStats']);
102
197
  };
103
198
 
104
- export const getRecord = async (builder, config, accessId) => {
199
+ export const getRecord = async (builder, accessIdWithoutLimit, episode = 0) => {
105
200
  await awaitStore();
201
+ const { io } = Scoped.ReleaseCacheData;
106
202
  const { projectUrl, dbUrl, dbName, path, command } = builder;
107
- const { find, findOne, sort, direction, limit, random } = command;
108
- const entityId = await generateRecordID({}, config);
109
- const colData = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data', entityId]);
110
- const colRecord = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'record', accessId]);
111
-
112
- if (!colRecord) return null;
113
- let choosenColData = colData.listing.filter(v =>
114
- !colRecord.ignore.includes(v._id) &&
115
- confirmFilterDoc(v, findOne || find || {})
116
- );
203
+ const { limit, sort, direction, random, findOne } = command;
204
+ const isEpisode = episode === 1;
205
+
206
+ const transformData = (data) => {
207
+ data = cloneDeep(data);
208
+ if (random) {
209
+ data = shuffleArray(data);
210
+ } else if (sort) {
211
+ data = sortArrayByObjectKey(data.slice(0), sort);
212
+ if (
213
+ direction === -1 ||
214
+ direction === 'desc' ||
215
+ direction === 'descending'
216
+ ) data = data.slice(0).reverse();
217
+ }
117
218
 
118
- if (random) {
119
- choosenColData = shuffleArray(choosenColData);
120
- } else if (sort) {
121
- sortArrayByObjectKey(choosenColData, sort);
122
- if (
123
- direction === -1 ||
124
- direction === 'desc' ||
125
- direction === 'descending'
126
- ) choosenColData.reverse();
219
+ if (findOne) {
220
+ data = data[0];
221
+ } else if (limit) data = data.slice(0, limit);
222
+
223
+ return data;
127
224
  }
128
225
 
129
- if (findOne) {
130
- choosenColData = choosenColData[0];
131
- } else if (limit) choosenColData.slice(0, limit);
226
+ if (!io) {
227
+ const record = await useSqliteLinearAccessId(builder, accessIdWithoutLimit, 'database')(async sqlite => {
228
+ const resultAccessId = `${accessIdWithoutLimit}-${limit}`;
229
+
230
+ const thisData = await (
231
+ isEpisode ? sqlite.executeSql(`SELECT * FROM ${LIMITER_RESULT(path)} WHERE access_id-limit = ?`, [resultAccessId]) :
232
+ sqlite.executeSql(`SELECT * FROM ${LIMITER_DATA(path)} WHERE access_id = ?`, [accessIdWithoutLimit])
233
+ ).then(v => {
234
+ const d = v[0].rows.item(0);
235
+ if (d) return deserializeBSON(d.value, true);
236
+ }).catch(() => null);
237
+
238
+ if (!thisData) return null;
132
239
 
133
- return [choosenColData, colRecord.result];
240
+ if (isEpisode) {
241
+ await sqlite.executeSql(SQLITE_COMMANDS.UPDATE_COLUMNS(LIMITER_RESULT(path), ['touched'], 'access_id-limit = ?'), [Date.now(), resultAccessId]);
242
+ return [thisData.data];
243
+ }
244
+
245
+ const { latest_limiter, data } = thisData;
246
+
247
+ if (
248
+ latest_limiter === undefined ||
249
+ (Validator.POSITIVE_NUMBER(limit) && latest_limiter >= limit)
250
+ ) {
251
+ await sqlite.executeSql(SQLITE_COMMANDS.UPDATE_COLUMNS(LIMITER_DATA(path), ['touched'], 'access_id = ?'), [Date.now(), accessIdWithoutLimit]);
252
+ return [transformData(data)];
253
+ }
254
+ });
255
+
256
+ return record || null;
257
+ }
258
+
259
+ if (isEpisode) {
260
+ const resultData = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'episode', accessIdWithoutLimit, limit]);
261
+ if (resultData) {
262
+ resultData.touched = Date.now();
263
+ return [cloneDeep(resultData.data)];
264
+ }
265
+ return null;
266
+ }
267
+
268
+ const instanceData = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'instance', accessIdWithoutLimit]);
269
+ if (!instanceData) return null;
270
+ const { latest_limiter, data } = instanceData;
271
+
272
+ if (
273
+ latest_limiter === undefined ||
274
+ (Validator.POSITIVE_NUMBER(limit) && latest_limiter >= limit)
275
+ ) {
276
+ instanceData.touched = Date.now();
277
+ return [transformData(data)];
278
+ }
279
+
280
+ return null;
134
281
  };
135
282
 
136
- export const generateRecordID = (builder, config) => {
283
+ export const generateRecordID = (builder, config, removeLimit) => {
137
284
  builder = builder && cloneDeep(builder);
138
285
  config = config && cloneDeep(config);
139
286
 
@@ -151,16 +298,16 @@ export const generateRecordID = (builder, config) => {
151
298
  }).filter(([_, v]) => v !== undefined)
152
299
  );
153
300
 
154
- if (command) recordObj.command = arrangeCommands(command);
301
+ if (command) recordObj.command = arrangeCommands(command, removeLimit);
155
302
  if (extraction) {
156
- if (Array.isArray(extraction)) recordObj.extraction = extraction.map(arrangeCommands);
303
+ if (Array.isArray(extraction)) recordObj.extraction = extraction.map(v => arrangeCommands(v));
157
304
  else recordObj.extraction = arrangeCommands(extraction);
158
305
  }
159
306
 
160
307
  return niceHash(serializeToBase64(recordObj));
161
308
  };
162
309
 
163
- const arrangeCommands = c => {
310
+ const arrangeCommands = (c, removeLimit) => {
164
311
  c = cloneDeep(c);
165
312
  const sortFind = f => {
166
313
  ['$and', '$or', '$nor'].forEach(n => {
@@ -174,11 +321,12 @@ const arrangeCommands = c => {
174
321
  if (c.sort) c.direction = [-1, 'desc', 'descending'].includes(c.direction) ? 'desc' : 'asc';
175
322
  if (c.find) c.find = sortFind(c.find);
176
323
  if (c.findOne) c.findOne = sortFind(c.findOne);
324
+ if (removeLimit && 'limit' in c) delete c.limit;
177
325
  return sortObject(c);
178
326
  };
179
327
 
180
328
  const sortObject = (o) => Object.fromEntries(
181
- Object.entries(o).sort((a, b) => (a > b) ? 1 : (a < b) ? -1 : 0)
329
+ Object.entries(o).sort(([a], [b]) => (a > b) ? 1 : (a < b) ? -1 : 0)
182
330
  );
183
331
 
184
332
  const recursiveFlat = (a) => {
@@ -270,31 +418,88 @@ export const addPendingWrites = async (builder, writeId, result) => {
270
418
  result = result && cloneDeep(result);
271
419
  await awaitStore();
272
420
 
421
+ const { io } = Scoped.ReleaseCacheData;
273
422
  const { projectUrl, dbUrl, dbName } = builder;
274
423
  const editions = [];
275
424
  const duplicateSets = {};
276
425
  const pathChanges = new Set([]);
426
+ const pendingSnapshot = cloneDeep(result);
277
427
 
278
- (
428
+ await Promise.all((
279
429
  result.type === 'batchWrite' ?
280
430
  result.value.map(({ scope, value, find, path }) =>
281
431
  ({ type: scope, value, find, path })
282
432
  )
283
433
  : [{ ...result, path: builder.path }]
284
- ).forEach(({ value: writeObj, find, type, path }) => {
434
+ ).map(async ({ value: writeObj, find, type, path }) => {
285
435
  WriteValidator[type]({ find, value: writeObj });
286
436
  validateCollectionName(path);
287
437
  pathChanges.add(path);
288
- const colObj = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data'], {});
289
438
 
290
- Object.entries(colObj).forEach(([entityId, { listing, config }]) => {
439
+ if (io) {
440
+ const colObj = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'instance']);
441
+
442
+ if (colObj)
443
+ await Promise.all(
444
+ Object.entries(colObj).map(e =>
445
+ MutateDataInstance(
446
+ e,
447
+ path =>
448
+ Object.values(
449
+ getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'instance'], {})
450
+ ).map(({ data }) => data).flat()
451
+ )
452
+ )
453
+ );
454
+ } else {
455
+ const sqlite = await openDB(builder);
456
+ try {
457
+ const colListing = await sqlite.executeSql(`SELECT access_id FROM ${LIMITER_DATA(path)}`).then(v =>
458
+ v[0].rows.raw().map(d => d.access_id)
459
+ ).catch(() => []);
460
+ const pathFinder = {};
461
+
462
+ await Promise.all(colListing.map(async access_id =>
463
+ useSqliteLinearAccessId(builder, access_id, 'database')(async sqlite => {
464
+ const data = await sqlite.executeSql(`SELECT * FROM ${LIMITER_DATA(path)} WHERE access_id = ?`, [access_id]).then(v =>
465
+ v[0].rows.raw().map(d => [d.access_id, deserializeBSON(d.value, true)])[0]
466
+ );
467
+ await MutateDataInstance(data, path =>
468
+ pathFinder[path] || (
469
+ pathFinder[path] = sqlite.executeSql(`SELECT value FROM ${LIMITER_DATA(path)}`).then(v =>
470
+ v[0].rows.raw().map(d => deserializeBSON(d.value, true).data).flat()
471
+ ).catch(() => [])
472
+ )
473
+ );
474
+ await sqlite.executeSql(
475
+ SQLITE_COMMANDS.MERGE(LIMITER_DATA(path), ['access_id', 'value', 'touched', 'size']),
476
+ [access_id, serializeToBase64(data[1]), Date.now(), data[1].size]
477
+ );
478
+ })
479
+ ));
480
+ } catch (error) {
481
+ throw error;
482
+ } finally {
483
+ sqlite.close();
484
+ }
485
+ }
486
+
487
+ async function MutateDataInstance([entityId, dataObj], pathGetter) {
488
+ const { data: instance_data, command, config } = dataObj;
489
+ const entityFind = command.findOne || command.find;
291
490
  const { extraction } = config || {};
292
491
 
293
492
  const logChanges = (d) => {
294
- editions.push([entityId, d, path]);
493
+ editions.push(cloneDeep([entityId, d, path]));
494
+ const [b4, af] = d;
495
+ const offset = docSize(af) - docSize(b4);
496
+ dataObj.size += offset;
497
+ incrementDatabaseSize(builder, path, offset);
295
498
  };
296
499
 
297
- const accessExtraction = obj => {
500
+ const snipUpdate = doc => snipDocument(doc, entityFind, config);
501
+
502
+ const accessExtraction = async obj => {
298
503
  const buildAssignedExtraction = (data) => {
299
504
  const d = (Array.isArray(extraction) ? extraction : [extraction]).map(thisExtraction => {
300
505
  const query = cloneDeep(thisExtraction);
@@ -311,18 +516,18 @@ export const addPendingWrites = async (builder, writeId, result) => {
311
516
  const extractionResultant = buildAssignedExtraction(obj);
312
517
  const extractionBinary = serializeToBase64({ _: extractionResultant });
313
518
 
314
- const sameProjection = listing.find(({ _foreign_doc, ...restDoc }) =>
519
+ const sameProjection = instance_data.find(({ _foreign_doc, ...restDoc }) =>
315
520
  extractionBinary === serializeToBase64({ _: buildAssignedExtraction(restDoc) })
316
521
  );
317
522
 
318
523
  if (sameProjection) return sameProjection._foreign_doc;
319
524
 
320
- // if no matching extraction was found, proceed to scrapping other paths
321
- const scrapedProjection = (Array.isArray(extractionResultant) ? extractionResultant : [extractionResultant]).map((query, i) => {
525
+ // if no matching extraction was found, proceed to scrapping each _foreign_doc segment
526
+ const scrapedProjection = await Promise.all((Array.isArray(extractionResultant) ? extractionResultant : [extractionResultant]).map(async (query, i) => {
322
527
  const { sort, direction, limit, find, findOne, collection: path } = query;
323
- const scrapDocs = [];
528
+ let scrapDocs = [];
324
529
 
325
- listing.forEach(({ _foreign_doc }) => {
530
+ instance_data.forEach(({ _foreign_doc }) => {
326
531
  _foreign_doc = (Array.isArray(_foreign_doc) ? _foreign_doc : [_foreign_doc])[i];
327
532
 
328
533
  recursiveFlat([_foreign_doc]).forEach(e => {
@@ -331,83 +536,102 @@ export const addPendingWrites = async (builder, writeId, result) => {
331
536
  }
332
537
  });
333
538
  });
539
+
334
540
  if (!scrapDocs.length) {
335
- const scrapYard = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data'], {});
336
- Object.values(scrapYard).forEach(v => {
337
- v.listing.forEach(doc => {
338
- if (confirmFilterDoc(doc, find || findOne)) {
339
- scrapDocs.push(snipDocument(doc, find || findOne, config));
340
- }
341
- });
541
+ // if no matching extraction was found, proceed to scrapping ancestor path
542
+ (await pathGetter(path)).forEach(({ _foreign_doc, ...doc }) => {
543
+ if (confirmFilterDoc(doc, find || findOne)) {
544
+ scrapDocs.push(doc);
545
+ }
342
546
  });
343
547
  }
548
+ scrapDocs = scrapDocs.filter((v, i, a) => a.findIndex(b => b._id === v._id) === i);
344
549
  if (sort) sortArrayByObjectKey(scrapDocs, sort);
345
550
  if ([-1, 'desc', 'descending'].includes(direction)) scrapDocs.reverse();
346
- if (limit) scrapDocs.splice(limit);
551
+ if (limit) scrapDocs = scrapDocs.slice(0, limit);
552
+ scrapDocs = scrapDocs.map(v => snipDocument(v, find || findOne, query));
347
553
 
348
554
  return findOne ? scrapDocs[0] : scrapDocs;
349
- });
555
+ }));
350
556
 
351
- return Array.isArray(extraction) ? scrapedProjection : scrapedProjection[0];
557
+ return cloneDeep(Array.isArray(extraction) ? scrapedProjection : scrapedProjection[0]);
352
558
  }
353
559
 
354
560
  if (['setOne', 'setMany'].includes(type)) {
355
- (type === 'setOne' ? [writeObj] : writeObj).forEach(e => {
356
- if (listing.findIndex(v => CompareBson.equal(v._id, e._id)) === -1) {
357
- const obj = deserializeNonAtomicWrite(e);
358
-
359
- if (extraction) obj._foreign_doc = accessExtraction(obj);
360
- listing.push(obj);
361
- logChanges([undefined, obj]);
362
- } else if (!duplicateSets[e._id])
363
- console.warn(`document with _id=${e._id} already exist locally with ${type}() operation, skipping to online commit`);
364
- duplicateSets[e._id] = true;
365
- });
561
+ await Promise.all((type === 'setOne' ? [writeObj] : writeObj).map(async e => {
562
+ const obj = deserializeNonAtomicWrite(e);
563
+ if (extraction) obj._foreign_doc = await accessExtraction(obj);
564
+
565
+ if (confirmFilterDoc(obj, entityFind)) {
566
+
567
+ if (instance_data.findIndex(v => CompareBson.equal(v._id, e._id)) === -1) {
568
+ const x = snipUpdate(obj);
569
+ instance_data.push(cloneDeep(x));
570
+ logChanges([undefined, x]);
571
+ } else if (!duplicateSets[e._id]) {
572
+ console.warn(`document with _id=${e._id} already exist locally with ${type}() operation, skipping to online commit`);
573
+ duplicateSets[e._id] = true;
574
+ }
575
+ }
576
+ }));
366
577
  return;
367
578
  }
368
579
 
369
580
  if (['putOne', 'replaceOne'].includes(type)) {
370
581
  const extras = createWriteFromFind(find);
371
582
 
372
- for (let i = 0; i < listing.length; i++) {
373
- const doc = listing[i];
583
+ let deletions = 0;
584
+ const cdata = instance_data.slice(0);
585
+
586
+ for (let i = 0; i < cdata.length; i++) {
587
+ const doc = cdata[i];
588
+
374
589
  if (confirmFilterDoc(doc, find)) {
375
590
  const obj = deserializeNonAtomicWrite({
376
591
  ...extras,
377
592
  ...writeObj,
378
593
  ...'_id' in extras ? {} : { _id: doc._id }
379
594
  });
380
-
381
- if (extraction) obj._foreign_doc = accessExtraction(obj);
382
- listing[i] = obj;
383
- logChanges([doc, obj]);
595
+ if (extraction) obj._foreign_doc = await accessExtraction(obj);
596
+
597
+ if (confirmFilterDoc(obj, entityFind)) {
598
+ const x = snipUpdate(obj);
599
+ instance_data[i - deletions] = x;
600
+ logChanges([doc, x]);
601
+ } else {
602
+ instance_data.splice(i - deletions++, 1);
603
+ logChanges([doc, undefined]);
604
+ }
384
605
  return;
385
606
  }
386
607
  }
608
+
387
609
  if (type === 'putOne') {
388
610
  const obj = deserializeNonAtomicWrite({
389
611
  ...extras,
390
612
  ...writeObj,
391
613
  ...'_id' in extras ? {} : { _id: new ObjectId() }
392
614
  });
615
+ if (extraction) obj._foreign_doc = await accessExtraction(obj);
393
616
 
394
- if (extraction) obj._foreign_doc = accessExtraction(obj);
395
- listing.push(obj);
396
- logChanges([undefined, obj]);
617
+ if (confirmFilterDoc(obj, entityFind)) {
618
+ const x = snipUpdate(obj);
619
+ instance_data.push(x);
620
+ logChanges([undefined, x]);
621
+ }
397
622
  }
398
623
  return;
399
624
  }
400
625
 
401
626
  if (['deleteOne', 'deleteMany'].includes(type)) {
402
627
  let deletions = 0;
628
+ const cdata = instance_data.slice(0);
403
629
 
404
- for (let i = 0; i < listing.length; i++) {
405
- const dex = deletions + i;
406
- const doc = listing[dex];
630
+ for (let i = 0; i < cdata.length; i++) {
631
+ const doc = cdata[i];
407
632
  if (confirmFilterDoc(doc, find)) {
408
- listing.splice(dex, 1);
409
- logChanges([doc]);
410
- --deletions;
633
+ instance_data.splice(i - deletions++, 1);
634
+ logChanges([doc, undefined]);
411
635
  if (type === 'deleteOne') return;
412
636
  }
413
637
  }
@@ -415,14 +639,23 @@ export const addPendingWrites = async (builder, writeId, result) => {
415
639
  }
416
640
 
417
641
  let founded;
418
- for (let i = 0; i < listing.length; i++) {
419
- const doc = listing[i];
642
+ let deletions = 0;
643
+ const cdata = instance_data.slice(0);
644
+
645
+ for (let i = 0; i < cdata.length; i++) {
646
+ const doc = cdata[i];
420
647
  if (confirmFilterDoc(doc, find)) {
421
648
  const obj = deserializeAtomicWrite(doc, deserializeWriteValue(writeObj), false, type);
422
-
423
- if (extraction) obj._foreign_doc = accessExtraction(obj);
424
- listing[i] = obj;
425
- logChanges([doc, obj]);
649
+ if (extraction) obj._foreign_doc = await accessExtraction(obj);
650
+
651
+ if (confirmFilterDoc(obj, entityFind)) {
652
+ const x = snipUpdate(obj);
653
+ instance_data[i - deletions] = x;
654
+ logChanges([doc, x]);
655
+ } else {
656
+ instance_data.splice(i - deletions++, 1);
657
+ logChanges([doc, undefined]);
658
+ }
426
659
 
427
660
  founded = true;
428
661
  if (type.endsWith('One')) return;
@@ -440,22 +673,25 @@ export const addPendingWrites = async (builder, writeId, result) => {
440
673
  type
441
674
  )
442
675
  };
676
+ if (extraction) obj._foreign_doc = await accessExtraction(obj);
443
677
 
444
- if (extraction) obj._foreign_doc = accessExtraction(obj);
445
- listing.push(obj);
446
- logChanges([undefined, obj]);
678
+ if (confirmFilterDoc(obj, entityFind)) {
679
+ const x = snipUpdate(obj);
680
+ instance_data.push(x);
681
+ logChanges([undefined, x]);
682
+ }
447
683
  }
448
- });
449
- });
684
+ };
685
+ }));
450
686
 
451
687
  setLodash(CacheStore.PendingWrites, [projectUrl, writeId], cloneDeep({
452
688
  builder,
453
- snapshot: result,
689
+ snapshot: pendingSnapshot,
454
690
  editions,
455
691
  addedOn: Date.now()
456
692
  }));
457
693
 
458
- updateCacheStore();
694
+ updateCacheStore(undefined, ['DatabaseStore', 'PendingWrites', 'DatabaseStats']);
459
695
  notifyDatabaseNodeChanges(builder, [...pathChanges]);
460
696
  };
461
697
 
@@ -463,40 +699,69 @@ export const removePendingWrite = async (builder, writeId, revert) => {
463
699
  await awaitStore();
464
700
  const { projectUrl, dbUrl, dbName } = builder;
465
701
  const pendingData = getLodash(CacheStore.PendingWrites, [projectUrl, writeId]);
702
+ const { io } = Scoped.ReleaseCacheData;
466
703
 
467
704
  if (!pendingData) return;
468
705
  const pathChanges = new Set([]);
469
706
 
470
707
  if (revert) {
471
- pendingData.editions.forEach(([entityId, [b4Doc, afDoc], path]) => {
472
- const colObj = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data']);
473
- const colList = colObj?.[entityId]?.listing;
474
-
475
- if (colList) {
476
- if (afDoc) {
477
- const editedIndex = colList.findIndex(e => CompareBson.equal(e._id, afDoc._id));
478
- if (editedIndex !== -1) {
479
- if (
480
- serializeToBase64(afDoc) === serializeToBase64(colList[editedIndex])
481
- ) {
482
- if (b4Doc) {
483
- colList[editedIndex] = b4Doc;
484
- } else colList.splice(editedIndex, 1);
708
+ await Promise.all(pendingData.editions.map(async ([access_id, [b4Doc, afDoc], path]) => {
709
+ if (io) {
710
+ RevertMutation(getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'instance', access_id]));
711
+ } else {
712
+ await useSqliteLinearAccessId(builder, access_id, 'database')(async sqlite => {
713
+ const colObj = await sqlite.executeSql(`SELECT * FROM ${LIMITER_DATA(path)} WHERE access_id = ?`, [access_id]).then(v =>
714
+ v[0].rows.raw().map(d => deserializeBSON(d.value))[0]
715
+ ).catch(() => null);
716
+ if (!colObj) return;
717
+ RevertMutation(colObj);
718
+ await sqlite.executeSql(
719
+ SQLITE_COMMANDS.MERGE(LIMITER_DATA(path), ['access_id', 'value', 'touched', 'size']),
720
+ [access_id, serializeToBase64(colObj), Date.now(), colObj.size]
721
+ );
722
+ });
723
+ }
724
+
725
+ function RevertMutation(colObj) {
726
+ const colList = colObj?.data;
727
+
728
+ const updateSize = (b4, af) => {
729
+ const offset = docSize(af) - docSize(b4);
730
+ colObj.size += offset;
731
+ incrementDatabaseSize(builder, path, offset);
732
+ }
733
+
734
+ if (colList) {
735
+ if (afDoc) {
736
+ const editedIndex = colList.findIndex(e => CompareBson.equal(e._id, afDoc._id));
737
+ if (editedIndex !== -1) {
738
+ if (
739
+ serializeToBase64(afDoc) === serializeToBase64(colList[editedIndex])
740
+ ) {
741
+ if (b4Doc) {
742
+ colList[editedIndex] = b4Doc;
743
+ updateSize(afDoc, b4Doc);
744
+ } else {
745
+ colList.splice(editedIndex, 1);
746
+ updateSize(afDoc, undefined);
747
+ }
748
+ }
485
749
  }
750
+ } else if (
751
+ b4Doc &&
752
+ colList.findIndex(e => CompareBson.equal(e._id, b4Doc._id)) === -1
753
+ ) {
754
+ colList.push(b4Doc);
755
+ updateSize(undefined, b4Doc);
486
756
  }
487
- } else if (
488
- b4Doc &&
489
- colList.findIndex(e => CompareBson.equal(e._id, b4Doc._id)) === -1
490
- ) {
491
- colList.push(b4Doc);
492
757
  }
758
+ pathChanges.add(path);
493
759
  }
494
- pathChanges.add(path);
495
- });
760
+ }));
496
761
  }
497
762
 
498
763
  unsetLodash(CacheStore.PendingWrites, [projectUrl, writeId]);
499
- updateCacheStore();
764
+ updateCacheStore(undefined, ['PendingWrites', 'DatabaseStore', 'DatabaseStats']);
500
765
  notifyDatabaseNodeChanges(builder, [...pathChanges]);
501
766
  };
502
767
 
@@ -541,7 +806,7 @@ const snipDocument = (data, find, config) => {
541
806
  if (!data || !config) return data;
542
807
  const { returnOnly, excludeFields } = config || {};
543
808
 
544
- let output = { ...data };
809
+ let output = cloneDeep(data);
545
810
 
546
811
  if (returnOnly) {
547
812
  output = {};
@@ -601,7 +866,6 @@ const deserializeWriteValue = (value) => {
601
866
 
602
867
  const deserializeNonAtomicWrite = (writeObj) => deserializeWriteValue(writeObj);
603
868
 
604
-
605
869
  const deserializeAtomicWrite = (b4Doc, writeObj, isNew, type) => {
606
870
  const resultantDoc = { ...b4Doc };
607
871