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.
- package/README.md +3 -7
- package/TODO +18 -8
- package/ios/Mosquitodb.swift +0 -4
- package/package.json +8 -7
- package/src/helpers/engine_api.js +2 -7
- package/src/helpers/fs_manager.js +36 -0
- package/src/helpers/peripherals.js +7 -31
- package/src/helpers/purger.js +275 -0
- package/src/helpers/sqlite_manager.js +144 -0
- package/src/helpers/utils.js +77 -43
- package/src/helpers/values.js +5 -20
- package/src/helpers/variables.js +36 -10
- package/src/index.d.ts +97 -56
- package/src/index.js +42 -38
- package/src/products/auth/accessor.js +10 -11
- package/src/products/auth/index.js +13 -28
- package/src/products/database/accessor.js +450 -165
- package/src/products/database/counter.js +12 -10
- package/src/products/database/index.js +180 -162
- package/src/products/database/types.js +1 -0
- package/src/products/database/validator.js +1 -1
- package/src/products/http_callable/accessor.js +77 -0
- package/src/products/http_callable/counter.js +11 -0
- package/src/products/http_callable/index.js +61 -67
- package/src/products/storage/index.js +20 -13
|
@@ -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
|
|
6
|
-
import setLodash from 'lodash
|
|
7
|
-
import unsetLodash from 'lodash
|
|
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
|
|
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 {
|
|
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,
|
|
29
|
-
if (cache) callback(cache[
|
|
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
|
|
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
|
|
51
|
-
const
|
|
52
|
-
|
|
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
|
-
|
|
128
|
+
await Scoped.initedSqliteInstances.database[initNode];
|
|
55
129
|
|
|
56
|
-
|
|
57
|
-
const trackedList = [...tracks || []];
|
|
58
|
-
const ignoreList = [...ignore || []];
|
|
130
|
+
const resultAccessId = `${accessIdWithoutLimit}-${limit}`;
|
|
59
131
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
(
|
|
84
|
-
|
|
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
|
-
|
|
93
|
-
setLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'record', accessId], {
|
|
185
|
+
const newData = {
|
|
94
186
|
command,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
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,
|
|
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 {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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 (
|
|
130
|
-
|
|
131
|
-
|
|
233
|
+
if (!io) {
|
|
234
|
+
const record = await useSqliteLinearAccessId(builder, accessIdWithoutLimit, 'database')(async sqlite => {
|
|
235
|
+
const resultAccessId = `${accessIdWithoutLimit}-${limit}`;
|
|
132
236
|
|
|
133
|
-
|
|
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
|
-
).
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
543
|
+
let scrapDocs = [];
|
|
324
544
|
|
|
325
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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.
|
|
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).
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
373
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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 (
|
|
395
|
-
|
|
396
|
-
|
|
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 <
|
|
405
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
419
|
-
|
|
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
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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 (
|
|
445
|
-
|
|
446
|
-
|
|
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:
|
|
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.
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|