react-native-mosquito-transport 0.0.19 → 0.0.21
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/.jshintignore +4 -0
- package/.jshintrc +16 -0
- package/README.md +75 -1
- package/TODO +8 -1
- package/ios/Mosquitodb.swift +14 -1
- package/package.json +13 -13
- package/src/helpers/engine_api.js +39 -0
- package/src/helpers/peripherals.js +73 -127
- package/src/helpers/utils.js +48 -19
- package/src/helpers/values.js +8 -47
- package/src/helpers/variables.js +14 -6
- package/src/index.d.ts +103 -43
- package/src/index.js +171 -96
- package/src/products/auth/accessor.js +97 -36
- package/src/products/auth/index.js +151 -82
- package/src/products/database/accessor.js +720 -223
- package/src/products/database/bson.js +16 -0
- package/src/products/database/counter.js +16 -0
- package/src/products/database/index.js +303 -190
- package/src/products/database/types.js +1 -1
- package/src/products/database/validator.js +517 -254
- package/src/products/http_callable/index.js +111 -106
- package/src/products/storage/index.js +97 -88
- package/src/helpers/EngineApi.js +0 -33
|
@@ -1,317 +1,814 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { niceHash, shuffleArray, sortArrayByObjectKey } from "../../helpers/peripherals";
|
|
2
2
|
import { awaitStore, updateCacheStore } from "../../helpers/utils";
|
|
3
|
-
import { CacheStore } from "../../helpers/variables";
|
|
4
|
-
import { confirmFilterDoc } from "./validator";
|
|
3
|
+
import { CacheStore, Scoped } from "../../helpers/variables";
|
|
4
|
+
import { assignExtractionFind, CompareBson, confirmFilterDoc, defaultBSON, downcastBSON, validateCollectionName, validateFilter } from "./validator";
|
|
5
5
|
import getLodash from 'lodash.get';
|
|
6
6
|
import setLodash from 'lodash.set';
|
|
7
7
|
import unsetLodash from 'lodash.unset';
|
|
8
|
-
import isEqual from 'lodash.isequal';
|
|
9
|
-
import { DEFAULT_DB_NAME, DEFAULT_DB_URL, DELIVERY, RETRIEVAL, WRITE_OPS, WRITE_OPS_LIST } from "../../helpers/values";
|
|
10
8
|
import { DatabaseRecordsListener } from "../../helpers/listeners";
|
|
9
|
+
import cloneDeep from "lodash.clonedeep";
|
|
10
|
+
import { BSONRegExp, ObjectId, Timestamp } from "bson";
|
|
11
|
+
import { niceGuard, Validator } from "guard-object";
|
|
12
|
+
import { TIMESTAMP } from "../..";
|
|
13
|
+
import { decrementDatabaseSize, incrementDatabaseSize } from "./counter";
|
|
14
|
+
import { serializeToBase64 } from "./bson";
|
|
11
15
|
|
|
12
16
|
export const listenQueryEntry = (callback, { accessId, builder, config, processId }) => {
|
|
13
|
-
|
|
17
|
+
const { projectUrl, dbName, dbUrl, path } = builder;
|
|
14
18
|
const { episode = 0 } = config || {};
|
|
15
19
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
const nodeID = `${projectUrl}${dbName}${dbUrl}${path}`;
|
|
21
|
+
|
|
22
|
+
if (!Scoped.ActiveDatabaseListeners[nodeID])
|
|
23
|
+
Scoped.ActiveDatabaseListeners[nodeID] = {};
|
|
24
|
+
Scoped.ActiveDatabaseListeners[nodeID][processId] = Date.now();
|
|
25
|
+
|
|
26
|
+
const listener = DatabaseRecordsListener.listenTo('d', async (dispatchId) => {
|
|
27
|
+
if (dispatchId !== processId) return;
|
|
28
|
+
const cache = await getRecord(builder, config, accessId);
|
|
29
|
+
if (cache) callback(cache[episode]);
|
|
24
30
|
});
|
|
25
31
|
|
|
26
32
|
return () => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
listener();
|
|
34
|
+
if (Scoped.ActiveDatabaseListeners[nodeID]?.[processId]) {
|
|
35
|
+
delete Scoped.ActiveDatabaseListeners[nodeID][processId];
|
|
36
|
+
|
|
37
|
+
if (!Object.keys(Scoped.ActiveDatabaseListeners[nodeID]).length)
|
|
38
|
+
delete Scoped.ActiveDatabaseListeners[nodeID];
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const insertRecord = async (builder, config, accessId, value) => {
|
|
44
|
+
builder = builder && cloneDeep(builder);
|
|
45
|
+
config = config && cloneDeep(config);
|
|
46
|
+
value = value && cloneDeep(value);
|
|
31
47
|
|
|
32
|
-
export const insertRecord = async (builder, accessId, query, value) => {
|
|
33
48
|
await awaitStore();
|
|
34
|
-
const { projectUrl, dbUrl
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
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]);
|
|
53
|
+
|
|
54
|
+
const newList = value ? Array.isArray(value) ? value : [value] : [];
|
|
55
|
+
|
|
56
|
+
const { tracks, ignore, registeredOn } = trackedData || {};
|
|
57
|
+
const trackedList = [...tracks || []];
|
|
58
|
+
const ignoreList = [...ignore || []];
|
|
59
|
+
|
|
60
|
+
const addSet = (arr, _id) => {
|
|
61
|
+
const dex = arr.findIndex(v => CompareBson.equal(v, _id));
|
|
62
|
+
if (dex === -1) arr.push(_id);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const deleteSet = (arr, _id) => {
|
|
66
|
+
const dex = arr.findIndex(v => CompareBson.equal(v, _id));
|
|
67
|
+
if (dex !== -1) arr.splice(dex, 1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
newList.forEach(e => {
|
|
71
|
+
const b4DocIndex = colData.listing.findIndex(v => CompareBson.equal(v._id, e._id));
|
|
42
72
|
if (b4DocIndex === -1) {
|
|
43
|
-
colData.push(e);
|
|
44
|
-
|
|
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
|
+
});
|
|
82
|
+
|
|
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);
|
|
45
90
|
});
|
|
46
91
|
|
|
47
|
-
setLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data',
|
|
92
|
+
setLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data', entityId], colData);
|
|
48
93
|
setLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'record', accessId], {
|
|
49
|
-
|
|
94
|
+
command,
|
|
50
95
|
result: value,
|
|
51
|
-
|
|
96
|
+
tracks: [...trackedList],
|
|
97
|
+
ignore: [...ignoreList],
|
|
98
|
+
registeredOn: registeredOn || Date.now(),
|
|
99
|
+
updatedOn: Date.now()
|
|
52
100
|
});
|
|
53
101
|
updateCacheStore();
|
|
54
|
-
}
|
|
102
|
+
};
|
|
55
103
|
|
|
56
|
-
export const getRecord = async (builder, accessId) => {
|
|
104
|
+
export const getRecord = async (builder, config, accessId) => {
|
|
57
105
|
await awaitStore();
|
|
58
|
-
const { projectUrl, dbUrl
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
colRecord = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'record', accessId]);
|
|
106
|
+
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]);
|
|
64
111
|
|
|
65
112
|
if (!colRecord) return null;
|
|
66
|
-
let choosenColData = colData.filter(v =>
|
|
113
|
+
let choosenColData = colData.listing.filter(v =>
|
|
114
|
+
!colRecord.ignore.includes(v._id) &&
|
|
115
|
+
confirmFilterDoc(v, findOne || find || {})
|
|
116
|
+
);
|
|
67
117
|
|
|
68
118
|
if (random) {
|
|
69
119
|
choosenColData = shuffleArray(choosenColData);
|
|
70
120
|
} else if (sort) {
|
|
71
|
-
|
|
121
|
+
sortArrayByObjectKey(choosenColData, sort);
|
|
72
122
|
if (
|
|
73
123
|
direction === -1 ||
|
|
74
124
|
direction === 'desc' ||
|
|
75
125
|
direction === 'descending'
|
|
76
|
-
) choosenColData
|
|
126
|
+
) choosenColData.reverse();
|
|
77
127
|
}
|
|
78
128
|
|
|
79
129
|
if (findOne) {
|
|
80
130
|
choosenColData = choosenColData[0];
|
|
81
|
-
} else if (limit) choosenColData.
|
|
131
|
+
} else if (limit) choosenColData.slice(0, limit);
|
|
82
132
|
|
|
83
133
|
return [choosenColData, colRecord.result];
|
|
84
|
-
}
|
|
134
|
+
};
|
|
85
135
|
|
|
86
136
|
export const generateRecordID = (builder, config) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
137
|
+
builder = builder && cloneDeep(builder);
|
|
138
|
+
config = config && cloneDeep(config);
|
|
139
|
+
|
|
140
|
+
const { command, path, countDoc } = builder;
|
|
141
|
+
const { extraction, excludeFields, returnOnly } = config || {};
|
|
142
|
+
|
|
143
|
+
const recordObj = Object.fromEntries(
|
|
144
|
+
Object.entries({
|
|
145
|
+
path,
|
|
146
|
+
command,
|
|
147
|
+
countDoc,
|
|
148
|
+
extraction,
|
|
149
|
+
excludeFields,
|
|
150
|
+
returnOnly
|
|
151
|
+
}).filter(([_, v]) => v !== undefined)
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (command) recordObj.command = arrangeCommands(command);
|
|
155
|
+
if (extraction) {
|
|
156
|
+
if (Array.isArray(extraction)) recordObj.extraction = extraction.map(arrangeCommands);
|
|
157
|
+
else recordObj.extraction = arrangeCommands(extraction);
|
|
158
|
+
}
|
|
91
159
|
|
|
92
|
-
return
|
|
93
|
-
}
|
|
160
|
+
return niceHash(serializeToBase64(recordObj));
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const arrangeCommands = c => {
|
|
164
|
+
c = cloneDeep(c);
|
|
165
|
+
const sortFind = f => {
|
|
166
|
+
['$and', '$or', '$nor'].forEach(n => {
|
|
167
|
+
if (f[n]) {
|
|
168
|
+
f[n] = f[n].map(v => sortObject(v));
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return sortObject(f);
|
|
173
|
+
};
|
|
174
|
+
if (c.sort) c.direction = [-1, 'desc', 'descending'].includes(c.direction) ? 'desc' : 'asc';
|
|
175
|
+
if (c.find) c.find = sortFind(c.find);
|
|
176
|
+
if (c.findOne) c.findOne = sortFind(c.findOne);
|
|
177
|
+
return sortObject(c);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const sortObject = (o) => Object.fromEntries(
|
|
181
|
+
Object.entries(o).sort((a, b) => (a > b) ? 1 : (a < b) ? -1 : 0)
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const recursiveFlat = (a) => {
|
|
185
|
+
return a.map(v => Array.isArray(v) ? recursiveFlat(v) : v).flat();
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const recurseNonAtomicWrite = (obj, i, type) => {
|
|
189
|
+
if (!Validator.OBJECT(obj)) throw `expected a document but got ${obj}`;
|
|
190
|
+
Object.entries(obj).forEach(([k, v]) => {
|
|
191
|
+
if (!i) {
|
|
192
|
+
if (k === '_id') throw `avoid providing "_id" for ${type}() operation as _id only reference a single document`;
|
|
193
|
+
if (k === '_foreign_doc') throw '"_foreign_doc" is readonly';
|
|
194
|
+
}
|
|
195
|
+
if (k.includes('$') || k.includes('.')) {
|
|
196
|
+
if (!(k === '$timestamp' && v === 'now'))
|
|
197
|
+
throw `invalid property "${k}", ${type}() operation fields must not contain .$`;
|
|
198
|
+
}
|
|
199
|
+
if (Validator.OBJECT(v)) recurseNonAtomicWrite(v, i + 1, type);
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const recurseAtomicWrite = (obj, i, type) => {
|
|
204
|
+
if (!Validator.OBJECT(obj)) throw `expected a document but got ${obj}`;
|
|
205
|
+
Object.entries(obj).forEach(([k, v]) => {
|
|
206
|
+
if (!i && !(k in AtomicWriter)) throw `Unknown update operator: ${k}`;
|
|
207
|
+
if (i === 1) {
|
|
208
|
+
if ((k === '_id' || k.startsWith('_id.')))
|
|
209
|
+
throw `avoid providing "_id" for ${type}() operation as _id only reference a single document`;
|
|
210
|
+
|
|
211
|
+
if (k === '_foreign_doc' || k.startsWith('_foreign_doc.'))
|
|
212
|
+
throw '"_foreign_doc" is readonly';
|
|
213
|
+
}
|
|
214
|
+
if (k.includes('.$')) throw `unsupported operation at "${k}"`;
|
|
215
|
+
if (!i || Validator.OBJECT(v)) recurseAtomicWrite(v, i + 1, type);
|
|
216
|
+
});
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const WriteValidator = {
|
|
220
|
+
setOne: ({ value, type = 'setOne' }) => {
|
|
221
|
+
if (!Validator.OBJECT(value)) throw `expected a document but got ${value}`;
|
|
222
|
+
const { _id, ...rest } = value;
|
|
223
|
+
|
|
224
|
+
if (_id === undefined || JSON.stringify(_id) === 'null')
|
|
225
|
+
throw `_id requires a valid bson value but got ${_id}`;
|
|
226
|
+
|
|
227
|
+
recurseNonAtomicWrite(rest, 0, type);
|
|
228
|
+
},
|
|
229
|
+
setMany: ({ value }) => {
|
|
230
|
+
value.forEach(v => {
|
|
231
|
+
WriteValidator.setOne({ value: v, type: 'setMany' });
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
replaceOne: ({ find, value }) => {
|
|
235
|
+
validateFilter(find);
|
|
236
|
+
recurseNonAtomicWrite(value, 0, 'replaceOne');
|
|
237
|
+
},
|
|
238
|
+
putOne: ({ find, value }) => {
|
|
239
|
+
validateFilter(find);
|
|
240
|
+
recurseNonAtomicWrite(value, 0, 'putOne');
|
|
241
|
+
},
|
|
242
|
+
updateOne: ({ find, value }) => {
|
|
243
|
+
validateFilter(find);
|
|
244
|
+
recurseAtomicWrite(value, 0, 'updateOne');
|
|
245
|
+
},
|
|
246
|
+
updateMany: ({ find, value }) => {
|
|
247
|
+
validateFilter(find);
|
|
248
|
+
recurseAtomicWrite(value, 0, 'updateMany');
|
|
249
|
+
},
|
|
250
|
+
mergeOne: ({ find, value }) => {
|
|
251
|
+
validateFilter(find);
|
|
252
|
+
recurseAtomicWrite(value, 0, 'mergeOne');
|
|
253
|
+
},
|
|
254
|
+
mergeMany: ({ find, value }) => {
|
|
255
|
+
validateFilter(find);
|
|
256
|
+
recurseAtomicWrite(value, 0, 'mergeMany');
|
|
257
|
+
},
|
|
258
|
+
deleteOne: ({ find }) => {
|
|
259
|
+
validateFilter(find);
|
|
260
|
+
},
|
|
261
|
+
deleteMany: ({ find }) => {
|
|
262
|
+
validateFilter(find);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export const validateWriteValue = ({ type, find, value }) => WriteValidator[type]({ find, value, type });
|
|
94
267
|
|
|
95
268
|
export const addPendingWrites = async (builder, writeId, result) => {
|
|
269
|
+
builder = builder && cloneDeep(builder);
|
|
270
|
+
result = result && cloneDeep(result);
|
|
96
271
|
await awaitStore();
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
} else {
|
|
136
|
-
afDoc = deserializeNonAtomicWrite({ ...writeObj });
|
|
137
|
-
hasEndCommit = true;
|
|
138
|
-
}
|
|
272
|
+
|
|
273
|
+
const { projectUrl, dbUrl, dbName } = builder;
|
|
274
|
+
const editions = [];
|
|
275
|
+
const duplicateSets = {};
|
|
276
|
+
const pathChanges = new Set([]);
|
|
277
|
+
|
|
278
|
+
(
|
|
279
|
+
result.type === 'batchWrite' ?
|
|
280
|
+
result.value.map(({ scope, value, find, path }) =>
|
|
281
|
+
({ type: scope, value, find, path })
|
|
282
|
+
)
|
|
283
|
+
: [{ ...result, path: builder.path }]
|
|
284
|
+
).forEach(({ value: writeObj, find, type, path }) => {
|
|
285
|
+
WriteValidator[type]({ find, value: writeObj });
|
|
286
|
+
validateCollectionName(path);
|
|
287
|
+
pathChanges.add(path);
|
|
288
|
+
const colObj = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data'], {});
|
|
289
|
+
|
|
290
|
+
Object.entries(colObj).forEach(([entityId, { listing, config }]) => {
|
|
291
|
+
const { extraction } = config || {};
|
|
292
|
+
|
|
293
|
+
const logChanges = (d) => {
|
|
294
|
+
editions.push([entityId, d, path]);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const accessExtraction = obj => {
|
|
298
|
+
const buildAssignedExtraction = (data) => {
|
|
299
|
+
const d = (Array.isArray(extraction) ? extraction : [extraction]).map(thisExtraction => {
|
|
300
|
+
const query = cloneDeep(thisExtraction);
|
|
301
|
+
|
|
302
|
+
['find', 'findOne'].forEach(n => {
|
|
303
|
+
if (query[n])
|
|
304
|
+
query[n] = assignExtractionFind(data, query[n]);
|
|
305
|
+
});
|
|
306
|
+
return arrangeCommands(query);
|
|
307
|
+
});
|
|
308
|
+
if (Array.isArray(extraction)) return d;
|
|
309
|
+
return d[0];
|
|
139
310
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
311
|
+
const extractionResultant = buildAssignedExtraction(obj);
|
|
312
|
+
const extractionBinary = serializeToBase64({ _: extractionResultant });
|
|
313
|
+
|
|
314
|
+
const sameProjection = listing.find(({ _foreign_doc, ...restDoc }) =>
|
|
315
|
+
extractionBinary === serializeToBase64({ _: buildAssignedExtraction(restDoc) })
|
|
316
|
+
);
|
|
144
317
|
|
|
145
|
-
|
|
146
|
-
let hasNoID;
|
|
318
|
+
if (sameProjection) return sameProjection._foreign_doc;
|
|
147
319
|
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
320
|
+
// if no matching extraction was found, proceed to scrapping other paths
|
|
321
|
+
const scrapedProjection = (Array.isArray(extractionResultant) ? extractionResultant : [extractionResultant]).map((query, i) => {
|
|
322
|
+
const { sort, direction, limit, find, findOne, collection: path } = query;
|
|
323
|
+
const scrapDocs = [];
|
|
151
324
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
325
|
+
listing.forEach(({ _foreign_doc }) => {
|
|
326
|
+
_foreign_doc = (Array.isArray(_foreign_doc) ? _foreign_doc : [_foreign_doc])[i];
|
|
327
|
+
|
|
328
|
+
recursiveFlat([_foreign_doc]).forEach(e => {
|
|
329
|
+
if (e && confirmFilterDoc(e, find || findOne)) {
|
|
330
|
+
scrapDocs.push(e);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
157
333
|
});
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
334
|
+
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
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
if (sort) sortArrayByObjectKey(scrapDocs, sort);
|
|
345
|
+
if ([-1, 'desc', 'descending'].includes(direction)) scrapDocs.reverse();
|
|
346
|
+
if (limit) scrapDocs.splice(limit);
|
|
347
|
+
|
|
348
|
+
return findOne ? scrapDocs[0] : scrapDocs;
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
return Array.isArray(extraction) ? scrapedProjection : scrapedProjection[0];
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
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
|
+
});
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (['putOne', 'replaceOne'].includes(type)) {
|
|
370
|
+
const extras = createWriteFromFind(find);
|
|
371
|
+
|
|
372
|
+
for (let i = 0; i < listing.length; i++) {
|
|
373
|
+
const doc = listing[i];
|
|
374
|
+
if (confirmFilterDoc(doc, find)) {
|
|
375
|
+
const obj = deserializeNonAtomicWrite({
|
|
376
|
+
...extras,
|
|
377
|
+
...writeObj,
|
|
378
|
+
...'_id' in extras ? {} : { _id: doc._id }
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
if (extraction) obj._foreign_doc = accessExtraction(obj);
|
|
382
|
+
listing[i] = obj;
|
|
383
|
+
logChanges([doc, obj]);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (type === 'putOne') {
|
|
388
|
+
const obj = deserializeNonAtomicWrite({
|
|
389
|
+
...extras,
|
|
390
|
+
...writeObj,
|
|
391
|
+
...'_id' in extras ? {} : { _id: new ObjectId() }
|
|
170
392
|
});
|
|
171
|
-
|
|
393
|
+
|
|
394
|
+
if (extraction) obj._foreign_doc = accessExtraction(obj);
|
|
395
|
+
listing.push(obj);
|
|
396
|
+
logChanges([undefined, obj]);
|
|
397
|
+
}
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (['deleteOne', 'deleteMany'].includes(type)) {
|
|
402
|
+
let deletions = 0;
|
|
403
|
+
|
|
404
|
+
for (let i = 0; i < listing.length; i++) {
|
|
405
|
+
const dex = deletions + i;
|
|
406
|
+
const doc = listing[dex];
|
|
407
|
+
if (confirmFilterDoc(doc, find)) {
|
|
408
|
+
listing.splice(dex, 1);
|
|
409
|
+
logChanges([doc]);
|
|
410
|
+
--deletions;
|
|
411
|
+
if (type === 'deleteOne') return;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return;
|
|
172
415
|
}
|
|
173
|
-
if (hasNoID) console.error(`no data found locally and _id was not provided for ${type}() operation, skipping local and proceeding to online commit`);
|
|
174
|
-
}
|
|
175
|
-
editions.push([kaf, editionSet]);
|
|
176
|
-
});
|
|
177
416
|
|
|
178
|
-
|
|
179
|
-
|
|
417
|
+
let founded;
|
|
418
|
+
for (let i = 0; i < listing.length; i++) {
|
|
419
|
+
const doc = listing[i];
|
|
420
|
+
if (confirmFilterDoc(doc, find)) {
|
|
421
|
+
const obj = deserializeAtomicWrite(doc, deserializeWriteValue(writeObj), false, type);
|
|
180
422
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
423
|
+
if (extraction) obj._foreign_doc = accessExtraction(obj);
|
|
424
|
+
listing[i] = obj;
|
|
425
|
+
logChanges([doc, obj]);
|
|
426
|
+
|
|
427
|
+
founded = true;
|
|
428
|
+
if (type.endsWith('One')) return;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (!founded && type.startsWith('merge')) {
|
|
433
|
+
const extras = createWriteFromFind(find);
|
|
434
|
+
const obj = {
|
|
435
|
+
...extras,
|
|
436
|
+
...deserializeAtomicWrite(
|
|
437
|
+
{ _id: '_id' in extras ? extras._id : new ObjectId() },
|
|
438
|
+
deserializeWriteValue(writeObj),
|
|
439
|
+
true,
|
|
440
|
+
type
|
|
441
|
+
)
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
if (extraction) obj._foreign_doc = accessExtraction(obj);
|
|
445
|
+
listing.push(obj);
|
|
446
|
+
logChanges([undefined, obj]);
|
|
187
447
|
}
|
|
188
448
|
});
|
|
189
449
|
});
|
|
190
450
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
value: writeObj,
|
|
195
|
-
type,
|
|
451
|
+
setLodash(CacheStore.PendingWrites, [projectUrl, writeId], cloneDeep({
|
|
452
|
+
builder,
|
|
453
|
+
snapshot: result,
|
|
196
454
|
editions,
|
|
197
455
|
addedOn: Date.now()
|
|
198
|
-
});
|
|
456
|
+
}));
|
|
199
457
|
|
|
200
458
|
updateCacheStore();
|
|
201
|
-
notifyDatabaseNodeChanges(builder);
|
|
202
|
-
}
|
|
459
|
+
notifyDatabaseNodeChanges(builder, [...pathChanges]);
|
|
460
|
+
};
|
|
203
461
|
|
|
204
462
|
export const removePendingWrite = async (builder, writeId, revert) => {
|
|
205
463
|
await awaitStore();
|
|
206
|
-
const { projectUrl, dbUrl
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (revert
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
464
|
+
const { projectUrl, dbUrl, dbName } = builder;
|
|
465
|
+
const pendingData = getLodash(CacheStore.PendingWrites, [projectUrl, writeId]);
|
|
466
|
+
|
|
467
|
+
if (!pendingData) return;
|
|
468
|
+
const pathChanges = new Set([]);
|
|
469
|
+
|
|
470
|
+
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);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
} else if (
|
|
488
|
+
b4Doc &&
|
|
489
|
+
colList.findIndex(e => CompareBson.equal(e._id, b4Doc._id)) === -1
|
|
490
|
+
) {
|
|
491
|
+
colList.push(b4Doc);
|
|
222
492
|
}
|
|
223
|
-
}
|
|
493
|
+
}
|
|
494
|
+
pathChanges.add(path);
|
|
224
495
|
});
|
|
496
|
+
}
|
|
225
497
|
|
|
226
|
-
unsetLodash(CacheStore.PendingWrites, [projectUrl,
|
|
498
|
+
unsetLodash(CacheStore.PendingWrites, [projectUrl, writeId]);
|
|
227
499
|
updateCacheStore();
|
|
228
|
-
notifyDatabaseNodeChanges(builder);
|
|
229
|
-
}
|
|
500
|
+
notifyDatabaseNodeChanges(builder, [...pathChanges]);
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
const notifyDatabaseNodeChanges = (builder, changedCollections = []) => {
|
|
504
|
+
const { projectUrl, dbName, dbUrl } = builder;
|
|
505
|
+
|
|
506
|
+
changedCollections.forEach(path => {
|
|
507
|
+
const nodeID = `${projectUrl}${dbName}${dbUrl}${path}`;
|
|
508
|
+
Object.entries(Scoped.ActiveDatabaseListeners[nodeID] || {})
|
|
509
|
+
.sort((a, b) => a[1] - b[1])
|
|
510
|
+
.forEach(([processId]) => {
|
|
511
|
+
DatabaseRecordsListener.dispatch('d', processId);
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
};
|
|
230
515
|
|
|
231
|
-
|
|
516
|
+
const createWriteFromFind = (find) => {
|
|
517
|
+
let result = {};
|
|
232
518
|
|
|
233
|
-
|
|
519
|
+
Object.entries(find).forEach(([k, v]) => {
|
|
520
|
+
if (['$and', '$or'].includes(k)) {
|
|
521
|
+
v.forEach(e => {
|
|
522
|
+
result = { ...result, ...createWriteFromFind(e) };
|
|
523
|
+
});
|
|
524
|
+
} else if (!k.startsWith('$')) {
|
|
525
|
+
if (Validator.OBJECT(v)) {
|
|
526
|
+
if (!Object.keys(v).some(v => v.startsWith('$'))) {
|
|
527
|
+
result[k] = v;
|
|
528
|
+
} else if ('$eq' in v) {
|
|
529
|
+
result[k] = v.$eq;
|
|
530
|
+
}
|
|
531
|
+
} else {
|
|
532
|
+
result[k] = v instanceof RegExp ? new BSONRegExp(v.source, v.flags) : v;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
});
|
|
234
536
|
|
|
235
|
-
|
|
537
|
+
return result;
|
|
538
|
+
};
|
|
236
539
|
|
|
237
|
-
|
|
540
|
+
const snipDocument = (data, find, config) => {
|
|
541
|
+
if (!data || !config) return data;
|
|
542
|
+
const { returnOnly, excludeFields } = config || {};
|
|
238
543
|
|
|
239
|
-
|
|
240
|
-
const bj = {};
|
|
544
|
+
let output = { ...data };
|
|
241
545
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
546
|
+
if (returnOnly) {
|
|
547
|
+
output = {};
|
|
548
|
+
(Array.isArray(returnOnly) ? returnOnly : [returnOnly]).filter(v => v).forEach(e => {
|
|
549
|
+
const thisData = getLodash(data, e);
|
|
550
|
+
if (thisData) setLodash(output, e, thisData);
|
|
551
|
+
});
|
|
552
|
+
} else if (excludeFields) {
|
|
553
|
+
(Array.isArray(excludeFields) ? excludeFields : [excludeFields]).filter(v => v).forEach(e => {
|
|
554
|
+
if (getLodash(data, e) && e !== '_id') unsetLodash(output, e);
|
|
555
|
+
});
|
|
556
|
+
}
|
|
245
557
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
558
|
+
getFindFields(find).forEach(field => {
|
|
559
|
+
if (!getLodash(output, field)) {
|
|
560
|
+
const mainData = getLodash(data, field);
|
|
561
|
+
if (mainData !== undefined) setLodash(output, field, mainData);
|
|
249
562
|
}
|
|
563
|
+
});
|
|
250
564
|
|
|
251
|
-
|
|
565
|
+
return output;
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
const getFindFields = (find) => {
|
|
569
|
+
const result = ['_id'];
|
|
570
|
+
|
|
571
|
+
Object.entries(find).forEach(([k, v]) => {
|
|
572
|
+
if (['$and', '$or', '$nor'].includes(k)) {
|
|
573
|
+
v.forEach(e => {
|
|
574
|
+
result.push(...getFindFields(e));
|
|
575
|
+
});
|
|
576
|
+
} else if (k === '$text') {
|
|
577
|
+
result.push(...Array.isArray(v.$field) ? v.$field : [v.$field]);
|
|
578
|
+
} else if (!k.startsWith('$')) {
|
|
579
|
+
result.push(k);
|
|
580
|
+
}
|
|
252
581
|
});
|
|
253
|
-
|
|
582
|
+
|
|
583
|
+
return result.filter((v, i, a) => a.findIndex(b => b === v) === i);
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
const deserializeWriteValue = (value) => {
|
|
587
|
+
if (!value) return value;
|
|
588
|
+
|
|
589
|
+
if (niceGuard(TIMESTAMP, value)) {
|
|
590
|
+
return Date.now();
|
|
591
|
+
} else if (Validator.OBJECT(value)) {
|
|
592
|
+
return Object.fromEntries(
|
|
593
|
+
Object.entries(value).map(([k, v]) =>
|
|
594
|
+
Validator.JSON(v) ? [k, deserializeWriteValue(v)] : [k, v]
|
|
595
|
+
)
|
|
596
|
+
);
|
|
597
|
+
} else if (Array.isArray(value)) {
|
|
598
|
+
return value.map(deserializeWriteValue);
|
|
599
|
+
} else return value;
|
|
254
600
|
}
|
|
255
601
|
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
602
|
+
const deserializeNonAtomicWrite = (writeObj) => deserializeWriteValue(writeObj);
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
const deserializeAtomicWrite = (b4Doc, writeObj, isNew, type) => {
|
|
606
|
+
const resultantDoc = { ...b4Doc };
|
|
607
|
+
|
|
608
|
+
Object.entries(writeObj).forEach(([key, value]) => {
|
|
609
|
+
if (key in AtomicWriter) {
|
|
610
|
+
if (Validator.OBJECT(value)) {
|
|
611
|
+
Object.entries(value).forEach(([k, v]) => {
|
|
612
|
+
AtomicWriter[key](k, v, resultantDoc, isNew, type);
|
|
613
|
+
});
|
|
614
|
+
} else throw `expected an object at ${key} but got ${value}`;
|
|
615
|
+
} else if (key.startsWith('$')) {
|
|
616
|
+
throw `Unknown update operator: ${key}`;
|
|
617
|
+
} else throw 'MongoInvalidArgumentError: Update document requires atomic operators';
|
|
618
|
+
});
|
|
259
619
|
|
|
260
|
-
|
|
261
|
-
|
|
620
|
+
return resultantDoc;
|
|
621
|
+
};
|
|
262
622
|
|
|
263
|
-
|
|
264
|
-
|
|
623
|
+
const AtomicWriter = {
|
|
624
|
+
$currentDate: (field, value, object) => {
|
|
625
|
+
const isDate = value === true || niceGuard({ $type: "date" }, value);
|
|
626
|
+
const isTimestamp = niceGuard({ $type: "timestamp" }, value);
|
|
265
627
|
|
|
266
628
|
if (
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
) {
|
|
271
|
-
|
|
272
|
-
|
|
629
|
+
!isDate &&
|
|
630
|
+
!isTimestamp
|
|
631
|
+
) throw `invalid value at $currentDate.${field}, expected any of boolean (true), { $type: "timestamp" } or { $type: "date" } but got ${value}`;
|
|
632
|
+
setLodash(object, field, isDate ? new Date() : new Timestamp({ t: Math.floor(Date.now() / 1000), i: 0 }));
|
|
633
|
+
},
|
|
634
|
+
$inc: (field, value, object) => {
|
|
635
|
+
const current = getLodash(object, field);
|
|
636
|
+
if (current === null) {
|
|
637
|
+
console.warn(`cannot use $inc operator on a null value at ${field}`);
|
|
638
|
+
return;
|
|
273
639
|
}
|
|
640
|
+
const castedCurrent = downcastBSON(current);
|
|
641
|
+
const castedValue = downcastBSON(value);
|
|
274
642
|
|
|
275
|
-
|
|
643
|
+
if (!Validator.NUMBER(castedValue)) throw `expected a number at $inc.${field} but got ${value}`;
|
|
276
644
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
645
|
+
setLodash(object, field, Validator.NUMBER(castedCurrent) ? defaultBSON(castedCurrent + castedValue, current) : value);
|
|
646
|
+
},
|
|
647
|
+
$min: (field, value, object) => {
|
|
648
|
+
const current = getLodash(object, field);
|
|
649
|
+
if (CompareBson.lesser(value, current)) {
|
|
650
|
+
setLodash(object, field, value);
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
$max: (field, value, object) => {
|
|
654
|
+
const current = getLodash(object, field);
|
|
655
|
+
if (CompareBson.greater(value, current)) {
|
|
656
|
+
setLodash(object, field, value);
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
$mul: (field, value, object) => {
|
|
660
|
+
const current = getLodash(object, field);
|
|
661
|
+
const castedValue = downcastBSON(value);
|
|
662
|
+
const castedCurrent = downcastBSON(current);
|
|
663
|
+
|
|
664
|
+
if (!Validator.NUMBER(castedValue))
|
|
665
|
+
throw `expected a number at $mul.${field} but got ${value}`;
|
|
666
|
+
|
|
667
|
+
setLodash(object, field, Validator.NUMBER(castedCurrent) ? defaultBSON(castedCurrent * castedValue, value) : 0);
|
|
668
|
+
},
|
|
669
|
+
$rename: (field, value, object) => {
|
|
670
|
+
if (!Validator.EMPTY_STRING(value))
|
|
671
|
+
throw `expected a non-empty string at $rename.${field} but got ${value}`;
|
|
672
|
+
const destStage = value.split('.');
|
|
673
|
+
const sourceStage = field.split('.');
|
|
674
|
+
|
|
675
|
+
sourceStage.forEach((e, i, a) => {
|
|
676
|
+
if (a.length !== destStage.length)
|
|
677
|
+
throw `dotnotation mismatch for ${value}`;
|
|
678
|
+
if (i !== a.length - 1) {
|
|
679
|
+
if (e !== destStage[i])
|
|
680
|
+
throw `dotnotation mismatch at ${destStage[i]}, expected "${e}"`;
|
|
291
681
|
}
|
|
682
|
+
if (!e) throw `empty node for ${field}`;
|
|
683
|
+
});
|
|
684
|
+
const [tipObj, tipSource, tipDest] = destStage.length === 1 ? [object, field, value]
|
|
685
|
+
: [getLodash(object, destStage.slice(0, -1).join('.')), sourceStage.slice(-1)[0], destStage.slice(-1)[0]];
|
|
292
686
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
687
|
+
if (tipObj && tipSource in tipObj) {
|
|
688
|
+
tipObj[tipDest] = cloneDeep(tipObj[tipSource]);
|
|
689
|
+
delete tipObj[tipSource];
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
$set: (field, value, object) => {
|
|
693
|
+
setLodash(object, field, value === undefined ? null : value);
|
|
694
|
+
},
|
|
695
|
+
$setOnInsert: (field, value, object, isNew) => {
|
|
696
|
+
if (isNew) AtomicWriter.$set(field, value, object);
|
|
697
|
+
},
|
|
698
|
+
$unset: (field, _, object) => {
|
|
699
|
+
unsetLodash(object, field);
|
|
700
|
+
},
|
|
701
|
+
$addToSet: (field, value, object) => {
|
|
702
|
+
const current = getLodash(object, field);
|
|
703
|
+
if (Array.isArray(current)) {
|
|
704
|
+
if (
|
|
705
|
+
Validator.OBJECT(value) &&
|
|
706
|
+
Object.keys(value).length === 1 &&
|
|
707
|
+
'$each' in value
|
|
708
|
+
) {
|
|
709
|
+
const { $each } = value;
|
|
710
|
+
if (!Array.isArray($each))
|
|
711
|
+
throw `expected an array at "$addToSet.${field}.$each" but got ${$each}`;
|
|
712
|
+
$each.forEach(e => {
|
|
713
|
+
if (!current.some(v => CompareBson.equal(v, e))) {
|
|
714
|
+
current.push(e);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
} else if (!current.some(v => CompareBson.equal(v, value))) {
|
|
718
|
+
current.push(value);
|
|
298
719
|
}
|
|
720
|
+
}
|
|
721
|
+
},
|
|
722
|
+
$pop: (field, value, object) => {
|
|
723
|
+
if (![1, -1].includes(value)) throw `expected 1 or -1 at "$pop.${field}" but got ${value}`;
|
|
724
|
+
const current = getLodash(object, field);
|
|
725
|
+
if (
|
|
726
|
+
Array.isArray(current) &&
|
|
727
|
+
current.length
|
|
728
|
+
) current[value === 1 ? 'pop' : 'shift']();
|
|
729
|
+
},
|
|
730
|
+
$pull: (field, value, object) => {
|
|
731
|
+
// TODO: issues
|
|
732
|
+
const current = getLodash(object, field);
|
|
733
|
+
const isQueryObject = Validator.OBJECT(value);
|
|
299
734
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
735
|
+
if (
|
|
736
|
+
Array.isArray(current) &&
|
|
737
|
+
current.length
|
|
738
|
+
) {
|
|
739
|
+
const remainingCurrent = current.filter(v => {
|
|
740
|
+
const isThisObject = Validator.OBJECT(v);
|
|
741
|
+
|
|
742
|
+
try {
|
|
743
|
+
if (
|
|
744
|
+
confirmFilterDoc(
|
|
745
|
+
isThisObject ? v : { __x_: v },
|
|
746
|
+
(isThisObject && isQueryObject) ? value : { __x_: value }
|
|
747
|
+
)
|
|
748
|
+
) {
|
|
749
|
+
return false;
|
|
750
|
+
}
|
|
751
|
+
} catch (_) { }
|
|
752
|
+
return true;
|
|
753
|
+
});
|
|
754
|
+
setLodash(object, field, remainingCurrent);
|
|
313
755
|
}
|
|
314
|
-
}
|
|
756
|
+
},
|
|
757
|
+
$push: (field, value, object) => {
|
|
758
|
+
const current = getLodash(object, field);
|
|
759
|
+
|
|
760
|
+
if (Array.isArray(current)) {
|
|
761
|
+
if (Validator.OBJECT(value)) {
|
|
762
|
+
const { $each, $sort, $slice, $position, ...rest } = value;
|
|
763
|
+
if (Object.keys(rest).length)
|
|
764
|
+
throw `unknown property "${Object.keys(rest)}" at $push.${field}`;
|
|
765
|
+
|
|
766
|
+
if ($position !== undefined) {
|
|
767
|
+
if (Validator.INTEGER($position))
|
|
768
|
+
throw '$position must have an integer value';
|
|
769
|
+
if (!$each) throw '$position operator requires an $each operator';
|
|
770
|
+
}
|
|
771
|
+
if ($each !== undefined) {
|
|
772
|
+
if (!Array.isArray($each))
|
|
773
|
+
throw `expected an array at "$push.${field}.$each" but got ${$each}`;
|
|
774
|
+
if ($position !== undefined) {
|
|
775
|
+
current.splice($position, 0, ...$each);
|
|
776
|
+
} else current.push(...$each);
|
|
777
|
+
}
|
|
778
|
+
if ($sort !== undefined) {
|
|
779
|
+
if (!$each) throw '$sort operator requires an $each operator';
|
|
780
|
+
if ([1, -1].includes($sort)) {
|
|
781
|
+
current.sort();
|
|
782
|
+
if ($sort === -1) current.reverse();
|
|
783
|
+
} else if (Validator.OBJECT($sort)) {
|
|
784
|
+
if (Object.keys($sort).length !== 1)
|
|
785
|
+
throw 'number of object keys in a $sort must be one';
|
|
786
|
+
|
|
787
|
+
Object.entries($sort).forEach(([k, v]) => {
|
|
788
|
+
sortArrayByObjectKey(current, k);
|
|
789
|
+
if (v === -1) current.reverse();
|
|
790
|
+
});
|
|
791
|
+
} else throw `expected either 1, -1 or an object at "$push.${field}.$sort" but got ${$sort}`;
|
|
792
|
+
}
|
|
793
|
+
if ($slice) {
|
|
794
|
+
if (Validator.POSITIVE_INTEGER($slice))
|
|
795
|
+
throw `$slice operator requires a positive integer but got ${$slice}`;
|
|
796
|
+
current.splice($slice);
|
|
797
|
+
}
|
|
798
|
+
} else current.push(value);
|
|
799
|
+
}
|
|
800
|
+
},
|
|
801
|
+
$pullAll: (field, value, object) => {
|
|
802
|
+
if (!Array.isArray(value))
|
|
803
|
+
throw `expected an array at $pullAll.${field}`;
|
|
804
|
+
|
|
805
|
+
const current = getLodash(object, field);
|
|
315
806
|
|
|
316
|
-
|
|
317
|
-
|
|
807
|
+
if (Array.isArray(current)) {
|
|
808
|
+
const remainingCurrent = current.filter(v =>
|
|
809
|
+
!value.some(k => CompareBson.equal(v, k))
|
|
810
|
+
);
|
|
811
|
+
setLodash(object, field, remainingCurrent);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
};
|