react-native-mosquito-transport 0.0.14

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.
Files changed (86) hide show
  1. package/CODE_OF_CONDUCT.md +133 -0
  2. package/CONTRIBUTING.md +114 -0
  3. package/LICENSE +21 -0
  4. package/README.md +1 -0
  5. package/android/build.gradle +77 -0
  6. package/android/gradle.properties +5 -0
  7. package/android/src/main/AndroidManifest.xml +4 -0
  8. package/android/src/main/java/com/mosquitodb/MosquitodbModule.java +32 -0
  9. package/android/src/main/java/com/mosquitodb/MosquitodbPackage.java +28 -0
  10. package/example/.bundle/config +2 -0
  11. package/example/.node-version +1 -0
  12. package/example/.watchmanconfig +1 -0
  13. package/example/Gemfile +6 -0
  14. package/example/android/app/build.gradle +170 -0
  15. package/example/android/app/debug.keystore +0 -0
  16. package/example/android/app/proguard-rules.pro +10 -0
  17. package/example/android/app/src/debug/AndroidManifest.xml +13 -0
  18. package/example/android/app/src/debug/java/com/mosquitodbexample/ReactNativeFlipper.java +75 -0
  19. package/example/android/app/src/main/AndroidManifest.xml +25 -0
  20. package/example/android/app/src/main/java/com/mosquitodbexample/MainActivity.java +35 -0
  21. package/example/android/app/src/main/java/com/mosquitodbexample/MainApplication.java +62 -0
  22. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +36 -0
  23. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  24. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  25. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  26. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  27. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  28. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  29. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  30. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  31. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  32. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  33. package/example/android/app/src/main/res/values/strings.xml +3 -0
  34. package/example/android/app/src/main/res/values/styles.xml +9 -0
  35. package/example/android/app/src/release/java/com/mosquitodbexample/ReactNativeFlipper.java +20 -0
  36. package/example/android/build.gradle +21 -0
  37. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  38. package/example/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  39. package/example/android/gradle.properties +44 -0
  40. package/example/android/gradlew +234 -0
  41. package/example/android/gradlew.bat +89 -0
  42. package/example/android/settings.gradle +4 -0
  43. package/example/app.json +4 -0
  44. package/example/babel.config.js +17 -0
  45. package/example/index.js +5 -0
  46. package/example/ios/.xcode.env +11 -0
  47. package/example/ios/File.swift +6 -0
  48. package/example/ios/MosquitodbExample/AppDelegate.h +6 -0
  49. package/example/ios/MosquitodbExample/AppDelegate.mm +36 -0
  50. package/example/ios/MosquitodbExample/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
  51. package/example/ios/MosquitodbExample/Images.xcassets/Contents.json +6 -0
  52. package/example/ios/MosquitodbExample/Info.plist +55 -0
  53. package/example/ios/MosquitodbExample/LaunchScreen.storyboard +47 -0
  54. package/example/ios/MosquitodbExample/main.m +10 -0
  55. package/example/ios/MosquitodbExample-Bridging-Header.h +3 -0
  56. package/example/ios/MosquitodbExample.xcodeproj/project.pbxproj +702 -0
  57. package/example/ios/MosquitodbExample.xcodeproj/xcshareddata/xcschemes/MosquitodbExample.xcscheme +88 -0
  58. package/example/ios/MosquitodbExampleTests/Info.plist +24 -0
  59. package/example/ios/MosquitodbExampleTests/MosquitodbExampleTests.m +66 -0
  60. package/example/ios/Podfile +60 -0
  61. package/example/metro.config.js +40 -0
  62. package/example/package.json +22 -0
  63. package/example/react-native.config.js +10 -0
  64. package/example/src/App.tsx +31 -0
  65. package/ios/Mosquitodb-Bridging-Header.h +2 -0
  66. package/ios/Mosquitodb.m +22 -0
  67. package/ios/Mosquitodb.swift +305 -0
  68. package/ios/Mosquitodb.xcodeproj/project.pbxproj +283 -0
  69. package/package.json +45 -0
  70. package/react-native-mosquitodb.podspec +35 -0
  71. package/src/helpers/EngineApi.js +34 -0
  72. package/src/helpers/listeners.js +7 -0
  73. package/src/helpers/peripherals.js +195 -0
  74. package/src/helpers/utils.js +113 -0
  75. package/src/helpers/values.js +72 -0
  76. package/src/helpers/variables.js +34 -0
  77. package/src/index.d.ts +373 -0
  78. package/src/index.js +369 -0
  79. package/src/products/auth/accessor.js +151 -0
  80. package/src/products/auth/index.js +279 -0
  81. package/src/products/database/accessor.js +316 -0
  82. package/src/products/database/index.js +603 -0
  83. package/src/products/database/types.js +22 -0
  84. package/src/products/database/validator.js +282 -0
  85. package/src/products/http_callable/index.js +230 -0
  86. package/src/products/storage/index.js +217 -0
@@ -0,0 +1,279 @@
1
+ import { io } from "socket.io-client";
2
+ import EngineApi from "../../helpers/EngineApi";
3
+ import { TokenRefreshListener } from "../../helpers/listeners";
4
+ import { awaitReachableServer, awaitStore, buildFetchInterface, simplifyError, updateCacheStore } from "../../helpers/utils";
5
+ import { CacheConstant, CacheStore, Scoped } from "../../helpers/variables";
6
+ import { awaitRefreshToken, initTokenRefresher, injectFreshToken, listenToken, parseToken, triggerAuthToken } from "./accessor";
7
+ import { deserializeE2E, encodeBinary, serializeE2E, simplifyCaughtError } from "../../helpers/peripherals";
8
+
9
+ const {
10
+ _listenUserVerification,
11
+ _signOut,
12
+ _customSignin,
13
+ _customSignup,
14
+ _googleSignin
15
+ } = EngineApi;
16
+
17
+ export class MTAuth {
18
+ constructor(config) {
19
+ this.builder = { ...config };
20
+ }
21
+
22
+ customSignin = (email, password) => doCustomSignin(this.builder, email, password);
23
+
24
+ customSignup = (email, password, name, metadata) => doCustomSignup(this.builder, email, password, name, metadata);
25
+
26
+ googleSignin = (token) => doGoogleSignin(this.builder, token);
27
+
28
+ appleSignin() {
29
+
30
+ }
31
+
32
+ facebookSignin() {
33
+
34
+ }
35
+
36
+ twitterSignin() {
37
+
38
+ }
39
+
40
+ githubSignin() {
41
+
42
+ }
43
+
44
+ listenVerifiedStatus(callback, onError) {
45
+ const { projectUrl, serverE2E_PublicKey, uglify, baseUrl } = this.builder;
46
+
47
+ let socket, wasDisconnected, lastToken = Scoped.AuthJWTToken[projectUrl] || null, lastInitRef = 0;
48
+
49
+ const init = async () => {
50
+ const processID = ++lastInitRef;
51
+ await awaitRefreshToken(projectUrl);
52
+
53
+ if (!Scoped.AuthJWTToken[projectUrl]) {
54
+ onError?.(simplifyError('user_login_required', 'You must be signed-in to use this method').simpleError);
55
+ return;
56
+ }
57
+ if (processID !== lastInitRef) return;
58
+ const mtoken = Scoped.AuthJWTToken[projectUrl],
59
+ [reqBuilder, [privateKey]] = uglify ? serializeE2E({ mtoken }, undefined, serverE2E_PublicKey) : [null, []];
60
+
61
+ socket = io(`ws://${baseUrl}`, {
62
+ auth: uglify ? {
63
+ e2e: reqBuilder,
64
+ _m_internal: true
65
+ } : { mtoken, _m_internal: true }
66
+ });
67
+
68
+ socket.emit(_listenUserVerification(uglify));
69
+
70
+ socket.on("onVerificationChanged", ([err, verified]) => {
71
+ const fatal = err ? simplifyCaughtError(err).simpleError : undefined;
72
+ if (fatal) {
73
+ onError?.(fatal);
74
+ } else {
75
+ callback?.(uglify ? deserializeE2E(verified, serverE2E_PublicKey, privateKey) : verified);
76
+ }
77
+ });
78
+
79
+ socket.on('connect', () => {
80
+ if (wasDisconnected) socket.emit(_listenUserVerification(uglify));
81
+ });
82
+
83
+ socket.on('disconnect', () => {
84
+ wasDisconnected = true;
85
+ });
86
+ };
87
+
88
+ init();
89
+
90
+ const tokenListener = listenToken(t => {
91
+ if ((t || null) !== lastToken) {
92
+ socket?.close?.();
93
+ wasDisconnected = undefined;
94
+ init();
95
+ }
96
+ lastToken = t;
97
+ }, projectUrl);
98
+
99
+ return () => {
100
+ socket?.close?.();
101
+ tokenListener?.();
102
+ }
103
+ }
104
+
105
+ listenAuthToken = (callback) => listenToken(callback, this.builder.projectUrl);
106
+
107
+ getAuthToken = () => new Promise(resolve => {
108
+ const l = listenToken(t => {
109
+ l();
110
+ resolve(t || null);
111
+ }, this.builder.projectUrl);
112
+ });
113
+
114
+ listenAuth = (callback) => {
115
+ let lastTrig;
116
+
117
+ return listenToken((t, initToken) => {
118
+ const { refreshToken } = CacheStore.AuthStore[this.builder.projectUrl] || {};
119
+
120
+ if (
121
+ (!!t || null) !== lastTrig ||
122
+ initToken
123
+ ) callback(t ? {
124
+ ...parseToken(t),
125
+ tokenManager: {
126
+ refreshToken,
127
+ accessToken: t
128
+ }
129
+ } : null);
130
+
131
+ lastTrig = !!t || null;
132
+ }, this.builder.projectUrl);
133
+ }
134
+
135
+ getAuth = () => new Promise(resolve => {
136
+ const l = this.listenAuth(d => {
137
+ l();
138
+ resolve(d);
139
+ });
140
+ });
141
+
142
+ signOut = () => doSignOut(this.builder);
143
+
144
+ forceRefreshToken = () => initTokenRefresher(this.builder, true);
145
+ }
146
+
147
+ const doCustomSignin = (builder, email, password) => new Promise(async (resolve, reject) => {
148
+ const { projectUrl, serverE2E_PublicKey, accessKey, uglify } = builder;
149
+
150
+ try {
151
+ await awaitStore();
152
+ const [reqBuilder, [privateKey]] = buildFetchInterface({
153
+ body: { _: `${encodeBinary(email)}.${encodeBinary(password)}` },
154
+ accessKey,
155
+ serverE2E_PublicKey,
156
+ uglify
157
+ });
158
+
159
+ const f = await (await fetch(_customSignin(projectUrl, uglify), reqBuilder)).json();
160
+ if (f.simpleError) throw f;
161
+
162
+ const r = uglify ? deserializeE2E(f.e2e, serverE2E_PublicKey, privateKey) : f;
163
+
164
+ resolve({
165
+ user: parseToken(r.result.token),
166
+ token: r.result.token,
167
+ refreshToken: r.result.refreshToken
168
+ });
169
+ await injectFreshToken(builder, r.result);
170
+ } catch (e) {
171
+ reject(simplifyCaughtError(e).simpleError);
172
+ }
173
+ });
174
+
175
+ const doCustomSignup = (builder, email, password, name, metadata) => new Promise(async (resolve, reject) => {
176
+ const { projectUrl, serverE2E_PublicKey, accessKey, uglify } = builder;
177
+
178
+ try {
179
+ await awaitStore();
180
+ const [reqBuilder, [privateKey]] = buildFetchInterface({
181
+ body: {
182
+ _: `${encodeBinary(email)}.${encodeBinary(password)}.${(encodeBinary(name || '').trim())}`,
183
+ metadata,
184
+ },
185
+ accessKey,
186
+ serverE2E_PublicKey,
187
+ uglify
188
+ });
189
+
190
+ const f = await (await fetch(_customSignup(projectUrl, uglify), reqBuilder)).json();
191
+ if (f.simpleError) throw f;
192
+
193
+ const r = uglify ? deserializeE2E(f.e2e, serverE2E_PublicKey, privateKey) : f;
194
+
195
+ resolve({
196
+ user: parseToken(r.result.token),
197
+ token: r.result.token,
198
+ refreshToken: r.result.refreshToken
199
+ });
200
+ await injectFreshToken(builder, r.result);
201
+ } catch (e) {
202
+ reject(simplifyCaughtError(e).simpleError);
203
+ }
204
+ });
205
+
206
+ export const doSignOut = async (builder) => {
207
+ await awaitStore();
208
+
209
+ const { projectUrl, serverE2E_PublicKey, accessKey, uglify } = builder,
210
+ { token, refreshToken: r_token } = CacheStore.AuthStore[projectUrl];
211
+
212
+ TokenRefreshListener.dispatch(projectUrl);
213
+ if (CacheStore.AuthStore[projectUrl]) delete CacheStore.AuthStore[projectUrl];
214
+ if (token) delete Scoped.AuthJWTToken[projectUrl];
215
+ Object.keys(CacheConstant).forEach(e => {
216
+ CacheStore[e] = CacheConstant[e];
217
+ });
218
+ triggerAuthToken(projectUrl);
219
+ updateCacheStore();
220
+ initTokenRefresher(builder);
221
+
222
+ if (token) {
223
+ TokenRefreshListener.dispatch(projectUrl, 'ready');
224
+ try {
225
+ await awaitReachableServer(projectUrl);
226
+
227
+ const [reqBuilder] = buildFetchInterface({
228
+ body: { token, r_token },
229
+ accessKey,
230
+ uglify,
231
+ serverE2E_PublicKey
232
+ });
233
+
234
+ const r = await (await fetch(_signOut(projectUrl, uglify), reqBuilder)).json();
235
+ if (r.simpleError) throw r;
236
+ } catch (e) {
237
+ throw simplifyCaughtError(e).simpleError;
238
+ }
239
+ }
240
+ }
241
+
242
+ const doGoogleSignin = (builder, token) => new Promise(async (resolve, reject) => {
243
+ const { projectUrl, serverE2E_PublicKey, accessKey, uglify } = builder;
244
+
245
+ try {
246
+ await awaitStore();
247
+ const [reqBuilder, [privateKey]] = buildFetchInterface({
248
+ body: { token },
249
+ accessKey,
250
+ uglify,
251
+ serverE2E_PublicKey
252
+ });
253
+
254
+ const r = await (await fetch(_googleSignin(projectUrl, uglify), reqBuilder)).json();
255
+ if (r.simpleError) throw r;
256
+
257
+ const f = uglify ? deserializeE2E(r.e2e, serverE2E_PublicKey, privateKey) : r;
258
+
259
+ resolve({
260
+ user: parseToken(f.result.token),
261
+ token: f.result.token,
262
+ refreshToken: f.result.refreshToken,
263
+ isNewUser: f.result.isNewUser
264
+ });
265
+ await injectFreshToken(builder, f.result);
266
+ } catch (e) {
267
+ reject(simplifyCaughtError(e).simpleError);
268
+ }
269
+ });
270
+
271
+ const doAppleSignin = async () => {
272
+
273
+ }
274
+
275
+ const validateEmailAndPassword = () => { }
276
+
277
+ const getAuthState = async (projectUrl) => {
278
+
279
+ }
@@ -0,0 +1,316 @@
1
+ import { IS_RAW_OBJECT, objToUniqueString, queryEntries, shuffleArray, sortArrayByObjectKey } from "../../helpers/peripherals";
2
+ import { awaitStore, updateCacheStore } from "../../helpers/utils";
3
+ import { CacheStore } from "../../helpers/variables";
4
+ import { confirmFilterDoc } from "./validator";
5
+ import getLodash from 'lodash/get';
6
+ import setLodash from 'lodash/set';
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
+ import { DatabaseRecordsListener } from "../../helpers/listeners";
11
+
12
+ export const listenQueryEntry = (callback, { accessId, builder, config, processId }) => {
13
+ let lastObj = '';
14
+ const { episode = 0 } = config || {};
15
+
16
+ const l = DatabaseRecordsListener.listenTo(accessId, async (dispatchId) => {
17
+ const cache = await getRecord(builder, accessId);
18
+ if (
19
+ cache &&
20
+ !isEqual(lastObj, cache[episode]) &&
21
+ dispatchId === processId
22
+ ) callback(cache[episode]);
23
+ lastObj = cache[episode];
24
+ });
25
+
26
+ return () => {
27
+ lastObj = undefined;
28
+ l();
29
+ }
30
+ }
31
+
32
+ export const insertRecord = async (builder, accessId, query, value) => {
33
+ await awaitStore();
34
+ const { projectUrl, dbUrl = DEFAULT_DB_URL, dbName = DEFAULT_DB_NAME, path } = builder,
35
+ { extraction, excludeFields, returnOnly } = query?.config,
36
+ kaf = `${objToUniqueString(extraction || {})},${(excludeFields || []).join(',')},${(returnOnly || []).join(',')}`,
37
+ colData = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data', kaf], []);
38
+
39
+ (Array.isArray(value) ? value : [value]).forEach(e => {
40
+ const b4DocIndex = colData.findIndex(v => v._id === e._id);
41
+ if (b4DocIndex === -1) {
42
+ colData.push(e);
43
+ } else colData[b4DocIndex] = e;
44
+ });
45
+
46
+ setLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data', kaf], [...colData]);
47
+ setLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'record', accessId], {
48
+ query,
49
+ result: value,
50
+ registeredOn: Date.now()
51
+ });
52
+ updateCacheStore();
53
+ }
54
+
55
+ export const getRecord = async (builder, accessId) => {
56
+ await awaitStore();
57
+ const { projectUrl, dbUrl = DEFAULT_DB_URL, dbName = DEFAULT_DB_NAME, path, command } = builder,
58
+ { config, find, findOne, sort, direction, limit, random } = command,
59
+ { extraction, excludeFields, returnOnly } = config || {},
60
+ kaf = `${objToUniqueString(extraction || {})},${(excludeFields || []).join(',')},${(returnOnly || []).join(',')}`,
61
+ colData = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data', kaf], []),
62
+ colRecord = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'record', accessId]);
63
+
64
+ if (!colRecord) return null;
65
+ let choosenColData = colData.filter(v => confirmFilterDoc(v, findOne || find || {}));
66
+
67
+ if (random) {
68
+ choosenColData = shuffleArray(choosenColData);
69
+ } else if (sort) {
70
+ choosenColData = sortArrayByObjectKey(choosenColData, sort);
71
+ if (
72
+ direction === -1 ||
73
+ direction === 'desc' ||
74
+ direction === 'descending'
75
+ ) choosenColData = choosenColData.reverse();
76
+ }
77
+
78
+ if (findOne) {
79
+ choosenColData = choosenColData[0];
80
+ } else if (limit) choosenColData.filter((_, i) => i < limit);
81
+
82
+ return [choosenColData, colRecord.result];
83
+ }
84
+
85
+ export const generateRecordID = (builder, config) => {
86
+ const { command, path, countDoc } = builder,
87
+ { find, findOne, sort, direction, limit } = command || {},
88
+ { extraction, retrieval = RETRIEVAL.DEFAULT, delivery = DELIVERY.DEFAULT, excludeFields = [], returnOnly = [] } = config || {},
89
+ accessId = `collection:${path}->excludes:${(Array.isArray(excludeFields) ? excludeFields : [excludeFields]).filter(v => v !== undefined).join(',')}->includes:${(Array.isArray(returnOnly) ? returnOnly : [returnOnly]).filter(v => v !== undefined).join(',')}->${countDoc ? 'countDoc:yes->' : ''}sort:${sort || ''}->direction:${direction || ''}->limit:${limit || ''}->${findOne ? 'findOne' : 'find'}:${objToUniqueString(findOne || find || {})}:extraction:${objToUniqueString(extraction || {})}:retrieval:${retrieval}:delivery:${delivery}`;
90
+
91
+ return accessId;
92
+ }
93
+
94
+ export const addPendingWrites = async (builder, writeId, result) => {
95
+ await awaitStore();
96
+ const { projectUrl, dbUrl = DEFAULT_DB_URL, dbName = DEFAULT_DB_NAME, path } = builder,
97
+ { value: writeObj, find, type } = result,
98
+ isAtomic = type === 'updateOne' ||
99
+ type === 'updateMany' ||
100
+ type === 'mergeOne' ||
101
+ type === 'mergeMany',
102
+ colObj = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data'], {});
103
+
104
+ let editions = [], duplicateSets = {};
105
+
106
+ Object.entries(colObj).forEach(([kaf, colList]) => {
107
+ let hasEndCommit, editionSet = [];
108
+
109
+ if (type === 'setOne' || type === 'setMany') {
110
+ (type === 'setOne' ? [writeObj] : writeObj).forEach(e => {
111
+ if (colList.findIndex(v => v._id === e._id) === -1) {
112
+ editionSet.push({ doc: deserializeNonAtomicWrite(e), dex: 'push', docId: writeObj._id });
113
+ } else if (!duplicateSets[e._id])
114
+ console.error(`document with _id=${e._id} already exist locally with ${type}() operation, will try committing it online`);
115
+ duplicateSets[e._id] = true;
116
+ });
117
+ } else {
118
+ colList.forEach((doc, docDex) => {
119
+ if (hasEndCommit) return;
120
+ let afDoc = undefined;
121
+
122
+ if (confirmFilterDoc(doc, find || {})) {
123
+ if (type === 'deleteMany') {
124
+ afDoc = null;
125
+ } else if (type === 'deleteOne') {
126
+ afDoc = null;
127
+ hasEndCommit = true;
128
+ } else if (isAtomic) {
129
+ if ((deserializeAtomicWrite({}, { ...writeObj })?._id || find?._id) && type.endsWith('Many'))
130
+ throw `avoid providing "_id" for ${type}() operation, use ${type.substring(0, type.length - 4)}One instead as _id only reference a single document`;
131
+
132
+ afDoc = deserializeAtomicWrite({ ...doc }, { ...writeObj });
133
+ if (type.endsWith('One')) hasEndCommit = true;
134
+ } else {
135
+ afDoc = deserializeNonAtomicWrite({ ...writeObj });
136
+ hasEndCommit = true;
137
+ }
138
+ }
139
+ if (afDoc !== undefined)
140
+ editionSet.push({ doc: afDoc, dex: docDex, docId: doc._id, b4Doc: { ...doc } });
141
+ });
142
+ }
143
+
144
+ if (!editionSet.length) {
145
+ let hasNoID;
146
+
147
+ if (type === 'putOne') {
148
+ const nDoc = deserializeNonAtomicWrite(writeObj),
149
+ nId = nDoc?._id || find?._id;
150
+
151
+ if (nId) {
152
+ editionSet.push({
153
+ doc: { ...nDoc, _id: nId },
154
+ dex: 'push',
155
+ docId: nId
156
+ });
157
+ } else hasNoID = true;
158
+ } else if (type === 'mergeOne' || type === 'mergeMany') {
159
+ const nDoc = deserializeAtomicWrite({}, writeObj),
160
+ nId = nDoc?._id || find?._id;
161
+
162
+ if (nId && type === 'mergeMany')
163
+ throw `avoid providing "_id" for mergeMany() operation, use mergeOne instead as _id only reference a single document`;
164
+ if (nId) {
165
+ editionSet.push({
166
+ doc: { ...nDoc, _id: nId },
167
+ dex: 'push',
168
+ docId: nId
169
+ });
170
+ } else hasNoID = true;
171
+ }
172
+ if (hasNoID) console.error(`no data found locally and _id was not provided for ${type}() operation, skipping local and proceeding to online commit`);
173
+ }
174
+ editions.push([kaf, editionSet]);
175
+ });
176
+
177
+ editions.forEach(([kaf, list]) => {
178
+ list.forEach(({ doc, dex, docId }) => {
179
+
180
+ if (dex === 'push') {
181
+ colObj[kaf].push({ ...doc });
182
+ } else if (doc === null) {
183
+ colObj[kaf] = colObj[kaf].filter(v => v._id !== docId);
184
+ } else {
185
+ colObj[kaf] = colObj[kaf].map(v => v._id === docId ? { ...doc } : v);
186
+ }
187
+ });
188
+ });
189
+
190
+
191
+ setLodash(CacheStore.PendingWrites, [projectUrl, `${dbUrl}${dbName}${path}`, writeId], {
192
+ find,
193
+ value: writeObj,
194
+ type,
195
+ editions,
196
+ addedOn: Date.now()
197
+ });
198
+
199
+ updateCacheStore();
200
+ notifyDatabaseNodeChanges(builder);
201
+ }
202
+
203
+ export const removePendingWrite = async (builder, writeId, revert) => {
204
+ await awaitStore();
205
+ const { projectUrl, dbUrl = DEFAULT_DB_URL, dbName = DEFAULT_DB_NAME, path } = builder,
206
+ pObj = getLodash(CacheStore.PendingWrites, [projectUrl, `${dbUrl}${dbName}${path}`, writeId]),
207
+ colObj = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data']);
208
+
209
+ if (!pObj) return;
210
+
211
+ if (revert && colObj)
212
+ pObj.editions.forEach(([kaf, list]) => {
213
+ list.forEach(({ doc, dex, docId, b4Doc }) => {
214
+
215
+ if (dex === 'push') {
216
+ colObj[kaf] = colObj[kaf].filter(v => v._id !== docId);
217
+ } else if (doc === null) {
218
+ colObj[kaf] = [...colObj[kaf], b4Doc];
219
+ } else {
220
+ colObj[kaf] = colObj[kaf].map(v => v._id === docId ? { ...b4Doc } : v);
221
+ }
222
+ });
223
+ });
224
+
225
+ unsetLodash(CacheStore.PendingWrites, [projectUrl, `${dbUrl}${dbName}${path}`, writeId]);
226
+ updateCacheStore();
227
+ notifyDatabaseNodeChanges(builder);
228
+ }
229
+
230
+ export const trySendPendingWrite = () => {
231
+
232
+ }
233
+
234
+ const notifyDatabaseNodeChanges = (builder) => {
235
+
236
+ }
237
+
238
+ const deserializeNonAtomicWrite = (writeObj) => {
239
+ const bj = {};
240
+
241
+ queryEntries(writeObj, []).forEach(([segment, value]) => {
242
+ if (segment[0].startsWith('$'))
243
+ throw `unexpected field "${segment[0]}"`;
244
+
245
+ if (segment.slice(-1)[0] === '$timestamp' && value === 'now') {
246
+ segment.pop();
247
+ value = Date.now();
248
+ }
249
+
250
+ setLodash(bj, segment.join('.'), value);
251
+ });
252
+ return bj;
253
+ }
254
+
255
+ const deserializeAtomicWrite = (b4Doc, writeObj) => {
256
+ const afDoc = { ...b4Doc },
257
+ affectedObj = {};
258
+
259
+ queryEntries(writeObj, []).forEach(([segment, value]) => {
260
+ const [op, path] = [segment[0], segment.filter((_, i) => i)];
261
+
262
+ if (!WRITE_OPS_LIST.includes(op) || !path.length)
263
+ throw `MongoInvalidArgumentError: Update document requires atomic operators`;
264
+
265
+ if (
266
+ path.length > 1 &&
267
+ IS_RAW_OBJECT(writeObj[op][path[0]]) &&
268
+ !affectedObj[path[0]]
269
+ ) {
270
+ affectedObj[path[0]] = true;
271
+ afDoc[path[0]] = {};
272
+ }
273
+
274
+ const nodeValue = getLodash(b4Doc, path.join('.'));
275
+
276
+ if (op === WRITE_OPS.$UNSET) {
277
+ unsetLodash(afDoc, path.join('.'));
278
+ } else {
279
+ if (
280
+ [WRITE_OPS.$MAX, WRITE_OPS.$MIN, WRITE_OPS.$INC, WRITE_OPS.$MUL].filter(v => v === op).length &&
281
+ isNaN(value)
282
+ ) throw `expected a number for "${op}" operation but got ${value}`;
283
+
284
+ if (path.slice(-1)[0] === '$timestamp' && value === 'now') {
285
+ const k = [WRITE_OPS.$SET, WRITE_OPS.$UNSET];
286
+ if (!k.includes(op))
287
+ throw `invalid operator for updating timestamp, expected any of ${k}`;
288
+ path.pop();
289
+ value = Date.now();
290
+ }
291
+
292
+ if (op === WRITE_OPS.$RENAME) {
293
+ if (nodeValue === undefined) return;
294
+ if (typeof value !== 'string') throw `${op} operator expected a string value at ${path.join('.')}`;
295
+ unsetLodash(afDoc, path.join('.'));
296
+ path[path.length - 1] = value;
297
+ }
298
+
299
+ setLodash(
300
+ afDoc,
301
+ path.join('.'),
302
+ op === WRITE_OPS.$SET ? value :
303
+ op === WRITE_OPS.$INC ? (isNaN(nodeValue) ? value : nodeValue + value) :
304
+ op === WRITE_OPS.$MAX ? (isNaN(nodeValue) ? value : value > nodeValue ? value : nodeValue) :
305
+ op === WRITE_OPS.$MIN ? (isNaN(nodeValue) ? value : value < nodeValue ? value : nodeValue) :
306
+ op === WRITE_OPS.$MUL ? (isNaN(nodeValue) ? 0 : value * nodeValue) :
307
+ op === WRITE_OPS.$PULL ? (Array.isArray(nodeValue) ? nodeValue.filter(v => !isEqual(v, value)) : [value]) :
308
+ op === WRITE_OPS.$PUSH ? (Array.isArray(nodeValue) ? [...nodeValue, value] : [value]) :
309
+ op === WRITE_OPS.$RENAME ? nodeValue :
310
+ null // TODO:
311
+ );
312
+ }
313
+ });
314
+
315
+ return afDoc;
316
+ }