gs-idb-pro 0.1.5 → 1.0.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.
package/lib/impl.cjs ADDED
@@ -0,0 +1,1247 @@
1
+ "use strict";
2
+ var type$1 = require('./type.cjs'), types = require('gs-base/types'), store = require('gs-idb-basic/store'), type = require('gs-idb-basic/type'), basic = require('gs-base/basic'), json = require('gs-base/json'), tx = require('gs-idb-basic/tx'), db = require('gs-idb-basic/db'), storage = require('gs-idb-basic/storage');
3
+ const toNum = (v) => v instanceof Date ? v.getTime() : v;
4
+ function parseIDbQuery(query) {
5
+ if (type.isDbQueryOrNull(query) || !types.isObject(query))
6
+ return query;
7
+ const range = query;
8
+ if ("lt" in range && "gt" in range) {
9
+ if (toNum(range.gt) > toNum(range.lt))
10
+ throw new Error(`Invalid IDBRange: gt (${range.gt}) cannot be greater than lt (${range.lt})`);
11
+ return IDBKeyRange.bound(range.gt, range.lt, !0, !0);
12
+ }
13
+ if ("lt" in range && "gte" in range) {
14
+ if (toNum(range.gte) > toNum(range.lt))
15
+ throw new Error(`Invalid IDBRange: gte (${range.gte}) cannot be greater than lt (${range.lt})`);
16
+ return IDBKeyRange.bound(range.gte, range.lt, !1, !0);
17
+ }
18
+ if ("lte" in range && "gt" in range) {
19
+ if (toNum(range.gt) > toNum(range.lte))
20
+ throw new Error(`Invalid IDBRange: gt (${range.gt}) cannot be greater than lte (${range.lte})`);
21
+ return IDBKeyRange.bound(range.gt, range.lte, !0, !1);
22
+ }
23
+ if ("lte" in range && "gte" in range) {
24
+ if (toNum(range.gte) > toNum(range.lte))
25
+ throw new Error(`Invalid IDBRange: gte (${range.gte}) cannot be greater than lte (${range.lte})`);
26
+ return IDBKeyRange.bound(range.gte, range.lte, !1, !1);
27
+ }
28
+ if ("lt" in range) return IDBKeyRange.upperBound(range.lt, !0);
29
+ if ("lte" in range) return IDBKeyRange.upperBound(range.lte, !1);
30
+ if ("gt" in range) return IDBKeyRange.lowerBound(range.gt, !0);
31
+ if ("gte" in range) return IDBKeyRange.lowerBound(range.gte, !1);
32
+ }
33
+ function isIDbQuery(query) {
34
+ return parseIDbQuery(query) != null;
35
+ }
36
+ async function openCursor(target, arg, fn) {
37
+ const { query, direction = "prev", preSkip, startKey, startPrimaryKey } = arg, request = target.openCursor(parseIDbQuery(query), direction);
38
+ return preSkip && (await store.requestDbResult(request)).advance(preSkip), startKey && (startPrimaryKey ? (await store.requestDbResult(request)).continuePrimaryKey(startKey, startPrimaryKey) : (await store.requestDbResult(request)).continue(startKey)), request;
39
+ }
40
+ function getMapperFn(mapper, keyPath, useArrayRecord) {
41
+ return Array.isArray(mapper) ? (v) => basic.copyFields({}, v, mapper) : types.isFunction(mapper) ? mapper : keyPath ? (v) => v : useArrayRecord ? (v, k) => [v, k] : (key, value) => ({ key, value });
42
+ }
43
+ class DataOperationBase {
44
+ idbPro;
45
+ target;
46
+ #storeSchema;
47
+ constructor(schema, db2) {
48
+ this.idbPro = db2, this.#storeSchema = schema.storeSchema, this.target = schema.target, type$1.isNativeTarget(schema.target) && (this.tx = this.#nativeOperation);
49
+ }
50
+ get storeName() {
51
+ return this.nativeStore?.name || this.target.store;
52
+ }
53
+ get storeSchema() {
54
+ if (this.#storeSchema) return this.#storeSchema;
55
+ const storeSchema = this.idbPro.getStoreSchema(this.storeName);
56
+ return Object.isFrozen(storeSchema) && (this.#storeSchema = storeSchema), storeSchema;
57
+ }
58
+ get factory() {
59
+ return this.idbPro.factory;
60
+ }
61
+ get nativeStore() {
62
+ const { target } = this;
63
+ if (target instanceof IDBObjectStore) return target;
64
+ if (target instanceof IDBIndex) return target.objectStore;
65
+ }
66
+ get keyPath() {
67
+ const { target } = this;
68
+ if (type$1.isNativeTarget(target))
69
+ return target.keyPath;
70
+ const { storeSchema } = this, { index } = target;
71
+ return index ? storeSchema.indexSchemas.find((i) => i.name === index)?.keyPath : storeSchema.keyPath;
72
+ }
73
+ async forEach(arg, returns) {
74
+ return arg = types.isFunction(arg) ? { fn: arg } : arg, returns ? this.cursorResult(arg, !1) : this.cursorVoid(arg, !1);
75
+ }
76
+ async tx(writable, fn, rollbackOnError) {
77
+ let { target } = this;
78
+ const { store: store2, index } = target, db2 = await this.idbPro.openNativeDb();
79
+ let tx2, nativeStore;
80
+ try {
81
+ tx2 = db2.transaction(store2, writable === !0 ? "readwrite" : "readonly"), target = nativeStore = tx2.objectStore(store2), index && (target = target.index(index));
82
+ } catch (e) {
83
+ throw db2.close(), e;
84
+ }
85
+ if (!fn)
86
+ return Object.freeze({ db: db2, tx: tx2, nativeStore, target });
87
+ try {
88
+ if (writable === !0) {
89
+ const result = await fn(target, nativeStore);
90
+ return tx2.commit(), result;
91
+ }
92
+ return fn(target);
93
+ } catch (e) {
94
+ throw rollbackOnError !== !1 && tx2.abort(), e;
95
+ } finally {
96
+ db2.close();
97
+ }
98
+ }
99
+ openCursor(arg, writable) {
100
+ return this.tx(writable, (store2) => new Promise(async (resolve, reject) => {
101
+ const { fn } = arg, request = await openCursor(store2, arg);
102
+ request.onsuccess = async () => {
103
+ request.result ? await fn(request.result) === !1 && resolve() : resolve();
104
+ }, request.onerror = () => reject(request.error);
105
+ }));
106
+ }
107
+ cursorVoid({ query, direction, preSkip, startKey, startPrimaryKey, fn }, writable) {
108
+ let i = 0;
109
+ return this.openCursor({
110
+ query,
111
+ direction,
112
+ preSkip,
113
+ startKey,
114
+ startPrimaryKey,
115
+ fn: async (cursor) => {
116
+ const { value: ov, primaryKey: op } = cursor, result = await fn(ov, op, i++), {
117
+ control,
118
+ key,
119
+ primaryKey,
120
+ modify,
121
+ value
122
+ } = types.isObject(result) ? result : { control: result };
123
+ switch (writable && (modify === type$1.Save ? cursor.update(value || ov) : modify === type$1.Delete && cursor.delete()), control) {
124
+ case type$1.Break:
125
+ return !1;
126
+ case type$1.ContinueKey:
127
+ cursor.continue(key);
128
+ break;
129
+ case type$1.ContinuePrimaryKey:
130
+ cursor.continuePrimaryKey(key, primaryKey);
131
+ break;
132
+ default:
133
+ cursor.continue();
134
+ }
135
+ }
136
+ }, !0);
137
+ }
138
+ async cursorResult({
139
+ query,
140
+ direction,
141
+ preSkip,
142
+ startKey,
143
+ startPrimaryKey,
144
+ fn,
145
+ mapper: mpr
146
+ }, writable) {
147
+ const { keyPath, defaultGetMapper } = this.storeSchema, mapper = getMapperFn(mpr || defaultGetMapper, keyPath), results = [];
148
+ let i = 0;
149
+ return await this.openCursor({
150
+ query,
151
+ direction,
152
+ preSkip,
153
+ startKey,
154
+ startPrimaryKey,
155
+ fn: async (cursor) => {
156
+ const { value: ov, primaryKey: op } = cursor, {
157
+ control,
158
+ value,
159
+ key,
160
+ primaryKey,
161
+ modify
162
+ } = await fn?.(ov, op, i, results) || {};
163
+ switch (writable && (modify === type$1.Save ? cursor.update(value || ov) : modify === type$1.Delete && cursor.delete()), (!control || control === type$1.Finished || control === type$1.NextKey || control === type$1.NextPrimaryKey) && results.push(mapper(value || ov, primaryKey || op, i)), control) {
164
+ case type$1.Break:
165
+ case type$1.Finished:
166
+ return !1;
167
+ case type$1.NextKey:
168
+ case type$1.ContinueKey:
169
+ cursor.continue(key);
170
+ break;
171
+ case type$1.NextPrimaryKey:
172
+ case type$1.ContinuePrimaryKey:
173
+ cursor.continuePrimaryKey(key, primaryKey);
174
+ break;
175
+ default:
176
+ cursor.continue();
177
+ }
178
+ i++;
179
+ }
180
+ }, writable), results;
181
+ }
182
+ // noinspection JSUnusedLocalSymbols
183
+ #nativeOperation(writable, fn) {
184
+ const { target } = this, nativeStore = target instanceof IDBObjectStore ? target : target.objectStore;
185
+ return fn ? fn(target, nativeStore) : Object.freeze({ nativeStore, target });
186
+ }
187
+ }
188
+ class DataOperators {
189
+ idbPro;
190
+ schemas;
191
+ #_storeNames;
192
+ constructor(idbPro, schemas) {
193
+ this.idbPro = idbPro, this.schemas = schemas;
194
+ }
195
+ get storeNames() {
196
+ return this.#_storeNames || (this.#_storeNames = Array.from(new Set(this.schemas.map((s) => s.target.store))));
197
+ }
198
+ read(fn) {
199
+ return this.tx("newReader", fn);
200
+ }
201
+ write(fn, rollbackOnError = !0) {
202
+ return this.tx("newWriter", fn, !0, rollbackOnError);
203
+ }
204
+ export() {
205
+ return this.tx("newReader", async (...stores) => {
206
+ const result = {};
207
+ for (const store2 of stores)
208
+ result[store2.storeName] = await store2.export();
209
+ return result;
210
+ });
211
+ }
212
+ import(data, returns, use) {
213
+ return use || (use = "addOrChangeMany"), this.tx("newWriter", async (...stores) => {
214
+ const result = {};
215
+ stores = Array.from(new Map(stores.map((s) => [s.storeName, s.asStore(!0)])).values());
216
+ for (const store2 of stores) {
217
+ const { storeName } = store2, rows = data[storeName];
218
+ rows && (result[storeName] = await store2[use](rows, returns));
219
+ }
220
+ if (returns) return result;
221
+ }, !0);
222
+ }
223
+ async tx(method, fn, writable, rollbackOnError) {
224
+ const { idbPro, schemas } = this, { factory } = idbPro.schema, db2 = await idbPro.openNativeDb();
225
+ try {
226
+ const tx2 = db2.transaction(this.storeNames, writable ? "readwrite" : "readonly");
227
+ try {
228
+ const stores = schemas.map(({ storeSchema, target: info }) => {
229
+ let target = tx2.objectStore(info.store);
230
+ return info.index && (target = target.index(info.index)), factory[method]({ storeSchema, target }, idbPro);
231
+ });
232
+ return await fn(...stores);
233
+ } catch (e) {
234
+ throw rollbackOnError !== !1 && tx2.abort(), e;
235
+ }
236
+ } finally {
237
+ db2.close();
238
+ }
239
+ }
240
+ }
241
+ class DbIterator extends DataOperationBase {
242
+ direction;
243
+ query;
244
+ writable;
245
+ parser;
246
+ endsWithNull;
247
+ preSkip;
248
+ startKey;
249
+ startPrimaryKey;
250
+ constructor(schema, db2, option) {
251
+ if (super(schema, db2), !option) return;
252
+ const { parser } = option;
253
+ this.direction = option.direction, this.query = option.query, this.writable = !!option.writable, this.endsWithNull = !!option.endsWithNull, this.preSkip = option.preSkip, this.startKey = option.startKey, this.startPrimaryKey = option.startPrimaryKey, parser && (this.parser = types.isFunction(parser) ? parser : type$1.DbIteratorParsers[parser]);
254
+ }
255
+ async *[Symbol.asyncIterator]() {
256
+ const { parser, writable, endsWithNull } = this, { db: db2, tx: tx2, target } = await this.tx(writable);
257
+ try {
258
+ const request = await openCursor(target, this);
259
+ let cursor;
260
+ if (parser)
261
+ for (; cursor = await store.requestDbResult(request); ) {
262
+ const { control, value } = await parser(cursor);
263
+ if (control || (yield value), control === type$1.Break) break;
264
+ cursor.continue();
265
+ }
266
+ else {
267
+ let ended = !1;
268
+ const end = () => {
269
+ ended = !0;
270
+ };
271
+ for (; !ended && (cursor = await store.requestDbResult(request)); )
272
+ yield { cursor, end };
273
+ }
274
+ writable && tx2?.commit(), endsWithNull && !cursor && (yield null);
275
+ } finally {
276
+ db2?.close();
277
+ }
278
+ }
279
+ }
280
+ function parseFilterArg(arg1, arg2, arg3, limit) {
281
+ const args = types.isObject(arg1) ? arg1 : {};
282
+ return types.isFunction(arg1) ? args.fn = arg1 : isIDbQuery(arg1) && (args.query = arg1), types.isFunction(arg2) ? args.fn = arg2 : arg2 && (args.direction = arg2), arg3 && (args.direction = arg3), limit ? args.limit = limit : args.limit || (args.limit = 1e3), args.maxEmptyChecks || (args.maxEmptyChecks = 2e4), args;
283
+ }
284
+ async function queryPage(anyReader, info, arg) {
285
+ info.size || (info.size = 100), info.nextSkip || (info.nextSkip = 0);
286
+ const { query, direction, total, maxEmptyChecks, fn } = info;
287
+ return await anyReader.batchRead(async (nativeReader) => (total || (info.total = await nativeReader.count({ query, direction, maxEmptyChecks, fn }), info.pages = Math.ceil(info.total / info.size)), info.total < 1 ? { info: basic.deepFreeze(info), rows: [] } : fn ? await queryFnPage(nativeReader, info, arg) : await queryNoneFnPage(nativeReader, info)));
288
+ }
289
+ async function queryNoneFnPage(nativeReader, info) {
290
+ const { keyPath } = nativeReader.storeSchema, { page, query, direction, size, mapper } = info, preSkip = (page - 1) * size, rows = await nativeReader.filter({
291
+ query,
292
+ preSkip,
293
+ direction,
294
+ limit: size,
295
+ mapper: getMapperFn(mapper, keyPath, !0)
296
+ });
297
+ return info.nextSkip = preSkip + rows.length, {
298
+ info: basic.deepFreeze(info),
299
+ rows
300
+ };
301
+ }
302
+ async function queryFnPage(reader, info, arg) {
303
+ info.maxEmptyChecks || (info.maxEmptyChecks = 2e4);
304
+ const { page, query, direction, nextSkip } = info, { keyPath } = reader.storeSchema;
305
+ return page === 1 ? await queryFnRow(reader, await openCursor(reader.target, { query, direction }), info, keyPath) : arg && nextSkip && page - arg.page === 1 ? await queryFnRow(reader, await openCursor(reader.target, {
306
+ query,
307
+ direction,
308
+ preSkip: nextSkip
309
+ }), info, keyPath) : await queryFnRow(reader, await openCursor(reader.target, { query, direction }), info, keyPath, !0);
310
+ }
311
+ async function queryFnRow({ storeSchema: { defaultGetMapper } }, request, info, keyPath, hasSkip) {
312
+ const { page, size, total, maxEmptyChecks, fn } = info, mapper = getMapperFn(info.mapper || defaultGetMapper, keyPath, !0), rows = [], preSkip = (page - 1) * info.size;
313
+ if (preSkip >= total) return { info: basic.deepFreeze(info), rows: [] };
314
+ let i = 0;
315
+ return await new Promise(async (resolve, reject) => {
316
+ let ept = 0, skipped = 0;
317
+ const rowFn = async () => {
318
+ const { result: cursor } = request;
319
+ if (!cursor) return resolve();
320
+ let { value, primaryKey } = cursor;
321
+ if (await fn(value, primaryKey, i) ? (ept = 0, rows.push(mapper(value, primaryKey, i))) : ept++, rows.length >= size || ept >= maxEmptyChecks) return resolve();
322
+ i++, cursor.continue();
323
+ };
324
+ request.onerror = () => reject(request.error), hasSkip ? request.onsuccess = async () => {
325
+ const { result: cursor } = request;
326
+ if (!cursor)
327
+ return resolve();
328
+ let { value, primaryKey } = cursor;
329
+ if (await fn(value, primaryKey, i) ? (ept = 0, skipped++) : ept++, skipped >= preSkip || ept >= maxEmptyChecks) {
330
+ i = ept = 0, request.onsuccess = rowFn, cursor.continue();
331
+ return;
332
+ }
333
+ cursor.continue(), i++;
334
+ } : request.onsuccess = rowFn;
335
+ }), i && (info.nextSkip += i + 1), {
336
+ info: basic.deepFreeze(info),
337
+ rows
338
+ };
339
+ }
340
+ class DataReader extends DataOperationBase {
341
+ all(query, limit) {
342
+ const param = isIDbQuery(query) ? { query } : query || {};
343
+ return types.isNumber(limit) && (param.count = limit), types.isNumber(param.count) || (param.count = 1e3), param.query = parseIDbQuery(param.query), this.tx(!1, (store$1) => store.requestDbResult(store$1.getAll(param)));
344
+ }
345
+ async count(arg1, arg2, arg3) {
346
+ const args = parseFilterArg(arg1, arg2, arg3), { query, direction, fn } = args;
347
+ if (!fn)
348
+ return await this.tx(!1, (store$1) => store.requestDbResult(store$1.count(parseIDbQuery(query))));
349
+ const { maxEmptyChecks } = args, findFn = fn;
350
+ let count = 0, ept = 0, i = 0;
351
+ return await this.openCursor({
352
+ query,
353
+ direction,
354
+ fn: (cursor) => {
355
+ if (findFn(cursor.value, cursor.primaryKey, i++))
356
+ count++, ept = 0;
357
+ else if (++ept >= maxEmptyChecks)
358
+ return !1;
359
+ cursor.continue();
360
+ }
361
+ }), count;
362
+ }
363
+ get(key) {
364
+ return this.tx(!1, (store$1) => store.requestDbResult(store$1.get(parseIDbQuery(key))));
365
+ }
366
+ getMany(keys, excludeEmpty) {
367
+ return this.batchRead(async (reader) => {
368
+ const rows = await basic.asyncMap(keys, (k) => reader.get(k));
369
+ return excludeEmpty ? rows.filter((v) => v) : rows;
370
+ });
371
+ }
372
+ getRange(query, direction) {
373
+ return this.forEach({ query, direction }, !0);
374
+ }
375
+ async getRangeMany(keys, direction, excludeEmpty) {
376
+ return (await this.batchRead((w) => basic.asyncMap(keys, (v) => w.getRange(v, direction)))).flat();
377
+ }
378
+ index(name, writable) {
379
+ let { target } = this;
380
+ return target instanceof IDBIndex && (target = target.objectStore), target instanceof IDBObjectStore ? (target = target.index(name), this.createOperator(target, writable)) : this.idbPro.store(target.store, name);
381
+ }
382
+ asStore(writable) {
383
+ let { target } = this;
384
+ if (target instanceof IDBObjectStore)
385
+ return this;
386
+ if (target instanceof IDBIndex)
387
+ target = target.objectStore;
388
+ else {
389
+ if (!("index" in target))
390
+ return this;
391
+ target = { store: target.store };
392
+ }
393
+ return this.createOperator(target, writable);
394
+ }
395
+ batchRead(fn) {
396
+ return type$1.isNativeTarget(this.target) ? fn(this) : this.tx(!1, (store2) => fn(this.idbPro.schema.factory.newReader({
397
+ storeSchema: this.storeSchema,
398
+ target: store2
399
+ }, this.idbPro)));
400
+ }
401
+ iterator(query, direction) {
402
+ const arg = {};
403
+ return types.isString(direction) && (arg.direction = direction), isIDbQuery(query) ? arg.query = query : types.isObject(query) && Object.assign(arg, query), new DbIterator({ storeSchema: this.storeSchema, target: this.target }, this.idbPro, {
404
+ ...arg,
405
+ parser: "value"
406
+ });
407
+ }
408
+ async filter(arg1, arg2, arg3, arg4) {
409
+ const args = parseFilterArg(arg1, arg2, arg3, arg4), { maxEmptyChecks, limit, fn } = args, { keyPath, defaultGetMapper } = this.storeSchema, mapper = getMapperFn(args.mapper || defaultGetMapper, keyPath, !0);
410
+ if (!fn)
411
+ return await this.forEach({
412
+ ...args,
413
+ mapper,
414
+ fn: (value, k, i) => {
415
+ if (i >= limit - 1)
416
+ return { control: type$1.Finished };
417
+ }
418
+ }, !0);
419
+ let ept = 0;
420
+ return this.forEach({
421
+ ...args,
422
+ mapper,
423
+ fn: (value, k, i, results) => {
424
+ if (fn(value, k, i))
425
+ ept = 0;
426
+ else
427
+ return ++ept >= maxEmptyChecks ? { control: type$1.Break } : { control: type$1.Continue };
428
+ if (results.length >= limit - 1)
429
+ return { control: type$1.Finished };
430
+ }
431
+ }, !0);
432
+ }
433
+ async find(query, fn, direction) {
434
+ const [value] = await this.filter(query, fn, direction, 1);
435
+ return value;
436
+ }
437
+ async page(arg, page) {
438
+ const info = { ...arg };
439
+ return info.page = page || arg?.page || 1, queryPage(this, info, arg);
440
+ }
441
+ nextPage(info) {
442
+ return this.page(info, info.page + 1);
443
+ }
444
+ export(arg1, arg2) {
445
+ const args = parseFilterArg(arg1, arg2), { keyPath, exportMapper, name, defaultGetMapper } = this.storeSchema;
446
+ if (!keyPath && Array.isArray(exportMapper))
447
+ throw new Error(`When store [ ${name} ] keyPath does not exist, exportMapper does not support string[].`);
448
+ return args.direction = "next", args.mapper = getMapperFn(exportMapper || defaultGetMapper, keyPath, !0), args.limit = args.maxEmptyChecks = Number.MAX_SAFE_INTEGER, this.filter(args);
449
+ }
450
+ asMap() {
451
+ let { target } = this;
452
+ return target instanceof IDBIndex ? target = target.objectStore : target instanceof IDBObjectStore || (target = { store: target.store }), this.factory.newDbMap({ target }, this.idbPro);
453
+ }
454
+ createOperator(target, writable) {
455
+ const { idbPro } = this, tmp = this.storeSchema, schema = { storeSchema: Object.isFrozen(tmp) ? tmp : void 0, target };
456
+ return writable ? this.factory.newWriter(schema, idbPro) : this.factory.newReader(schema, idbPro);
457
+ }
458
+ }
459
+ function getKeyValueToObject(keyPath, value) {
460
+ if (!Array.isArray(keyPath))
461
+ return { [keyPath]: value };
462
+ const rv = {}, arrayKeyPath = keyPath;
463
+ for (let i = 0; i < arrayKeyPath.length; i++)
464
+ rv[arrayKeyPath[i]] = value[i];
465
+ return rv;
466
+ }
467
+ function getValidKeyValue(keyPath, value) {
468
+ if (!Array.isArray(keyPath))
469
+ return value[keyPath];
470
+ const rv = [];
471
+ for (const k of keyPath) {
472
+ if (!value[k]) return;
473
+ rv.push(value[k]);
474
+ }
475
+ return rv;
476
+ }
477
+ function checkAddValue(storeSchema, value) {
478
+ if (!value || !(value instanceof Object) || Array.isArray(value)) return value;
479
+ const {
480
+ addedTimeField,
481
+ updatedTimeField,
482
+ updatedCountField,
483
+ softDeletedField
484
+ } = storeSchema;
485
+ return value = { ...value }, addedTimeField?.name && !types.isNumber(value[addedTimeField.name]) && (value[addedTimeField.name] = Date.now()), updatedTimeField?.name && !types.isNumber(value[updatedTimeField.name]) && (value[updatedTimeField.name] = Date.now()), softDeletedField?.name && !types.isNumber(value[softDeletedField.name]) && (value[softDeletedField.name] = 0), updatedCountField?.name && (value[updatedCountField.name] = 0), value;
486
+ }
487
+ function checkUpdateValue(storeSchema, newValue, oldValue) {
488
+ if (!newValue || !(newValue instanceof Object) || Array.isArray(newValue)) return newValue;
489
+ const {
490
+ updatedTimeField,
491
+ updatedCountField
492
+ } = storeSchema;
493
+ return newValue = { ...oldValue, ...newValue }, updatedTimeField.name && (newValue[updatedTimeField.name] = Date.now()), updatedCountField.name && (newValue[updatedCountField.name] = (newValue[updatedCountField.name] || 0) + 1), newValue;
494
+ }
495
+ class DataWriter extends DataReader {
496
+ add(record) {
497
+ return this.changeByPk({
498
+ record,
499
+ fn: async (store$1, pk, newValue, oldValue, keyPath) => {
500
+ const { storeSchema } = this;
501
+ return newValue = checkAddValue(storeSchema, newValue), keyPath ? [{ ...newValue, ...getKeyValueToObject(keyPath, await store.requestDbResult(store$1.add(newValue))) }, pk] : [newValue, await store.requestDbResult(store$1.add(newValue, pk))];
502
+ }
503
+ });
504
+ }
505
+ addMany(records, returns) {
506
+ return this.batchWrite((w) => returns ? basic.asyncMap(records, (v) => w.add(v)) : basic.asyncForEach(records, (v) => w.add(v)), !0);
507
+ }
508
+ addOrSkip(record) {
509
+ return this.batchWrite(async (w) => {
510
+ const { keyPath: storeKeyPath, defaultGetMapper } = w.storeSchema, { key: pk, value } = storeKeyPath ? {
511
+ key: getValidKeyValue(storeKeyPath, record),
512
+ value: record
513
+ } : type$1.parseDbNoneKeyPathRecord(record);
514
+ if (pk) {
515
+ const item = await store.requestDbResult(w.nativeStore.get(pk));
516
+ if (item)
517
+ return getMapperFn(defaultGetMapper, storeKeyPath)?.(item, pk);
518
+ }
519
+ if (w.target instanceof IDBIndex) {
520
+ const { keyPath } = w, keyValue = getValidKeyValue(keyPath, value);
521
+ if (!keyValue) return w.add(record);
522
+ let oldValue = await w.find(keyValue);
523
+ if (oldValue) return oldValue;
524
+ }
525
+ return w.add(record);
526
+ });
527
+ }
528
+ addOrSkipMany(records, returns) {
529
+ return this.batchWrite((w) => returns ? basic.asyncMap(records, (v) => w.addOrSkip(v)) : basic.asyncForEach(records, (v) => w.addOrSkip(v)), !0);
530
+ }
531
+ replace(record) {
532
+ return this.changeByPk({
533
+ record,
534
+ getOld: !0,
535
+ fn: async (store$1, pk, newValue, oldValue, keyPath) => {
536
+ const { storeSchema } = this, { updatedTimeField, updatedCountField, addedTimeField } = storeSchema;
537
+ return oldValue ? newValue = checkUpdateValue(storeSchema, newValue, {
538
+ [updatedTimeField.name]: oldValue[updatedTimeField.name],
539
+ [updatedCountField.name]: oldValue[updatedCountField.name],
540
+ [addedTimeField.name]: oldValue[addedTimeField.name]
541
+ }) : newValue = checkAddValue(storeSchema, newValue), keyPath ? [{ ...newValue, ...getKeyValueToObject(keyPath, await store.requestDbResult(store$1.put(newValue))) }, pk] : [newValue, await store.requestDbResult(store$1.put(newValue, pk))];
542
+ }
543
+ });
544
+ }
545
+ replaceMany(records, returns) {
546
+ return this.batchWrite((w) => returns ? basic.asyncMap(records, (v) => w.replace(v)) : basic.asyncForEach(records, (v) => w.replace(v)), !0);
547
+ }
548
+ change(record, throwIfMissing) {
549
+ return this.changeByPk({
550
+ record,
551
+ getOld: !0,
552
+ requiredOld: throwIfMissing,
553
+ requiredPk: throwIfMissing,
554
+ fn: async (store$1, pk, newValue, oldValue, keyPath) => {
555
+ const { storeSchema } = this;
556
+ if (oldValue)
557
+ return newValue = checkUpdateValue(storeSchema, newValue, oldValue), keyPath ? [{ ...newValue, ...getKeyValueToObject(keyPath, await store.requestDbResult(store$1.put(newValue))) }, pk] : [newValue, await store.requestDbResult(store$1.put(newValue, pk))];
558
+ }
559
+ });
560
+ }
561
+ changeMany(records, option) {
562
+ const {
563
+ returns,
564
+ throwIfMissing
565
+ } = types.isBoolean(option) ? { returns: option } : option || {};
566
+ return this.batchWrite((w) => returns ? basic.asyncMap(records, (v) => w.change(v, throwIfMissing)) : basic.asyncForEach(records, (v) => w.change(v, throwIfMissing)), !0);
567
+ }
568
+ addOrChange(record) {
569
+ return this.changeByPk({
570
+ record,
571
+ getOld: !0,
572
+ fn: async (store$1, pk, newValue, oldValue, keyPath) => {
573
+ const { storeSchema } = this;
574
+ return oldValue ? (newValue = checkUpdateValue(storeSchema, newValue, oldValue), keyPath ? [{ ...newValue, ...getKeyValueToObject(keyPath, await store.requestDbResult(store$1.put(newValue))) }, pk] : [newValue, await store.requestDbResult(store$1.put(newValue, pk))]) : (newValue = checkAddValue(storeSchema, newValue), keyPath ? [{ ...newValue, ...getKeyValueToObject(keyPath, await store.requestDbResult(store$1.add(newValue))) }, pk] : [newValue, await store.requestDbResult(store$1.add(newValue, pk))]);
575
+ }
576
+ });
577
+ }
578
+ addOrChangeMany(records, returns) {
579
+ return this.batchWrite((w) => returns ? basic.asyncMap(records, (v) => w.addOrChange(v)) : basic.asyncForEach(records, (v) => w.addOrChange(v)), !0);
580
+ }
581
+ delete(pk, returns) {
582
+ const {
583
+ returns: getOld,
584
+ physical
585
+ } = types.isBoolean(returns) ? { returns } : returns || {}, { storeSchema } = this, softDeletedField = storeSchema.softDeletedField, requiredOld = !!(!physical && softDeletedField?.name);
586
+ return this.changeByPk({
587
+ pk,
588
+ getOld: requiredOld || getOld,
589
+ fn: (store2, pk2, newValue, oldValue) => {
590
+ if (requiredOld ? oldValue && (oldValue = checkUpdateValue(storeSchema, { [softDeletedField.name]: type.Bool.True }, oldValue), store2.put(oldValue)) : store2.delete(pk2), !!getOld)
591
+ return [oldValue, pk2];
592
+ }
593
+ });
594
+ }
595
+ deleteMany(keys, returns) {
596
+ const { returns: hasRtn } = types.isBoolean(returns) ? { returns } : returns || {};
597
+ return this.batchWrite((w) => hasRtn ? basic.asyncMap(keys, (v) => w.delete(v, !0)) : basic.asyncForEach(keys, (v) => w.delete(v)), !0);
598
+ }
599
+ deleteRange(query, returns) {
600
+ const {
601
+ returns: hasRtn,
602
+ physical,
603
+ direction
604
+ } = types.isBoolean(returns) ? { returns } : returns || {}, { name } = this.storeSchema.softDeletedField || {};
605
+ return this.cursor({
606
+ query,
607
+ direction,
608
+ fn: (value) => physical || !name ? { modify: type$1.Delete } : (value[name] = type.Bool.True, { modify: type$1.Save })
609
+ }, hasRtn);
610
+ }
611
+ deleteRangeMany(keys, returns) {
612
+ const option = types.isBoolean(returns) ? { returns } : returns;
613
+ return this.batchWrite((w) => option?.returns ? basic.asyncMap(keys, (v) => w.deleteRange(v, option)) : basic.asyncForEach(keys, (v) => w.deleteRange(v, option)), !0);
614
+ }
615
+ changeRange(arg, returns) {
616
+ let {
617
+ direction = "next",
618
+ query,
619
+ newValue
620
+ } = "newValue" in arg ? arg : { newValue: arg };
621
+ if (query || (query = getValidKeyValue(this.keyPath, newValue)), !query)
622
+ throw new Error(`query is required:${JSON.stringify(arg)}`);
623
+ return this.cursor({
624
+ query,
625
+ direction,
626
+ fn: (value) => value instanceof Object ? { modify: type$1.Save, value: { ...value, ...newValue } } : { modify: type$1.Save, value: newValue }
627
+ }, returns);
628
+ }
629
+ changeRangeMany(args, returns) {
630
+ return this.batchWrite((w) => returns ? basic.asyncMap(args, (v) => w.changeRange(v, returns)) : basic.asyncForEach(args, (v) => w.changeRange(v, returns)), !0);
631
+ }
632
+ cursor(arg, returns) {
633
+ return arg = types.isFunction(arg) ? { fn: arg } : arg || {}, arg.fn ? returns ? this.cursorResult(arg, !0) : this.cursorVoid(arg, !0) : new DbIterator(this, this.idbPro);
634
+ }
635
+ batchWrite(fn, rollbackOnError) {
636
+ const { target } = this;
637
+ if (type$1.isNativeTarget(target))
638
+ try {
639
+ return fn(this);
640
+ } catch (e) {
641
+ throw rollbackOnError !== !1 && (target instanceof IDBIndex ? target.objectStore : target).transaction.abort(), e;
642
+ }
643
+ return this.tx(!0, (store2) => fn(this.idbPro.schema.factory.newWriter({
644
+ storeSchema: this.storeSchema,
645
+ target: store2
646
+ }, this.idbPro)), rollbackOnError);
647
+ }
648
+ changeByPk({ pk, record, fn, requiredPk, getOld, requiredOld, saveMapper, getMapper }) {
649
+ const { storeSchema } = this, { keyPath, defaultSaveMapper, defaultGetMapper } = storeSchema;
650
+ return record && (saveMapper || defaultSaveMapper) && (record = getMapperFn(saveMapper || defaultSaveMapper, keyPath)?.(record)), this.batchWrite(async (w) => {
651
+ let query, newValue = record;
652
+ if (pk)
653
+ query = parseIDbQuery(pk);
654
+ else if (keyPath)
655
+ query = getValidKeyValue(keyPath, record);
656
+ else {
657
+ const { key, value } = type$1.parseDbNoneKeyPathRecord(record);
658
+ query = key, newValue = value;
659
+ }
660
+ if (requiredPk && !query)
661
+ throw new Error(`key is required: ${JSON.stringify(record)}`);
662
+ const oldValue = query && (getOld || requiredOld) ? await store.requestDbResult(w.nativeStore.get(query)) : void 0;
663
+ if (requiredOld && !oldValue)
664
+ throw new Error(`record not found: ${JSON.stringify(record)}`);
665
+ const result = await fn(w.nativeStore, query, newValue, oldValue, keyPath);
666
+ if (result)
667
+ return getMapperFn(getMapper || defaultGetMapper, keyPath)?.(result[0], result[1]);
668
+ });
669
+ }
670
+ }
671
+ class DbMap extends DataOperationBase {
672
+ get size() {
673
+ return this.tx(!1, (store$1) => store.requestDbResult(store$1.count()));
674
+ }
675
+ delete(key) {
676
+ return this.tx(!0, async (t, store2) => {
677
+ store2.delete(key);
678
+ });
679
+ }
680
+ batch(fn) {
681
+ const { idbPro, storeSchema, factory } = this;
682
+ return this.tx(!0, async (t, store2) => await fn(factory.newDbMap({ storeSchema, target: store2 }, idbPro)));
683
+ }
684
+ asStore(writable) {
685
+ const { factory } = this.idbPro.schema;
686
+ return writable ? factory.newWriter(this, this.idbPro) : factory.newReader({ target: this.target }, this.idbPro);
687
+ }
688
+ entries() {
689
+ return new DbIterator({
690
+ target: this.target
691
+ }, this.idbPro, { parser: "keyValue" });
692
+ }
693
+ async get(key, defaultValue) {
694
+ return await this.tx(!1, (store$1) => store.requestDbResult(store$1.get(key))) || defaultValue;
695
+ }
696
+ getMany(keys) {
697
+ return this.tx(!1, async (store$1) => {
698
+ const result = [];
699
+ for (const k of keys)
700
+ result.push(await store.requestDbResult(store$1.get(k)));
701
+ return result;
702
+ });
703
+ }
704
+ async has(key) {
705
+ return !!await this.get(key);
706
+ }
707
+ keys() {
708
+ return new DbIterator({ storeSchema: this.storeSchema, target: this.target }, this.idbPro, { parser: "key" });
709
+ }
710
+ set(key, value) {
711
+ return this.tx(!0, async (t, store$1) => {
712
+ await store.requestDbResult(store$1.put(value, key));
713
+ });
714
+ }
715
+ setMany(values) {
716
+ return this.tx(!0, async (t, store2) => {
717
+ for (const [k, v] of values)
718
+ store2.put(v, k);
719
+ });
720
+ }
721
+ values() {
722
+ return new DbIterator({ storeSchema: this.storeSchema, target: this.target }, this.idbPro, { parser: "value" });
723
+ }
724
+ }
725
+ function equalKeyPath(p1, p2) {
726
+ if (p1 == p2) return !0;
727
+ if (typeof p1 != typeof p2) return !1;
728
+ if (Array.isArray(p1) && Array.isArray(p2)) {
729
+ if (p1.length !== p2.length) return !1;
730
+ for (let i = 0; i < p1.length; i++)
731
+ if (p1[i] !== p2[i]) return !1;
732
+ return !0;
733
+ }
734
+ return !1;
735
+ }
736
+ const versionDiffValidate = ({ stores, schema }) => {
737
+ const storeSchemas = schema.storeSchemas;
738
+ let info = "";
739
+ for (const store2 of stores) {
740
+ const ss = storeSchemas.find((s) => s.name === store2.name);
741
+ if (ss) {
742
+ if (!equalKeyPath(store2.keyPath, ss.keyPath)) {
743
+ info = `store [ ${store2.name} ] keyPath not equal,schema.keyPath:${ss.keyPath},store.keyPath:${store2.keyPath}[]`;
744
+ break;
745
+ }
746
+ if (!store2.autoIncrement != !ss.autoIncrement) {
747
+ info = `store [ ${store2.name} ] autoIncrement not equal`;
748
+ break;
749
+ }
750
+ }
751
+ }
752
+ return info ? `The existing database is inconsistent with the definition and cannot be corrected: ${info}` : !0;
753
+ }, versionSameValidate = async (context) => {
754
+ let result = versionDiffValidate(context);
755
+ return types.isString(result) || (result = validateStoreAndIndexes(context)), result;
756
+ }, validateStoreAndIndexes = ({ stores, schema }) => {
757
+ const storeSchemas = schema.storeSchemas, dbStoreNames = stores.map((s) => s.name);
758
+ let info = "";
759
+ const missingStoreNames = storeSchemas.map((s) => s.name).filter((s) => !dbStoreNames.includes(s));
760
+ if (missingStoreNames.length)
761
+ info = `store [ ${missingStoreNames.join(",")} ] not exist`;
762
+ else for (const store2 of stores) {
763
+ const ss = storeSchemas.find((s) => s.name === store2.name);
764
+ if (ss && (info = validateIndexes$1(store2, Array.from(store2.indexNames), ss.indexSchemas), info))
765
+ break;
766
+ }
767
+ return info ? `The existing database Store index is inconsistent with the definition and requires a database version upgrade to be fixed: ${info}` : !0;
768
+ };
769
+ function validateIndexes$1(store2, indexNames, schemas) {
770
+ if (indexNames.length !== schemas.length)
771
+ return `store [ ${store2.name} ] index count not equal`;
772
+ for (const name of indexNames) {
773
+ const schema = schemas.find((s) => s.name === name);
774
+ if (!schema)
775
+ return `store [ ${store2.name} ] index [ ${name} ] not exist`;
776
+ const index = store2.index(name);
777
+ if (!schema.unique != !index.unique)
778
+ return `store [ ${store2.name} ] index [ ${name} ] unique not equal`;
779
+ if (!schema.multiEntry != !index.multiEntry)
780
+ return `store [ ${store2.name} ] index [ ${name} ] multiEntry not equal`;
781
+ if (!equalKeyPath(schema.keyPath, index.keyPath))
782
+ return `store [ ${store2.name} ] index [ ${name} ] keyPath not equal`;
783
+ }
784
+ return "";
785
+ }
786
+ class StoreUpgradeable {
787
+ upgradeContext;
788
+ storeSchema;
789
+ nativeStore;
790
+ #writer;
791
+ constructor(upgradeContext, storeSchema, nativeStore) {
792
+ this.upgradeContext = upgradeContext, this.storeSchema = storeSchema, this.nativeStore = nativeStore;
793
+ }
794
+ get writer() {
795
+ return this.#writer || (this.#writer = this.upgradeContext.dbSchema.factory?.newWriter({
796
+ target: this.nativeStore,
797
+ storeSchema: this.storeSchema
798
+ }));
799
+ }
800
+ add(versionBounds, values, returns) {
801
+ return this.upgradeContext.versionIn(versionBounds) ? this.writer.addMany(values, returns) : Promise.resolve();
802
+ }
803
+ addOrChange(versionBounds, values, returns) {
804
+ return this.upgradeContext.versionIn(versionBounds) ? this.writer.addOrChangeMany(values, returns) : Promise.resolve();
805
+ }
806
+ async call(versionBounds, fn) {
807
+ if (this.upgradeContext.versionIn(versionBounds))
808
+ return await fn(this.writer, this.upgradeContext);
809
+ }
810
+ replace(versionBounds, values, returns) {
811
+ return this.upgradeContext.versionIn(versionBounds) ? this.writer.replaceMany(values, returns) : Promise.resolve();
812
+ }
813
+ }
814
+ const DataOperatorFactory = Object.freeze({
815
+ newDataOperators(schemas, db2) {
816
+ return new DataOperators(db2, schemas);
817
+ },
818
+ newDbMap(schema, db2) {
819
+ return new DbMap(schema, db2);
820
+ },
821
+ newReader(schema, db2) {
822
+ return new DataReader(schema, db2);
823
+ },
824
+ newStoreUpgradeable(nativeStore, storeSchema, upgradeContext) {
825
+ return new StoreUpgradeable(upgradeContext, storeSchema, nativeStore);
826
+ },
827
+ newWriter(schema, db2) {
828
+ return new DataWriter(schema, db2);
829
+ }
830
+ });
831
+ function validateSpecialFields(schema) {
832
+ Object.isFrozen(schema) || (schema.addedTimeField = validateSpecialField(schema, schema.addedTimeField, "added_at", !0), schema.updatedTimeField = validateSpecialField(schema, schema.updatedTimeField, "updated_at", !0), schema.updatedCountField = validateSpecialField(schema, schema.updatedCountField, "updated_count", !1), schema.softDeletedField = validateSpecialField(schema, schema.softDeletedField, "deleted", !1));
833
+ }
834
+ function validateSpecialField(schema, field, defaultName, useDefault) {
835
+ const validField = validateSpecialFieldBase(field, defaultName, useDefault);
836
+ if (!validField)
837
+ return validField;
838
+ const schemaField = validField;
839
+ if (schemaField.isIndexed !== !1) {
840
+ schemaField.isIndexed || (schemaField.isIndexed = !0);
841
+ const { name } = schemaField, indexSchemas = schema.indexSchemas;
842
+ indexSchemas.some((i) => i === name || i.name === name) || indexSchemas.push(name);
843
+ }
844
+ return schemaField;
845
+ }
846
+ function validateSpecialFieldBase(field, defaultName, useDefault) {
847
+ if (field === !1)
848
+ return !1;
849
+ if (types.isString(field))
850
+ return { name: field };
851
+ if (types.isObject(field)) {
852
+ const r = field;
853
+ return types.isBoolean(r.name) && (r.name = defaultName), field;
854
+ } else if (field === !0 || useDefault)
855
+ return { name: defaultName };
856
+ return !1;
857
+ }
858
+ function validateStoreSchema(schema, storeTemplate) {
859
+ let validSchema = types.isString(schema) ? { name: schema } : schema;
860
+ return storeTemplate && (validSchema = { ...storeTemplate, ...validSchema }), validSchema.indexSchemas || (validSchema.indexSchemas = []), validateSpecialFields(validSchema), Object.isFrozen(validSchema) || (validSchema.indexSchemas = validSchema.indexSchemas.map(indexMapper)), validateDefaultData(validSchema), validSchema;
861
+ }
862
+ function indexMapper(index) {
863
+ const schema = types.isString(index) ? { name: index } : index;
864
+ return schema.keyPath || (schema.keyPath = schema.name), schema;
865
+ }
866
+ function validateDefaultData(schema) {
867
+ if (!(schema.keyPath || !schema.defaultData?.length)) {
868
+ for (const row of schema.defaultData)
869
+ if (!Array.isArray(row) && !("value" in row))
870
+ throw new Error(`When \`defaultData\` must contain \`value\` fields or be an array:${JSON.stringify(row)}`);
871
+ }
872
+ }
873
+ const validateSchemaWithDefaults = (schema) => {
874
+ const { versionDiffValidate: vdf, versionSameValidate: vsf, factory: dof } = schema;
875
+ return schema.storeSchemas || (schema.storeSchemas = []), schema.storeTemplate || (schema.storeTemplate = { ...type$1.defaultStoreSchemaTemplate }), !vdf && vdf !== !1 && (schema.versionDiffValidate = versionDiffValidate), !vsf && vsf !== !1 && (schema.versionSameValidate = versionSameValidate), dof ? dof !== DataOperatorFactory && (schema.factory = { ...DataOperatorFactory, ...dof }) : schema.factory = DataOperatorFactory, schema.storeSchemas = schema.storeSchemas.map((s) => validateStoreSchema(s, schema.storeTemplate)), schema;
876
+ };
877
+ async function validateBeforeOpen(schema) {
878
+ const { name, version } = schema, existDb = await db.findExistDb(name);
879
+ if (!existDb)
880
+ return !0;
881
+ const { versionDiffValidate: versionDiffValidate2, versionSameValidate: versionSameValidate2 } = schema, db$1 = await db.openDb(name);
882
+ try {
883
+ if (schema.version < db$1.version)
884
+ return "The existing database version is greater than the current version";
885
+ const validate = schema.version === void 0 || db$1.version === schema.version ? versionSameValidate2 : versionDiffValidate2;
886
+ if (!validate)
887
+ return !0;
888
+ const storeNames = Array.from(db$1.objectStoreNames);
889
+ if (storeNames.length < 1)
890
+ return `The existing database [ ${name} ] is empty`;
891
+ const stores = await tx.readTx(db$1, storeNames), diffResult = await validate({ schema, db: db$1, stores });
892
+ if (types.isString(diffResult) || version !== existDb.version)
893
+ return diffResult;
894
+ } finally {
895
+ db$1?.close();
896
+ }
897
+ return !0;
898
+ }
899
+ function validateIndexes(storeSchema, store2) {
900
+ const { indexSchemas } = storeSchema, existNames = store2.indexNames, schemaNames = indexSchemas.map((option) => option.name);
901
+ for (const name of Array.from(existNames))
902
+ schemaNames.includes(name) || store2.deleteIndex(name);
903
+ for (const indexSchema of indexSchemas)
904
+ existNames.contains(indexSchema.name) ? validateIndex(store2, indexSchema) : createIndex(store2, indexSchema);
905
+ return store2;
906
+ }
907
+ function validateIndex(store2, indexOption) {
908
+ const index = store2.index(indexOption.name);
909
+ checkIndex(index, indexOption) || (store2.deleteIndex(indexOption.name), createIndex(store2, indexOption));
910
+ }
911
+ function checkIndex(index, indexOption) {
912
+ return index.unique !== indexOption.unique || index.multiEntry !== indexOption.multiEntry ? !1 : equalKeyPath(indexOption.keyPath, index.keyPath);
913
+ }
914
+ function createIndex(store2, schema) {
915
+ try {
916
+ store2.createIndex(schema.name, schema.keyPath, {
917
+ unique: schema.unique,
918
+ multiEntry: schema.multiEntry
919
+ });
920
+ } catch {
921
+ throw new Error(`store [ ${store2.name} ] index [ ${schema.name} ] create error: ${JSON.stringify(schema)}`);
922
+ }
923
+ }
924
+ function validateStoreDefine(context, schema) {
925
+ return context.database.objectStoreNames.contains(schema.name) ? validateIndexes(schema, context.transaction?.objectStore(schema.name)) : defineStore(schema, context.database);
926
+ }
927
+ function defineStore(schema, db2) {
928
+ const store2 = db2.createObjectStore(schema.name, {
929
+ keyPath: schema.keyPath,
930
+ autoIncrement: schema.autoIncrement
931
+ });
932
+ for (const option of schema.indexSchemas)
933
+ createIndex(store2, option);
934
+ return store2;
935
+ }
936
+ class UpgradeContext {
937
+ database;
938
+ newVersion;
939
+ oldVersion;
940
+ dbSchema;
941
+ transaction;
942
+ #stores = {};
943
+ constructor(args) {
944
+ this.database = args.database, this.newVersion = args.newVersion, this.oldVersion = args.oldVersion, this.dbSchema = args.dbSchema, this.transaction = args.transaction;
945
+ }
946
+ deleteStoreIfExists(storeName) {
947
+ const db2 = this.database;
948
+ db2.objectStoreNames.contains(storeName) && db2.deleteObjectStore(storeName);
949
+ }
950
+ destroy() {
951
+ try {
952
+ basic.destroyRecords(this.#stores);
953
+ } finally {
954
+ for (const k of Object.keys(this.#stores))
955
+ delete this.#stores[k];
956
+ }
957
+ }
958
+ store(storeName) {
959
+ if (storeName in this.#stores)
960
+ return this.#stores[storeName];
961
+ const { factory } = this.dbSchema, { storeSchemas } = this.dbSchema, storeSchema = storeSchemas.find((s) => s.name === storeName), nativeStore = validateStoreDefine(this, storeSchema);
962
+ return this.#stores[storeName] = factory.newStoreUpgradeable(nativeStore, storeSchema, this);
963
+ }
964
+ versionIn({ oldMin, oldMax, newMax, newMin }) {
965
+ if (oldMax === void 0 && newMax === void 0 && oldMin === void 0 && newMin === void 0)
966
+ throw new Error(`versionIn bounds must not be empty ${JSON.stringify({ oldMax, newMax, oldMin, newMin })}`);
967
+ if (oldMax < oldMin)
968
+ throw new Error(`oldMax (${oldMax}) cannot be less than oldMin (${oldMin})`);
969
+ if (newMax < newMin)
970
+ throw new Error(`newMax (${newMax}) cannot be less than newMin (${newMin})`);
971
+ const { oldVersion, newVersion } = this;
972
+ return oldMin !== void 0 && oldVersion < oldMin || oldMax !== void 0 && oldVersion > oldMax || newMin !== void 0 && newVersion < newMin ? !1 : !(newMax !== void 0 && newVersion > newMax);
973
+ }
974
+ }
975
+ async function upgradeDb(dbSchema, database, e, request) {
976
+ const { storeSchemas, beforeUpgrade, afterUpgrade, version } = dbSchema, { newVersion = version, oldVersion } = e, { transaction } = request, context = new UpgradeContext({ database, newVersion, oldVersion, dbSchema, transaction });
977
+ try {
978
+ const errors = [];
979
+ if (types.isFunction(beforeUpgrade)) try {
980
+ await beforeUpgrade(context);
981
+ } catch (e2) {
982
+ errors.push(e2);
983
+ }
984
+ for (const storeName of storeSchemas.map((s) => s.name))
985
+ try {
986
+ context.store(storeName);
987
+ } catch (e2) {
988
+ errors.push(e2);
989
+ }
990
+ for (const { name, defaultData } of storeSchemas)
991
+ if (defaultData) try {
992
+ await context.store(name).add({ oldMax: 0 }, defaultData);
993
+ } catch (e2) {
994
+ errors.push(e2);
995
+ }
996
+ for (const { name, versionData } of storeSchemas)
997
+ if (versionData) for (const { version: version2, data, use = "addOrChange" } of versionData)
998
+ try {
999
+ await context.store(name)[use](version2, data);
1000
+ } catch (e2) {
1001
+ errors.push(e2);
1002
+ }
1003
+ for (const { name, storeDefined } of storeSchemas)
1004
+ try {
1005
+ await storeDefined?.(context.store(name));
1006
+ } catch (e2) {
1007
+ errors.push(e2);
1008
+ }
1009
+ if (types.isFunction(afterUpgrade)) try {
1010
+ await afterUpgrade(context);
1011
+ } catch (e2) {
1012
+ errors.push(e2);
1013
+ }
1014
+ if (!errors.length) return;
1015
+ throw errors.length === 1 ? errors[0] : new AggregateError(errors, "Database upgrade error");
1016
+ } finally {
1017
+ basic.destroy(context);
1018
+ }
1019
+ }
1020
+ function validateDataOperationSchema(schema, dbSchema) {
1021
+ return Object.isFrozen(dbSchema) ? frozenValidate(schema) : initValidate(schema, dbSchema);
1022
+ }
1023
+ function frozenValidate(schema) {
1024
+ let { store: store2, index } = schema;
1025
+ const target = { store: types.isString(store2) ? store2 : store2.name };
1026
+ return index && (target.index = types.isString(index) ? index : index.name), {
1027
+ target
1028
+ };
1029
+ }
1030
+ function initValidate(schema, dbSchema) {
1031
+ let { store: store2, index } = schema;
1032
+ const { storeTemplate } = dbSchema, { storeSchemas = [] } = dbSchema, storeName = types.isString(store2) ? store2 : store2.name, storeIndex = storeSchemas.findIndex((s) => s === storeName || s.name === storeName), existStore = storeIndex > -1 && storeSchemas[storeIndex];
1033
+ let tmp;
1034
+ types.isString(store2) ? tmp = existStore || storeName : !existStore || types.isString(existStore) || store2 === existStore ? tmp = store2 : tmp = { ...existStore, ...store2 }, (index || existStore?.indexSchemas?.length || store2?.indexSchemas?.length) && (types.isString(tmp) && (tmp = { name: tmp }), tmp.indexSchemas = mergeIndexes(existStore.indexSchemas || [], store2.indexSchemas || [], index));
1035
+ const validSchema = validateStoreSchema(tmp, storeTemplate);
1036
+ storeIndex > -1 ? storeSchemas[storeIndex] = validSchema : storeSchemas.push(validSchema), dbSchema.storeSchemas = storeSchemas;
1037
+ const target = { store: storeName };
1038
+ return index && (target.index = types.isString(index) ? index : index.name), {
1039
+ target
1040
+ };
1041
+ }
1042
+ function mergeIndexes(existIndexes, newIndexes, index) {
1043
+ index && newIndexes.push(index);
1044
+ for (const index2 of newIndexes) {
1045
+ const indexName = types.isString(index2) ? index2 : index2.name, existIndex = existIndexes.findIndex((i) => i === indexName || i.name === indexName);
1046
+ if (existIndex > -1) {
1047
+ const old = existIndexes[existIndex];
1048
+ types.isString(old) ? existIndexes[existIndex] = index2 : types.isString(index2) || (existIndexes[existIndex] = Object.assign(old, index2));
1049
+ } else
1050
+ existIndexes.push(index2);
1051
+ }
1052
+ return existIndexes;
1053
+ }
1054
+ const mapStoreSchema = Object.assign({
1055
+ name: "",
1056
+ addedTimeField: !1,
1057
+ autoIncrement: !1,
1058
+ indexSchemas: [],
1059
+ keyPath: void 0,
1060
+ softDeletedField: !1,
1061
+ updatedCountField: !1,
1062
+ updatedTimeField: !1
1063
+ });
1064
+ class IDbPro {
1065
+ static #_db;
1066
+ #schema;
1067
+ #beforeUseValid;
1068
+ #storeSchemaRecord = {};
1069
+ constructor(schema, skipPreOpenValidation) {
1070
+ this.#schema = schema = types.isString(schema) ? { name: schema } : schema, Array.isArray(schema.storeSchemas) || (schema.storeSchemas = []), types.isObject(schema.storeTemplate) || (schema.storeTemplate = type$1.defaultStoreSchemaTemplate), skipPreOpenValidation && (this.#beforeUseValid = !0);
1071
+ }
1072
+ /**
1073
+ * 默认实例
1074
+ */
1075
+ static get defaultDb() {
1076
+ return this.#_db || (this.#_db = new IDbPro(storage.DefaultDbName));
1077
+ }
1078
+ get initialized() {
1079
+ return Object.isFrozen(this.#schema);
1080
+ }
1081
+ get schema() {
1082
+ return this.#schema;
1083
+ }
1084
+ get storeNames() {
1085
+ return Array.from(new Set(this.#schema.storeSchemas.map((s) => types.isString(s) ? s : s.name)));
1086
+ }
1087
+ get factory() {
1088
+ return this.#schema.factory || (this.#schema.factory = DataOperatorFactory);
1089
+ }
1090
+ /**
1091
+ * 释放默认数据库实例(如果存在)
1092
+ */
1093
+ static releaseDefaultDB() {
1094
+ this.#_db = void 0;
1095
+ }
1096
+ /**
1097
+ * 从已经存在的`name`数据库中打开
1098
+ * @warning 该方法仅用于不需要修改原有数据库结构的场景
1099
+ * - 如果需要修改,请使用 `generateDbSchema()` 从现有数据库生成配置,且适当修改后重新打开
1100
+ * @param name
1101
+ */
1102
+ static async openExistDb(name) {
1103
+ const { generateDbSchema: generateDbSchema2 } = await Promise.resolve().then(function() {
1104
+ return generateDbSchema$1;
1105
+ });
1106
+ return new IDbPro(await generateDbSchema2(name));
1107
+ }
1108
+ static store(arg1, index) {
1109
+ return IDbPro.defaultDb.store(arg1, index);
1110
+ }
1111
+ static stores(schemas) {
1112
+ return IDbPro.defaultDb.stores(schemas);
1113
+ }
1114
+ static map(storeName, defaultData) {
1115
+ return IDbPro.defaultDb.map(storeName, defaultData);
1116
+ }
1117
+ async openNativeDb() {
1118
+ const schema = this.#schema = basic.deepFreeze(this.initSchema());
1119
+ await this.#isBeforeUseValidate();
1120
+ const { name, version } = schema;
1121
+ return await db.openDb({
1122
+ name,
1123
+ version,
1124
+ onupgradeneeded: (db2, e, request) => upgradeDb(schema, db2, e, request)
1125
+ });
1126
+ }
1127
+ store(arg1, index) {
1128
+ const operation = arg1.store ? arg1 : { store: arg1 };
1129
+ return operation.store || (operation.store = arg1), operation.index || (operation.index = index), this.factory.newWriter(validateDataOperationSchema(operation, this.schema), this);
1130
+ }
1131
+ stores(schemas) {
1132
+ const { schema } = this, results = schemas.map((store2) => validateDataOperationSchema(types.isString(store2) ? { store: store2 } : store2, schema));
1133
+ return this.factory.newDataOperators(results, this);
1134
+ }
1135
+ initSchema() {
1136
+ if (this.initialized) return this.#schema;
1137
+ const { validateSchemaWithDefaults: validate = validateSchemaWithDefaults } = this.#schema;
1138
+ return this.#schema = validate(this.#schema);
1139
+ }
1140
+ async traceSchema(showFn) {
1141
+ await json.logJson(this.schema, showFn);
1142
+ }
1143
+ map(storeName, defaultData) {
1144
+ const name = types.isString(storeName) ? storeName : types.isString(defaultData) ? defaultData : storage.DefaultStorageStoreName;
1145
+ Array.isArray(storeName) && (defaultData = storeName);
1146
+ const { storeSchemas } = this.schema;
1147
+ return storeSchemas.find((s) => s.name === name || s === name) || storeSchemas.push({ ...mapStoreSchema, name, defaultData }), this.factory.newDbMap({ target: { store: name } }, this);
1148
+ }
1149
+ export() {
1150
+ return this.stores(this.storeNames).export();
1151
+ }
1152
+ import(data, returns, use) {
1153
+ return this.stores(this.storeNames).import(data, returns, use);
1154
+ }
1155
+ getStoreSchema(name) {
1156
+ if (name in this.#storeSchemaRecord)
1157
+ return this.#storeSchemaRecord[name];
1158
+ const index = this.schema.storeSchemas.findIndex((s) => s === name || s.name === name);
1159
+ let storeSchema = this.schema.storeSchemas[index];
1160
+ return this.initialized ? this.#storeSchemaRecord[name] = storeSchema : types.isString(storeSchema) && (this.schema.storeSchemas[index] = storeSchema = { name: storeSchema }), storeSchema;
1161
+ }
1162
+ async #isBeforeUseValidate() {
1163
+ let valid = this.#beforeUseValid;
1164
+ if (valid === void 0 && (valid = this.#beforeUseValid = await validateBeforeOpen(this.#schema)), valid === !0)
1165
+ return !0;
1166
+ if (types.isString(valid))
1167
+ throw new Error(valid);
1168
+ }
1169
+ }
1170
+ function dbStore(arg1, index) {
1171
+ return IDbPro.defaultDb.store(arg1, index);
1172
+ }
1173
+ function dbMap(storeName, defaultData) {
1174
+ return IDbPro.defaultDb.map(storeName, defaultData);
1175
+ }
1176
+ function dbStores(schemas) {
1177
+ return IDbPro.defaultDb.stores(schemas);
1178
+ }
1179
+ function releaseDefaultDB() {
1180
+ IDbPro.releaseDefaultDB();
1181
+ }
1182
+ function generateStoreSchema(store2, fields) {
1183
+ const { name, keyPath, autoIncrement } = store2;
1184
+ return {
1185
+ name,
1186
+ keyPath,
1187
+ autoIncrement,
1188
+ ...parseIndexes(store2, fields)
1189
+ };
1190
+ }
1191
+ function parseIndexes(store2, fields) {
1192
+ const indexes = Array.from(store2.indexNames).map((n) => store2.index(n)), indexSchemas = [], paths = [];
1193
+ for (const { name, keyPath, unique, multiEntry } of indexes)
1194
+ Array.isArray(keyPath) ? paths.push(...keyPath) : paths.push(keyPath), indexSchemas.push({ name, keyPath, unique, multiEntry });
1195
+ const keySet = new Set(paths);
1196
+ return {
1197
+ indexSchemas,
1198
+ addedTimeField: convertSpecialField(fields.addedTimeField, keySet),
1199
+ updatedTimeField: convertSpecialField(fields.updatedTimeField, keySet),
1200
+ updatedCountField: convertSpecialField(fields.updatedCountField, keySet),
1201
+ softDeletedField: convertSpecialField(fields.softDeletedField, keySet)
1202
+ };
1203
+ }
1204
+ function convertSpecialField(field, keySet) {
1205
+ return keySet.has(field) ? { name: field, isIndexed: !1 } : !1;
1206
+ }
1207
+ async function generateDbSchema(db$1, option) {
1208
+ if (!await db.findExistDb(db$1))
1209
+ throw new Error(`db [ ${db$1} ] not exist`);
1210
+ let { asString, specialFields = type$1.defaultSpecialFields, dataExportTarget } = option || {};
1211
+ asString === !0 && (asString = 160), isNaN(asString) || asString < 1 && (asString = 1);
1212
+ let dbSchema = await generateRoot(db$1, specialFields);
1213
+ return dataExportTarget && await generateData(dbSchema, dataExportTarget), asString ? await json.toJson({
1214
+ rootData$: dbSchema,
1215
+ spaceEffectiveLength: asString
1216
+ }) : dbSchema;
1217
+ }
1218
+ async function generateData(dbSchema, dataExportTarget) {
1219
+ const data = await new IDbPro(basic.copyObject(dbSchema), !0).export();
1220
+ for (const schema of dbSchema.storeSchemas) {
1221
+ const rows = data[schema.name];
1222
+ rows?.length && (dataExportTarget === "defaultData" ? schema.defaultData = rows : dataExportTarget === "versionData" && (schema.versionData || (schema.versionData = []), schema.versionData.push({
1223
+ version: { oldMax: dbSchema.version },
1224
+ data: rows
1225
+ })));
1226
+ }
1227
+ return dbSchema;
1228
+ }
1229
+ function generateRoot(db$1, specialFields) {
1230
+ return db.openDb(db$1, async function(existDb) {
1231
+ const names = Array.from(existDb.objectStoreNames), tx2 = existDb.transaction(names, "readonly");
1232
+ try {
1233
+ return {
1234
+ name: existDb.name,
1235
+ version: existDb.version,
1236
+ storeSchemas: names.map((name) => generateStoreSchema(tx2.objectStore(name), specialFields))
1237
+ };
1238
+ } finally {
1239
+ tx2.abort();
1240
+ }
1241
+ });
1242
+ }
1243
+ var generateDbSchema$1 = /* @__PURE__ */ Object.freeze({
1244
+ __proto__: null,
1245
+ generateDbSchema
1246
+ });
1247
+ exports.DataOperationBase = DataOperationBase, exports.DataOperators = DataOperators, exports.DataReader = DataReader, exports.DataWriter = DataWriter, exports.DbIterator = DbIterator, exports.DbMap = DbMap, exports.IDbPro = IDbPro, exports.StoreUpgradeable = StoreUpgradeable, exports.UpgradeContext = UpgradeContext, exports.dbMap = dbMap, exports.dbStore = dbStore, exports.dbStores = dbStores, exports.generateDbSchema = generateDbSchema, exports.isIDbQuery = isIDbQuery, exports.parseIDbQuery = parseIDbQuery, exports.releaseDefaultDB = releaseDefaultDB, exports.validateSchemaWithDefaults = validateSchemaWithDefaults, exports.versionDiffValidate = versionDiffValidate, exports.versionSameValidate = versionSameValidate;