tinybase 6.3.1 → 6.4.0

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.
@@ -0,0 +1,1189 @@
1
+ const getTypeOf = (thing) => typeof thing;
2
+ const TINYBASE = 'tinybase';
3
+ const EMPTY_STRING = '';
4
+ const COMMA = ',';
5
+ const DOT = '.';
6
+ const STRING = getTypeOf(EMPTY_STRING);
7
+ const TRUE = 'true';
8
+ const UNDEFINED = '\uFFFC';
9
+ const strSplit = (str, separator = EMPTY_STRING, limit) =>
10
+ str.split(separator, limit);
11
+ const strReplace = (str, searchValue, replaceValue) =>
12
+ str.replace(searchValue, replaceValue);
13
+
14
+ const promise = Promise;
15
+ const THOUSAND = 1e3;
16
+ const startInterval = (callback, sec, immediate) => {
17
+ return setInterval(callback, sec * THOUSAND);
18
+ };
19
+ const stopInterval = clearInterval;
20
+ const isUndefined = (thing) => thing == void 0;
21
+ const ifNotUndefined = (value, then, otherwise) =>
22
+ isUndefined(value) ? otherwise?.() : then(value);
23
+ const isString = (thing) => getTypeOf(thing) == STRING;
24
+ const isArray = (thing) => Array.isArray(thing);
25
+ const slice = (arrayOrString, start, end) => arrayOrString.slice(start, end);
26
+ const size = (arrayOrString) => arrayOrString.length;
27
+ const test = (regex, subject) => regex.test(subject);
28
+ const noop = () => {};
29
+ const promiseAll = async (promises) => promise.all(promises);
30
+ const errorNew = (message) => {
31
+ throw new Error(message);
32
+ };
33
+ const tryCatch = async (action, then1, then2) => {
34
+ try {
35
+ return await action();
36
+ } catch (error) {
37
+ /* istanbul ignore next */
38
+ then1?.(error);
39
+ }
40
+ };
41
+
42
+ const arrayForEach = (array, cb) => array.forEach(cb);
43
+ const arrayJoin = (array, sep = EMPTY_STRING) => array.join(sep);
44
+ const arrayMap = (array, cb) => array.map(cb);
45
+ const arrayIsEmpty = (array) => size(array) == 0;
46
+ const arrayFilter = (array, cb) => array.filter(cb);
47
+ const arrayClear = (array, to) => array.splice(0, to);
48
+ const arrayPush = (array, ...values) => array.push(...values);
49
+ const arrayShift = (array) => array.shift();
50
+
51
+ const collSize = (coll) => coll?.size ?? 0;
52
+ const collHas = (coll, keyOrValue) => coll?.has(keyOrValue) ?? false;
53
+ const collIsEmpty = (coll) => isUndefined(coll) || collSize(coll) == 0;
54
+ const collValues = (coll) => [...(coll?.values() ?? [])];
55
+ const collClear = (coll) => coll.clear();
56
+ const collForEach = (coll, cb) => coll?.forEach(cb);
57
+ const collDel = (coll, keyOrValue) => coll?.delete(keyOrValue);
58
+
59
+ const SINGLE_ROW_ID = '_';
60
+ const DEFAULT_ROW_ID_COLUMN_NAME = '_id';
61
+ const SELECT = 'SELECT';
62
+ const WHERE = 'WHERE';
63
+ const TABLE = 'TABLE';
64
+ const INSERT = 'INSERT';
65
+ const DELETE = 'DELETE';
66
+ const UPDATE = 'UPDATE';
67
+ const ALTER_TABLE = 'ALTER ' + TABLE;
68
+ const FROM = 'FROM';
69
+ const DELETE_FROM = DELETE + ' ' + FROM;
70
+ const SELECT_STAR_FROM = SELECT + '*' + FROM;
71
+ const PRAGMA = 'pragma_';
72
+ const DATA_VERSION = 'data_version';
73
+ const SCHEMA_VERSION = 'schema_version';
74
+ const PRAGMA_TABLE = 'pragma_table_';
75
+ const CREATE = 'CREATE ';
76
+ const CREATE_TABLE = CREATE + TABLE;
77
+ const TABLE_NAME_PLACEHOLDER = '$tableName';
78
+ const getWrappedCommand = (executeCommand, onSqlCommand) =>
79
+ onSqlCommand
80
+ ? async (sql, params) => {
81
+ onSqlCommand(sql, params);
82
+ return await executeCommand(sql, params);
83
+ }
84
+ : executeCommand;
85
+ const escapeId = (str) =>
86
+ arrayJoin(
87
+ arrayMap(strSplit(str, DOT), (part) => `"${strReplace(part, /"/g, '""')}"`),
88
+ DOT,
89
+ );
90
+ const escapeColumnNames = (...columnNames) =>
91
+ arrayJoin(arrayMap(columnNames, escapeId), COMMA);
92
+ const getPlaceholders = (array, offset = [1]) =>
93
+ arrayJoin(
94
+ arrayMap(array, () => '$' + offset[0]++),
95
+ COMMA,
96
+ );
97
+ const getWhereCondition = (tableName, condition = TRUE) =>
98
+ WHERE +
99
+ `(${strReplace(condition, TABLE_NAME_PLACEHOLDER, escapeId(tableName))})`;
100
+
101
+ const object = Object;
102
+ const getPrototypeOf = (obj) => object.getPrototypeOf(obj);
103
+ const objEntries = object.entries;
104
+ const isObject = (obj) =>
105
+ !isUndefined(obj) &&
106
+ ifNotUndefined(
107
+ getPrototypeOf(obj),
108
+ (objPrototype) =>
109
+ objPrototype == object.prototype ||
110
+ isUndefined(getPrototypeOf(objPrototype)),
111
+
112
+ /* istanbul ignore next */
113
+ () => true,
114
+ );
115
+ const objIds = object.keys;
116
+ const objFreeze = object.freeze;
117
+ const objNew = (entries = []) => object.fromEntries(entries);
118
+ const objMerge = (...objs) => object.assign({}, ...objs);
119
+ const objHas = (obj, id) => id in obj;
120
+ const objDel = (obj, id) => {
121
+ delete obj[id];
122
+ return obj;
123
+ };
124
+ const objToArray = (obj, cb) =>
125
+ arrayMap(objEntries(obj), ([id, value]) => cb(value, id));
126
+ const objMap = (obj, cb) =>
127
+ objNew(objToArray(obj, (value, id) => [id, cb(value, id)]));
128
+ const objValues = (obj) => object.values(obj);
129
+ const objSize = (obj) => size(objIds(obj));
130
+ const objIsEmpty = (obj) => isObject(obj) && objSize(obj) == 0;
131
+
132
+ const mapNew = (entries) => new Map(entries);
133
+ const mapGet = (map, key) => map?.get(key);
134
+ const mapForEach = (map, cb) =>
135
+ collForEach(map, (value, key) => cb(key, value));
136
+ const mapMap = (coll, cb) =>
137
+ arrayMap([...(coll?.entries() ?? [])], ([key, value]) => cb(value, key));
138
+ const mapSet = (map, key, value) =>
139
+ isUndefined(value) ? (collDel(map, key), map) : map?.set(key, value);
140
+ const mapEnsure = (map, key, getDefaultValue, hadExistingValue) => {
141
+ if (!collHas(map, key)) {
142
+ mapSet(map, key, getDefaultValue());
143
+ } else {
144
+ hadExistingValue?.(mapGet(map, key));
145
+ }
146
+ return mapGet(map, key);
147
+ };
148
+ const visitTree = (node, path, ensureLeaf, pruneLeaf, p = 0) =>
149
+ ifNotUndefined(
150
+ (ensureLeaf ? mapEnsure : mapGet)(
151
+ node,
152
+ path[p],
153
+ p > size(path) - 2 ? ensureLeaf : mapNew,
154
+ ),
155
+ (nodeOrLeaf) => {
156
+ if (p > size(path) - 2) {
157
+ if (pruneLeaf?.(nodeOrLeaf)) {
158
+ mapSet(node, path[p]);
159
+ }
160
+ return nodeOrLeaf;
161
+ }
162
+ const leaf = visitTree(nodeOrLeaf, path, ensureLeaf, pruneLeaf, p + 1);
163
+ if (collIsEmpty(nodeOrLeaf)) {
164
+ mapSet(node, path[p]);
165
+ }
166
+ return leaf;
167
+ },
168
+ );
169
+
170
+ const setNew = (entryOrEntries) =>
171
+ new Set(
172
+ isArray(entryOrEntries) || isUndefined(entryOrEntries)
173
+ ? entryOrEntries
174
+ : [entryOrEntries],
175
+ );
176
+ const setAdd = (set, value) => set?.add(value);
177
+
178
+ const COLUMN_NAME = 'ColumnName';
179
+ const STORE = 'store';
180
+ const JSON$1 = 'json';
181
+ const STORE_TABLE_NAME = STORE + 'TableName';
182
+ const STORE_ID_COLUMN_NAME = STORE + 'Id' + COLUMN_NAME;
183
+ const STORE_COLUMN_NAME = STORE + COLUMN_NAME;
184
+ const AUTO_LOAD_INTERVAL_SECONDS = 'autoLoadIntervalSeconds';
185
+ const ROW_ID_COLUMN_NAME = 'rowId' + COLUMN_NAME;
186
+ const TABLE_ID = 'tableId';
187
+ const TABLE_NAME = 'tableName';
188
+ const DELETE_EMPTY_COLUMNS = 'deleteEmptyColumns';
189
+ const DELETE_EMPTY_TABLE = 'deleteEmptyTable';
190
+ const CONDITION = 'condition';
191
+ const DEFAULT_CONFIG = {
192
+ mode: JSON$1,
193
+ [AUTO_LOAD_INTERVAL_SECONDS]: 1,
194
+ };
195
+ const DEFAULT_TABULAR_VALUES_CONFIG = {
196
+ load: 0,
197
+ save: 0,
198
+ [TABLE_NAME]: TINYBASE + '_values',
199
+ };
200
+ const getDefaultedConfig = (configOrStoreTableName) =>
201
+ objMerge(
202
+ DEFAULT_CONFIG,
203
+ isString(configOrStoreTableName)
204
+ ? {[STORE_TABLE_NAME]: configOrStoreTableName}
205
+ : (configOrStoreTableName ?? {}),
206
+ );
207
+ const getDefaultedTabularConfigMap = (
208
+ configsObj,
209
+ defaultObj,
210
+ tableField,
211
+ exclude,
212
+ then,
213
+ ) => {
214
+ const configMap = mapNew();
215
+ objMap(configsObj, (configObj, id) => {
216
+ const defaultedConfig = slice(
217
+ objValues(
218
+ objMerge(
219
+ defaultObj,
220
+ isString(configObj) ? {[tableField]: configObj} : configObj,
221
+ ),
222
+ ),
223
+ 0,
224
+ objSize(defaultObj),
225
+ );
226
+ if (!isUndefined(defaultedConfig[0]) && !exclude(id, defaultedConfig[0])) {
227
+ then(id, defaultedConfig[0]);
228
+ mapSet(configMap, id, defaultedConfig);
229
+ }
230
+ });
231
+ return configMap;
232
+ };
233
+ const getConfigStructures = (configOrStoreTableName) => {
234
+ const config = getDefaultedConfig(configOrStoreTableName);
235
+ const autoLoadIntervalSeconds = config[AUTO_LOAD_INTERVAL_SECONDS];
236
+ if (config.mode == JSON$1) {
237
+ const storeTableName = config[STORE_TABLE_NAME] ?? TINYBASE;
238
+ return [
239
+ 1,
240
+ autoLoadIntervalSeconds,
241
+ [
242
+ storeTableName,
243
+ config[STORE_ID_COLUMN_NAME] ?? DEFAULT_ROW_ID_COLUMN_NAME,
244
+ config[STORE_COLUMN_NAME] ?? STORE,
245
+ ],
246
+ setNew(storeTableName),
247
+ ];
248
+ }
249
+ const {tables: {load = {}, save = {}} = {}, values = {}} = config;
250
+ const valuesConfig = slice(
251
+ objValues(objMerge(DEFAULT_TABULAR_VALUES_CONFIG, values)),
252
+ 0,
253
+ objSize(DEFAULT_TABULAR_VALUES_CONFIG),
254
+ );
255
+ const valuesTable = valuesConfig[2];
256
+ const managedTableNames = setNew(valuesTable);
257
+ const excludedTableNames = setNew(valuesTable);
258
+ const tablesLoadConfig = getDefaultedTabularConfigMap(
259
+ load,
260
+ {
261
+ [TABLE_ID]: null,
262
+ [ROW_ID_COLUMN_NAME]: DEFAULT_ROW_ID_COLUMN_NAME,
263
+ [CONDITION]: TRUE,
264
+ },
265
+ TABLE_ID,
266
+ (tableName) => collHas(excludedTableNames, tableName),
267
+ (tableName) => setAdd(managedTableNames, tableName),
268
+ );
269
+ const tablesSaveConfig = getDefaultedTabularConfigMap(
270
+ save,
271
+ {
272
+ [TABLE_NAME]: null,
273
+ [ROW_ID_COLUMN_NAME]: DEFAULT_ROW_ID_COLUMN_NAME,
274
+ [DELETE_EMPTY_COLUMNS]: 0,
275
+ [DELETE_EMPTY_TABLE]: 0,
276
+ [CONDITION]: null,
277
+ },
278
+ TABLE_NAME,
279
+ (_, tableName) => collHas(excludedTableNames, tableName),
280
+ (_, tableName) => setAdd(managedTableNames, tableName),
281
+ );
282
+ mapForEach(
283
+ tablesSaveConfig,
284
+ (_, tableSaveConfig) =>
285
+ (tableSaveConfig[4] ??=
286
+ mapGet(tablesLoadConfig, tableSaveConfig[0])?.[2] ?? TRUE),
287
+ );
288
+ return [
289
+ 0,
290
+ autoLoadIntervalSeconds,
291
+ [tablesLoadConfig, tablesSaveConfig, valuesConfig],
292
+ managedTableNames,
293
+ ];
294
+ };
295
+
296
+ const jsonString = JSON.stringify;
297
+ const jsonParse = JSON.parse;
298
+ const jsonStringWithUndefined = (obj) =>
299
+ jsonString(obj, (_key, value) => (value === void 0 ? UNDEFINED : value));
300
+ const jsonParseWithUndefined = (str) =>
301
+ jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
302
+
303
+ const INTEGER = /^\d+$/;
304
+ const getPoolFunctions = () => {
305
+ const pool = [];
306
+ let nextId = 0;
307
+ return [
308
+ (reuse) => (reuse ? arrayShift(pool) : null) ?? EMPTY_STRING + nextId++,
309
+ (id) => {
310
+ if (test(INTEGER, id) && size(pool) < 1e3) {
311
+ arrayPush(pool, id);
312
+ }
313
+ },
314
+ ];
315
+ };
316
+
317
+ const getWildcardedLeaves = (deepIdSet, path = [EMPTY_STRING]) => {
318
+ const leaves = [];
319
+ const deep = (node, p) =>
320
+ p == size(path)
321
+ ? arrayPush(leaves, node)
322
+ : path[p] === null
323
+ ? collForEach(node, (node2) => deep(node2, p + 1))
324
+ : arrayForEach([path[p], null], (id) => deep(mapGet(node, id), p + 1));
325
+ deep(deepIdSet, 0);
326
+ return leaves;
327
+ };
328
+ const getListenerFunctions = (getThing) => {
329
+ let thing;
330
+ const [getId, releaseId] = getPoolFunctions();
331
+ const allListeners = mapNew();
332
+ const addListener = (
333
+ listener,
334
+ idSetNode,
335
+ path,
336
+ pathGetters = [],
337
+ extraArgsGetter = () => [],
338
+ ) => {
339
+ thing ??= getThing();
340
+ const id = getId(1);
341
+ mapSet(allListeners, id, [
342
+ listener,
343
+ idSetNode,
344
+ path,
345
+ pathGetters,
346
+ extraArgsGetter,
347
+ ]);
348
+ setAdd(visitTree(idSetNode, path ?? [EMPTY_STRING], setNew), id);
349
+ return id;
350
+ };
351
+ const callListeners = (idSetNode, ids, ...extraArgs) =>
352
+ arrayForEach(getWildcardedLeaves(idSetNode, ids), (set) =>
353
+ collForEach(set, (id) =>
354
+ mapGet(allListeners, id)[0](thing, ...(ids ?? []), ...extraArgs),
355
+ ),
356
+ );
357
+ const delListener = (id) =>
358
+ ifNotUndefined(mapGet(allListeners, id), ([, idSetNode, idOrNulls]) => {
359
+ visitTree(idSetNode, idOrNulls ?? [EMPTY_STRING], void 0, (idSet) => {
360
+ collDel(idSet, id);
361
+ return collIsEmpty(idSet) ? 1 : 0;
362
+ });
363
+ mapSet(allListeners, id);
364
+ releaseId(id);
365
+ return idOrNulls;
366
+ });
367
+ const callListener = (id) =>
368
+ ifNotUndefined(
369
+ mapGet(allListeners, id),
370
+ ([listener, , path = [], pathGetters, extraArgsGetter]) => {
371
+ const callWithIds = (...ids) => {
372
+ const index = size(ids);
373
+ if (index == size(path)) {
374
+ listener(thing, ...ids, ...extraArgsGetter(ids));
375
+ } else if (isUndefined(path[index])) {
376
+ arrayForEach(pathGetters[index]?.(...ids) ?? [], (id2) =>
377
+ callWithIds(...ids, id2),
378
+ );
379
+ } else {
380
+ callWithIds(...ids, path[index]);
381
+ }
382
+ };
383
+ callWithIds();
384
+ },
385
+ );
386
+ return [addListener, callListeners, delListener, callListener];
387
+ };
388
+
389
+ const scheduleRunning = mapNew();
390
+ const scheduleActions = mapNew();
391
+ const getStoreFunctions = (
392
+ persist = 1 /* StoreOnly */,
393
+ store,
394
+ isSynchronizer,
395
+ ) =>
396
+ persist != 1 /* StoreOnly */ && store.isMergeable()
397
+ ? [
398
+ 1,
399
+ store.getMergeableContent,
400
+ () => store.getTransactionMergeableChanges(!isSynchronizer),
401
+ ([[changedTables], [changedValues]]) =>
402
+ !objIsEmpty(changedTables) || !objIsEmpty(changedValues),
403
+ store.setDefaultContent,
404
+ ]
405
+ : persist != 2 /* MergeableStoreOnly */
406
+ ? [
407
+ 0,
408
+ store.getContent,
409
+ store.getTransactionChanges,
410
+ ([changedTables, changedValues]) =>
411
+ !objIsEmpty(changedTables) || !objIsEmpty(changedValues),
412
+ store.setContent,
413
+ ]
414
+ : errorNew('Store type not supported by this Persister');
415
+ const createCustomPersister = (
416
+ store,
417
+ getPersisted,
418
+ setPersisted,
419
+ addPersisterListener,
420
+ delPersisterListener,
421
+ onIgnoredError,
422
+ persist,
423
+ extra = {},
424
+ isSynchronizer = 0,
425
+ scheduleId = [],
426
+ ) => {
427
+ let status = 0; /* Idle */
428
+ let loads = 0;
429
+ let saves = 0;
430
+ let action;
431
+ let autoLoadHandle;
432
+ let autoSaveListenerId;
433
+ mapEnsure(scheduleRunning, scheduleId, () => 0);
434
+ mapEnsure(scheduleActions, scheduleId, () => []);
435
+ const statusListeners = mapNew();
436
+ const [
437
+ isMergeableStore,
438
+ getContent,
439
+ getChanges,
440
+ hasChanges,
441
+ setDefaultContent,
442
+ ] = getStoreFunctions(persist, store, isSynchronizer);
443
+ const [addListener, callListeners, delListenerImpl] = getListenerFunctions(
444
+ () => persister,
445
+ );
446
+ const setStatus = (newStatus) => {
447
+ if (newStatus != status) {
448
+ status = newStatus;
449
+ callListeners(statusListeners, void 0, status);
450
+ }
451
+ };
452
+ const run = async () => {
453
+ /* istanbul ignore else */
454
+ if (!mapGet(scheduleRunning, scheduleId)) {
455
+ mapSet(scheduleRunning, scheduleId, 1);
456
+ while (
457
+ !isUndefined((action = arrayShift(mapGet(scheduleActions, scheduleId))))
458
+ ) {
459
+ await tryCatch(action, onIgnoredError);
460
+ }
461
+ mapSet(scheduleRunning, scheduleId, 0);
462
+ }
463
+ };
464
+ const setContentOrChanges = (contentOrChanges) => {
465
+ (isMergeableStore && isArray(contentOrChanges?.[0])
466
+ ? contentOrChanges?.[2] === 1
467
+ ? store.applyMergeableChanges
468
+ : store.setMergeableContent
469
+ : contentOrChanges?.[2] === 1
470
+ ? store.applyChanges
471
+ : store.setContent)(contentOrChanges);
472
+ };
473
+ const load = async (initialContent) => {
474
+ /* istanbul ignore else */
475
+ if (status != 2 /* Saving */) {
476
+ setStatus(1 /* Loading */);
477
+ loads++;
478
+ await schedule(async () => {
479
+ await tryCatch(
480
+ async () => {
481
+ const content = await getPersisted();
482
+ if (isArray(content)) {
483
+ setContentOrChanges(content);
484
+ } else if (initialContent) {
485
+ setDefaultContent(initialContent);
486
+ } else {
487
+ errorNew(`Content is not an array: ${content}`);
488
+ }
489
+ },
490
+ () => {
491
+ if (initialContent) {
492
+ setDefaultContent(initialContent);
493
+ }
494
+ },
495
+ );
496
+ setStatus(0 /* Idle */);
497
+ });
498
+ }
499
+ return persister;
500
+ };
501
+ const startAutoLoad = async (initialContent) => {
502
+ stopAutoLoad();
503
+ await load(initialContent);
504
+ await tryCatch(
505
+ async () =>
506
+ (autoLoadHandle = await addPersisterListener(
507
+ async (content, changes) => {
508
+ if (changes || content) {
509
+ /* istanbul ignore else */
510
+ if (status != 2 /* Saving */) {
511
+ setStatus(1 /* Loading */);
512
+ loads++;
513
+ setContentOrChanges(changes ?? content);
514
+ setStatus(0 /* Idle */);
515
+ }
516
+ } else {
517
+ await load();
518
+ }
519
+ },
520
+ )),
521
+ onIgnoredError,
522
+ );
523
+ return persister;
524
+ };
525
+ const stopAutoLoad = async () => {
526
+ if (autoLoadHandle) {
527
+ await tryCatch(
528
+ () => delPersisterListener(autoLoadHandle),
529
+ onIgnoredError,
530
+ );
531
+ autoLoadHandle = void 0;
532
+ }
533
+ return persister;
534
+ };
535
+ const isAutoLoading = () => !isUndefined(autoLoadHandle);
536
+ const save = async (changes) => {
537
+ /* istanbul ignore else */
538
+ if (status != 1 /* Loading */) {
539
+ setStatus(2 /* Saving */);
540
+ saves++;
541
+ await schedule(async () => {
542
+ await tryCatch(() => setPersisted(getContent, changes), onIgnoredError);
543
+ setStatus(0 /* Idle */);
544
+ });
545
+ }
546
+ return persister;
547
+ };
548
+ const startAutoSave = async () => {
549
+ stopAutoSave();
550
+ await save();
551
+ autoSaveListenerId = store.addDidFinishTransactionListener(() => {
552
+ const changes = getChanges();
553
+ if (hasChanges(changes)) {
554
+ save(changes);
555
+ }
556
+ });
557
+ return persister;
558
+ };
559
+ const stopAutoSave = async () => {
560
+ if (autoSaveListenerId) {
561
+ store.delListener(autoSaveListenerId);
562
+ autoSaveListenerId = void 0;
563
+ }
564
+ return persister;
565
+ };
566
+ const isAutoSaving = () => !isUndefined(autoSaveListenerId);
567
+ const startAutoPersisting = async (
568
+ initialContent,
569
+ startSaveFirst = false,
570
+ ) => {
571
+ const [call1, call2] = startSaveFirst
572
+ ? [startAutoSave, startAutoLoad]
573
+ : [startAutoLoad, startAutoSave];
574
+ await call1(initialContent);
575
+ await call2(initialContent);
576
+ return persister;
577
+ };
578
+ const stopAutoPersisting = async (stopSaveFirst = false) => {
579
+ const [call1, call2] = stopSaveFirst
580
+ ? [stopAutoSave, stopAutoLoad]
581
+ : [stopAutoLoad, stopAutoSave];
582
+ await call1();
583
+ await call2();
584
+ return persister;
585
+ };
586
+ const getStatus = () => status;
587
+ const addStatusListener = (listener) =>
588
+ addListener(listener, statusListeners);
589
+ const delListener = (listenerId) => {
590
+ delListenerImpl(listenerId);
591
+ return store;
592
+ };
593
+ const schedule = async (...actions) => {
594
+ arrayPush(mapGet(scheduleActions, scheduleId), ...actions);
595
+ await run();
596
+ return persister;
597
+ };
598
+ const getStore = () => store;
599
+ const destroy = () => {
600
+ arrayClear(mapGet(scheduleActions, scheduleId));
601
+ return stopAutoPersisting();
602
+ };
603
+ const getStats = () => ({loads, saves});
604
+ const persister = {
605
+ load,
606
+ startAutoLoad,
607
+ stopAutoLoad,
608
+ isAutoLoading,
609
+ save,
610
+ startAutoSave,
611
+ stopAutoSave,
612
+ isAutoSaving,
613
+ startAutoPersisting,
614
+ stopAutoPersisting,
615
+ getStatus,
616
+ addStatusListener,
617
+ delListener,
618
+ schedule,
619
+ getStore,
620
+ destroy,
621
+ getStats,
622
+ ...extra,
623
+ };
624
+ return objFreeze(persister);
625
+ };
626
+
627
+ const getCommandFunctions = (
628
+ databaseExecuteCommand,
629
+ managedTableNames,
630
+ querySchema,
631
+ onIgnoredError,
632
+ columnType,
633
+ upsert = defaultUpsert,
634
+ encode,
635
+ decode,
636
+ ) => {
637
+ const schemaMap = mapNew();
638
+ const canSelect = (tableName, rowIdColumnName) =>
639
+ collHas(mapGet(schemaMap, tableName), rowIdColumnName);
640
+ const refreshSchema = async () => {
641
+ collClear(schemaMap);
642
+ arrayMap(
643
+ await querySchema(databaseExecuteCommand, managedTableNames),
644
+ ({tn, cn}) => setAdd(mapEnsure(schemaMap, tn, setNew), cn),
645
+ );
646
+ };
647
+ const loadTable = async (tableName, rowIdColumnName, condition) =>
648
+ canSelect(tableName, rowIdColumnName)
649
+ ? objNew(
650
+ arrayFilter(
651
+ arrayMap(
652
+ await databaseExecuteCommand(
653
+ SELECT_STAR_FROM +
654
+ escapeId(tableName) +
655
+ getWhereCondition(tableName, condition),
656
+ ),
657
+ (row) => [
658
+ row[rowIdColumnName],
659
+ decode
660
+ ? objMap(objDel(row, rowIdColumnName), decode)
661
+ : objDel(row, rowIdColumnName),
662
+ ],
663
+ ),
664
+ ([rowId, row]) => !isUndefined(rowId) && !objIsEmpty(row),
665
+ ),
666
+ )
667
+ : {};
668
+ const saveTable = async (
669
+ tableName,
670
+ rowIdColumnName,
671
+ content,
672
+ deleteEmptyColumns,
673
+ deleteEmptyTable,
674
+ partial = false,
675
+ condition = TRUE,
676
+ ) => {
677
+ const settingColumnNameSet = setNew();
678
+ objMap(content ?? {}, (contentRow) =>
679
+ arrayMap(objIds(contentRow ?? {}), (cellOrValueId) =>
680
+ setAdd(settingColumnNameSet, cellOrValueId),
681
+ ),
682
+ );
683
+ const settingColumnNames = collValues(settingColumnNameSet);
684
+ if (
685
+ !partial &&
686
+ deleteEmptyTable &&
687
+ condition == TRUE &&
688
+ arrayIsEmpty(settingColumnNames) &&
689
+ collHas(schemaMap, tableName)
690
+ ) {
691
+ await databaseExecuteCommand('DROP ' + TABLE + escapeId(tableName));
692
+ mapSet(schemaMap, tableName);
693
+ return;
694
+ }
695
+ const currentColumnNames = mapGet(schemaMap, tableName);
696
+ const unaccountedColumnNames = setNew(collValues(currentColumnNames));
697
+ if (!arrayIsEmpty(settingColumnNames)) {
698
+ if (!collHas(schemaMap, tableName)) {
699
+ await databaseExecuteCommand(
700
+ CREATE_TABLE +
701
+ escapeId(tableName) +
702
+ `(${escapeId(rowIdColumnName)}${columnType} PRIMARY KEY${arrayJoin(
703
+ arrayMap(
704
+ settingColumnNames,
705
+ (settingColumnName) =>
706
+ COMMA + escapeId(settingColumnName) + columnType,
707
+ ),
708
+ )});`,
709
+ );
710
+ mapSet(
711
+ schemaMap,
712
+ tableName,
713
+ setNew([rowIdColumnName, ...settingColumnNames]),
714
+ );
715
+ } else {
716
+ await promiseAll(
717
+ arrayMap(
718
+ [rowIdColumnName, ...settingColumnNames],
719
+ async (settingColumnName, index) => {
720
+ if (!collDel(unaccountedColumnNames, settingColumnName)) {
721
+ await databaseExecuteCommand(
722
+ ALTER_TABLE +
723
+ escapeId(tableName) +
724
+ 'ADD' +
725
+ escapeId(settingColumnName) +
726
+ columnType,
727
+ );
728
+ if (index == 0) {
729
+ await databaseExecuteCommand(
730
+ 'CREATE UNIQUE INDEX pk ON ' +
731
+ escapeId(tableName) +
732
+ `(${escapeId(rowIdColumnName)})`,
733
+ );
734
+ }
735
+ setAdd(currentColumnNames, settingColumnName);
736
+ }
737
+ },
738
+ ),
739
+ );
740
+ }
741
+ }
742
+ await promiseAll([
743
+ ...(!partial && deleteEmptyColumns
744
+ ? arrayMap(
745
+ collValues(unaccountedColumnNames),
746
+ async (unaccountedColumnName) => {
747
+ if (unaccountedColumnName != rowIdColumnName) {
748
+ await databaseExecuteCommand(
749
+ ALTER_TABLE +
750
+ escapeId(tableName) +
751
+ 'DROP' +
752
+ escapeId(unaccountedColumnName),
753
+ );
754
+ collDel(currentColumnNames, unaccountedColumnName);
755
+ }
756
+ },
757
+ )
758
+ : []),
759
+ ]);
760
+ if (partial) {
761
+ if (isUndefined(content)) {
762
+ await databaseExecuteCommand(
763
+ DELETE_FROM +
764
+ escapeId(tableName) +
765
+ getWhereCondition(tableName, condition),
766
+ );
767
+ } else {
768
+ await promiseAll(
769
+ objToArray(content, async (row, rowId) => {
770
+ if (isUndefined(row)) {
771
+ await databaseExecuteCommand(
772
+ DELETE_FROM +
773
+ escapeId(tableName) +
774
+ getWhereCondition(tableName, condition) +
775
+ `AND(${escapeId(rowIdColumnName)}=$1)`,
776
+ [rowId],
777
+ );
778
+ } else if (!arrayIsEmpty(settingColumnNames)) {
779
+ await upsert(
780
+ databaseExecuteCommand,
781
+ tableName,
782
+ rowIdColumnName,
783
+ objIds(row),
784
+ {
785
+ [rowId]: encode
786
+ ? arrayMap(objValues(row), encode)
787
+ : objValues(row),
788
+ },
789
+ currentColumnNames,
790
+ );
791
+ }
792
+ }),
793
+ );
794
+ }
795
+ } else {
796
+ if (!arrayIsEmpty(settingColumnNames)) {
797
+ const changingColumnNames = arrayFilter(
798
+ collValues(mapGet(schemaMap, tableName)),
799
+ (changingColumnName) => changingColumnName != rowIdColumnName,
800
+ );
801
+ const rows = {};
802
+ const deleteRowIds = [];
803
+ objMap(content ?? {}, (row, rowId) => {
804
+ rows[rowId] = arrayMap(changingColumnNames, (cellId) =>
805
+ encode ? encode(row?.[cellId]) : row?.[cellId],
806
+ );
807
+ arrayPush(deleteRowIds, rowId);
808
+ });
809
+ await upsert(
810
+ databaseExecuteCommand,
811
+ tableName,
812
+ rowIdColumnName,
813
+ changingColumnNames,
814
+ rows,
815
+ );
816
+ await databaseExecuteCommand(
817
+ DELETE_FROM +
818
+ escapeId(tableName) +
819
+ getWhereCondition(tableName, condition) + // eslint-disable-next-line max-len
820
+ `AND${escapeId(rowIdColumnName)}NOT IN(${getPlaceholders(deleteRowIds)})`,
821
+ deleteRowIds,
822
+ );
823
+ } else if (collHas(schemaMap, tableName)) {
824
+ await databaseExecuteCommand(
825
+ DELETE_FROM +
826
+ escapeId(tableName) +
827
+ getWhereCondition(tableName, condition),
828
+ );
829
+ }
830
+ }
831
+ };
832
+ const transaction = async (actions) => {
833
+ let result;
834
+ await databaseExecuteCommand('BEGIN');
835
+ await tryCatch(async () => (result = await actions()), onIgnoredError);
836
+ await databaseExecuteCommand('END');
837
+ return result;
838
+ };
839
+ return [refreshSchema, loadTable, saveTable, transaction];
840
+ };
841
+ const defaultUpsert = async (
842
+ executeCommand,
843
+ tableName,
844
+ rowIdColumnName,
845
+ changingColumnNames,
846
+ rows,
847
+ ) => {
848
+ const offset = [1];
849
+ await executeCommand(
850
+ INSERT +
851
+ ' INTO' +
852
+ escapeId(tableName) +
853
+ '(' +
854
+ escapeColumnNames(rowIdColumnName, ...changingColumnNames) +
855
+ ')VALUES' +
856
+ arrayJoin(
857
+ objToArray(
858
+ rows,
859
+ (row) =>
860
+ '($' + offset[0]++ + ',' + getPlaceholders(row, offset) + ')',
861
+ ),
862
+ COMMA,
863
+ ) +
864
+ 'ON CONFLICT(' +
865
+ escapeId(rowIdColumnName) +
866
+ `)DO ${UPDATE} SET` +
867
+ arrayJoin(
868
+ arrayMap(
869
+ changingColumnNames,
870
+ (columnName) =>
871
+ escapeId(columnName) + '=excluded.' + escapeId(columnName),
872
+ ),
873
+ COMMA,
874
+ ),
875
+ objToArray(rows, (row, id) => [
876
+ id,
877
+ ...arrayMap(row, (value) => value ?? null),
878
+ ]).flat(),
879
+ );
880
+ };
881
+
882
+ const createJsonPersister = (
883
+ store,
884
+ executeCommand,
885
+ addPersisterListener,
886
+ delPersisterListener,
887
+ onIgnoredError,
888
+ extraDestroy,
889
+ persist,
890
+ [storeTableName, storeIdColumnName, storeColumnName],
891
+ managedTableNames,
892
+ querySchema,
893
+ thing,
894
+ getThing,
895
+ columnType,
896
+ upsert,
897
+ ) => {
898
+ const [refreshSchema, loadTable, saveTable, transaction] =
899
+ getCommandFunctions(
900
+ executeCommand,
901
+ managedTableNames,
902
+ querySchema,
903
+ onIgnoredError,
904
+ columnType,
905
+ upsert,
906
+ );
907
+ const getPersisted = () =>
908
+ transaction(async () => {
909
+ await refreshSchema();
910
+ return jsonParseWithUndefined(
911
+ (await loadTable(storeTableName, storeIdColumnName))[SINGLE_ROW_ID]?.[
912
+ storeColumnName
913
+ ] ?? 'null',
914
+ );
915
+ });
916
+ const setPersisted = (getContent) =>
917
+ transaction(async () => {
918
+ await refreshSchema();
919
+ await saveTable(
920
+ storeTableName,
921
+ storeIdColumnName,
922
+ {
923
+ [SINGLE_ROW_ID]: {
924
+ [storeColumnName]: jsonStringWithUndefined(getContent() ?? null),
925
+ },
926
+ },
927
+ true,
928
+ true,
929
+ );
930
+ });
931
+ const destroy = async () => {
932
+ await persister.stopAutoPersisting();
933
+ extraDestroy();
934
+ return persister;
935
+ };
936
+ const persister = createCustomPersister(
937
+ store,
938
+ getPersisted,
939
+ setPersisted,
940
+ addPersisterListener,
941
+ delPersisterListener,
942
+ onIgnoredError,
943
+ persist,
944
+ {[getThing]: () => thing, destroy},
945
+ 0,
946
+ thing,
947
+ );
948
+ return persister;
949
+ };
950
+
951
+ const createTabularPersister = (
952
+ store,
953
+ executeCommand,
954
+ addPersisterListener,
955
+ delPersisterListener,
956
+ onIgnoredError,
957
+ extraDestroy,
958
+ persist,
959
+ [
960
+ tablesLoadConfig,
961
+ tablesSaveConfig,
962
+ [valuesLoad, valuesSave, valuesTableName],
963
+ ],
964
+ managedTableNames,
965
+ querySchema,
966
+ thing,
967
+ getThing,
968
+ columnType,
969
+ upsert,
970
+ encode,
971
+ decode,
972
+ ) => {
973
+ const [refreshSchema, loadTable, saveTable, transaction] =
974
+ getCommandFunctions(
975
+ executeCommand,
976
+ managedTableNames,
977
+ querySchema,
978
+ onIgnoredError,
979
+ columnType,
980
+ upsert,
981
+ encode,
982
+ decode,
983
+ );
984
+ const saveTables = (tables, partial) =>
985
+ promiseAll(
986
+ mapMap(
987
+ tablesSaveConfig,
988
+ async (
989
+ [
990
+ tableName,
991
+ rowIdColumnName,
992
+ deleteEmptyColumns,
993
+ deleteEmptyTable,
994
+ condition,
995
+ ],
996
+ tableId,
997
+ ) => {
998
+ if (!partial || objHas(tables, tableId)) {
999
+ await saveTable(
1000
+ tableName,
1001
+ rowIdColumnName,
1002
+ tables[tableId],
1003
+ deleteEmptyColumns,
1004
+ deleteEmptyTable,
1005
+ partial,
1006
+ condition,
1007
+ );
1008
+ }
1009
+ },
1010
+ ),
1011
+ );
1012
+ const saveValues = async (values, partial) =>
1013
+ valuesSave
1014
+ ? await saveTable(
1015
+ valuesTableName,
1016
+ DEFAULT_ROW_ID_COLUMN_NAME,
1017
+ {[SINGLE_ROW_ID]: values},
1018
+ true,
1019
+ true,
1020
+ partial,
1021
+ )
1022
+ : null;
1023
+ const loadTables = async () =>
1024
+ objNew(
1025
+ arrayFilter(
1026
+ await promiseAll(
1027
+ mapMap(
1028
+ tablesLoadConfig,
1029
+ async ([tableId, rowIdColumnName, condition], tableName) => [
1030
+ tableId,
1031
+ await loadTable(tableName, rowIdColumnName, condition),
1032
+ ],
1033
+ ),
1034
+ ),
1035
+ (pair) => !objIsEmpty(pair[1]),
1036
+ ),
1037
+ );
1038
+ const loadValues = async () =>
1039
+ valuesLoad
1040
+ ? (await loadTable(valuesTableName, DEFAULT_ROW_ID_COLUMN_NAME))[
1041
+ SINGLE_ROW_ID
1042
+ ]
1043
+ : {};
1044
+ const getPersisted = () =>
1045
+ transaction(async () => {
1046
+ await refreshSchema();
1047
+ const tables = await loadTables();
1048
+ const values = await loadValues();
1049
+ return !objIsEmpty(tables) || !isUndefined(values)
1050
+ ? [tables, values]
1051
+ : void 0;
1052
+ });
1053
+ const setPersisted = (getContent, changes) =>
1054
+ transaction(async () => {
1055
+ await refreshSchema();
1056
+ if (!isUndefined(changes)) {
1057
+ await saveTables(changes[0], true);
1058
+ await saveValues(changes[1], true);
1059
+ } else {
1060
+ const [tables, values] = getContent();
1061
+ await saveTables(tables);
1062
+ await saveValues(values);
1063
+ }
1064
+ });
1065
+ const destroy = async () => {
1066
+ await persister.stopAutoPersisting();
1067
+ extraDestroy();
1068
+ return persister;
1069
+ };
1070
+ const persister = createCustomPersister(
1071
+ store,
1072
+ getPersisted,
1073
+ setPersisted,
1074
+ addPersisterListener,
1075
+ delPersisterListener,
1076
+ onIgnoredError,
1077
+ persist,
1078
+ {[getThing]: () => thing, destroy},
1079
+ 0,
1080
+ thing,
1081
+ );
1082
+ return persister;
1083
+ };
1084
+
1085
+ const createCustomSqlitePersister = (
1086
+ store,
1087
+ configOrStoreTableName,
1088
+ rawExecuteCommand,
1089
+ addChangeListener,
1090
+ delChangeListener,
1091
+ onSqlCommand,
1092
+ onIgnoredError,
1093
+ destroy,
1094
+ persist,
1095
+ thing,
1096
+ getThing = 'getDb',
1097
+ upsert,
1098
+ ) => {
1099
+ let dataVersion;
1100
+ let schemaVersion;
1101
+ let totalChanges;
1102
+ const executeCommand = getWrappedCommand(rawExecuteCommand, onSqlCommand);
1103
+ const [
1104
+ isJson,
1105
+ autoLoadIntervalSeconds,
1106
+ defaultedConfig,
1107
+ managedTableNamesSet,
1108
+ ] = getConfigStructures(configOrStoreTableName);
1109
+ const addPersisterListener = (listener) => {
1110
+ let interval;
1111
+ const startPolling = () =>
1112
+ (interval = startInterval(
1113
+ () =>
1114
+ tryCatch(async () => {
1115
+ const [{d, s, c}] = await executeCommand(
1116
+ SELECT + // eslint-disable-next-line max-len
1117
+ ` ${DATA_VERSION} d,${SCHEMA_VERSION} s,TOTAL_CHANGES() c FROM ${PRAGMA}${DATA_VERSION} JOIN ${PRAGMA}${SCHEMA_VERSION}`,
1118
+ );
1119
+ if (d != dataVersion || s != schemaVersion || c != totalChanges) {
1120
+ if (dataVersion != null) {
1121
+ listener();
1122
+ }
1123
+ dataVersion = d;
1124
+ schemaVersion = s;
1125
+ totalChanges = c;
1126
+ }
1127
+ }),
1128
+ autoLoadIntervalSeconds,
1129
+ ));
1130
+ const stopPolling = () => {
1131
+ dataVersion = schemaVersion = totalChanges = null;
1132
+ stopInterval(interval);
1133
+ };
1134
+ startPolling();
1135
+ return () => {
1136
+ stopPolling();
1137
+ };
1138
+ };
1139
+ const delPersisterListener = (stopPollingAndDelUpdateListener) =>
1140
+ stopPollingAndDelUpdateListener();
1141
+ return (isJson ? createJsonPersister : createTabularPersister)(
1142
+ store,
1143
+ executeCommand,
1144
+ addPersisterListener,
1145
+ delPersisterListener,
1146
+ onIgnoredError,
1147
+ destroy,
1148
+ persist,
1149
+ defaultedConfig,
1150
+ collValues(managedTableNamesSet),
1151
+ async (executeCommand2, managedTableNames) =>
1152
+ await executeCommand2(
1153
+ SELECT + // eslint-disable-next-line max-len
1154
+ ` t.name tn,c.name cn FROM ${PRAGMA_TABLE}list()t,${PRAGMA_TABLE}info(t.name)c ${WHERE} t.schema='main'AND t.type IN('table','view')AND t.name IN(${getPlaceholders(managedTableNames)})ORDER BY t.name,c.name`,
1155
+ managedTableNames,
1156
+ ),
1157
+ thing,
1158
+ getThing,
1159
+ EMPTY_STRING,
1160
+ upsert,
1161
+ (cellOrValue) =>
1162
+ cellOrValue === true ? 1 : cellOrValue === false ? 0 : cellOrValue,
1163
+ void 0,
1164
+ );
1165
+ };
1166
+
1167
+ const createReactNativeSqlitePersister = (
1168
+ store,
1169
+ db,
1170
+ configOrStoreTableName,
1171
+ onSqlCommand,
1172
+ onIgnoredError,
1173
+ ) =>
1174
+ createCustomSqlitePersister(
1175
+ store,
1176
+ configOrStoreTableName,
1177
+ async (sql, params = []) =>
1178
+ (await db.executeSql(sql, params))[0].rows.raw(),
1179
+ noop,
1180
+ noop,
1181
+ onSqlCommand,
1182
+ onIgnoredError,
1183
+ noop,
1184
+ 3,
1185
+ // StoreOrMergeableStore,
1186
+ db,
1187
+ );
1188
+
1189
+ export {createReactNativeSqlitePersister};