dexie 4.0.8 → 4.1.0-alpha.2

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.
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * By David Fahlander, david.fahlander@gmail.com
6
6
  *
7
- * Version 4.0.8, Wed Jul 10 2024
7
+ * Version 4.1.0-alpha.2, Fri Oct 04 2024
8
8
  *
9
9
  * https://dexie.org
10
10
  *
@@ -1094,7 +1094,7 @@ function tempTransaction(db, mode, storeNames, fn) {
1094
1094
  }
1095
1095
  }
1096
1096
 
1097
- const DEXIE_VERSION = '4.0.8';
1097
+ const DEXIE_VERSION = '4.1.0-alpha.2';
1098
1098
  const maxString = String.fromCharCode(65535);
1099
1099
  const minKey = -Infinity;
1100
1100
  const INVALID_KEY_ARGUMENT = "Invalid key provided. Keys must be of type string, number, Date or Array<string | number | Date>.";
@@ -1212,6 +1212,89 @@ function getUint8Array(a) {
1212
1212
  return new Uint8Array(a);
1213
1213
  }
1214
1214
 
1215
+ function getDocCache(db) {
1216
+ return db._novip['_docCache'] ??= {
1217
+ cache: {},
1218
+ get size() {
1219
+ return Object.keys(this.cache).length;
1220
+ },
1221
+ find(table, primaryKey, ydocProp) {
1222
+ const cacheKey = getYDocCacheKey(table, primaryKey, ydocProp);
1223
+ const docRef = this.cache[cacheKey];
1224
+ return docRef ? docRef.deref() : undefined;
1225
+ },
1226
+ add(doc) {
1227
+ const { parentTable, parentId, parentProp } = doc.meta;
1228
+ if (!parentTable || !parentProp || parentId == null)
1229
+ throw new Error(`Missing Dexie-related metadata in Y.Doc`);
1230
+ const cacheKey = getYDocCacheKey(parentTable, parentId, parentProp);
1231
+ this.cache[cacheKey] = new WeakRef(doc);
1232
+ docRegistry.register(doc, { cache: this.cache, key: cacheKey });
1233
+ },
1234
+ delete(doc) {
1235
+ docRegistry.unregister(doc);
1236
+ delete this.cache[getYDocCacheKey(doc.meta.parentTable, doc.meta.parentId, doc.meta.parentProp)];
1237
+ },
1238
+ };
1239
+ }
1240
+ const docRegistry = new FinalizationRegistry(({ cache, key }) => {
1241
+ delete cache[key];
1242
+ });
1243
+ const destroyedDocs = new WeakSet();
1244
+ function throwIfDestroyed(doc) {
1245
+ if (destroyedDocs.has(doc))
1246
+ throw new Error('Y.Doc has been destroyed');
1247
+ }
1248
+ function getYDocCacheKey(table, primaryKey, ydocProp) {
1249
+ return `${table}[${primaryKey}].${ydocProp}`;
1250
+ }
1251
+
1252
+ function createYDocProperty(db, Y, table, prop, updatesTable) {
1253
+ const pkKeyPath = table.schema.primKey.keyPath;
1254
+ const docCache = getDocCache(db);
1255
+ return {
1256
+ get() {
1257
+ const id = getByKeyPath(this, pkKeyPath);
1258
+ let doc = docCache.find(table.name, id, prop);
1259
+ if (doc)
1260
+ return doc;
1261
+ doc = new Y.Doc({
1262
+ meta: {
1263
+ db,
1264
+ updatesTable,
1265
+ parentProp: prop,
1266
+ parentTable: table.name,
1267
+ parentId: id
1268
+ },
1269
+ });
1270
+ docCache.add(doc);
1271
+ doc.on('destroy', () => {
1272
+ destroyedDocs.add(doc);
1273
+ docCache.delete(doc);
1274
+ });
1275
+ return doc;
1276
+ },
1277
+ };
1278
+ }
1279
+
1280
+ function builtInDeletionTrigger(table, keys, res) {
1281
+ const { yProps } = table.schema;
1282
+ if (!yProps)
1283
+ return res;
1284
+ if (keys && res.numFailures > 0)
1285
+ keys = keys.filter((_, i) => !res.failures[i]);
1286
+ return Promise.all(yProps.map(({ updatesTable }) => keys
1287
+ ? table.db.table(updatesTable).where('k').anyOf(keys).delete()
1288
+ : table.db.table(updatesTable).clear())).then(() => res);
1289
+ }
1290
+
1291
+ function getYLibrary(db) {
1292
+ const Y = db._options.Y;
1293
+ if (!Y)
1294
+ throw new exceptions.MissingAPI('Y library not supplied to Dexie constructor');
1295
+ return Y;
1296
+ }
1297
+
1215
1298
  class Table {
1216
1299
  _trans(mode, fn, writeLocked) {
1217
1300
  const trans = this._tx || PSD.trans;
@@ -1347,6 +1430,14 @@ class Table {
1347
1430
  table() { return tableName; }
1348
1431
  };
1349
1432
  }
1433
+ if (this.schema.yProps) {
1434
+ const Y = getYLibrary(db);
1435
+ constructor = class extends constructor {
1436
+ };
1437
+ this.schema.yProps.forEach(({ prop, updatesTable }) => {
1438
+ Object.defineProperty(constructor.prototype, prop, createYDocProperty(db, Y, this, prop, updatesTable));
1439
+ });
1440
+ }
1350
1441
  const inheritedProps = new Set();
1351
1442
  for (let proto = constructor.prototype; proto; proto = getProto(proto)) {
1352
1443
  Object.getOwnPropertyNames(proto).forEach(propName => inheritedProps.add(propName));
@@ -1425,11 +1516,13 @@ class Table {
1425
1516
  });
1426
1517
  }
1427
1518
  delete(key) {
1428
- return this._trans('readwrite', trans => this.core.mutate({ trans, type: 'delete', keys: [key] }))
1429
- .then(res => res.numFailures ? DexiePromise.reject(res.failures[0]) : undefined);
1519
+ return this._trans('readwrite', trans => this.core.mutate({ trans, type: 'delete', keys: [key] })
1520
+ .then(res => builtInDeletionTrigger(this, [key], res))
1521
+ .then(res => res.numFailures ? DexiePromise.reject(res.failures[0]) : undefined));
1430
1522
  }
1431
1523
  clear() {
1432
- return this._trans('readwrite', trans => this.core.mutate({ trans, type: 'deleteRange', range: AnyRange }))
1524
+ return this._trans('readwrite', trans => this.core.mutate({ trans, type: 'deleteRange', range: AnyRange })
1525
+ .then(res => builtInDeletionTrigger(this, null, res)))
1433
1526
  .then(res => res.numFailures ? DexiePromise.reject(res.failures[0]) : undefined);
1434
1527
  }
1435
1528
  bulkGet(keys) {
@@ -1545,7 +1638,8 @@ class Table {
1545
1638
  bulkDelete(keys) {
1546
1639
  const numKeys = keys.length;
1547
1640
  return this._trans('readwrite', trans => {
1548
- return this.core.mutate({ trans, type: 'delete', keys: keys });
1641
+ return this.core.mutate({ trans, type: 'delete', keys: keys })
1642
+ .then(res => builtInDeletionTrigger(this, keys, res));
1549
1643
  }).then(({ numFailures, lastResult, failures }) => {
1550
1644
  if (numFailures === 0)
1551
1645
  return lastResult;
@@ -2053,45 +2147,48 @@ class Collection {
2053
2147
  totalFailures.push(failures[pos]);
2054
2148
  }
2055
2149
  };
2150
+ const isUnconditionalDelete = changes === deleteCallback;
2056
2151
  return this.clone().primaryKeys().then(keys => {
2057
2152
  const criteria = isPlainKeyRange(ctx) &&
2058
2153
  ctx.limit === Infinity &&
2059
- (typeof changes !== 'function' || changes === deleteCallback) && {
2154
+ (typeof changes !== 'function' || isUnconditionalDelete) && {
2060
2155
  index: ctx.index,
2061
2156
  range: ctx.range
2062
2157
  };
2063
2158
  const nextChunk = (offset) => {
2064
2159
  const count = Math.min(limit, keys.length - offset);
2065
- return coreTable.getMany({
2160
+ const keysInChunk = keys.slice(offset, offset + count);
2161
+ return (isUnconditionalDelete ? Promise.resolve([]) : coreTable.getMany({
2066
2162
  trans,
2067
- keys: keys.slice(offset, offset + count),
2163
+ keys: keysInChunk,
2068
2164
  cache: "immutable"
2069
- }).then(values => {
2165
+ })).then(values => {
2070
2166
  const addValues = [];
2071
2167
  const putValues = [];
2072
2168
  const putKeys = outbound ? [] : null;
2073
- const deleteKeys = [];
2074
- for (let i = 0; i < count; ++i) {
2075
- const origValue = values[i];
2076
- const ctx = {
2077
- value: deepClone(origValue),
2078
- primKey: keys[offset + i]
2079
- };
2080
- if (modifyer.call(ctx, ctx.value, ctx) !== false) {
2081
- if (ctx.value == null) {
2082
- deleteKeys.push(keys[offset + i]);
2083
- }
2084
- else if (!outbound && cmp(extractKey(origValue), extractKey(ctx.value)) !== 0) {
2085
- deleteKeys.push(keys[offset + i]);
2086
- addValues.push(ctx.value);
2087
- }
2088
- else {
2089
- putValues.push(ctx.value);
2090
- if (outbound)
2091
- putKeys.push(keys[offset + i]);
2169
+ const deleteKeys = isUnconditionalDelete ? keysInChunk : [];
2170
+ if (!isUnconditionalDelete)
2171
+ for (let i = 0; i < count; ++i) {
2172
+ const origValue = values[i];
2173
+ const ctx = {
2174
+ value: deepClone(origValue),
2175
+ primKey: keys[offset + i]
2176
+ };
2177
+ if (modifyer.call(ctx, ctx.value, ctx) !== false) {
2178
+ if (ctx.value == null) {
2179
+ deleteKeys.push(keys[offset + i]);
2180
+ }
2181
+ else if (!outbound && cmp(extractKey(origValue), extractKey(ctx.value)) !== 0) {
2182
+ deleteKeys.push(keys[offset + i]);
2183
+ addValues.push(ctx.value);
2184
+ }
2185
+ else {
2186
+ putValues.push(ctx.value);
2187
+ if (outbound)
2188
+ putKeys.push(keys[offset + i]);
2189
+ }
2092
2190
  }
2093
2191
  }
2094
- }
2095
2192
  return Promise.resolve(addValues.length > 0 &&
2096
2193
  coreTable.mutate({ trans, type: 'add', values: addValues })
2097
2194
  .then(res => {
@@ -2109,14 +2206,15 @@ class Collection {
2109
2206
  changeSpec: typeof changes !== 'function'
2110
2207
  && changes,
2111
2208
  isAdditionalChunk: offset > 0
2112
- }).then(res => applyMutateResult(putValues.length, res))).then(() => (deleteKeys.length > 0 || (criteria && changes === deleteCallback)) &&
2209
+ }).then(res => applyMutateResult(putValues.length, res))).then(() => (deleteKeys.length > 0 || (criteria && isUnconditionalDelete)) &&
2113
2210
  coreTable.mutate({
2114
2211
  trans,
2115
2212
  type: 'delete',
2116
2213
  keys: deleteKeys,
2117
2214
  criteria,
2118
2215
  isAdditionalChunk: offset > 0
2119
- }).then(res => applyMutateResult(deleteKeys.length, res))).then(() => {
2216
+ }).then(res => builtInDeletionTrigger(ctx.table, deleteKeys, res))
2217
+ .then(res => applyMutateResult(deleteKeys.length, res))).then(() => {
2120
2218
  return keys.length > offset + count && nextChunk(offset + limit);
2121
2219
  });
2122
2220
  });
@@ -2132,6 +2230,7 @@ class Collection {
2132
2230
  delete() {
2133
2231
  var ctx = this._ctx, range = ctx.range;
2134
2232
  if (isPlainKeyRange(ctx) &&
2233
+ !ctx.table.schema.yProps &&
2135
2234
  (ctx.isPrimKey || range.type === 3 ))
2136
2235
  {
2137
2236
  return this._write(trans => {
@@ -2139,7 +2238,7 @@ class Collection {
2139
2238
  const coreRange = range;
2140
2239
  return ctx.table.core.count({ trans, query: { index: primaryKey, range: coreRange } }).then(count => {
2141
2240
  return ctx.table.core.mutate({ trans, type: 'deleteRange', range: coreRange })
2142
- .then(({ failures, lastResult, results, numFailures }) => {
2241
+ .then(({ failures, numFailures }) => {
2143
2242
  if (numFailures)
2144
2243
  throw new ModifyError("Could not delete some values", Object.keys(failures).map(pos => failures[pos]), count - numFailures);
2145
2244
  return count - numFailures;
@@ -2723,6 +2822,12 @@ class Transaction {
2723
2822
 
2724
2823
  function createTransactionConstructor(db) {
2725
2824
  return makeClassConstructor(Transaction.prototype, function Transaction(mode, storeNames, dbschema, chromeTransactionDurability, parent) {
2825
+ if (mode !== 'readonly')
2826
+ storeNames.forEach(storeName => {
2827
+ const yProps = dbschema[storeName]?.yProps;
2828
+ if (yProps)
2829
+ storeNames = storeNames.concat(yProps.map(p => p.updatesTable));
2830
+ });
2726
2831
  this.db = db;
2727
2832
  this.mode = mode;
2728
2833
  this.storeNames = storeNames;
@@ -2758,7 +2863,7 @@ function createTransactionConstructor(db) {
2758
2863
  });
2759
2864
  }
2760
2865
 
2761
- function createIndexSpec(name, keyPath, unique, multi, auto, compound, isPrimKey) {
2866
+ function createIndexSpec(name, keyPath, unique, multi, auto, compound, isPrimKey, type) {
2762
2867
  return {
2763
2868
  name,
2764
2869
  keyPath,
@@ -2766,7 +2871,8 @@ function createIndexSpec(name, keyPath, unique, multi, auto, compound, isPrimKey
2766
2871
  multi,
2767
2872
  auto,
2768
2873
  compound,
2769
- src: (unique && !isPrimKey ? '&' : '') + (multi ? '*' : '') + (auto ? "++" : "") + nameFromKeyPath(keyPath)
2874
+ src: (unique && !isPrimKey ? '&' : '') + (multi ? '*' : '') + (auto ? "++" : "") + nameFromKeyPath(keyPath),
2875
+ type
2770
2876
  };
2771
2877
  }
2772
2878
  function nameFromKeyPath(keyPath) {
@@ -2775,13 +2881,17 @@ function nameFromKeyPath(keyPath) {
2775
2881
  keyPath ? ('[' + [].join.call(keyPath, '+') + ']') : "";
2776
2882
  }
2777
2883
 
2778
- function createTableSchema(name, primKey, indexes) {
2884
+ function createTableSchema(name, primKey, indexes, yProps) {
2779
2885
  return {
2780
2886
  name,
2781
2887
  primKey,
2782
2888
  indexes,
2783
2889
  mappedClass: null,
2784
- idxByName: arrayToObject(indexes, index => [index.name, index])
2890
+ yProps: yProps?.map((prop) => ({
2891
+ prop,
2892
+ updatesTable: `$${name}.${prop}_updates`,
2893
+ })),
2894
+ idxByName: arrayToObject(indexes, (index) => [index.name, index]),
2785
2895
  };
2786
2896
  }
2787
2897
 
@@ -3507,49 +3617,68 @@ function adjustToExistingIndexNames(db, schema, idbtrans) {
3507
3617
  }
3508
3618
  function parseIndexSyntax(primKeyAndIndexes) {
3509
3619
  return primKeyAndIndexes.split(',').map((index, indexNum) => {
3510
- index = index.trim();
3620
+ const typeSplit = index.split(':');
3621
+ const type = typeSplit[1]?.trim();
3622
+ index = typeSplit[0].trim();
3623
+ if (type && type !== 'Y')
3624
+ throw new exceptions.Schema(`Unsupported type '${type}'`);
3511
3625
  const name = index.replace(/([&*]|\+\+)/g, "");
3512
3626
  const keyPath = /^\[/.test(name) ? name.match(/^\[(.*)\]$/)[1].split('+') : name;
3513
- return createIndexSpec(name, keyPath || null, /\&/.test(index), /\*/.test(index), /\+\+/.test(index), isArray(keyPath), indexNum === 0);
3627
+ return createIndexSpec(name, keyPath || null, /\&/.test(index), /\*/.test(index), /\+\+/.test(index), isArray(keyPath), indexNum === 0, type);
3514
3628
  });
3515
3629
  }
3516
3630
 
3517
3631
  class Version {
3518
3632
  _parseStoresSpec(stores, outSchema) {
3519
- keys(stores).forEach(tableName => {
3633
+ keys(stores).forEach((tableName) => {
3520
3634
  if (stores[tableName] !== null) {
3521
- var indexes = parseIndexSyntax(stores[tableName]);
3522
- var primKey = indexes.shift();
3635
+ let indexes = parseIndexSyntax(stores[tableName]);
3636
+ const yProps = indexes.filter((idx) => idx.type === 'Y').map((idx) => idx.name);
3637
+ indexes = indexes.filter((idx) => idx.type !== 'Y');
3638
+ const primKey = indexes.shift();
3639
+ if (!primKey) {
3640
+ throw new exceptions.Schema('Invalid schema for table ' + tableName + ': ' + stores[tableName]);
3641
+ }
3523
3642
  primKey.unique = true;
3524
3643
  if (primKey.multi)
3525
- throw new exceptions.Schema("Primary key cannot be multi-valued");
3526
- indexes.forEach(idx => {
3644
+ throw new exceptions.Schema('Primary key cannot be multiEntry*');
3645
+ indexes.forEach((idx) => {
3527
3646
  if (idx.auto)
3528
- throw new exceptions.Schema("Only primary key can be marked as autoIncrement (++)");
3647
+ throw new exceptions.Schema('Only primary key can be marked as autoIncrement (++)');
3529
3648
  if (!idx.keyPath)
3530
- throw new exceptions.Schema("Index must have a name and cannot be an empty string");
3649
+ throw new exceptions.Schema('Index must have a name and cannot be an empty string');
3531
3650
  });
3532
- outSchema[tableName] = createTableSchema(tableName, primKey, indexes);
3651
+ const tblSchema = createTableSchema(tableName, primKey, indexes, yProps.length ? yProps : undefined);
3652
+ outSchema[tableName] = tblSchema;
3653
+ for (const yProp of tblSchema.yProps || []) {
3654
+ this._parseStoresSpec(
3655
+ { [yProp.updatesTable]: '++i,k' }, outSchema);
3656
+ }
3533
3657
  }
3534
3658
  });
3535
3659
  }
3536
3660
  stores(stores) {
3537
3661
  const db = this.db;
3538
- this._cfg.storesSource = this._cfg.storesSource ?
3539
- extend(this._cfg.storesSource, stores) :
3540
- stores;
3662
+ this._cfg.storesSource = this._cfg.storesSource
3663
+ ? extend(this._cfg.storesSource, stores)
3664
+ : stores;
3541
3665
  const versions = db._versions;
3542
3666
  const storesSpec = {};
3543
3667
  let dbschema = {};
3544
- versions.forEach(version => {
3668
+ versions.forEach((version) => {
3545
3669
  extend(storesSpec, version._cfg.storesSource);
3546
- dbschema = (version._cfg.dbschema = {});
3670
+ dbschema = version._cfg.dbschema = {};
3547
3671
  version._parseStoresSpec(storesSpec, dbschema);
3548
3672
  });
3549
3673
  db._dbSchema = dbschema;
3550
3674
  removeTablesApi(db, [db._allTables, db, db.Transaction.prototype]);
3551
3675
  setApiOnPlace(db, [db._allTables, db, db.Transaction.prototype, this._cfg.tables], keys(dbschema), dbschema);
3552
3676
  db._storeNames = keys(dbschema);
3677
+ db._storeNames.forEach((tableName) => {
3678
+ if (dbschema[tableName].yProps) {
3679
+ db.table(tableName).mapToClass(dbschema[tableName].mappedClass || Object);
3680
+ }
3681
+ });
3553
3682
  return this;
3554
3683
  }
3555
3684
  upgrade(upgradeFunction) {
@@ -5119,6 +5248,124 @@ function vipify(target, vipDb) {
5119
5248
  });
5120
5249
  }
5121
5250
 
5251
+ function compressYDocs(db, interval) {
5252
+ let p = Promise.resolve();
5253
+ for (const table of db.tables) {
5254
+ for (const yProp of table.schema.yProps || []) {
5255
+ p = p.then(() => compressYDocsTable(db, yProp, interval));
5256
+ }
5257
+ }
5258
+ return p;
5259
+ }
5260
+ function compressYDocsTable(db, { updatesTable }, skipIfRunnedSince
5261
+ ) {
5262
+ const updTbl = db.table(updatesTable);
5263
+ return Promise.all([
5264
+ updTbl
5265
+ .where('i')
5266
+ .startsWith('')
5267
+ .toArray(),
5268
+ db.transaction('rw', updatesTable, () => updTbl.get(0).then((lastCompressed) => {
5269
+ if (lastCompressed &&
5270
+ skipIfRunnedSince &&
5271
+ lastCompressed.lastRun.getTime() > Date.now() - skipIfRunnedSince) {
5272
+ return null;
5273
+ }
5274
+ lastCompressed = lastCompressed || { i: 0, lastCompressed: 0 };
5275
+ return updTbl
5276
+ .put({
5277
+ ...lastCompressed,
5278
+ lastRun: new Date(),
5279
+ })
5280
+ .then(() => lastCompressed);
5281
+ })),
5282
+ ]).then(([syncers, stamp]) => {
5283
+ if (!stamp)
5284
+ return;
5285
+ const lastCompressedUpdate = stamp.lastCompressed;
5286
+ const unsyncedFrom = Math.min(...syncers.map((s) => Math.min(s.unsentFrom || Infinity, s.receivedUntil != null ? s.receivedUntil + 1 : Infinity)));
5287
+ return updTbl
5288
+ .where('i')
5289
+ .between(lastCompressedUpdate, Infinity, false)
5290
+ .toArray((addedUpdates) => {
5291
+ if (addedUpdates.length <= 1)
5292
+ return;
5293
+ const docsToCompress = [];
5294
+ let lastUpdateToCompress = lastCompressedUpdate + 1;
5295
+ for (let j = 0; j < addedUpdates.length; ++j) {
5296
+ const updateRow = addedUpdates[j];
5297
+ const { i, f, k } = updateRow;
5298
+ if (i >= unsyncedFrom && (f & 0x01))
5299
+ break;
5300
+ const entry = docsToCompress.find((entry) => cmp(entry.docId, k) === 0);
5301
+ if (entry)
5302
+ entry.updates.push(updateRow);
5303
+ else
5304
+ docsToCompress.push({ docId: k, updates: [updateRow] });
5305
+ lastUpdateToCompress = i;
5306
+ }
5307
+ let p = Promise.resolve();
5308
+ for (const { docId, updates } of docsToCompress) {
5309
+ p = p.then(() => compressUpdatesForDoc(db, updatesTable, docId, updates));
5310
+ }
5311
+ return p.then(() => {
5312
+ return db.transaction('rw', updTbl, () => updTbl.get(0).then((current) => updTbl.put({
5313
+ ...current,
5314
+ lastCompressed: Math.max(lastUpdateToCompress, current?.lastCompressed || 0),
5315
+ })));
5316
+ });
5317
+ });
5318
+ });
5319
+ }
5320
+ function compressUpdatesForDoc(db, updatesTable, parentId, addedUpdatesToCompress) {
5321
+ if (addedUpdatesToCompress.length < 1)
5322
+ throw new Error('Invalid input');
5323
+ return db.transaction('rw', updatesTable, (tx) => {
5324
+ const updTbl = tx.table(updatesTable);
5325
+ return updTbl.where({ k: parentId }).first((mainUpdate) => {
5326
+ const updates = [mainUpdate].concat(addedUpdatesToCompress);
5327
+ const Y = getYLibrary(db);
5328
+ const doc = new Y.Doc({ gc: true });
5329
+ updates.forEach((update) => {
5330
+ Y.applyUpdateV2(doc, update.u);
5331
+ });
5332
+ const compressedUpdate = Y.encodeStateAsUpdateV2(doc);
5333
+ const lastUpdate = updates.pop();
5334
+ return updTbl
5335
+ .put({
5336
+ i: lastUpdate.i,
5337
+ k: parentId,
5338
+ u: compressedUpdate,
5339
+ })
5340
+ .then(() => updTbl.bulkDelete(updates.map((update) => update.i)));
5341
+ });
5342
+ });
5343
+ }
5344
+
5345
+ const INTERVAL = 10000;
5346
+ function periodicGC(db) {
5347
+ let timer = null;
5348
+ db.on('ready', (db) => {
5349
+ if (db.tables.some(tbl => tbl.schema.yProps)) {
5350
+ const gc = () => {
5351
+ if (!db.isOpen())
5352
+ return;
5353
+ compressYDocs(db, INTERVAL).catch(err => {
5354
+ console.debug('Error during periodic GC', err);
5355
+ }).then(() => {
5356
+ timer = setTimeout(gc, INTERVAL);
5357
+ });
5358
+ };
5359
+ timer = setTimeout(gc, INTERVAL);
5360
+ }
5361
+ }, true);
5362
+ db.on('close', () => {
5363
+ if (timer)
5364
+ clearTimeout(timer);
5365
+ timer = null;
5366
+ });
5367
+ }
5368
+
5122
5369
  class Dexie$1 {
5123
5370
  constructor(name, options) {
5124
5371
  this._middlewares = {};
@@ -5164,7 +5411,14 @@ class Dexie$1 {
5164
5411
  });
5165
5412
  this._state = state;
5166
5413
  this.name = name;
5167
- this.on = Events(this, "populate", "blocked", "versionchange", "close", { ready: [promisableChain, nop] });
5414
+ this.on = Events(this, "populate", "blocked", "versionchange", "close", "y", { ready: [promisableChain, nop] });
5415
+ this.once = (event, callback) => {
5416
+ const fn = (...args) => {
5417
+ this.on(event).unsubscribe(fn);
5418
+ callback.apply(this, args);
5419
+ };
5420
+ return this.on(event, fn);
5421
+ };
5168
5422
  this.on.ready.subscribe = override(this.on.ready.subscribe, subscribe => {
5169
5423
  return (subscriber, bSticky) => {
5170
5424
  Dexie$1.vip(() => {
@@ -5243,6 +5497,8 @@ class Dexie$1 {
5243
5497
  }
5244
5498
  });
5245
5499
  this.vip = vipDB;
5500
+ if (options?.Y)
5501
+ periodicGC(this);
5246
5502
  addons.forEach(addon => addon(this));
5247
5503
  }
5248
5504
  version(versionNumber) {
@@ -5299,6 +5555,7 @@ class Dexie$1 {
5299
5555
  () => dexieOpen(this));
5300
5556
  }
5301
5557
  _close() {
5558
+ this.on.close.fire(new CustomEvent('close'));
5302
5559
  const state = this._state;
5303
5560
  const idx = connections.indexOf(this);
5304
5561
  if (idx >= 0)
@@ -5447,6 +5704,9 @@ class Dexie$1 {
5447
5704
  }
5448
5705
  return this._allTables[tableName];
5449
5706
  }
5707
+ gc() {
5708
+ return compressYDocs(this);
5709
+ }
5450
5710
  }
5451
5711
 
5452
5712
  const symbolObservable = typeof Symbol !== "undefined" && "observable" in Symbol
@@ -5765,8 +6025,170 @@ function replacePrefix(a, b) {
5765
6025
  return new PropModification({ replacePrefix: [a, b] });
5766
6026
  }
5767
6027
 
6028
+ function observeYDocUpdates(provider, doc, db, parentTableName, updatesTableName, parentId, Y) {
6029
+ let lastUpdateId = 0;
6030
+ let initial = true;
6031
+ const subscription = liveQuery(() => {
6032
+ throwIfDestroyed(doc);
6033
+ const updatesTable = db.table(updatesTableName);
6034
+ return Promise.all([
6035
+ (lastUpdateId > 0
6036
+ ? updatesTable
6037
+ .where('i')
6038
+ .between(lastUpdateId, Infinity, false)
6039
+ .toArray()
6040
+ .then((updates) => updates.filter((update) => cmp(update.k, parentId) === 0))
6041
+ : updatesTable.where({ k: parentId }).toArray()).then((updates) => {
6042
+ if (updates.length > 0)
6043
+ lastUpdateId = updates[updates.length - 1].i;
6044
+ return updates;
6045
+ }),
6046
+ db.table(parentTableName).where(':id').equals(parentId).toArray(),
6047
+ ]);
6048
+ }).subscribe(([updates, parentRow]) => {
6049
+ if (parentRow.length === 0) {
6050
+ doc.destroy();
6051
+ return;
6052
+ }
6053
+ throwIfDestroyed(doc);
6054
+ if (updates.length > 0) {
6055
+ Y.transact(doc, () => {
6056
+ updates.forEach((update) => {
6057
+ try {
6058
+ DexieYProvider.currentUpdateRow = update;
6059
+ Y.applyUpdateV2(doc, update.u);
6060
+ }
6061
+ finally {
6062
+ DexieYProvider.currentUpdateRow = null;
6063
+ }
6064
+ });
6065
+ }, provider, false);
6066
+ }
6067
+ if (initial) {
6068
+ initial = false;
6069
+ doc.emit('load', [doc]);
6070
+ }
6071
+ }, (error) => {
6072
+ provider.on('error').fire(error);
6073
+ });
6074
+ const onUpdate = (update, origin) => {
6075
+ if (origin === provider)
6076
+ return;
6077
+ db.table(updatesTableName)
6078
+ .add({
6079
+ k: parentId,
6080
+ u: update,
6081
+ f: 1,
6082
+ })
6083
+ .then((i) => {
6084
+ if (i === lastUpdateId - 1)
6085
+ ++lastUpdateId;
6086
+ })
6087
+ .catch((error) => {
6088
+ provider.on('error').fire(error);
6089
+ });
6090
+ };
6091
+ const stopObserving = () => {
6092
+ subscription.unsubscribe();
6093
+ doc.off('updateV2', onUpdate);
6094
+ doc.off('destroy', stopObserving);
6095
+ };
6096
+ doc.on('updateV2', onUpdate);
6097
+ doc.on('destroy', stopObserving);
6098
+ return stopObserving;
6099
+ }
6100
+
6101
+ const wm = new WeakMap();
6102
+ function createEvents() {
6103
+ return Events(null, 'load', 'sync', 'error');
6104
+ }
6105
+ class DexieYProvider {
6106
+ static load(doc) {
6107
+ let p = wm.get(doc);
6108
+ if (p) {
6109
+ ++p.refCount;
6110
+ }
6111
+ else {
6112
+ p = new DexieYProvider(doc);
6113
+ wm.set(doc, p);
6114
+ }
6115
+ return p;
6116
+ }
6117
+ static release(doc, options) {
6118
+ if (destroyedDocs.has(doc))
6119
+ return;
6120
+ const p = wm.get(doc);
6121
+ if (!p) {
6122
+ if (--p.refCount === 0) {
6123
+ if (isNaN(options?.gracePeriod)) {
6124
+ doc.destroy();
6125
+ }
6126
+ else {
6127
+ setTimeout(() => p.refCount || doc.destroy(),
6128
+ options.gracePeriod
6129
+ );
6130
+ }
6131
+ }
6132
+ }
6133
+ else {
6134
+ doc.destroy();
6135
+ }
6136
+ }
6137
+ static for(doc) {
6138
+ return wm.get(doc);
6139
+ }
6140
+ constructor(doc) {
6141
+ this.refCount = 1;
6142
+ this.cleanupHandlers = [];
6143
+ this.destroyed = false;
6144
+ const { meta: { db, parentTable, parentId, updatesTable }, } = doc || {};
6145
+ if (!db || !parentTable || !updatesTable)
6146
+ throw new Error('Y.Doc not generated by Dexie');
6147
+ if (!db.table(parentTable) || !db.table(updatesTable)) {
6148
+ throw new Error(`Table ${parentTable} or ${updatesTable} not found in db`);
6149
+ }
6150
+ throwIfDestroyed(doc);
6151
+ const Y = getYLibrary(db);
6152
+ this.on = createEvents();
6153
+ this.doc = doc;
6154
+ this.off = (name, f) => this.on[name]?.unsubscribe(f);
6155
+ this.whenLoaded = new Promise((resolve, reject) => {
6156
+ this.on('load', resolve);
6157
+ this.on('error', reject);
6158
+ });
6159
+ this.whenSynced = new Promise((resolve, reject) => {
6160
+ this.on('sync', resolve);
6161
+ this.on('error', reject);
6162
+ });
6163
+ if ('dispose' in Symbol) {
6164
+ this[Symbol.dispose] = this.dispose;
6165
+ }
6166
+ this.stopObserving = observeYDocUpdates(this, doc, db, parentTable, updatesTable, parentId, Y);
6167
+ doc.on('load', () => this.on('load').fire());
6168
+ doc.on('sync', (sync) => sync !== false && this.on('sync').fire());
6169
+ doc.on('destroy', this.destroy.bind(this));
6170
+ db.on.y.fire(this, Y);
6171
+ }
6172
+ destroy() {
6173
+ wm.delete(this.doc);
6174
+ this.doc = null;
6175
+ this.destroyed = true;
6176
+ this.refCount = 0;
6177
+ this.stopObserving?.();
6178
+ this.on = createEvents();
6179
+ this.cleanupHandlers.forEach((cleanup) => cleanup());
6180
+ }
6181
+ addCleanupHandler(cleanupHandler) {
6182
+ this.cleanupHandlers.push(typeof cleanupHandler === 'function'
6183
+ ? cleanupHandler
6184
+ : () => cleanupHandler.unsubscribe());
6185
+ }
6186
+ }
6187
+ DexieYProvider.getDocCache = getDocCache;
6188
+ DexieYProvider.currentUpdateRow = null;
6189
+
5768
6190
  DexiePromise.rejectionMapper = mapError;
5769
6191
  setDebug(debug);
5770
6192
 
5771
- export { Dexie$1 as Dexie, Entity, PropModSymbol, PropModification, RangeSet, add, cmp, Dexie$1 as default, liveQuery, mergeRanges, rangesOverlap, remove, replacePrefix };
6193
+ export { Dexie$1 as Dexie, DexieYProvider, Entity, PropModSymbol, PropModification, RangeSet, add, cmp, Dexie$1 as default, liveQuery, mergeRanges, rangesOverlap, remove, replacePrefix };
5772
6194
  //# sourceMappingURL=dexie.mjs.map