react-native-mosquito-transport 0.0.19 → 0.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.jshintignore +4 -0
- package/.jshintrc +16 -0
- package/README.md +71 -1
- package/TODO +19 -2
- package/ios/Mosquitodb.swift +14 -5
- package/package.json +17 -17
- package/src/helpers/engine_api.js +34 -0
- package/src/helpers/peripherals.js +57 -135
- package/src/helpers/purger.js +264 -0
- package/src/helpers/sqlite_manager.js +138 -0
- package/src/helpers/utils.js +96 -47
- package/src/helpers/values.js +12 -66
- package/src/helpers/variables.js +45 -11
- package/src/index.d.ts +186 -85
- package/src/index.js +198 -119
- package/src/products/auth/accessor.js +99 -39
- package/src/products/auth/index.js +153 -99
- package/src/products/database/accessor.js +1008 -247
- package/src/products/database/bson.js +16 -0
- package/src/products/database/counter.js +18 -0
- package/src/products/database/index.js +394 -266
- package/src/products/database/types.js +2 -1
- package/src/products/database/validator.js +518 -255
- package/src/products/http_callable/accessor.js +72 -0
- package/src/products/http_callable/counter.js +11 -0
- package/src/products/http_callable/index.js +143 -147
- package/src/products/storage/index.js +109 -93
- package/src/helpers/EngineApi.js +0 -33
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { openDB, SQLITE_COMMANDS, SQLITE_PATH } from "./sqlite_manager";
|
|
2
|
+
import { Validator } from "guard-object";
|
|
3
|
+
import { incrementDatabaseSizeCore } from "../products/database/counter";
|
|
4
|
+
import { incrementFetcherSizeCore } from "../products/http_callable/counter";
|
|
5
|
+
import unsetLodash from 'lodash/unset';
|
|
6
|
+
|
|
7
|
+
const { LIMITER_DATA, LIMITER_RESULT, DB_COUNT_QUERY, FETCH_RESOURCES } = SQLITE_PATH;
|
|
8
|
+
|
|
9
|
+
export const purgeRedundantRecords = async (data, builder) => {
|
|
10
|
+
const { io, maxLocalDatabaseSize = 10485760, maxLocalFetchHttpSize = 10485760 } = builder;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @type {import('./variables')['CacheStore']['DatabaseStats']}
|
|
14
|
+
*/
|
|
15
|
+
const { _db_size, _fetcher_size, counters, database, fetchers } = data.DatabaseStats || {};
|
|
16
|
+
|
|
17
|
+
if (io) {
|
|
18
|
+
const purgeDatabase = () => {
|
|
19
|
+
if (!Validator.POSITIVE_NUMBER(_db_size) || !maxLocalDatabaseSize || _db_size < maxLocalDatabaseSize) return;
|
|
20
|
+
const DbListing = [];
|
|
21
|
+
|
|
22
|
+
breakDbMap(data.DatabaseStore, (projectUrl, dbUrl, dbName, path, value) => {
|
|
23
|
+
Object.entries(value.instance).forEach(([access_id, obj]) => {
|
|
24
|
+
DbListing.push({
|
|
25
|
+
builder: { projectUrl, dbUrl, dbName },
|
|
26
|
+
path,
|
|
27
|
+
access_id,
|
|
28
|
+
value: obj
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
Object.entries(value.episode).forEach(([access_id, limitObj]) => {
|
|
32
|
+
Object.entries(limitObj).forEach(([limit, obj]) => {
|
|
33
|
+
DbListing.push({
|
|
34
|
+
builder: { projectUrl, dbUrl, dbName },
|
|
35
|
+
path,
|
|
36
|
+
access_id,
|
|
37
|
+
limit,
|
|
38
|
+
value: obj,
|
|
39
|
+
isEpisode: true
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
breakDbMap(data.DatabaseCountResult, (projectUrl, dbUrl, dbName, path, value) => {
|
|
46
|
+
Object.entries(value).forEach(([access_id, obj]) => {
|
|
47
|
+
DbListing.push({
|
|
48
|
+
builder: { projectUrl, dbUrl, dbName },
|
|
49
|
+
path,
|
|
50
|
+
access_id,
|
|
51
|
+
value: obj,
|
|
52
|
+
isCount: true
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const redundantDbRanking = DbListing.sort((a, b) =>
|
|
58
|
+
a.value.touched - b.value.touched
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const newSize = maxLocalDatabaseSize / 2;
|
|
62
|
+
let sizer = _db_size;
|
|
63
|
+
let cuts = 0;
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < redundantDbRanking.length; i++) {
|
|
66
|
+
sizer -= redundantDbRanking[i].value.size || 0;
|
|
67
|
+
++cuts;
|
|
68
|
+
if (sizer < newSize) break;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.warn(`purging ${cuts} of ${redundantDbRanking.length} db entities`);
|
|
72
|
+
redundantDbRanking.slice(0, cuts).forEach(({
|
|
73
|
+
builder,
|
|
74
|
+
path,
|
|
75
|
+
access_id,
|
|
76
|
+
isCount,
|
|
77
|
+
isEpisode,
|
|
78
|
+
limit,
|
|
79
|
+
value: { size }
|
|
80
|
+
}) => {
|
|
81
|
+
const { projectUrl, dbUrl, dbName } = builder;
|
|
82
|
+
if (isCount) {
|
|
83
|
+
unsetLodash(data.DatabaseCountResult, [projectUrl, dbUrl, dbName, path, access_id]);
|
|
84
|
+
} else {
|
|
85
|
+
incrementDatabaseSizeCore(data.DatabaseStats, builder, path, -size);
|
|
86
|
+
if (isEpisode) {
|
|
87
|
+
unsetLodash(data.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'episode', access_id, limit]);
|
|
88
|
+
} else {
|
|
89
|
+
unsetLodash(data.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'instance', access_id]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const purgeFetcher = () => {
|
|
95
|
+
if (!Validator.POSITIVE_NUMBER(_fetcher_size) || !maxLocalFetchHttpSize || _fetcher_size < maxLocalFetchHttpSize) return;
|
|
96
|
+
const redundantFetchRanking = Object.entries(data.FetchedStore).map(([projectUrl, access_id_Obj]) =>
|
|
97
|
+
Object.entries(access_id_Obj).map(([access_id, data]) => ({
|
|
98
|
+
access_id,
|
|
99
|
+
projectUrl,
|
|
100
|
+
data
|
|
101
|
+
}))
|
|
102
|
+
).flat().sort(([a], [b]) =>
|
|
103
|
+
a.data.touched - b.data.touched
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const newSize = maxLocalFetchHttpSize / 2;
|
|
107
|
+
let sizer = _fetcher_size;
|
|
108
|
+
let cuts = 0;
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < redundantFetchRanking.length; i++) {
|
|
111
|
+
sizer -= redundantFetchRanking[i].data.size || 0;
|
|
112
|
+
++cuts;
|
|
113
|
+
if (sizer < newSize) break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.warn(`purging ${cuts} of ${redundantFetchRanking.length} fetcher entities`);
|
|
117
|
+
redundantFetchRanking.slice(0, cuts).forEach(({ access_id, data: { size }, projectUrl }) => {
|
|
118
|
+
incrementFetcherSizeCore(data.DatabaseStats, projectUrl, -size);
|
|
119
|
+
unsetLodash(data.FetchedStore, [projectUrl, access_id]);
|
|
120
|
+
});
|
|
121
|
+
console.log('fetcher purging complete');
|
|
122
|
+
}
|
|
123
|
+
purgeDatabase();
|
|
124
|
+
purgeFetcher();
|
|
125
|
+
} else {
|
|
126
|
+
// purge redundant data
|
|
127
|
+
await Promise.allSettled([
|
|
128
|
+
(async () => {
|
|
129
|
+
try {
|
|
130
|
+
if (!Validator.POSITIVE_NUMBER(_db_size) || !maxLocalDatabaseSize || _db_size < maxLocalDatabaseSize) return;
|
|
131
|
+
const instances = [];
|
|
132
|
+
|
|
133
|
+
[database, counters].forEach((map, i) => {
|
|
134
|
+
breakDbMap(map, (projectUrl, dbUrl, dbName, path) => {
|
|
135
|
+
instances.push({
|
|
136
|
+
builder: { projectUrl, dbUrl, dbName },
|
|
137
|
+
isCounter: !!i,
|
|
138
|
+
path
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const redundantDbRanking = await Promise.all(
|
|
144
|
+
instances.map(async obj => {
|
|
145
|
+
const { builder, isCounter, path } = obj;
|
|
146
|
+
|
|
147
|
+
const sqlite = await openDB(builder);
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
if (isCounter) {
|
|
151
|
+
const data = await sqlite.executeSql(`SELECT * FROM ${DB_COUNT_QUERY(path)}`).then(r =>
|
|
152
|
+
r[0].rows.raw()
|
|
153
|
+
);
|
|
154
|
+
return data.map(v => [v, obj]);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const [instanceData, resultData] = await Promise.all([
|
|
158
|
+
sqlite.executeSql(`SELECT access_id, touched, size FROM ${LIMITER_DATA(path)}`),
|
|
159
|
+
sqlite.executeSql(`SELECT access_id-limit, touched, size FROM ${LIMITER_RESULT(path)}`)
|
|
160
|
+
]).then(r =>
|
|
161
|
+
r.map(v => v[0].rows.raw())
|
|
162
|
+
);
|
|
163
|
+
return [...instanceData, ...resultData].map(v => [v, obj]);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error('redundantDbRanking err:', error);
|
|
166
|
+
return [];
|
|
167
|
+
} finally {
|
|
168
|
+
sqlite.close();
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
).then(r =>
|
|
172
|
+
r.flat().sort(([a], [b]) =>
|
|
173
|
+
a.touched - b.touched
|
|
174
|
+
)
|
|
175
|
+
);
|
|
176
|
+
const newSize = maxLocalDatabaseSize / 2;
|
|
177
|
+
let sizer = _db_size;
|
|
178
|
+
let cuts = 0;
|
|
179
|
+
|
|
180
|
+
for (let i = 0; i < redundantDbRanking.length; i++) {
|
|
181
|
+
sizer -= redundantDbRanking[i][0].size || 0;
|
|
182
|
+
++cuts;
|
|
183
|
+
if (sizer < newSize) break;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.warn(`purging ${cuts} of ${redundantDbRanking.length} db entities`);
|
|
187
|
+
await Promise.all(redundantDbRanking.slice(0, cuts).map(async ([v, { builder, isCounter, path }]) => {
|
|
188
|
+
const sqlite = await openDB(builder);
|
|
189
|
+
try {
|
|
190
|
+
const table = (isCounter ? DB_COUNT_QUERY : 'access_id-limit' in v ? LIMITER_RESULT : LIMITER_DATA)(path);
|
|
191
|
+
const id_field = 'access_id-limit' in v ? 'access_id-limit' : 'access_id';
|
|
192
|
+
|
|
193
|
+
await sqlite.executeSql(SQLITE_COMMANDS.DELETE_ROW(table, `${id_field} = ?`), [v[id_field]]);
|
|
194
|
+
if (!isCounter) incrementDatabaseSizeCore(data.DatabaseStats, builder, path, -v.size);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.log('db redundantClearing err:', error);
|
|
197
|
+
}
|
|
198
|
+
sqlite.close();
|
|
199
|
+
}));
|
|
200
|
+
console.log('database purging complete');
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error('database purging err:', error);
|
|
203
|
+
}
|
|
204
|
+
})(),
|
|
205
|
+
(async () => {
|
|
206
|
+
try {
|
|
207
|
+
if (!Validator.POSITIVE_NUMBER(_fetcher_size) || !maxLocalFetchHttpSize || _fetcher_size < maxLocalFetchHttpSize) return;
|
|
208
|
+
|
|
209
|
+
const redundantFetchRanking = await Promise.all(Object.entries(fetchers).map(async ([projectUrl]) => {
|
|
210
|
+
const sqlite = await openDB(FETCH_RESOURCES(projectUrl));
|
|
211
|
+
const data = await sqlite.executeSql('SELECT access_id, touched, size FROM main').then(r =>
|
|
212
|
+
r[0].rows.raw()
|
|
213
|
+
);
|
|
214
|
+
return data.map(v => [v, projectUrl]);
|
|
215
|
+
})).then(r =>
|
|
216
|
+
r.flat().sort(([a], [b]) =>
|
|
217
|
+
a.touched - b.touched
|
|
218
|
+
)
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const newSize = maxLocalFetchHttpSize / 2;
|
|
222
|
+
let sizer = _fetcher_size;
|
|
223
|
+
let cuts = 0;
|
|
224
|
+
|
|
225
|
+
for (let i = 0; i < redundantFetchRanking.length; i++) {
|
|
226
|
+
sizer -= redundantFetchRanking[i][0].size || 0;
|
|
227
|
+
++cuts;
|
|
228
|
+
if (sizer < newSize) break;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
console.warn(`purging ${cuts} of ${redundantFetchRanking.length} fetcher entities`);
|
|
232
|
+
await Promise.all(redundantFetchRanking.slice(0, cuts).map(async ([v, projectUrl]) => {
|
|
233
|
+
const sqlite = await openDB(FETCH_RESOURCES(projectUrl));
|
|
234
|
+
try {
|
|
235
|
+
await sqlite.executeSql(SQLITE_COMMANDS.DELETE_ROW('main', 'access_id = ?'), [v.access_id]);
|
|
236
|
+
incrementFetcherSizeCore(data.DatabaseStats, projectUrl, -v.size);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.log('fetcher redundantClearing err:', error);
|
|
239
|
+
}
|
|
240
|
+
sqlite.close();
|
|
241
|
+
}));
|
|
242
|
+
console.log('fetcher purging complete');
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error('fetcher purging err:', error);
|
|
245
|
+
}
|
|
246
|
+
})()
|
|
247
|
+
]);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const breakDbMap = (obj, callback) =>
|
|
252
|
+
Object.entries(obj).forEach(([projectUrl, dbUrlObj]) => {
|
|
253
|
+
Object.entries(dbUrlObj).forEach(([dbUrl, dbNameObj]) => {
|
|
254
|
+
Object.entries(dbNameObj).forEach(([dbName, pathObj]) => {
|
|
255
|
+
Object.entries(pathObj).forEach(([path, value]) => {
|
|
256
|
+
callback(projectUrl, dbUrl, dbName, path, value);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
export const purgeInstance = (projectUrl) => {
|
|
263
|
+
// TODO: purge when signed-out
|
|
264
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { enablePromise, openDatabase } from 'react-native-sqlite-storage';
|
|
2
|
+
import { Scoped, SqliteCollective } from './variables';
|
|
3
|
+
import { niceHash } from './peripherals';
|
|
4
|
+
|
|
5
|
+
enablePromise(true);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* this method implement a centralize approach for opening and closing of sqlite database to ensure consistency across multiple task opening and closing the database in diferent order
|
|
9
|
+
*
|
|
10
|
+
* @param {string} name
|
|
11
|
+
* @returns {Promise<import('react-native-sqlite-storage').SQLiteDatabase>}
|
|
12
|
+
*/
|
|
13
|
+
export const openDB = async (name) => {
|
|
14
|
+
|
|
15
|
+
if (name?.projectUrl) {
|
|
16
|
+
const { projectUrl, dbUrl, dbName } = name;
|
|
17
|
+
name = encodeURIComponent(`${projectUrl}_${dbUrl}_${dbName}`) + '.db';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { sqliteKey } = Scoped.ReleaseCacheData;
|
|
21
|
+
|
|
22
|
+
if (sqliteKey) name = `${niceHash(sqliteKey)}__${name}`;
|
|
23
|
+
|
|
24
|
+
if (!SqliteCollective.openedDb[name]) {
|
|
25
|
+
SqliteCollective.openedDbProcess[name] = 0;
|
|
26
|
+
SqliteCollective.openedDb[name] = (SqliteCollective.closeDbPromises[name] || Promise.resolve()).finally(() =>
|
|
27
|
+
openDatabase({
|
|
28
|
+
location: 'default',
|
|
29
|
+
name,
|
|
30
|
+
key: sqliteKey
|
|
31
|
+
}).then(db => {
|
|
32
|
+
const prevClose = db.close.bind(db);
|
|
33
|
+
|
|
34
|
+
db.close = () => new Promise((resolve, reject) => {
|
|
35
|
+
if (--SqliteCollective.openedDbProcess[name] === 0) {
|
|
36
|
+
let willClose;
|
|
37
|
+
const timer = setTimeout(async () => {
|
|
38
|
+
willClose = true;
|
|
39
|
+
delete SqliteCollective.openedDb[name];
|
|
40
|
+
delete SqliteCollective.openedDbProcess[name];
|
|
41
|
+
SqliteCollective.closeDbPromises[name] = prevClose().then(() => {
|
|
42
|
+
resolve('active');
|
|
43
|
+
}).catch(e => {
|
|
44
|
+
reject(new Error(`${e}`));
|
|
45
|
+
}).finally(() => {
|
|
46
|
+
delete SqliteCollective.closeDbPromises[name];
|
|
47
|
+
});
|
|
48
|
+
delete SqliteCollective.openedDbReducerTimer[name];
|
|
49
|
+
}, 7);
|
|
50
|
+
|
|
51
|
+
SqliteCollective.openedDbReducerTimer[name] = () => {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
if (!willClose) resolve('passive');
|
|
54
|
+
delete SqliteCollective.openedDbReducerTimer[name];
|
|
55
|
+
}
|
|
56
|
+
} else resolve('passive');
|
|
57
|
+
});
|
|
58
|
+
return db;
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
SqliteCollective.openedDbReducerTimer[name]?.();
|
|
64
|
+
++SqliteCollective.openedDbProcess[name];
|
|
65
|
+
const thisDb = await SqliteCollective.openedDb[name];
|
|
66
|
+
let hasClosed;
|
|
67
|
+
|
|
68
|
+
const thisClose = async () => {
|
|
69
|
+
if (hasClosed) return;
|
|
70
|
+
hasClosed = true;
|
|
71
|
+
return (await thisDb.close());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return new Proxy({}, {
|
|
75
|
+
get: (_, n) => {
|
|
76
|
+
if (n === 'close') {
|
|
77
|
+
return thisClose;
|
|
78
|
+
} else if (typeof thisDb[n] === 'function')
|
|
79
|
+
return thisDb[n].bind(thisDb);
|
|
80
|
+
return thisDb[n];
|
|
81
|
+
},
|
|
82
|
+
set: (_, n, v) => {
|
|
83
|
+
thisDb[n] = v;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* this method linearize read/write on sqlite ensuring consistency across concurrent operations
|
|
90
|
+
*
|
|
91
|
+
* @param {any} builder
|
|
92
|
+
* @param {string} access_id
|
|
93
|
+
* @param {'database' | 'dbQueryCount' | 'httpFetch'} node
|
|
94
|
+
* @returns {(task: (sqlite: import("react-native-sqlite-storage").SQLiteDatabase) => Promise<{any}> )=> Promise<{any}>}
|
|
95
|
+
*/
|
|
96
|
+
export const useSqliteLinearAccessId = (builder, access_id, node) => async (task) => {
|
|
97
|
+
const { projectUrl, dbUrl, dbName } = builder;
|
|
98
|
+
const nodeId = typeof builder === 'string' ? `${builder}_${access_id}` : `${projectUrl}_${dbUrl}_${dbName}_${access_id}`;
|
|
99
|
+
|
|
100
|
+
const sqlite = await openDB(builder);
|
|
101
|
+
|
|
102
|
+
const thatProcess = Scoped.linearSqliteProcess[node][nodeId];
|
|
103
|
+
|
|
104
|
+
const thisPromise = new Promise(async (resolve, reject) => {
|
|
105
|
+
try {
|
|
106
|
+
if (thatProcess !== undefined) await thatProcess;
|
|
107
|
+
} catch (_) { }
|
|
108
|
+
try {
|
|
109
|
+
resolve(await task(sqlite));
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error('useSqliteLinearAccessId err:', error);
|
|
112
|
+
reject(error);
|
|
113
|
+
} finally {
|
|
114
|
+
if (Scoped.linearSqliteProcess[node][nodeId] === thisPromise)
|
|
115
|
+
delete Scoped.linearSqliteProcess[node][nodeId];
|
|
116
|
+
sqlite.close();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
Scoped.linearSqliteProcess[node][nodeId] = thisPromise;
|
|
121
|
+
return (await thisPromise);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const SQLITE_PATH = {
|
|
125
|
+
FILE_NAME: 'MOSQUITO_TRANSPORT.db',
|
|
126
|
+
TABLE_NAME: 'MT_MAIN',
|
|
127
|
+
LIMITER_RESULT: path => `"${encodeURIComponent(path)}-LIMITER_RESULT"`,
|
|
128
|
+
LIMITER_DATA: path => `"${encodeURIComponent(path)}-LIMITER_DATA"`,
|
|
129
|
+
DB_COUNT_QUERY: path => `"${encodeURIComponent(path)}-DB_COUNT_QUERY"`,
|
|
130
|
+
FETCH_RESOURCES: projectUrl => `FETCH_RESOURCES-${encodeURIComponent(projectUrl)}.db`
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const SQLITE_COMMANDS = {
|
|
134
|
+
MERGE: (table, columns = []) => `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(() => '?').join(', ')}) ON CONFLICT(${columns[0]}) DO UPDATE SET ${columns.slice(1).map(v => `${v} = excluded.${v}`).join(', ')}`,
|
|
135
|
+
UPDATE_COLUMNS: (table, columns = [], query = '') => `UPDATE ${table} SET ${columns.map(v => `${v} = ?`).join(', ')} WHERE ${query}`,
|
|
136
|
+
CREATE_INDEX: (table, columns) => `CREATE INDEX idx_${columns.join('_')} ON ${table}(${columns.join(', ')})`,
|
|
137
|
+
DELETE_ROW: (table, query) => `DELETE FROM ${table} WHERE ${query}`
|
|
138
|
+
}
|
package/src/helpers/utils.js
CHANGED
|
@@ -1,57 +1,103 @@
|
|
|
1
|
-
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
2
1
|
import { ServerReachableListener, StoreReadyListener } from "./listeners";
|
|
3
|
-
import { CACHE_PROTOCOL, CACHE_STORAGE_PATH, DEFAULT_CACHE_PASSWORD, LOCAL_STORAGE_PATH } from "./values";
|
|
4
2
|
import { CacheStore, Scoped } from "./variables";
|
|
5
|
-
import {
|
|
3
|
+
import { serializeE2E } from "./peripherals";
|
|
4
|
+
import { deserializeBSON, serializeToBase64 } from "../products/database/bson";
|
|
5
|
+
import { trySendPendingWrite } from "../products/database";
|
|
6
|
+
import { deserialize } from "entity-serializer";
|
|
7
|
+
import { openDB, SQLITE_COMMANDS, SQLITE_PATH } from "./sqlite_manager";
|
|
8
|
+
import { purgeRedundantRecords } from "./purger";
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
const { cachePassword = DEFAULT_CACHE_PASSWORD, cacheProtocol = CACHE_PROTOCOL.ASYNC_STORAGE, io } = Scoped.ReleaseCacheData;
|
|
10
|
+
const { FILE_NAME, TABLE_NAME } = SQLITE_PATH;
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
const CacheKeys = Object.keys(CacheStore);
|
|
13
|
+
|
|
14
|
+
export const updateCacheStore = (timer = 300, node) => {
|
|
15
|
+
const { io, promoteCache } = Scoped.ReleaseCacheData;
|
|
16
|
+
|
|
17
|
+
const doUpdate = async () => {
|
|
18
|
+
const {
|
|
19
|
+
AuthStore,
|
|
20
|
+
EmulatedAuth,
|
|
21
|
+
PendingAuthPurge,
|
|
22
|
+
DatabaseStore,
|
|
23
|
+
PendingWrites,
|
|
24
|
+
...restStore
|
|
25
|
+
} = CacheStore;
|
|
17
26
|
|
|
18
27
|
if (io) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
const txt = JSON.stringify({
|
|
29
|
+
AuthStore,
|
|
30
|
+
EmulatedAuth,
|
|
31
|
+
PendingAuthPurge,
|
|
32
|
+
...promoteCache ? {
|
|
33
|
+
DatabaseStore: serializeToBase64(DatabaseStore),
|
|
34
|
+
PendingWrites: serializeToBase64(PendingWrites)
|
|
35
|
+
} : {},
|
|
36
|
+
...promoteCache ? restStore : {}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
io.output(txt, node);
|
|
22
40
|
} else {
|
|
23
|
-
|
|
24
|
-
|
|
41
|
+
// use sqlite
|
|
42
|
+
const exclusion = ['DatabaseStore', 'DatabaseCountResult', 'FetchedStore'];
|
|
43
|
+
const updationKey = (node ? Array.isArray(node) ? node : [node] : CacheKeys).filter(v => !exclusion.includes(v));
|
|
44
|
+
|
|
45
|
+
if (!updationKey.length) return;
|
|
46
|
+
const sqlite = await openDB(FILE_NAME);
|
|
47
|
+
await Promise.allSettled(
|
|
48
|
+
updationKey
|
|
49
|
+
.map(v => [v, v === 'PendingWrites' ? serializeToBase64(CacheStore[v]) : CacheStore[v]])
|
|
50
|
+
.map(([ref, value]) =>
|
|
51
|
+
sqlite.executeSql(SQLITE_COMMANDS.MERGE(TABLE_NAME, ['ref', 'value']), [ref, JSON.stringify(value)])
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
sqlite.close();
|
|
25
55
|
}
|
|
26
|
-
}
|
|
27
|
-
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
clearTimeout(Scoped.cacheStorageReducer);
|
|
59
|
+
if (timer) {
|
|
60
|
+
Scoped.cacheStorageReducer = setTimeout(doUpdate, timer);
|
|
61
|
+
} else doUpdate();
|
|
62
|
+
};
|
|
28
63
|
|
|
29
64
|
export const releaseCacheStore = async (builder) => {
|
|
30
|
-
const {
|
|
65
|
+
const { io } = builder;
|
|
31
66
|
|
|
32
|
-
let
|
|
67
|
+
let data = {};
|
|
33
68
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
69
|
+
try {
|
|
70
|
+
if (io) {
|
|
71
|
+
data = JSON.parse((await io.input()) || '{}');
|
|
72
|
+
} else {
|
|
73
|
+
const sqlite = await openDB(FILE_NAME);
|
|
74
|
+
await sqlite.executeSql(`CREATE TABLE IF NOT EXISTS ${TABLE_NAME} ( ref TEXT PRIMARY KEY, value TEXT )`).catch(() => null);
|
|
75
|
+
const [query] = await sqlite.executeSql(`SELECT * FROM ${TABLE_NAME}`);
|
|
76
|
+
data = Object.fromEntries(query.rows.raw().map(v => [v.ref, JSON.parse(v.value)]));
|
|
77
|
+
sqlite.close();
|
|
78
|
+
}
|
|
79
|
+
await purgeRedundantRecords(data, builder);
|
|
80
|
+
} catch (e) {
|
|
81
|
+
console.error('releaseCacheStore data err:', e);
|
|
41
82
|
}
|
|
42
83
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
CacheStore[k] = v;
|
|
84
|
+
Object.entries(data).forEach(([k, v]) => {
|
|
85
|
+
if (['DatabaseStore', 'PendingWrites'].includes(k)) {
|
|
86
|
+
CacheStore[k] = deserializeBSON(v);
|
|
87
|
+
} else CacheStore[k] = v;
|
|
47
88
|
});
|
|
48
89
|
Object.entries(CacheStore.AuthStore).forEach(([key, value]) => {
|
|
49
90
|
Scoped.AuthJWTToken[key] = value?.token;
|
|
50
91
|
});
|
|
92
|
+
Object.keys(CacheStore.PendingWrites).forEach(projectUrl => {
|
|
93
|
+
if (Scoped.IS_CONNECTED[projectUrl])
|
|
94
|
+
trySendPendingWrite(projectUrl);
|
|
95
|
+
});
|
|
51
96
|
Scoped.IsStoreReady = true;
|
|
52
97
|
StoreReadyListener.dispatch('_', 'ready');
|
|
53
|
-
|
|
54
|
-
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const getPrefferTime = () => Date.now() + (Scoped.serverTimeOffset || 0);
|
|
55
101
|
|
|
56
102
|
export const awaitStore = () => new Promise(resolve => {
|
|
57
103
|
if (Scoped.IsStoreReady) {
|
|
@@ -92,26 +138,29 @@ export const getReachableServer = (projectUrl) => new Promise(resolve => {
|
|
|
92
138
|
}, true);
|
|
93
139
|
});
|
|
94
140
|
|
|
95
|
-
export const buildFetchInterface = ({ body,
|
|
141
|
+
export const buildFetchInterface = async ({ body, authToken, method, uglify, serverE2E_PublicKey, extraHeaders }) => {
|
|
96
142
|
if (!uglify) body = JSON.stringify({ ...body });
|
|
97
|
-
const [plate, keyPair] = uglify ? serializeE2E(body, authToken, serverE2E_PublicKey) : [undefined, []];
|
|
143
|
+
const [plate, keyPair] = uglify ? await serializeE2E(body, authToken, serverE2E_PublicKey) : [undefined, []];
|
|
98
144
|
|
|
99
145
|
return [{
|
|
100
146
|
body: uglify ? plate : body,
|
|
101
|
-
cache: 'no-cache',
|
|
147
|
+
// cache: 'no-cache',
|
|
102
148
|
headers: {
|
|
103
|
-
|
|
104
|
-
'
|
|
105
|
-
...(
|
|
149
|
+
...extraHeaders,
|
|
150
|
+
'Content-type': uglify ? 'request/buffer' : 'application/json',
|
|
151
|
+
...(authToken && !uglify) ? { 'Mosquito-Token': authToken } : {}
|
|
106
152
|
},
|
|
107
153
|
method: method || 'POST'
|
|
108
154
|
}, keyPair];
|
|
109
155
|
};
|
|
110
156
|
|
|
111
|
-
export const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
157
|
+
export const buildFetchResult = async (fetchRef, ugly) => {
|
|
158
|
+
if (ugly) {
|
|
159
|
+
const [data, simpleError] = deserialize(await fetchRef.arrayBuffer());
|
|
160
|
+
if (simpleError) throw simpleError;
|
|
161
|
+
return data;
|
|
162
|
+
}
|
|
163
|
+
const json = await fetchRef.json();
|
|
164
|
+
if (json.simpleError) throw json;
|
|
165
|
+
return json;
|
|
166
|
+
};
|
package/src/helpers/values.js
CHANGED
|
@@ -1,82 +1,28 @@
|
|
|
1
|
-
import { Platform } from 'react-native';
|
|
2
|
-
import { encodeBinary } from './peripherals';
|
|
3
|
-
|
|
4
|
-
export const CACHE_STORAGE_PATH = encodeBinary('MOSQUITO_TRANSPORT_FREEZER'),
|
|
5
|
-
DEFAULT_CACHE_PASSWORD = encodeBinary('MOSQUITO_TRANSPORT_CACHE_PASSWORD'),
|
|
6
|
-
LOCAL_STORAGE_PATH = () => {
|
|
7
|
-
const fs = require('react-native-fs');
|
|
8
|
-
return `${Platform.OS === 'android' ? fs.ExternalCachesDirectoryPath : fs.CachesDirectoryPath}/${encodeBinary('MOSQUITO_TRANSPORT_STORAGE')}`;
|
|
9
|
-
},
|
|
10
|
-
DEFAULT_DB_NAME = 'DEFAULT_DB',
|
|
11
|
-
DEFAULT_DB_URL = 'mongodb://127.0.0.1:27017',
|
|
12
|
-
DEFAULT_ENCRYPT_IV = '****';
|
|
13
|
-
|
|
14
|
-
export const CACHE_PROTOCOL = {
|
|
15
|
-
ASYNC_STORAGE: 'async-storage',
|
|
16
|
-
REACT_NATIVE_FS: 'reat-native-fs',
|
|
17
|
-
SQLITE: 'sqlite' // TODO:
|
|
18
|
-
};
|
|
19
1
|
|
|
20
2
|
export const RETRIEVAL = {
|
|
21
3
|
STICKY: 'sticky',
|
|
22
4
|
STICKY_NO_AWAIT: 'sticky-no-await',
|
|
23
5
|
STICKY_RELOAD: 'sticky-reload',
|
|
24
6
|
DEFAULT: 'default',
|
|
7
|
+
CACHE_AWAIT: 'cache-await',
|
|
25
8
|
CACHE_NO_AWAIT: 'cache-no-await',
|
|
26
|
-
NO_CACHE_NO_AWAIT: 'no-cache-no-await'
|
|
9
|
+
NO_CACHE_NO_AWAIT: 'no-cache-no-await',
|
|
10
|
+
NO_CACHE_AWAIT: 'no-cache-await'
|
|
27
11
|
};
|
|
28
12
|
|
|
29
13
|
export const DELIVERY = {
|
|
30
14
|
DEFAULT: 'default',
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
CACHE_NO_AWAIT: 'cache-no-await'
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export const WRITE_OPS = {
|
|
39
|
-
$SET: '$set',
|
|
40
|
-
$PUSH: '$push',
|
|
41
|
-
$PULL: '$pull',
|
|
42
|
-
$UNSET: '$unset',
|
|
43
|
-
$INC: '$inc',
|
|
44
|
-
$MAX: '$max',
|
|
45
|
-
$MIN: '$min',
|
|
46
|
-
$MUL: '$mul',
|
|
47
|
-
$RENAME: '$rename',
|
|
48
|
-
$SET_ON_INSERT: '$setOnInsert'
|
|
49
|
-
};
|
|
50
|
-
export const WRITE_OPS_LIST = Object.values(WRITE_OPS);
|
|
51
|
-
|
|
52
|
-
export const READ_OPS = {
|
|
53
|
-
$IN: '$in',
|
|
54
|
-
$ALL: '$all',
|
|
55
|
-
$NIN: '$nin',
|
|
56
|
-
$GT: '$gt',
|
|
57
|
-
$GTE: '$gte',
|
|
58
|
-
$LT: '$lt',
|
|
59
|
-
$LTE: '$lte',
|
|
60
|
-
$TEXT: '$text',
|
|
61
|
-
// $EQ: '$eq',
|
|
62
|
-
// $REGEX: '$regex',
|
|
63
|
-
// $EXISTS: '$exists',
|
|
64
|
-
$NEAR: '$near',
|
|
65
|
-
$TYPE: '$type',
|
|
66
|
-
$SIZE: '$size',
|
|
67
|
-
// $NE: '$ne'
|
|
68
|
-
};
|
|
69
|
-
export const READ_OPS_LIST = Object.values(READ_OPS);
|
|
70
|
-
|
|
71
|
-
export const Regexs = {
|
|
72
|
-
LINK: () => /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig
|
|
15
|
+
CACHE_AWAIT: 'cache-await',
|
|
16
|
+
CACHE_NO_AWAIT: 'cache-no-await',
|
|
17
|
+
NO_CACHE_NO_AWAIT: 'no-cache-no-await',
|
|
18
|
+
NO_CACHE_AWAIT: 'no-cache-await'
|
|
73
19
|
};
|
|
74
20
|
|
|
75
21
|
export const AUTH_PROVIDER_ID = {
|
|
76
|
-
GOOGLE: 'google
|
|
77
|
-
FACEBOOK: 'facebook
|
|
22
|
+
GOOGLE: 'google',
|
|
23
|
+
FACEBOOK: 'facebook',
|
|
78
24
|
PASSWORD: 'password',
|
|
79
|
-
TWITTER: 'x
|
|
80
|
-
GITHUB: 'github
|
|
81
|
-
APPLE: 'apple
|
|
25
|
+
TWITTER: 'x',
|
|
26
|
+
GITHUB: 'github',
|
|
27
|
+
APPLE: 'apple'
|
|
82
28
|
};
|