dexie 4.3.0 → 4.4.1

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/README.md CHANGED
@@ -12,7 +12,7 @@ But also:
12
12
 
13
13
  * Dexie.js is widely used by 100,000 of web sites, apps and other projects and supports all browsers, Electron for Desktop apps, Capacitor for iOS / Android apps and of course pure PWAs.
14
14
  * Dexie.js works around bugs in the IndexedDB implementations, giving a more stable user experience.
15
- * It's an easy step to [make it sync](https://dexie.org/#sync).
15
+ * Need sync? [Dexie Cloud](https://dexie.org/cloud/) adds real-time sync, auth, and collaboration on top of Dexie.js — no backend needed.
16
16
 
17
17
  #### Hello World (vanilla JS)
18
18
 
@@ -213,11 +213,39 @@ This is a mix of methods from [WhereClause](https://dexie.org/docs/WhereClause/W
213
213
 
214
214
  ## Dexie Cloud
215
215
 
216
- [Dexie Cloud](https://dexie.org/cloud/) is a commercial offering that can be used as an add-on to Dexie.js. It syncs a Dexie database with a server and enables developers to build apps without having to care about backend or database layer else than the frontend code with Dexie.js as the sole database layer.
216
+ [Dexie Cloud](https://dexie.org/cloud/) is the easiest way to add sync, authentication, and real-time collaboration to your Dexie app. You keep writing frontend code with Dexie.js — Dexie Cloud handles the rest.
217
217
 
218
- Source for a sample Dexie Cloud app: [Dexie Cloud To-do app](https://github.com/dexie/Dexie.js/tree/master/samples/dexie-cloud-todo-app)
218
+ **What you get:**
219
+ - 🔄 **Sync across devices** — changes propagate in real time, no polling needed
220
+ - 🔐 **Authentication** — built-in user auth, no identity provider required
221
+ - 🛡️ **Access control** — share data between users with fine-grained permissions
222
+ - 📁 **File & blob storage** — store attachments alongside your structured data
223
+ - ✈️ **Offline-first** — works fully offline, syncs when back online
219
224
 
220
- See the sample Dexie Cloud app in action: https://dexie.github.io/Dexie.js/dexie-cloud-todo-app/
225
+ **Getting started is just a few lines:**
226
+
227
+ ```bash
228
+ npm install dexie-cloud-addon
229
+ ```
230
+
231
+ ```ts
232
+ import Dexie from 'dexie';
233
+ import dexieCloud from 'dexie-cloud-addon';
234
+
235
+ const db = new Dexie('MyDatabase', { addons: [dexieCloud] });
236
+ db.version(1).stores({ items: '@id, title' });
237
+ db.cloud.configure({ databaseUrl: 'https://<your-db>.dexie.cloud' });
238
+ ```
239
+
240
+ That's it. Your existing Dexie app now syncs. Hosted cloud or self-hosted on your own infrastructure. 👋
241
+
242
+ → [Quickstart guide](https://dexie.org/cloud/docs/quickstart)
243
+
244
+ **Sample app:**
245
+
246
+ Source: [Dexie Cloud To-do app](https://github.com/dexie/Dexie.js/tree/master/samples/dexie-cloud-todo-app)
247
+
248
+ Live demo: https://dexie.github.io/Dexie.js/dexie-cloud-todo-app/
221
249
 
222
250
  ## Samples
223
251
 
package/dist/dexie.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * By David Fahlander, david.fahlander@gmail.com
6
6
  *
7
- * Version 4.3.0, Sat Dec 20 2025
7
+ * Version 4.4.1, Wed Mar 25 2026
8
8
  *
9
9
  * https://dexie.org
10
10
  *
@@ -643,6 +643,7 @@ export interface DBCoreQueryRequest {
643
643
  values?: boolean;
644
644
  limit?: number;
645
645
  query: DBCoreQuery;
646
+ direction?: "next" | "nextunique" | "prev" | "prevunique";
646
647
  obsSet?: ObservabilitySet;
647
648
  }
648
649
  export interface DBCoreQueryResponse {
@@ -1119,6 +1120,7 @@ export interface DexieOptions {
1119
1120
  };
1120
1121
  chromeTransactionDurability?: ChromeTransactionDurability;
1121
1122
  cache?: "immutable" | "cloned" | "disabled";
1123
+ maxConnections?: number;
1122
1124
  }
1123
1125
  export interface DexieConstructor extends DexieExceptionClasses {
1124
1126
  new (databaseName: string, options?: DexieOptions): Dexie;
@@ -1199,7 +1201,7 @@ export interface _Collection<T, TKey> extends Collection<T, TKey> {
1199
1201
  }
1200
1202
  // Besides being the only exported value, let Dexie also be
1201
1203
  // a namespace for types...
1202
- export declare module Dexie {
1204
+ export declare namespace Dexie {
1203
1205
  // The "Dexie.Promise" type.
1204
1206
  type Promise<T = any> = PromiseExtended<T>; // Because many samples have been Dexie.Promise.
1205
1207
  // The "Dexie.Table" interface. Same as named exported interface Table.
package/dist/dexie.js CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * By David Fahlander, david.fahlander@gmail.com
6
6
  *
7
- * Version 4.3.0, Sat Dec 20 2025
7
+ * Version 4.4.1, Wed Mar 25 2026
8
8
  *
9
9
  * https://dexie.org
10
10
  *
@@ -228,12 +228,6 @@
228
228
  }
229
229
  return rv;
230
230
  }
231
- function objectIsEmpty(o) {
232
- for (var k in o)
233
- if (hasOwn(o, k))
234
- return false;
235
- return true;
236
- }
237
231
  var circularRefs = null;
238
232
  function deepClone(any) {
239
233
  circularRefs = new WeakMap();
@@ -1144,12 +1138,12 @@
1144
1138
  }
1145
1139
  }
1146
1140
 
1147
- var DEXIE_VERSION = '4.3.0';
1141
+ var DEXIE_VERSION = '4.4.1';
1148
1142
  var maxString = String.fromCharCode(65535);
1149
1143
  var minKey = -Infinity;
1150
1144
  var INVALID_KEY_ARGUMENT = "Invalid key provided. Keys must be of type string, number, Date or Array<string | number | Date>.";
1151
1145
  var STRING_EXPECTED = "String expected.";
1152
- var connections = [];
1146
+ var DEFAULT_MAX_CONNECTIONS = 1000;
1153
1147
  var DBNAMES_DB = '__dbnames';
1154
1148
  var READONLY = 'readonly';
1155
1149
  var READWRITE = 'readwrite';
@@ -1981,13 +1975,14 @@
1981
1975
  var _this = this;
1982
1976
  return this._read(function (trans) {
1983
1977
  var ctx = _this._ctx;
1984
- if (ctx.dir === 'next' && isPlainKeyRange(ctx, true) && ctx.limit > 0) {
1978
+ if (isPlainKeyRange(ctx, true) && ctx.limit > 0) {
1985
1979
  var valueMapper_1 = ctx.valueMapper;
1986
1980
  var index = getIndexOrStore(ctx, ctx.table.core.schema);
1987
1981
  return ctx.table.core.query({
1988
1982
  trans: trans,
1989
1983
  limit: ctx.limit,
1990
1984
  values: true,
1985
+ direction: ctx.dir === 'prev' ? 'prev' : undefined,
1991
1986
  query: {
1992
1987
  index: index,
1993
1988
  range: ctx.range
@@ -2112,13 +2107,14 @@
2112
2107
  };
2113
2108
  Collection.prototype.primaryKeys = function (cb) {
2114
2109
  var ctx = this._ctx;
2115
- if (ctx.dir === 'next' && isPlainKeyRange(ctx, true) && ctx.limit > 0) {
2110
+ if (isPlainKeyRange(ctx, true) && ctx.limit > 0) {
2116
2111
  return this._read(function (trans) {
2117
2112
  var index = getIndexOrStore(ctx, ctx.table.core.schema);
2118
2113
  return ctx.table.core.query({
2119
2114
  trans: trans,
2120
2115
  values: false,
2121
2116
  limit: ctx.limit,
2117
+ direction: ctx.dir === 'prev' ? 'prev' : undefined,
2122
2118
  query: {
2123
2119
  index: index,
2124
2120
  range: ctx.range
@@ -3008,6 +3004,9 @@
3008
3004
  function createDBCore(db, IdbKeyRange, tmpTrans) {
3009
3005
  function extractSchema(db, trans) {
3010
3006
  var tables = arrayify(db.objectStoreNames);
3007
+ var tempStore = tables.length > 0
3008
+ ? trans.objectStore(tables[0])
3009
+ : {};
3011
3010
  return {
3012
3011
  schema: {
3013
3012
  name: db.name,
@@ -3052,10 +3051,11 @@
3052
3051
  return result;
3053
3052
  })
3054
3053
  },
3055
- hasGetAll: tables.length > 0 && ('getAll' in trans.objectStore(tables[0])) &&
3054
+ hasGetAll: tables.length > 0 && ('getAll' in tempStore) &&
3056
3055
  !(typeof navigator !== 'undefined' && /Safari/.test(navigator.userAgent) &&
3057
3056
  !/(Chrome\/|Edge\/)/.test(navigator.userAgent) &&
3058
- [].concat(navigator.userAgent.match(/Safari\/(\d*)/))[1] < 604)
3057
+ [].concat(navigator.userAgent.match(/Safari\/(\d*)/))[1] < 604),
3058
+ hasIdb3Features: 'getAllRecords' in tempStore
3059
3059
  };
3060
3060
  }
3061
3061
  function makeIDBKeyRange(range) {
@@ -3226,11 +3226,13 @@
3226
3226
  }, reject);
3227
3227
  });
3228
3228
  }
3229
- function query(hasGetAll) {
3229
+ function query(hasGetAll, hasIdb3Features) {
3230
3230
  return function (request) {
3231
3231
  return new Promise(function (resolve, reject) {
3232
+ var _a;
3232
3233
  resolve = wrap(resolve);
3233
3234
  var trans = request.trans, values = request.values, limit = request.limit, query = request.query;
3235
+ var direction = (_a = request.direction) !== null && _a !== void 0 ? _a : 'next';
3234
3236
  var nonInfinitLimit = limit === Infinity ? undefined : limit;
3235
3237
  var index = query.index, range = query.range;
3236
3238
  var store = trans.objectStore(tableName);
@@ -3238,7 +3240,19 @@
3238
3240
  var idbKeyRange = makeIDBKeyRange(range);
3239
3241
  if (limit === 0)
3240
3242
  return resolve({ result: [] });
3241
- if (hasGetAll) {
3243
+ if (hasIdb3Features) {
3244
+ var options = {
3245
+ query: idbKeyRange,
3246
+ count: nonInfinitLimit,
3247
+ direction: direction
3248
+ };
3249
+ var req = values ?
3250
+ source.getAll(options) :
3251
+ source.getAllKeys(options);
3252
+ req.onsuccess = function (event) { return resolve({ result: event.target.result }); };
3253
+ req.onerror = eventRejectHandler(reject);
3254
+ }
3255
+ else if (hasGetAll && direction === 'next') {
3242
3256
  var req = values ?
3243
3257
  source.getAll(idbKeyRange, nonInfinitLimit) :
3244
3258
  source.getAllKeys(idbKeyRange, nonInfinitLimit);
@@ -3248,10 +3262,10 @@
3248
3262
  else {
3249
3263
  var count_1 = 0;
3250
3264
  var req_1 = values || !('openKeyCursor' in source) ?
3251
- source.openCursor(idbKeyRange) :
3252
- source.openKeyCursor(idbKeyRange);
3265
+ source.openCursor(idbKeyRange, direction) :
3266
+ source.openKeyCursor(idbKeyRange, direction);
3253
3267
  var result_1 = [];
3254
- req_1.onsuccess = function (event) {
3268
+ req_1.onsuccess = function () {
3255
3269
  var cursor = req_1.result;
3256
3270
  if (!cursor)
3257
3271
  return resolve({ result: result_1 });
@@ -3311,7 +3325,7 @@
3311
3325
  req.onerror = eventRejectHandler(reject);
3312
3326
  });
3313
3327
  },
3314
- query: query(hasGetAll),
3328
+ query: query(hasGetAll, hasIdb3Features),
3315
3329
  openCursor: openCursor,
3316
3330
  count: function (_a) {
3317
3331
  var query = _a.query, trans = _a.trans;
@@ -3327,7 +3341,7 @@
3327
3341
  }
3328
3342
  };
3329
3343
  }
3330
- var _a = extractSchema(db, tmpTrans), schema = _a.schema, hasGetAll = _a.hasGetAll;
3344
+ var _a = extractSchema(db, tmpTrans), schema = _a.schema, hasGetAll = _a.hasGetAll, hasIdb3Features = _a.hasIdb3Features;
3331
3345
  var tables = schema.tables.map(function (tableSchema) { return createDbCoreTable(tableSchema); });
3332
3346
  var tableMap = {};
3333
3347
  tables.forEach(function (table) { return tableMap[table.name] = table; });
@@ -3779,6 +3793,63 @@
3779
3793
  });
3780
3794
  }
3781
3795
 
3796
+ var connections = createConnectionsManager();
3797
+ function createConnectionsManager() {
3798
+ if (typeof FinalizationRegistry !== 'undefined' && typeof WeakRef !== 'undefined') {
3799
+ var _refs_1 = new Set();
3800
+ var _registry_1 = new FinalizationRegistry(function (ref) {
3801
+ _refs_1.delete(ref);
3802
+ });
3803
+ var toArray = function () {
3804
+ return Array.from(_refs_1)
3805
+ .map(function (ref) { return ref.deref(); })
3806
+ .filter(function (db) { return db !== undefined; });
3807
+ };
3808
+ var add = function (db) {
3809
+ var ref = new WeakRef(db._novip);
3810
+ _refs_1.add(ref);
3811
+ _registry_1.register(db._novip, ref, ref);
3812
+ if (_refs_1.size > db._options.maxConnections) {
3813
+ var oldestRef = _refs_1.values().next().value;
3814
+ _refs_1.delete(oldestRef);
3815
+ _registry_1.unregister(oldestRef);
3816
+ }
3817
+ };
3818
+ var remove = function (db) {
3819
+ if (!db)
3820
+ return;
3821
+ var iterator = _refs_1.values();
3822
+ var result = iterator.next();
3823
+ while (!result.done) {
3824
+ var ref = result.value;
3825
+ if (ref.deref() === db._novip) {
3826
+ _refs_1.delete(ref);
3827
+ _registry_1.unregister(ref);
3828
+ return;
3829
+ }
3830
+ result = iterator.next();
3831
+ }
3832
+ };
3833
+ return { toArray: toArray, add: add, remove: remove };
3834
+ }
3835
+ else {
3836
+ var connections_1 = [];
3837
+ var toArray = function () { return connections_1; };
3838
+ var add = function (db) {
3839
+ connections_1.push(db._novip);
3840
+ };
3841
+ var remove = function (db) {
3842
+ if (!db)
3843
+ return;
3844
+ var index = connections_1.indexOf(db._novip);
3845
+ if (index !== -1) {
3846
+ connections_1.splice(index, 1);
3847
+ }
3848
+ };
3849
+ return { toArray: toArray, add: add, remove: remove };
3850
+ }
3851
+ }
3852
+
3782
3853
  function getDbNamesTable(indexedDB, IDBKeyRange) {
3783
3854
  var dbNamesDB = indexedDB["_dbNamesDB"];
3784
3855
  if (!dbNamesDB) {
@@ -4157,7 +4228,7 @@
4157
4228
  }
4158
4229
  catch (e) {
4159
4230
  }
4160
- connections.push(db);
4231
+ connections.add(db);
4161
4232
  idbdb.onversionchange = wrap(function (ev) {
4162
4233
  state.vcFired = true;
4163
4234
  db.on("versionchange").fire(ev);
@@ -5002,10 +5073,13 @@
5002
5073
  }, result);
5003
5074
  if (finalResult === result)
5004
5075
  return result;
5005
- finalResult.sort(function (a, b) {
5076
+ var sorter = function (a, b) {
5006
5077
  return cmp(extractLowLevelIndex(a), extractLowLevelIndex(b)) ||
5007
5078
  cmp(extractPrimKey(a), extractPrimKey(b));
5008
- });
5079
+ };
5080
+ finalResult.sort(req.direction === 'prev' || req.direction === 'prevunique'
5081
+ ? function (a, b) { return sorter(b, a); }
5082
+ : sorter);
5009
5083
  if (req.limit && req.limit < Infinity) {
5010
5084
  if (finalResult.length > req.limit) {
5011
5085
  finalResult.length = req.limit;
@@ -5062,6 +5136,7 @@
5062
5136
  }
5063
5137
 
5064
5138
  function findCompatibleQuery(dbName, tableName, type, req) {
5139
+ var _a;
5065
5140
  var tblCache = cache["idb://".concat(dbName, "/").concat(tableName)];
5066
5141
  if (!tblCache)
5067
5142
  return [];
@@ -5074,9 +5149,12 @@
5074
5149
  return [null, false, tblCache, null];
5075
5150
  switch (type) {
5076
5151
  case 'query':
5152
+ var reqDirection_1 = (_a = req.direction) !== null && _a !== void 0 ? _a : 'next';
5077
5153
  var equalEntry = entries.find(function (entry) {
5154
+ var _a;
5078
5155
  return entry.req.limit === req.limit &&
5079
5156
  entry.req.values === req.values &&
5157
+ ((_a = entry.req.direction) !== null && _a !== void 0 ? _a : 'next') === reqDirection_1 &&
5080
5158
  areRangesEqual(entry.req.query.range, req.query.range);
5081
5159
  });
5082
5160
  if (equalEntry)
@@ -5087,8 +5165,10 @@
5087
5165
  entries,
5088
5166
  ];
5089
5167
  var superEntry = entries.find(function (entry) {
5168
+ var _a;
5090
5169
  var limit = 'limit' in entry.req ? entry.req.limit : Infinity;
5091
5170
  return (limit >= req.limit &&
5171
+ ((_a = entry.req.direction) !== null && _a !== void 0 ? _a : 'next') === reqDirection_1 &&
5092
5172
  (req.values ? entry.req.values : true) &&
5093
5173
  isSuperRange(entry.req.query.range, req.query.range));
5094
5174
  });
@@ -5338,7 +5418,7 @@
5338
5418
  var deps = Dexie.dependencies;
5339
5419
  this._options = options = __assign({
5340
5420
  addons: Dexie.addons, autoOpen: true,
5341
- indexedDB: deps.indexedDB, IDBKeyRange: deps.IDBKeyRange, cache: 'cloned' }, options);
5421
+ indexedDB: deps.indexedDB, IDBKeyRange: deps.IDBKeyRange, cache: 'cloned', maxConnections: DEFAULT_MAX_CONNECTIONS }, options);
5342
5422
  this._deps = {
5343
5423
  indexedDB: options.indexedDB,
5344
5424
  IDBKeyRange: options.IDBKeyRange
@@ -5432,7 +5512,7 @@
5432
5512
  this._createTransaction = function (mode, storeNames, dbschema, parentTransaction) { return new _this.Transaction(mode, storeNames, dbschema, _this._options.chromeTransactionDurability, parentTransaction); };
5433
5513
  this._fireOnBlocked = function (ev) {
5434
5514
  _this.on("blocked").fire(ev);
5435
- connections
5515
+ connections.toArray()
5436
5516
  .filter(function (c) { return c.name === _this.name && c !== _this && !c._state.vcFired; })
5437
5517
  .map(function (c) { return c.on("versionchange").fire(ev); });
5438
5518
  };
@@ -5525,9 +5605,7 @@
5525
5605
  Dexie.prototype._close = function () {
5526
5606
  this.on.close.fire(new CustomEvent('close'));
5527
5607
  var state = this._state;
5528
- var idx = connections.indexOf(this);
5529
- if (idx >= 0)
5530
- connections.splice(idx, 1);
5608
+ connections.remove(this);
5531
5609
  if (this.idbdb) {
5532
5610
  try {
5533
5611
  this.idbdb.close();
@@ -5778,22 +5856,32 @@
5778
5856
  signal: abortController.signal,
5779
5857
  requery: doQuery,
5780
5858
  querier: querier,
5781
- trans: null
5859
+ trans: null,
5782
5860
  };
5783
5861
  var ret = execute(ctx);
5862
+ if (!startedListening) {
5863
+ globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, mutationListener);
5864
+ startedListening = true;
5865
+ }
5784
5866
  Promise.resolve(ret).then(function (result) {
5785
5867
  hasValue = true;
5786
5868
  currentValue = result;
5787
5869
  if (closed || ctx.signal.aborted) {
5788
5870
  return;
5789
5871
  }
5790
- accumMuts = {};
5791
- currentObs = subscr;
5792
- if (!objectIsEmpty(currentObs) && !startedListening) {
5793
- globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, mutationListener);
5794
- startedListening = true;
5872
+ if (shouldNotify()) {
5873
+ doQuery();
5874
+ }
5875
+ else {
5876
+ currentObs = subscr;
5877
+ if (shouldNotify()) {
5878
+ doQuery();
5879
+ }
5880
+ else {
5881
+ accumMuts = {};
5882
+ execInGlobalContext(function () { return !closed && observer.next && observer.next(result); });
5883
+ }
5795
5884
  }
5796
- execInGlobalContext(function () { return !closed && observer.next && observer.next(result); });
5797
5885
  }, function (err) {
5798
5886
  hasValue = false;
5799
5887
  if (!['DatabaseClosedError', 'AbortError'].includes(err === null || err === void 0 ? void 0 : err.name)) {
@@ -5889,7 +5977,9 @@
5889
5977
  getByKeyPath: getByKeyPath, setByKeyPath: setByKeyPath, delByKeyPath: delByKeyPath, shallowClone: shallowClone, deepClone: deepClone, getObjectDiff: getObjectDiff, cmp: cmp, asap: asap$1,
5890
5978
  minKey: minKey,
5891
5979
  addons: [],
5892
- connections: connections,
5980
+ connections: {
5981
+ get: connections.toArray
5982
+ },
5893
5983
  errnames: errnames,
5894
5984
  dependencies: domDeps, cache: cache,
5895
5985
  semVer: DEXIE_VERSION, version: DEXIE_VERSION.split('.')
@@ -5953,8 +6043,8 @@
5953
6043
  if (debug)
5954
6044
  console.debug('Dexie: handling persisted pagehide');
5955
6045
  bc === null || bc === void 0 ? void 0 : bc.close();
5956
- for (var _i = 0, connections_1 = connections; _i < connections_1.length; _i++) {
5957
- var db = connections_1[_i];
6046
+ for (var _i = 0, _a = connections.toArray(); _i < _a.length; _i++) {
6047
+ var db = _a[_i];
5958
6048
  db.close({ disableAutoOpen: false });
5959
6049
  }
5960
6050
  }
@@ -5986,6 +6076,7 @@
5986
6076
 
5987
6077
  var namedExports = /*#__PURE__*/Object.freeze({
5988
6078
  __proto__: null,
6079
+ DEFAULT_MAX_CONNECTIONS: DEFAULT_MAX_CONNECTIONS,
5989
6080
  Dexie: Dexie$1,
5990
6081
  Entity: Entity,
5991
6082
  PropModification: PropModification,