tinybase 6.1.0-beta.2 → 6.1.0-beta.4

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