indexeddbshim 17.3.1 → 17.3.3

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.
Files changed (33) hide show
  1. package/dist/IDBCursor.d.ts.map +1 -1
  2. package/dist/IDBFactory.d.ts.map +1 -1
  3. package/dist/IDBObjectStore.d.ts +28 -0
  4. package/dist/IDBObjectStore.d.ts.map +1 -1
  5. package/dist/IDBTransaction.d.ts.map +1 -1
  6. package/dist/indexeddbshim-Key.js +1 -1
  7. package/dist/indexeddbshim-Key.min.js +1 -1
  8. package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs +507 -150
  9. package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs.map +1 -1
  10. package/dist/indexeddbshim-UnicodeIdentifiers.js +334 -89
  11. package/dist/indexeddbshim-UnicodeIdentifiers.js.map +1 -1
  12. package/dist/indexeddbshim-UnicodeIdentifiers.min.js +3 -3
  13. package/dist/indexeddbshim-UnicodeIdentifiers.min.js.map +1 -1
  14. package/dist/indexeddbshim-node.cjs +507 -150
  15. package/dist/indexeddbshim-node.cjs.map +1 -1
  16. package/dist/indexeddbshim-noninvasive.js +334 -89
  17. package/dist/indexeddbshim-noninvasive.js.map +1 -1
  18. package/dist/indexeddbshim-noninvasive.min.js +3 -3
  19. package/dist/indexeddbshim-noninvasive.min.js.map +1 -1
  20. package/dist/indexeddbshim.js +334 -89
  21. package/dist/indexeddbshim.js.map +1 -1
  22. package/dist/indexeddbshim.min.js +3 -3
  23. package/dist/indexeddbshim.min.js.map +1 -1
  24. package/dist/nodeSQLiteDatabase.d.ts.map +1 -1
  25. package/dist/nodeWebSQL.d.ts.map +1 -1
  26. package/package.json +4 -3
  27. package/src/IDBCursor.js +34 -4
  28. package/src/IDBFactory.js +119 -21
  29. package/src/IDBObjectStore.js +103 -1
  30. package/src/IDBRecord.js +8 -0
  31. package/src/IDBTransaction.js +16 -2
  32. package/src/nodeSQLiteDatabase.js +92 -26
  33. package/src/nodeWebSQL.js +8 -1
@@ -1,4 +1,4 @@
1
- /*! indexeddbshim - v17.3.1 - 8/24/2026 */
1
+ /*! indexeddbshim - v17.3.3 - 8/25/2026 */
2
2
 
3
3
  'use strict';
4
4
 
@@ -5708,9 +5708,15 @@ IDBTransaction.prototype.__abortTransaction = function (err) {
5708
5708
  if (err !== null) {
5709
5709
  me.__error = err;
5710
5710
  }
5711
- if (me.__requestsFinished) {
5711
+ if (me.__requestsFinished && err !== null) {
5712
5712
  // The transaction has already completed, so we can't call "onerror" or "onabort".
5713
- // So throw the error instead.
5713
+ // So throw the error instead. `err` is only ever `null` here via
5714
+ // `IDBTransaction.prototype.abort`'s own `__abortTransaction(null)`
5715
+ // call, which now checks `__requestsFinished` itself first and
5716
+ // throws `InvalidStateError` synchronously before ever reaching
5717
+ // this point -- so this guard is just defense in depth against
5718
+ // `err` somehow being `null` some other way, not something this
5719
+ // path should see in practice.
5714
5720
  setTimeout(() => {
5715
5721
  throw err;
5716
5722
  }, 0);
@@ -5828,6 +5834,14 @@ IDBTransaction.prototype.abort = function () {
5828
5834
  if (me.__committed) {
5829
5835
  throw createDOMException('InvalidStateError', 'The transaction has already been committed');
5830
5836
  }
5837
+ if (me.__requestsFinished) {
5838
+ // All requests have already finished and the transaction is
5839
+ // auto-committing (the async SQL commit round trip just hasn't
5840
+ // resolved yet) -- too late to abort per spec, even though
5841
+ // `__committed` itself isn't set until that round trip actually
5842
+ // finishes.
5843
+ throw createDOMException('InvalidStateError', 'The transaction is already committing');
5844
+ }
5831
5845
  me.__abortTransaction(null);
5832
5846
  };
5833
5847
 
@@ -6135,8 +6149,8 @@ class Typeson {
6135
6149
  {
6136
6150
  sync: c
6137
6151
  } = i,
6138
- y = {},
6139
- u = [],
6152
+ u = {},
6153
+ y = [],
6140
6154
  l = [],
6141
6155
  p = [],
6142
6156
  f = !("cyclic" in i) || i.cyclic,
@@ -6145,14 +6159,14 @@ class Typeson {
6145
6159
  encapsulateError: m
6146
6160
  } = i,
6147
6161
  finish = e => {
6148
- const t = Object.values(y);
6162
+ const t = Object.values(u);
6149
6163
  if (i.iterateNone) return t.length ? t[0] : getJSONType(e);
6150
6164
  if (t.length) {
6151
6165
  if (i.returnTypeNames) return [...new Set(t)];
6152
- e && isPlainObject(e) && !n(e, "$types") ? e.$types = y : e = {
6166
+ e && isPlainObject(e) && !n(e, "$types") ? e.$types = u : e = {
6153
6167
  $: e,
6154
6168
  $types: {
6155
- $: y
6169
+ $: u
6156
6170
  }
6157
6171
  };
6158
6172
  } else isObject(e) && n(e, "$types") && (e = {
@@ -6166,10 +6180,10 @@ class Typeson {
6166
6180
  return await Promise.all(r.map(async function (r) {
6167
6181
  const n = [],
6168
6182
  [a] = t.splice(0, 1),
6169
- [o,, s, i, c, y, u] = a,
6170
- l = _encapsulate(o, r, s, i, n, true, u),
6183
+ [o,, s, i, c, u, y] = a,
6184
+ l = _encapsulate(o, r, s, i, n, true, y),
6171
6185
  p = hasConstructorOf(l, TypesonPromise);
6172
- return o && p ? (setOwnEnumerable(c, y, await l.p), checkPromises(e, n)) : (o ? setOwnEnumerable(c, y, l) : e = p ? l.p : l, checkPromises(e, n));
6186
+ return o && p ? (setOwnEnumerable(c, u, await l.p), checkPromises(e, n)) : (o ? setOwnEnumerable(c, u, l) : e = p ? l.p : l, checkPromises(e, n));
6173
6187
  })), e;
6174
6188
  },
6175
6189
  _adaptBuiltinStateObjectProperties = (e, t, r) => {
@@ -6229,11 +6243,11 @@ class Typeson {
6229
6243
  })) : h = t, b && b(), h;
6230
6244
  if (null === t) return b && b(), t;
6231
6245
  if (o && t && "object" == typeof t && !s.iterateIn && !s.iterateUnsetNumeric) {
6232
- const r = u.indexOf(t);
6233
- if (-1 !== r) return y[e] = "#", b && b({
6246
+ const r = y.indexOf(t);
6247
+ if (-1 !== r) return u[e] = "#", b && b({
6234
6248
  cyclicKeypath: l[r]
6235
6249
  }), "#" + l[r];
6236
- true === o && (u.push(t), l.push(e));
6250
+ true === o && (y.push(t), l.push(e));
6237
6251
  }
6238
6252
  const v = isPlainObject(t),
6239
6253
  w = a(t),
@@ -6297,16 +6311,16 @@ class Typeson {
6297
6311
  },
6298
6312
  replace = (e, t, r, n, a, o, s) => {
6299
6313
  const i = a ? this.plainObjectReplacers : this.nonplainObjectReplacers;
6300
- let u = i.length;
6301
- for (; u--;) {
6302
- const a = i[u];
6314
+ let y = i.length;
6315
+ for (; y--;) {
6316
+ const a = i[y];
6303
6317
  if (a.test(t, r)) {
6304
6318
  const {
6305
6319
  type: i
6306
6320
  } = a;
6307
6321
  if (Object.hasOwn(this.revivers, i)) {
6308
- const t = y[e];
6309
- y[e] = t ? [i].concat(t) : i;
6322
+ const t = u[e];
6323
+ u[e] = t ? [i].concat(t) : i;
6310
6324
  }
6311
6325
  if (Object.assign(r, {
6312
6326
  type: i,
@@ -6314,14 +6328,14 @@ class Typeson {
6314
6328
  }), (c || !a.replaceAsync) && !a.replace) return s && s({
6315
6329
  typeDetected: true
6316
6330
  }), _encapsulate(e, t, f && "readonly", r, n, o, i);
6317
- let u;
6331
+ let y;
6318
6332
  if (s && s({
6319
6333
  replacing: true
6320
6334
  }), c || !a.replaceAsync) {
6321
6335
  if (void 0 === a.replace) throw new TypeError("Missing replacer");
6322
- u = a.replace(t, r);
6323
- } else u = a.replaceAsync(t, r);
6324
- return _encapsulate(e, u, f && "readonly", r, n, o, i);
6336
+ y = a.replace(t, r);
6337
+ } else y = a.replaceAsync(t, r);
6338
+ return _encapsulate(e, y, f && "readonly", r, n, o, i);
6325
6339
  }
6326
6340
  }
6327
6341
  return t;
@@ -6366,16 +6380,16 @@ class Typeson {
6366
6380
  if (true === i) return finishRevival(e.$);
6367
6381
  if (!i || "object" != typeof i || Array.isArray(i)) return finishRevival(e);
6368
6382
  const c = [],
6369
- y = Object.create(null),
6370
- u = {};
6383
+ u = Object.create(null),
6384
+ y = {};
6371
6385
  let l = true;
6372
6386
  i.$ && isPlainObject(i.$) && (e = e.$, i = i.$, l = false);
6373
6387
  const executeReviver = (e, t) => {
6374
6388
  const [r] = this.revivers[e] ?? [];
6375
6389
  if (!r) throw new Error("Unregistered type: " + e);
6376
6390
  if (s && !("revive" in r)) return t;
6377
- if (!s && r.reviveAsync) return r.reviveAsync(t, u);
6378
- if (r.revive) return r.revive(t, u);
6391
+ if (!s && r.reviveAsync) return r.reviveAsync(t, y);
6392
+ if (r.revive) return r.revive(t, y);
6379
6393
  throw new Error("Missing reviver");
6380
6394
  },
6381
6395
  p = [];
@@ -6413,7 +6427,7 @@ class Typeson {
6413
6427
  }, void 0);
6414
6428
  })();
6415
6429
  let d;
6416
- return hasConstructorOf(f, TypesonPromise) ? d = f.then(() => e) : (d = function _revive(e, t, o, u, f) {
6430
+ return hasConstructorOf(f, TypesonPromise) ? d = f.then(() => e) : (d = function _revive(e, t, o, y, f) {
6417
6431
  if (l && "$types" === e) return;
6418
6432
  const d = p.length,
6419
6433
  m = n(i, e) ? i[e] : void 0,
@@ -6426,18 +6440,18 @@ class Typeson {
6426
6440
  hasConstructorOf(n, TypesonPromise) ? p.push(n.then(e => set(e))) : set(n);
6427
6441
  }), t = a; c.length;) {
6428
6442
  const [[e, t, r, a]] = c,
6429
- o = n(y, t),
6430
- s = o ? y[t] : getByKeyPath(e, t);
6443
+ o = n(u, t),
6444
+ s = o ? u[t] : getByKeyPath(e, t);
6431
6445
  if (!o && void 0 === s) break;
6432
6446
  setOwnEnumerable(r, a, s), c.shift();
6433
6447
  }
6434
6448
  }
6435
- if (!m) return y[e] = t, t;
6449
+ if (!m) return u[e] = t, t;
6436
6450
  if ("#" === m) {
6437
6451
  const e = t.slice(1),
6438
- r = n(y, e),
6439
- a = r ? y[e] : getByKeyPath(o, e);
6440
- return r || void 0 !== a || c.push([o, e, u, f]), a;
6452
+ r = n(u, e),
6453
+ a = r ? u[e] : getByKeyPath(o, e);
6454
+ return r || void 0 !== a || c.push([o, e, y, f]), a;
6441
6455
  }
6442
6456
  const applyType = t => {
6443
6457
  const r = [].concat(m).reduce(function reducer(e, t) {
@@ -6445,7 +6459,7 @@ class Typeson {
6445
6459
  if ("string" != typeof t) throw new TypeError("Bad type JSON");
6446
6460
  return executeReviver(t, e);
6447
6461
  }, t);
6448
- return hasConstructorOf(r, TypesonPromise) ? r.then(t => (y[e] = t, t)) : (y[e] = r, r);
6462
+ return hasConstructorOf(r, TypesonPromise) ? r.then(t => (u[e] = t, t)) : (u[e] = r, r);
6449
6463
  };
6450
6464
  return !s && p.length > d ? TypesonPromise.all(p.slice(d)).then(() => applyType(t)) : applyType(t);
6451
6465
  }("", e, null), p.length && (d = TypesonPromise.resolve(d).then(e => TypesonPromise.all([e, ...p])).then(([e]) => e))), isThenable(d) ? s && o.throwOnBadSyncType ? (() => {
@@ -6521,8 +6535,8 @@ class Typeson {
6521
6535
  class Undefined {}
6522
6536
  Undefined.__typeson__type__ = "TypesonUndefined";
6523
6537
  const s = ["null", "boolean", "number", "string", "array", "object"];
6524
- for (var i = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", c = new Uint8Array(256), y = 0; y < 64; y++) c[i.codePointAt(y)] = y;
6525
- var u = function encode(e, t, r) {
6538
+ for (var i = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", c = new Uint8Array(256), u = 0; u < 64; u++) c[i.codePointAt(u)] = u;
6539
+ var y = function encode(e, t, r) {
6526
6540
  null == r && (r = e.byteLength);
6527
6541
  for (var n = new Uint8Array(e, 0, r), a = n.length, o = "", s = 0; s < a; s += 3) o += i[n[s] >> 2], o += i[(3 & n[s]) << 4 | n[s + 1] >> 4], o += i[(15 & n[s + 1]) << 2 | n[s + 2] >> 6], o += i[63 & n[s + 2]];
6528
6542
  return a % 3 == 2 ? o = o.slice(0, -1) + "=" : a % 3 == 1 && (o = o.slice(0, -2) + "=="), o;
@@ -6535,10 +6549,10 @@ var u = function encode(e, t, r) {
6535
6549
  o,
6536
6550
  s,
6537
6551
  i = .75 * e.length,
6538
- y = 0;
6552
+ u = 0;
6539
6553
  "=" === e[e.length - 1] && (i--, "=" === e[e.length - 2] && i--);
6540
- for (var u = new ArrayBuffer(i, t), l = new Uint8Array(u), p = 0; p < r; p += 4) n = c[e.codePointAt(p)], a = c[e.codePointAt(p + 1)], o = c[e.codePointAt(p + 2)], s = c[e.codePointAt(p + 3)], l[y++] = n << 2 | a >> 4, l[y++] = (15 & a) << 4 | o >> 2, l[y++] = (3 & o) << 6 | 63 & s;
6541
- return u;
6554
+ for (var y = new ArrayBuffer(i, t), l = new Uint8Array(y), p = 0; p < r; p += 4) n = c[e.codePointAt(p)], a = c[e.codePointAt(p + 1)], o = c[e.codePointAt(p + 2)], s = c[e.codePointAt(p + 3)], l[u++] = n << 2 | a >> 4, l[u++] = (15 & a) << 4 | o >> 2, l[u++] = (3 & o) << 6 | 63 & s;
6555
+ return y;
6542
6556
  };
6543
6557
  const p = {
6544
6558
  arraybuffer: {
@@ -6549,7 +6563,7 @@ const p = {
6549
6563
  return -1 !== r ? {
6550
6564
  index: r
6551
6565
  } : (t.buffers.push(e), {
6552
- s: u(e),
6566
+ s: y(e),
6553
6567
  maxByteLength: e.maxByteLength,
6554
6568
  resizable: e.resizable
6555
6569
  });
@@ -6583,13 +6597,13 @@ const p = {
6583
6597
  });
6584
6598
  i.push(r), c += r;
6585
6599
  }
6586
- const y = new ArrayBuffer(c);
6587
- let u = 0;
6600
+ const u = new ArrayBuffer(c);
6601
+ let y = 0;
6588
6602
  for (let t = 0; t < s; t++) {
6589
- const r = new Uint8Array(y, u, i[t]);
6603
+ const r = new Uint8Array(u, y, i[t]);
6590
6604
  e.copyTo(r, {
6591
6605
  planeIndex: t
6592
- }), u += i[t];
6606
+ }), y += i[t];
6593
6607
  }
6594
6608
  return {
6595
6609
  format: t,
@@ -6597,7 +6611,7 @@ const p = {
6597
6611
  numberOfFrames: n,
6598
6612
  numberOfChannels: a,
6599
6613
  timestamp: o,
6600
- data: y
6614
+ data: u
6601
6615
  };
6602
6616
  },
6603
6617
  revive: ({
@@ -6713,7 +6727,7 @@ const v = {
6713
6727
  byteOffset: t,
6714
6728
  byteLength: r
6715
6729
  } : (n.buffers.push(e), {
6716
- encoded: u(e),
6730
+ encoded: y(e),
6717
6731
  maxByteLength: e.maxByteLength,
6718
6732
  resizable: e.resizable,
6719
6733
  byteOffset: t,
@@ -7199,7 +7213,7 @@ const X = {};
7199
7213
  } : (n.buffers.push(e), {
7200
7214
  maxByteLength: e.maxByteLength,
7201
7215
  resizable: e.resizable,
7202
- encoded: u(e),
7216
+ encoded: y(e),
7203
7217
  byteOffset: t,
7204
7218
  length: r
7205
7219
  });
@@ -7214,10 +7228,10 @@ const X = {};
7214
7228
  maxByteLength: i,
7215
7229
  resizable: c
7216
7230
  } = t;
7217
- let y;
7218
- return "index" in t ? y = r.buffers[s] : (y = l(o, c ? {
7231
+ let u;
7232
+ return "index" in t ? u = r.buffers[s] : (u = l(o, c ? {
7219
7233
  maxByteLength: i
7220
- } : void 0), r.buffers.push(y)), new e(y, n, a);
7234
+ } : void 0), r.buffers.push(u)), new e(u, n, a);
7221
7235
  }
7222
7236
  };
7223
7237
  }(e));
@@ -7250,8 +7264,8 @@ const Y = {
7250
7264
  duration: s,
7251
7265
  visibleRect: i,
7252
7266
  displayWidth: c,
7253
- displayHeight: y,
7254
- colorSpace: u
7267
+ displayHeight: u,
7268
+ colorSpace: y
7255
7269
  } = e,
7256
7270
  l = new ArrayBuffer(e.allocationSize());
7257
7271
  await e.copyTo(l), t({
@@ -7267,12 +7281,12 @@ const Y = {
7267
7281
  height: i.height
7268
7282
  },
7269
7283
  displayWidth: c,
7270
- displayHeight: y,
7284
+ displayHeight: u,
7271
7285
  colorSpace: {
7272
- primaries: u.primaries,
7273
- transfer: u.transfer,
7274
- matrix: u.matrix,
7275
- fullRange: u.fullRange
7286
+ primaries: y.primaries,
7287
+ transfer: y.transfer,
7288
+ matrix: y.matrix,
7289
+ fullRange: y.fullRange
7276
7290
  },
7277
7291
  data: l
7278
7292
  });
@@ -7290,8 +7304,8 @@ const Y = {
7290
7304
  displayWidth: s,
7291
7305
  displayHeight: i,
7292
7306
  colorSpace: c,
7293
- data: y
7294
- }) => new VideoFrame(new Uint8Array(y), {
7307
+ data: u
7308
+ }) => new VideoFrame(new Uint8Array(u), {
7295
7309
  format: e,
7296
7310
  codedWidth: t,
7297
7311
  codedHeight: r,
@@ -7345,11 +7359,11 @@ const Y = {
7345
7359
  }],
7346
7360
  ne = [D, M, L, F],
7347
7361
  ce = [Z, Y, re, K, ne, O, z, U, _, B, I, h, N, C].concat("function" == typeof Map ? k : [], "function" == typeof Set ? H : [], "function" == typeof ArrayBuffer ? p : [], "function" == typeof Uint8Array ? X : [], "function" == typeof DataView ? w : [], "undefined" != typeof crypto ? v : [], "undefined" != typeof BigInt ? [m, d] : [], "undefined" != typeof DOMException ? A : [], "undefined" != typeof QuotaExceededError ? W : [], "undefined" != typeof WebTransportError ? te : [], "undefined" != typeof DOMRect ? x : [], "undefined" != typeof DOMPoint ? S : [], "undefined" != typeof DOMQuad ? P : [], "undefined" != typeof DOMMatrix ? T : [], "undefined" != typeof AudioData ? f : [], "undefined" != typeof EncodedAudioChunk ? E : [], "undefined" != typeof EncodedVideoChunk ? j : [], "undefined" != typeof VideoFrame ? ee : []);
7348
- const ye = ce.concat({
7362
+ const ue = ce.concat({
7349
7363
  checkDataCloneException: {
7350
7364
  test(e) {
7351
7365
  const t = {}.toString.call(e).slice(8, -1);
7352
- if (["symbol", "function"].includes(typeof e) || ["Arguments", "Module", "Promise", "WeakMap", "WeakSet", "Event", "MessageChannel"].includes(t) || ["ArrayBuffer", "DataView", "Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array", "Int32Array", "Uint32Array", "Float32Array", "Float64Array", "BigInt64Array", "BigUint64Array"].includes(t) && function isBufferDetached(e) {
7366
+ if (["symbol", "function"].includes(typeof e) || ["Arguments", "Module", "Promise", "WeakMap", "WeakSet", "Event", "MessageChannel", "MessagePort"].includes(t) || e && e.constructor && ["MessageChannel", "MessagePort"].includes(e.constructor.name) || ["ArrayBuffer", "DataView", "Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array", "Int32Array", "Uint32Array", "Float32Array", "Float64Array", "BigInt64Array", "BigUint64Array"].includes(t) && function isBufferDetached(e) {
7353
7367
  if ("boolean" == typeof e.detached) return e.detached;
7354
7368
  if (0 !== e.byteLength) return false;
7355
7369
  try {
@@ -7365,7 +7379,7 @@ const ye = ce.concat({
7365
7379
 
7366
7380
  // See: https://stackoverflow.com/questions/42170826/categories-for-rejection-by-the-structured-cloning-algorithm
7367
7381
 
7368
- let typeson = new Typeson().register(ye);
7382
+ let typeson = new Typeson().register(ue);
7369
7383
 
7370
7384
  /**
7371
7385
  * @param {(preset: import('typeson-registry').Preset) =>
@@ -7374,7 +7388,7 @@ let typeson = new Typeson().register(ye);
7374
7388
  */
7375
7389
  function register(func) {
7376
7390
  // eslint-disable-next-line unicorn/no-top-level-assignment-in-function -- Should be one-time cache
7377
- typeson = new Typeson().register(func(ye));
7391
+ typeson = new Typeson().register(func(ue));
7378
7392
  }
7379
7393
 
7380
7394
  /**
@@ -8424,6 +8438,11 @@ const IDBObjectStoreAlias = IDBObjectStore;
8424
8438
  * success: (key: import('./Key.js').Key, cn?: Integer) => void,
8425
8439
  * failCb: import('./Key.js').SQLFailureCallback
8426
8440
  * ) => void,
8441
+ * __checkIndexConstraints: (
8442
+ * tx: WebSQLTransaction,
8443
+ * value: import('./Key.js').Value,
8444
+ * excludeKey: import('./Key.js').Key|Integer|undefined
8445
+ * ) => SyncPromise,
8427
8446
  * __insertData: (
8428
8447
  * tx: WebSQLTransaction,
8429
8448
  * encoded: string,
@@ -8844,6 +8863,69 @@ IDBObjectStore.prototype.__deriveKey = function (tx, value, key, success, failCb
8844
8863
  }
8845
8864
  };
8846
8865
 
8866
+ /**
8867
+ * Validates `value`'s unique index entries against the table's *current*
8868
+ * state without mutating anything -- used by `__storingRecordObjectStore`
8869
+ * to check a `put()`'s constraints *before* `__overwrite` deletes the
8870
+ * existing row for that key, so a rejected `put()` can't end up losing
8871
+ * the record it was trying to replace (see
8872
+ * `idbobjectstore-put-unique-index-constraint-is-atomic.any.js`).
8873
+ * `excludeKey`, when given, treats a conflicting record as a non-conflict
8874
+ * if it's the very record being overwritten (its own current entry would
8875
+ * otherwise self-conflict on every no-op `put()` of an unchanged value).
8876
+ * `__insertData` below still runs its own (now redundant, but harmless --
8877
+ * the old row is gone by then) equivalent check as its first step; this
8878
+ * isn't merged into it because `__insertData` also performs the actual
8879
+ * `INSERT`, which cannot safely run before `__overwrite`'s `DELETE` when
8880
+ * overwriting an existing key.
8881
+ * @param {WebSQLTransaction} tx
8882
+ * @param {import('./Key.js').Value} value
8883
+ * @param {import('./Key.js').Key|Integer|undefined} excludeKey
8884
+ * @this {IDBObjectStoreFull}
8885
+ * @returns {SyncPromise}
8886
+ */
8887
+ IDBObjectStore.prototype.__checkIndexConstraints = function (tx, value, excludeKey) {
8888
+ const me = this;
8889
+ const indexPromises = Object.keys(me.__indexes).map(indexName => {
8890
+ return new SyncPromise((resolve, reject) => {
8891
+ const index = me.__indexes[indexName];
8892
+ if (index.__pendingCreate || index.__deleted || !index.unique) {
8893
+ resolve(undefined);
8894
+ return;
8895
+ }
8896
+ /**
8897
+ * @type {import('./Key.js').KeyValueObject|
8898
+ * import('./Key.js').KeyPathEvaluateValue}
8899
+ */
8900
+ let indexKey;
8901
+ try {
8902
+ indexKey = extractKeyValueDecodedFromValueUsingKeyPath(value, index.keyPath, index.multiEntry);
8903
+ if ('invalid' in indexKey && indexKey.invalid || 'failure' in indexKey && indexKey.failure) {
8904
+ throw new Error('Go to catch');
8905
+ }
8906
+ } catch (err) {
8907
+ resolve(undefined);
8908
+ return;
8909
+ }
8910
+ indexKey = indexKey.value;
8911
+ if (indexKey === undefined) {
8912
+ resolve(undefined);
8913
+ return;
8914
+ }
8915
+ const multiCheck = index.multiEntry && Array.isArray(indexKey);
8916
+ const fetchArgs = buildFetchIndexDataSQL(true, index, indexKey, 'key', multiCheck);
8917
+ executeFetchIndexData(null, ...fetchArgs, tx, null, function success(key) {
8918
+ if (key === undefined || excludeKey !== undefined && cmp(key, excludeKey) === 0) {
8919
+ resolve(undefined);
8920
+ return;
8921
+ }
8922
+ reject(createDOMException('ConstraintError', 'Index already contains a record equal to ' + (multiCheck ? 'one of the subkeys of' : '') + '`indexKey`'));
8923
+ }, reject);
8924
+ });
8925
+ });
8926
+ return SyncPromise.all(indexPromises);
8927
+ };
8928
+
8847
8929
  /**
8848
8930
  *
8849
8931
  * @param {WebSQLTransaction} tx
@@ -9096,7 +9178,32 @@ IDBObjectStore.__storingRecordObjectStore = function (request, store, invalidate
9096
9178
  }, error);
9097
9179
  }
9098
9180
  if (!noOverwrite) {
9099
- store.__overwrite(tx, clonedKeyOrCurrentNumber, insert, error);
9181
+ // Validate unique index constraints *before* `__overwrite`
9182
+ // deletes the existing row at this key -- otherwise a
9183
+ // `put()` that ends up rejected still permanently loses
9184
+ // the record it was trying to replace (see
9185
+ // `idbobjectstore-put-unique-index-constraint-is-atomic.any.js`).
9186
+ store.__checkIndexConstraints(tx, value, clonedKeyOrCurrentNumber).then(() => {
9187
+ store.__overwrite(tx, clonedKeyOrCurrentNumber, insert, error);
9188
+ return undefined;
9189
+ /**
9190
+ * @param {Error|DOMException} err
9191
+ * @returns {null}
9192
+ */
9193
+ }).catch(function (err) {
9194
+ /**
9195
+ * @returns {void}
9196
+ */
9197
+ function fail() {
9198
+ error(err);
9199
+ }
9200
+ if (typeof oldCn === 'number') {
9201
+ assignCurrentNumber(tx, store, oldCn, fail, fail);
9202
+ return null;
9203
+ }
9204
+ fail();
9205
+ return null;
9206
+ });
9100
9207
  return;
9101
9208
  }
9102
9209
  insert(tx);
@@ -9970,7 +10077,29 @@ function triggerAnyVersionChangeAndBlockedEvents(openConnections, req, oldVersio
9970
10077
  return new SyncPromise(function (resolve) {
9971
10078
  setTimeout(() => {
9972
10079
  entry.dispatchEvent(e); // No need to catch errors
9973
- resolve(undefined);
10080
+ // Unlike a native `Promise`, `SyncPromise#then` chains
10081
+ // synchronously off `resolve()` (verified directly:
10082
+ // `SyncPromise.resolve().then(cb)` runs `cb` before
10083
+ // even the calling code below it finishes) -- so
10084
+ // resolving immediately here would let the
10085
+ // `connectionsClosed()` check below run before a
10086
+ // same-task-but-microtask-later continuation of a
10087
+ // `versionchange` listener above (e.g. an `await`-
10088
+ // based one that then calls `db.close()`, as in
10089
+ // `transaction-lifetime.any.js`) ever gets a turn,
10090
+ // incorrectly firing `blocked` even though the
10091
+ // connection was about to close in time. Give real
10092
+ // microtask-deferred continuations a small, bounded
10093
+ // number of turns first -- same pattern as
10094
+ // `IDBTransaction.js`'s `checkQueueEntry`.
10095
+ let attemptsLeft = 10;
10096
+ (function wait() {
10097
+ if (attemptsLeft-- <= 0) {
10098
+ resolve(undefined);
10099
+ return;
10100
+ }
10101
+ queueMicrotask(wait);
10102
+ })();
9974
10103
  }, 0);
9975
10104
  });
9976
10105
  });
@@ -10030,6 +10159,23 @@ function triggerAnyVersionChangeAndBlockedEvents(openConnections, req, oldVersio
10030
10159
  */
10031
10160
  const websqlDBCache = {};
10032
10161
 
10162
+ /**
10163
+ * Tracks databases with a creation/upgrade currently in flight but not yet
10164
+ * committed or aborted -- keyed by (unescaped) database name. The
10165
+ * `dbVersions` row in `sysdb` these entries shadow is written *before*
10166
+ * `upgradeneeded` is even dispatched to user code (as the success
10167
+ * callback of that very `INSERT`/`UPDATE`), on the same shared `sysdb`
10168
+ * connection `databases()` itself reads from -- so, absent this, a
10169
+ * `databases()` call made while an upgrade is still pending would see
10170
+ * the new row (or new version) immediately, rather than only once the
10171
+ * corresponding `versionchange` transaction has genuinely committed, as
10172
+ * required by `get-databases.any.js`'s "doesn't pick up changes that
10173
+ * haven't committed" test. `IDBFactory.prototype.databases` filters
10174
+ * its SQL results against this map.
10175
+ * @type {Map<string, {oldVersion: Integer, newVersion: Integer}>}
10176
+ */
10177
+ const pendingVersionChanges = new Map();
10178
+
10033
10179
  /** @type {import('websql-configurable/lib/websql/WebSQLDatabase.js').default} */
10034
10180
  let sysdb;
10035
10181
  let nameCounter = 0;
@@ -10332,6 +10478,19 @@ IDBFactory.prototype.open = function (name /* , version */) {
10332
10478
  if (calledDbCreateError) {
10333
10479
  return false;
10334
10480
  }
10481
+ // Defensive cleanup: `pendingVersionChanges.set(name, ...)` (see
10482
+ // below) runs *before* the `dbVersions` `INSERT`/`UPDATE` it
10483
+ // guards is even attempted, but only `versionSet`'s own
10484
+ // `on__beforecomplete`/`on__preabort` handlers ever clear it --
10485
+ // and `versionSet` never runs at all if that `INSERT`/`UPDATE`,
10486
+ // or an earlier step in this same `open()` flow, fails first.
10487
+ // Without this, such a failure would leave the entry orphaned
10488
+ // forever, silently corrupting `databases()` for any *other*,
10489
+ // unrelated later `open()` call that happens to reuse the same
10490
+ // database name (common in WPT tests, e.g. generic names like
10491
+ // "DB1"/"TestDatabase" reused across different test files in
10492
+ // the same process). A no-op if no entry was ever set.
10493
+ pendingVersionChanges.delete(name);
10335
10494
  const er = err ? webSQLErrback(err) : (/** @type {Error} */tx);
10336
10495
  calledDbCreateError = true;
10337
10496
  // Re: why bubbling here (and how cancelable is only really relevant for `window.onerror`) see: https://github.com/w3c/IndexedDB/issues/86
@@ -10443,22 +10602,43 @@ IDBFactory.prototype.open = function (name /* , version */) {
10443
10602
  // open/close the transaction's active-handler window itself.
10444
10603
  req.transaction.__handlerActive = true;
10445
10604
  req.dispatchEvent(e);
10446
- // Give any same-tick microtask scheduled from within the
10605
+ // Give any microtask-scheduled continuation of the
10447
10606
  // `upgradeneeded` handler (e.g. a plain
10448
- // `Promise.resolve().then(...)`) a chance to run -- and still
10449
- // observe the transaction as active -- before we deactivate it
10450
- // again, per https://github.com/w3c/IndexedDB/issues/87. Only
10451
- // the flag reset itself is deferred here -- unlike
10452
- // `IDBTransaction.js`'s `advanceAfterDispatch`, `finished()`
10453
- // (this transaction's own queue-advancement/completion signal)
10454
- // still runs synchronously, at exactly its previous timing: an
10455
- // earlier attempt at deferring `finished()` too raced against
10456
- // unrelated test setup that assumed a freshly deleted/created
10457
- // database's upgrade transaction had already fully completed by
10458
- // the time this function returns.
10459
- queueMicrotask(() => {
10460
- req.transaction.__handlerActive = false;
10461
- });
10607
+ // `Promise.resolve().then(...)`, or an `await`-based
10608
+ // continuation of a promise resolved from within the handler,
10609
+ // such as testharness.js's own `EventWatcher`) a chance to
10610
+ // run -- and still observe the transaction as active -- before
10611
+ // we deactivate it again, per
10612
+ // https://github.com/w3c/IndexedDB/issues/87. A single deferred
10613
+ // tick isn't always enough: an `await`-based continuation can
10614
+ // take more than one microtask turn to resume (e.g.
10615
+ // `transaction-lifetime.any.js`'s `EventWatcher`-based
10616
+ // `await eventWatcher.wait_for('upgradeneeded')`), so retry a
10617
+ // small, bounded number of times first -- same pattern as
10618
+ // `IDBTransaction.js`'s `checkQueueEntry` -- rather than
10619
+ // resetting after only one. Only the flag reset itself is
10620
+ // deferred here -- unlike `IDBTransaction.js`'s
10621
+ // `advanceAfterDispatch`, `finished()` (this transaction's own
10622
+ // queue-advancement/completion signal) still runs
10623
+ // synchronously, at exactly its previous timing: an earlier
10624
+ // attempt at deferring `finished()` too raced against unrelated
10625
+ // test setup that assumed a freshly deleted/created database's
10626
+ // upgrade transaction had already fully completed by the time
10627
+ // this function returns.
10628
+ /**
10629
+ * @param {Integer} attemptsLeft
10630
+ * @returns {void}
10631
+ */
10632
+ function deferHandlerActiveReset(attemptsLeft) {
10633
+ if (attemptsLeft <= 0) {
10634
+ req.transaction.__handlerActive = false;
10635
+ return;
10636
+ }
10637
+ queueMicrotask(() => {
10638
+ deferHandlerActiveReset(attemptsLeft - 1);
10639
+ });
10640
+ }
10641
+ deferHandlerActiveReset(10);
10462
10642
  if (e.__legacyOutputDidListenersThrowError) {
10463
10643
  logError('Error', 'An error occurred in an upgradeneeded handler attached to request chain', /** @type {Error} */e.__legacyOutputDidListenersThrowError); // We do nothing else with this error as per spec
10464
10644
  req.transaction.__abortTransaction(createDOMException('AbortError', 'A request was aborted.'));
@@ -10474,6 +10654,7 @@ IDBFactory.prototype.open = function (name /* , version */) {
10474
10654
  * @returns {void}
10475
10655
  */
10476
10656
  function (ev) {
10657
+ pendingVersionChanges.delete(name);
10477
10658
  connection.__upgradeTransaction = null;
10478
10659
  /** @type {import('./IDBDatabase.js').IDBDatabaseFull} */
10479
10660
  req.__result.__versionTransaction = null;
@@ -10488,6 +10669,7 @@ IDBFactory.prototype.open = function (name /* , version */) {
10488
10669
 
10489
10670
  // eslint-disable-next-line camelcase -- Clear API
10490
10671
  req.transaction.on__preabort = function () {
10672
+ pendingVersionChanges.delete(name);
10491
10673
  connection.__upgradeTransaction = null;
10492
10674
  // We ensure any cache is deleted before any request error events fire and try to reopen
10493
10675
  if (useDatabaseCache) {
@@ -10550,6 +10732,10 @@ IDBFactory.prototype.open = function (name /* , version */) {
10550
10732
  // });
10551
10733
  };
10552
10734
  }
10735
+ pendingVersionChanges.set(name, {
10736
+ oldVersion,
10737
+ newVersion: version
10738
+ });
10553
10739
  if (oldVersion === 0) {
10554
10740
  systx.executeSql('INSERT INTO dbVersions VALUES (?,?)', [sqlSafeName, version], versionSet, dbCreateError);
10555
10741
  } else {
@@ -10853,6 +11039,15 @@ IDBFactory.prototype.cmp = function (key1, key2) {
10853
11039
  IDBFactory.prototype.databases = function () {
10854
11040
  const me = this;
10855
11041
  let calledDbCreateError = false;
11042
+ // Snapshotted *now*, synchronously, at call time -- not read later
11043
+ // from inside the SQL query's callback below, which runs on a
11044
+ // deferred macrotask (see `nodeSQLiteDatabase.js`'s `exec`) and so
11045
+ // could otherwise race against (and lose to) an in-flight upgrade's
11046
+ // own commit/abort handler clearing its `pendingVersionChanges`
11047
+ // entry in the meantime -- which would make this method incorrectly
11048
+ // reflect a since-committed change that hadn't committed yet when
11049
+ // it was actually called.
11050
+ const pendingVersionChangesSnapshot = new Map(pendingVersionChanges);
10856
11051
  return new Promise(function (resolve, reject) {
10857
11052
  // eslint-disable-line promise/avoid-new -- Own polyfill
10858
11053
  if (!(me instanceof IDBFactory)) {
@@ -10882,11 +11077,30 @@ IDBFactory.prototype.databases = function () {
10882
11077
  const dbNames = [];
10883
11078
  for (let i = 0; i < data.rows.length; i++) {
10884
11079
  const {
10885
- name,
11080
+ name: encodedName,
10886
11081
  version
10887
11082
  } = /** @type {{name: string, version: Integer}} */data.rows.item(i);
11083
+ const name = unescapeSQLiteResponse(encodedName);
11084
+ // A row for a database whose creation/upgrade hasn't
11085
+ // committed yet (see `pendingVersionChanges`) must
11086
+ // not reflect that in-flight change: a brand new
11087
+ // database (`oldVersion === 0`) isn't reported at
11088
+ // all until its creation commits, and an existing
11089
+ // database being upgraded is still reported, but
11090
+ // with its pre-upgrade version.
11091
+ const pending = pendingVersionChangesSnapshot.get(name);
11092
+ if (pending) {
11093
+ if (pending.oldVersion === 0) {
11094
+ continue;
11095
+ }
11096
+ dbNames.push({
11097
+ name,
11098
+ version: pending.oldVersion
11099
+ });
11100
+ continue;
11101
+ }
10888
11102
  dbNames.push({
10889
- name: unescapeSQLiteResponse(name),
11103
+ name,
10890
11104
  version
10891
11105
  });
10892
11106
  }
@@ -11034,6 +11248,14 @@ readonlyProperties.forEach(prop => {
11034
11248
  * @returns {import('./Key.js').Key|import('./Key.js').Value}
11035
11249
  */
11036
11250
  get [prop]() {
11251
+ // This is defined on the *shared* prototype (see above), so,
11252
+ // unlike a per-instance getter, it's reachable by calling it
11253
+ // directly on `IDBRecord.prototype` itself (not a real
11254
+ // instance) -- WebIDL requires that to throw.
11255
+ if (!(this instanceof IDBRecordAlias)) {
11256
+ throw new TypeError('Illegal invocation');
11257
+ }
11258
+ // @ts-ignore `this` is a real instance past the check above (not an issue in TS7)
11037
11259
  return this['__' + prop];
11038
11260
  }
11039
11261
  };
@@ -11291,9 +11513,26 @@ IDBCursor.prototype.__findBasic = function (key, primaryKey, tx, success, error,
11291
11513
  // Key.convertValueToKey(key); // Already checked by `continue` or `continuePrimaryKey`
11292
11514
  sqlValues.push(/** @type {string} */encode$1(key));
11293
11515
  } else if (continueCall && me.__key !== undefined) {
11294
- sql.push('AND', quotedKeyColumnName, op + ' ?');
11295
11516
  // Key.convertValueToKey(me.__key); // Already checked when stored
11296
- sqlValues.push(/** @type {string} */encode$1(me.__key));
11517
+ if (!me.__unique && me.__keyColumnName !== 'key' && me.__primaryKey !== undefined) {
11518
+ // A plain `continue()` on a non-unique index cursor must find
11519
+ // the next record strictly after the (key, primaryKey) pair
11520
+ // this cursor last returned -- a scalar `key > lastKey` alone
11521
+ // would wrongly exclude a *different*, not-yet-visited record
11522
+ // that's still tied on key with the last one (e.g. another
11523
+ // record that always had that same indexed value), and would
11524
+ // also wrongly re-admit the *same* record forever if a
11525
+ // same-transaction `update()` bumped its own key back above
11526
+ // the threshold, since a scalar comparison can't distinguish
11527
+ // "some other record newly tied" from "this record moved
11528
+ // past its own last position." Comparing the full tuple (via
11529
+ // this OR) against both key and primary key resolves both.
11530
+ sql.push('AND (', quotedKeyColumnName, op, '?', 'OR (', quotedKeyColumnName, '= ?', 'AND', quotedKey, op, '?))');
11531
+ sqlValues.push(/** @type {string} */encode$1(me.__key), /** @type {string} */encode$1(me.__key), /** @type {string} */encode$1(me.__primaryKey));
11532
+ } else {
11533
+ sql.push('AND', quotedKeyColumnName, op + ' ?');
11534
+ sqlValues.push(/** @type {string} */encode$1(me.__key));
11535
+ }
11297
11536
  }
11298
11537
  if (!me.__count) {
11299
11538
  // 1. Sort by key
@@ -11907,9 +12146,15 @@ IDBCursor.prototype.update = function (valueToUpdate) {
11907
12146
  * @returns {void}
11908
12147
  */
11909
12148
  function addToQueue(clonedValue) {
11910
- // We set the `invalidateCache` argument to `false` since the old value shouldn't be accessed
12149
+ // `invalidateCache: true` so any cursor on this store (including
12150
+ // this one) drops its prefetched row batch, forcing its next
12151
+ // `continue()` to re-query live rather than serve rows snapshotted
12152
+ // before this update -- needed for the compound-tuple
12153
+ // continuation logic in `__findBasic` to see this update's
12154
+ // effect on ordering (see "Modify records during cursor
12155
+ // iteration" in idbcursor_update_index.any.js).
11911
12156
  // @ts-ignore -- API (not erring in TS 6)
11912
- IDBObjectStore.__storingRecordObjectStore(request, me.__store, false, clonedValue, false, key);
12157
+ IDBObjectStore.__storingRecordObjectStore(request, me.__store, true, clonedValue, false, key);
11913
12158
  }
11914
12159
  if (me.__store.keyPath !== null) {
11915
12160
  const [evaluatedKey, clonedValue] = me.__store.__validateKeyAndValueAndCloneValue(valueToUpdate, undefined, true);
@@ -13031,11 +13276,9 @@ function runBatch(self, batch) {
13031
13276
  self._running = false;
13032
13277
  runAllSql(self);
13033
13278
  }
13034
- const currentTask = /** @type {import('./WebSQLDatabase.js').TransactionTask} */
13035
- self._websqlDatabase._currentTask;
13036
13279
  const {
13037
13280
  readOnly
13038
- } = currentTask;
13281
+ } = self._task;
13039
13282
  self._websqlDatabase._db.exec(batch, readOnly, function (err, results) {
13040
13283
  /* c8 ignore next */
13041
13284
  if (err || !results) {
@@ -13123,10 +13366,12 @@ function executeSql(self, sql, args, sqlCallback, sqlErrorCallback, executeDelay
13123
13366
  class WebSQLTransaction {
13124
13367
  /**
13125
13368
  * @param {import('./WebSQLDatabase.js').default} websqlDatabase
13369
+ * @param {import('./WebSQLDatabase.js').TransactionTask} task
13126
13370
  * @param {import('../types.js').Delay} [executeDelay]
13127
13371
  */
13128
- constructor(websqlDatabase, executeDelay) {
13372
+ constructor(websqlDatabase, task, executeDelay) {
13129
13373
  this._websqlDatabase = websqlDatabase;
13374
+ this._task = task;
13130
13375
  /** @type {Error | null} */
13131
13376
  this._error = null;
13132
13377
  this._complete = false;
@@ -13135,11 +13380,14 @@ class WebSQLTransaction {
13135
13380
  this._executeDelay = executeDelay || immediate;
13136
13381
  /** @type {import('tiny-queue').default<SQLTask>} */
13137
13382
  this._sqlQueue = new Queue();
13138
- const currentTask = /** @type {import('./WebSQLDatabase.js').TransactionTask} */
13139
- websqlDatabase._currentTask;
13140
- if (!currentTask.readOnly) {
13141
- // Since we serialize all access to the database, there is no need to
13142
- // run read-only tasks in a transaction. This is a perf boost.
13383
+ if (!task.readOnly) {
13384
+ // A read-only task never needs a transaction wrapper for its own
13385
+ // sake (reads don't need atomicity against each other) -- true
13386
+ // whether or not `concurrentReaders` (see `WebSQLDatabase`) lets
13387
+ // multiple read-only tasks actually run at once. This is a perf
13388
+ // boost either way. A non-read-only task always still gets one, and
13389
+ // always still runs with full exclusivity (see
13390
+ // `WebSQLDatabase#runNextTransaction`).
13143
13391
  this._sqlQueue.push(new SQLTask('BEGIN;', [], noop, noop));
13144
13392
  }
13145
13393
  }
@@ -13220,39 +13468,68 @@ class WebSQLDatabase {
13220
13468
  this._db = db;
13221
13469
  /** @type {import('tiny-queue').default<TransactionTask>} */
13222
13470
  this._txnQueue = new Queue();
13223
- this._running = false;
13471
+ // Off by default: queued transactions -- read or write alike -- run
13472
+ // strictly one at a time, in the order requested, per the WebSQL spec
13473
+ // (see `#runNextTransaction`). When true, any number of read-only
13474
+ // tasks may instead run concurrently; a non-read-only task still
13475
+ // always runs with full exclusivity either way.
13476
+ this._concurrentReaders = Boolean(webSQLOverrides.concurrentReaders);
13477
+ /** @type {Set<TransactionTask>} */
13478
+ this._activeReaders = new Set();
13224
13479
  /** @type {TransactionTask | null} */
13225
- this._currentTask = null;
13480
+ this._activeWriter = null;
13226
13481
  this._transactionDelay = webSQLOverrides.transactionDelay || immediate;
13227
13482
  this._executeDelay = webSQLOverrides.executeDelay || immediate;
13228
13483
  }
13229
13484
 
13230
13485
  /**
13231
- *
13486
+ * @param {TransactionTask} task
13232
13487
  */
13233
- #runTransaction() {
13234
- const txn = new WebSQLTransaction(this, this._executeDelay);
13488
+ #runTransaction(task) {
13489
+ const txn = new WebSQLTransaction(this, task, this._executeDelay);
13235
13490
  this._transactionDelay(() => {
13236
- const currentTask = /** @type {TransactionTask} */this._currentTask;
13237
- currentTask.txnCallback(txn);
13491
+ task.txnCallback(txn);
13238
13492
  txn._checkDone();
13239
13493
  });
13240
13494
  }
13241
13495
 
13242
13496
  /**
13243
- *
13497
+ * Starts as many queued tasks as the current lock state allows. With
13498
+ * `concurrentReaders` off (the default), a task only starts once
13499
+ * nothing else is active at all -- full mutual exclusion, matching the
13500
+ * WebSQL spec's strict one-at-a-time, in-request-order guarantee (see
13501
+ * this file's own test suite, "callback order 2"). With it on, any
13502
+ * leading run of read-only tasks can all start together instead
13503
+ * (concurrent reads are always fine); a non-read-only task still needs
13504
+ * exclusivity regardless, so nothing past it may start until it
13505
+ * finishes. Not starvation-proof in the `concurrentReaders` case -- a
13506
+ * steady stream of arriving readers could in principle keep a waiting
13507
+ * writer waiting indefinitely -- but that's an acceptable tradeoff over
13508
+ * the added complexity of tracking arrival order across reader/writer
13509
+ * kinds.
13244
13510
  */
13245
13511
  #runNextTransaction() {
13246
- if (this._running) {
13247
- return;
13248
- }
13249
- const task = this._txnQueue.shift();
13250
- if (!task) {
13251
- return;
13512
+ for (;;) {
13513
+ const [nextTask] = this._txnQueue.slice(0, 1);
13514
+ if (!nextTask) {
13515
+ return;
13516
+ }
13517
+ const readerMayShare = this._concurrentReaders && nextTask.readOnly;
13518
+ if (readerMayShare) {
13519
+ if (this._activeWriter) {
13520
+ return;
13521
+ }
13522
+ } else if (this._activeWriter || this._activeReaders.size) {
13523
+ return;
13524
+ }
13525
+ this._txnQueue.shift();
13526
+ if (nextTask.readOnly) {
13527
+ this._activeReaders.add(nextTask);
13528
+ } else {
13529
+ this._activeWriter = nextTask;
13530
+ }
13531
+ this.#runTransaction(nextTask);
13252
13532
  }
13253
- this._currentTask = task;
13254
- this._running = true;
13255
- this.#runTransaction();
13256
13533
  }
13257
13534
 
13258
13535
  /**
@@ -13280,6 +13557,7 @@ class WebSQLDatabase {
13280
13557
  */
13281
13558
  // eslint-disable-next-line unicorn/prefer-private-class-fields -- see above
13282
13559
  _onTransactionComplete(err, transaction) {
13560
+ const task = transaction._task;
13283
13561
  /**
13284
13562
  * @param {Error | boolean | null} [er]
13285
13563
  */
@@ -13294,14 +13572,15 @@ class WebSQLDatabase {
13294
13572
  transaction._complete = true;
13295
13573
  }
13296
13574
  if (er) {
13297
- if (this._currentTask) {
13298
- this._currentTask.errorCallback(/** @type {Error} */er);
13299
- }
13300
- } else if (this._currentTask) {
13301
- this._currentTask.successCallback();
13575
+ task.errorCallback(/** @type {Error} */er);
13576
+ } else {
13577
+ task.successCallback();
13578
+ }
13579
+ if (task.readOnly) {
13580
+ this._activeReaders.delete(task);
13581
+ } else if (this._activeWriter === task) {
13582
+ this._activeWriter = null;
13302
13583
  }
13303
- this._running = false;
13304
- this._currentTask = null;
13305
13584
  this.#runNextTransaction();
13306
13585
  };
13307
13586
  /**
@@ -13327,13 +13606,13 @@ class WebSQLDatabase {
13327
13606
  }
13328
13607
  });
13329
13608
  };
13330
- if (this._currentTask && this._currentTask.nonstandardTransCb) {
13331
- const cont = this._currentTask.nonstandardTransCb.call(this, this._currentTask, err, done, rollback, commit);
13609
+ if (task.nonstandardTransCb) {
13610
+ const cont = task.nonstandardTransCb.call(this, task, err, done, rollback, commit);
13332
13611
  if (!cont) {
13333
13612
  return;
13334
13613
  }
13335
13614
  }
13336
- if (this._currentTask && this._currentTask.readOnly) {
13615
+ if (task.readOnly) {
13337
13616
  done(err); // read-only doesn't require a transaction
13338
13617
  } else if (err) {
13339
13618
  rollback(err);
@@ -14291,16 +14570,28 @@ const READ_ONLY_ERROR = new Error('could not prepare statement (23 not authorize
14291
14570
  // connection and a newly-opened one during an upgrade. `PRAGMA
14292
14571
  // busy_timeout` can't safely arbitrate between them here: it blocks the
14293
14572
  // single JS thread synchronously while it retries, but the lock holder's
14294
- // own release is itself scheduled via `setTimeout` below, which can never
14295
- // fire while that retry loop is blocking the same thread -- so instead of
14296
- // waiting, the second connection's write just fails with "database is
14297
- // locked". Serialize writes per file path instead, so a second
14298
- // connection's `BEGIN` waits for the first connection's transaction to
14299
- // actually finish rather than colliding with it.
14573
+ // own release is itself scheduled via `setImmediate` below, which can
14574
+ // never fire while that retry loop is blocking the same thread -- so
14575
+ // instead of waiting, a second connection's write just fails with
14576
+ // "database is locked".
14577
+ //
14578
+ // `fileReaders`/`fileWriter` implement a simple (not starvation-proof --
14579
+ // see the release loop below) reader/writer lock per file path instead:
14580
+ // any number of `readonly` transactions may hold the file concurrently,
14581
+ // matching what SQLite itself already natively supports (multiple
14582
+ // readers never need to exclude each other, only a writer needs
14583
+ // exclusivity), while a non-`readonly` transaction still waits for
14584
+ // exclusive access -- no concurrent readers, no concurrent writer. A
14585
+ // simple single-owner mutex (this file's prior implementation) would
14586
+ // otherwise serialize even purely-concurrent-reader scenarios that
14587
+ // never touch a writer at all, for no reason `better-sqlite3`/SQLite
14588
+ // itself requires.
14589
+ /** @type {Map<string, Set<{_db: any, _qFilePath: string}>>} */
14590
+ const fileReaders = new Map();
14300
14591
  /** @type {Map<string, {_db: any, _qFilePath: string}>} */
14301
- const fileLockOwners = new Map();
14302
- /** @type {Map<string, (() => void)[]>} */
14303
- const fileLockWaiters = new Map();
14592
+ const fileWriter = new Map();
14593
+ /** @type {Map<string, {isReader: boolean, resume: () => void}[]>} */
14594
+ const fileWaiters = new Map();
14304
14595
  const beginRe = /^\s*BEGIN\b/iu;
14305
14596
  const endRe = /^\s*(END|COMMIT|ROLLBACK)\b/iu;
14306
14597
 
@@ -14412,14 +14703,29 @@ SQLiteDatabase.prototype.exec = function exec(queries, readOnly, callback) {
14412
14703
  // already fully independent -- so there is nothing to serialize.
14413
14704
  const filePath = this._qFilePath === ':memory:' ? null : this._qFilePath;
14414
14705
  if (filePath && queries[0] && beginRe.test(queries[0].sql)) {
14415
- const owner = fileLockOwners.get(filePath);
14416
- if (owner && owner !== this) {
14417
- const waiters = fileLockWaiters.get(filePath) || [];
14418
- waiters.push(() => this.exec(queries, readOnly, callback));
14419
- fileLockWaiters.set(filePath, waiters);
14706
+ const activeReaders = fileReaders.get(filePath);
14707
+ const hasActiveReaders = Boolean(activeReaders && activeReaders.size);
14708
+ const activeWriter = fileWriter.get(filePath);
14709
+ const blockedByWriter = Boolean(activeWriter && activeWriter !== this);
14710
+ const blocked = blockedByWriter || !readOnly && hasActiveReaders;
14711
+ if (blocked) {
14712
+ const waiters = fileWaiters.get(filePath) || [];
14713
+ waiters.push({
14714
+ isReader: readOnly,
14715
+ resume: () => this.exec(queries, readOnly, callback)
14716
+ });
14717
+ fileWaiters.set(filePath, waiters);
14420
14718
  return;
14421
14719
  }
14422
- fileLockOwners.set(filePath, this);
14720
+ if (readOnly) {
14721
+ if (activeReaders) {
14722
+ activeReaders.add(this);
14723
+ } else {
14724
+ fileReaders.set(filePath, new Set([this]));
14725
+ }
14726
+ } else {
14727
+ fileWriter.set(filePath, this);
14728
+ }
14423
14729
  }
14424
14730
  const db = this._db._db;
14425
14731
  const len = queries.length;
@@ -14461,13 +14767,15 @@ SQLiteDatabase.prototype.exec = function exec(queries, readOnly, callback) {
14461
14767
  }
14462
14768
  }
14463
14769
  }
14464
- // A real timer (not `queueMicrotask`) so this yields to the macrotask
14465
- // queue: code that synchronously issues a new request from within
14466
- // each request's callback (e.g. to keep a transaction alive) would
14770
+ // A real macrotask (not `queueMicrotask`) so this yields properly:
14771
+ // code that synchronously issues a new request from within each
14772
+ // request's callback (e.g. to keep a transaction alive) would
14467
14773
  // otherwise chain microtask to microtask forever, starving out any
14468
14774
  // `setTimeout`-based code (including IndexedDB's own internal request
14469
- // scheduling) that never gets a turn to run.
14470
- setTimeout(() => {
14775
+ // scheduling) that never gets a turn to run. `setImmediate` (Node's
14776
+ // "check" phase) still yields the same way `setTimeout(..., 0)` did,
14777
+ // but runs sooner in Node's event loop.
14778
+ setImmediate(() => {
14471
14779
  // Release the file lock (if held) and hand it to the next waiting
14472
14780
  // connection, if any, only once this transaction has genuinely
14473
14781
  // finished -- and only here, on its own turn, so a resumed
@@ -14482,15 +14790,52 @@ SQLiteDatabase.prototype.exec = function exec(queries, readOnly, callback) {
14482
14790
  // and never see the matching release, deadlocking every later
14483
14791
  // connection to the same file.
14484
14792
  if (filePath && queries.some(q => endRe.test(q.sql))) {
14485
- fileLockOwners.delete(filePath);
14486
- const waiters = fileLockWaiters.get(filePath);
14487
- const nextWaitingExec = waiters && waiters.shift();
14488
- if (nextWaitingExec) {
14489
- nextWaitingExec();
14793
+ const activeReaders = fileReaders.get(filePath);
14794
+ if (activeReaders) {
14795
+ activeReaders.delete(this);
14796
+ if (!activeReaders.size) {
14797
+ fileReaders.delete(filePath);
14798
+ }
14799
+ }
14800
+ if (fileWriter.get(filePath) === this) {
14801
+ fileWriter.delete(filePath);
14802
+ }
14803
+ // Resume as many waiters as the lock now genuinely allows:
14804
+ // any leading run of readers (concurrent reads are always
14805
+ // fine), then at most one writer, since a writer needs
14806
+ // exclusivity -- stop there rather than looking past it.
14807
+ // Not starvation-proof: a steady stream of arriving readers
14808
+ // could in principle keep a waiting writer waiting
14809
+ // indefinitely, but that's an acceptable tradeoff here over
14810
+ // the added complexity of tracking arrival order across
14811
+ // reader/writer kinds.
14812
+ const waiters = fileWaiters.get(filePath);
14813
+ if (waiters) {
14814
+ while (waiters.length) {
14815
+ if (fileWriter.has(filePath)) {
14816
+ break;
14817
+ }
14818
+ const next = waiters[0];
14819
+ const stillHasReaders = fileReaders.get(filePath);
14820
+ if (!next.isReader && stillHasReaders && stillHasReaders.size) {
14821
+ break;
14822
+ }
14823
+ waiters.shift();
14824
+ next.resume();
14825
+ if (!next.isReader) {
14826
+ // A writer just took exclusive ownership (inside
14827
+ // `resume()`, synchronously, via the acquire
14828
+ // logic above) -- nothing else may proceed now.
14829
+ break;
14830
+ }
14831
+ }
14832
+ if (!waiters.length) {
14833
+ fileWaiters.delete(filePath);
14834
+ }
14490
14835
  }
14491
14836
  }
14492
14837
  callback(null, results);
14493
- }, 0);
14838
+ });
14494
14839
  };
14495
14840
 
14496
14841
  /**
@@ -14530,7 +14875,19 @@ function wrappedSQLiteDatabase(name) {
14530
14875
  }
14531
14876
  return db;
14532
14877
  }
14533
- const nodeWebSQL = customOpenDatabase(/** @type {SQLiteDatabaseConstructor} */ /** @type {unknown} */wrappedSQLiteDatabase, {});
14878
+
14879
+ // `concurrentReaders` is off by default in `websql-configurable` itself (to
14880
+ // preserve the WebSQL spec's strict, one-at-a-time transaction ordering
14881
+ // that library's own test suite depends on), but IndexedDBShim only ever
14882
+ // uses it as an internal SQL execution engine -- it doesn't need or expose
14883
+ // that ordering guarantee itself -- so it's safe, and needed, to opt in
14884
+ // here: without it, two same-scope `readonly` IDBTransactions can deadlock
14885
+ // waiting on each other (see `transaction-scheduling-within-database.any.js`).
14886
+ const nodeWebSQL = customOpenDatabase(/** @type {SQLiteDatabaseConstructor} */ /** @type {unknown} */wrappedSQLiteDatabase, {
14887
+ websql: {
14888
+ concurrentReaders: true
14889
+ }
14890
+ });
14534
14891
 
14535
14892
  // ID_Start (includes Other_ID_Start)
14536
14893
  const UnicodeIDStart = String.raw`(?:[$A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309B-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AE\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDF00-\uDF19]|\uD806[\uDCA0-\uDCDF\uDCFF\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F\uDFE0]|\uD821[\uDC00-\uDFEC]|\uD822[\uDC00-\uDEF2]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1]|\uD87E[\uDC00-\uDE1D])`;