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