dexie-cloud-addon 4.2.0 → 4.2.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.
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * ==========================================================================
10
10
  *
11
- * Version 4.2.0, Wed Aug 13 2025
11
+ * Version 4.2.2, Sat Oct 04 2025
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -409,17 +409,14 @@
409
409
  : mutationTable;
410
410
  if (limit < Infinity)
411
411
  query = query.limit(limit);
412
- const muts = yield query.toArray();
413
- //const objTable = db.table(tableName);
414
- /*for (const mut of muts) {
415
- if (mut.type === "insert" || mut.type === "upsert") {
416
- mut.values = await objTable.bulkGet(mut.keys);
417
- }
418
- }*/
419
- return muts.map((mut) => ({
412
+ let muts = yield query.toArray();
413
+ muts = canonicalizeToUpdateOps(muts);
414
+ muts = removeRedundantUpdateOps(muts);
415
+ const rv = muts.map((mut) => ({
420
416
  table: tableName,
421
417
  mut,
422
418
  }));
419
+ return rv;
423
420
  })));
424
421
  // Sort by time to get a true order of the operations (between tables)
425
422
  const sorted = flatten(allMutsOnTables).sort((a, b) => a.mut.txid === b.mut.txid
@@ -448,6 +445,69 @@
448
445
  return result;
449
446
  });
450
447
  }
448
+ function removeRedundantUpdateOps(muts) {
449
+ const updateCoverage = new Map();
450
+ for (const mut of muts) {
451
+ if (mut.type === 'update') {
452
+ if (mut.keys.length !== 1 || mut.changeSpecs.length !== 1) {
453
+ continue; // Don't optimize multi-key updates
454
+ }
455
+ const strKey = '' + mut.keys[0];
456
+ const changeSpecs = mut.changeSpecs[0];
457
+ if (Object.values(changeSpecs).some(v => typeof v === "object" && v && "@@propmod" in v)) {
458
+ continue; // Cannot optimize if any PropModification is present
459
+ }
460
+ let keyCoverage = updateCoverage.get(strKey);
461
+ if (keyCoverage) {
462
+ keyCoverage.push({ txid: mut.txid, updateSpec: changeSpecs });
463
+ }
464
+ else {
465
+ updateCoverage.set(strKey, [{ txid: mut.txid, updateSpec: changeSpecs }]);
466
+ }
467
+ }
468
+ }
469
+ muts = muts.filter(mut => {
470
+ // Only apply optimization to update mutations that are single-key
471
+ if (mut.type !== 'update')
472
+ return true;
473
+ if (mut.keys.length !== 1 || mut.changeSpecs.length !== 1)
474
+ return true;
475
+ // Keep track of properties that aren't overlapped by later transactions
476
+ const unoverlappedProps = new Set(Object.keys(mut.changeSpecs[0]));
477
+ const strKey = '' + mut.keys[0];
478
+ const keyCoverage = updateCoverage.get(strKey);
479
+ for (let i = keyCoverage.length - 1; i >= 0; --i) {
480
+ const { txid, updateSpec } = keyCoverage[i];
481
+ if (txid === mut.txid)
482
+ break; // Stop when reaching own txid
483
+ // If all changes in updateSpec are covered by all props on all mut.changeSpecs then
484
+ // txid is redundant and can be removed.
485
+ for (const keyPath of Object.keys(updateSpec)) {
486
+ unoverlappedProps.delete(keyPath);
487
+ }
488
+ }
489
+ if (unoverlappedProps.size === 0) {
490
+ // This operation is completely overlapped by later operations. It can be removed.
491
+ return false;
492
+ }
493
+ return true;
494
+ });
495
+ return muts;
496
+ }
497
+ function canonicalizeToUpdateOps(muts) {
498
+ muts = muts.map(mut => {
499
+ if (mut.type === 'modify' && mut.criteria.index === null) {
500
+ // The criteria is on primary key. Convert to an update operation instead.
501
+ // It is simpler for the server to handle and also more efficient.
502
+ const updateMut = Object.assign(Object.assign({}, mut), { criteria: undefined, changeSpec: undefined, type: 'update', keys: mut.keys, changeSpecs: [mut.changeSpec] });
503
+ delete updateMut.criteria;
504
+ delete updateMut.changeSpec;
505
+ return updateMut;
506
+ }
507
+ return mut;
508
+ });
509
+ return muts;
510
+ }
451
511
 
452
512
  function randomString$1(bytes) {
453
513
  const buf = new Uint8Array(bytes);
@@ -13889,7 +13949,7 @@
13889
13949
  *
13890
13950
  * ==========================================================================
13891
13951
  *
13892
- * Version 4.2.0, Wed Aug 13 2025
13952
+ * Version 4.2.1, Sat Oct 04 2025
13893
13953
  *
13894
13954
  * https://dexie.org
13895
13955
  *
@@ -16005,6 +16065,7 @@
16005
16065
  let values = 'values' in req ? req.values : [];
16006
16066
  let changeSpec = 'changeSpec' in req ? req.changeSpec : undefined;
16007
16067
  let updates = 'updates' in req ? req.updates : undefined;
16068
+ let upsert = updates && 'upsert' in req ? req.upsert : false;
16008
16069
  if (hasFailures) {
16009
16070
  keys = keys.filter((_, idx) => !failures[idx]);
16010
16071
  values = values.filter((_, idx) => !failures[idx]);
@@ -16036,29 +16097,32 @@
16036
16097
  };
16037
16098
  const validKeys = new Dexie.RangeSet();
16038
16099
  let anyChangeSpecBecameEmpty = false;
16039
- for (let i = 0, l = strippedChangeSpecs.length; i < l; ++i) {
16040
- if (Object.keys(strippedChangeSpecs[i]).length > 0) {
16041
- newUpdates.keys.push(updates.keys[i]);
16042
- newUpdates.changeSpecs.push(strippedChangeSpecs[i]);
16043
- validKeys.addKey(updates.keys[i]);
16044
- }
16045
- else {
16046
- anyChangeSpecBecameEmpty = true;
16100
+ if (!upsert) {
16101
+ for (let i = 0, l = strippedChangeSpecs.length; i < l; ++i) {
16102
+ if (Object.keys(strippedChangeSpecs[i]).length > 0) {
16103
+ newUpdates.keys.push(updates.keys[i]);
16104
+ newUpdates.changeSpecs.push(strippedChangeSpecs[i]);
16105
+ validKeys.addKey(updates.keys[i]);
16106
+ }
16107
+ else {
16108
+ anyChangeSpecBecameEmpty = true;
16109
+ }
16047
16110
  }
16048
- }
16049
- updates = newUpdates;
16050
- if (anyChangeSpecBecameEmpty) {
16051
- // Some keys were stripped. We must also strip them from keys and values
16052
- let newKeys = [];
16053
- let newValues = [];
16054
- for (let i = 0, l = keys.length; i < l; ++i) {
16055
- if (validKeys.hasKey(keys[i])) {
16056
- newKeys.push(keys[i]);
16057
- newValues.push(values[i]);
16111
+ updates = newUpdates;
16112
+ if (anyChangeSpecBecameEmpty) {
16113
+ // Some keys were stripped. We must also strip them from keys and values
16114
+ // unless this is an upsert operation in which case we want to send them all.
16115
+ let newKeys = [];
16116
+ let newValues = [];
16117
+ for (let i = 0, l = keys.length; i < l; ++i) {
16118
+ if (validKeys.hasKey(keys[i])) {
16119
+ newKeys.push(keys[i]);
16120
+ newValues.push(values[i]);
16121
+ }
16058
16122
  }
16123
+ keys = newKeys;
16124
+ values = newValues;
16059
16125
  }
16060
- keys = newKeys;
16061
- values = newValues;
16062
16126
  }
16063
16127
  }
16064
16128
  }
@@ -16100,49 +16164,59 @@
16100
16164
  userId,
16101
16165
  values,
16102
16166
  }
16103
- : criteria && changeSpec
16104
- ? {
16105
- // Common changeSpec for all keys
16106
- type: 'modify',
16107
- ts,
16108
- opNo,
16109
- keys,
16110
- criteria,
16111
- changeSpec,
16112
- txid,
16113
- userId,
16114
- }
16115
- : changeSpec
16167
+ : upsert ? {
16168
+ type: 'upsert',
16169
+ ts,
16170
+ opNo,
16171
+ keys,
16172
+ values,
16173
+ changeSpecs: updates.changeSpecs.filter((_, idx) => !failures[idx]),
16174
+ txid,
16175
+ userId,
16176
+ }
16177
+ : criteria && changeSpec
16116
16178
  ? {
16117
- // In case criteria involved an unsynced property, we go for keys instead.
16118
- type: 'update',
16179
+ // Common changeSpec for all keys
16180
+ type: 'modify',
16119
16181
  ts,
16120
16182
  opNo,
16121
16183
  keys,
16122
- changeSpecs: keys.map(() => changeSpec),
16184
+ criteria,
16185
+ changeSpec,
16123
16186
  txid,
16124
16187
  userId,
16125
16188
  }
16126
- : updates
16189
+ : changeSpec
16127
16190
  ? {
16128
- // One changeSpec per key
16191
+ // In case criteria involved an unsynced property, we go for keys instead.
16129
16192
  type: 'update',
16130
16193
  ts,
16131
16194
  opNo,
16132
- keys: updates.keys,
16133
- changeSpecs: updates.changeSpecs,
16134
- txid,
16135
- userId,
16136
- }
16137
- : {
16138
- type: 'upsert',
16139
- ts,
16140
- opNo,
16141
16195
  keys,
16142
- values,
16196
+ changeSpecs: keys.map(() => changeSpec),
16143
16197
  txid,
16144
16198
  userId,
16145
- };
16199
+ }
16200
+ : updates
16201
+ ? {
16202
+ // One changeSpec per key
16203
+ type: 'update',
16204
+ ts,
16205
+ opNo,
16206
+ keys: updates.keys,
16207
+ changeSpecs: updates.changeSpecs,
16208
+ txid,
16209
+ userId,
16210
+ }
16211
+ : {
16212
+ type: 'upsert',
16213
+ ts,
16214
+ opNo,
16215
+ keys,
16216
+ values,
16217
+ txid,
16218
+ userId,
16219
+ };
16146
16220
  if ('isAdditionalChunk' in req && req.isAdditionalChunk) {
16147
16221
  mut.isAdditionalChunk = true;
16148
16222
  }
@@ -17299,34 +17373,85 @@
17299
17373
  alignItems: "center",
17300
17374
  display: "flex",
17301
17375
  justifyContent: "center",
17376
+ padding: "16px",
17377
+ boxSizing: "border-box"
17302
17378
  },
17303
17379
  DialogInner: {
17304
17380
  position: "relative",
17305
17381
  color: "#222",
17306
17382
  backgroundColor: "#fff",
17307
- padding: "30px",
17383
+ padding: "24px",
17308
17384
  marginBottom: "2em",
17309
- maxWidth: "90%",
17385
+ maxWidth: "400px",
17386
+ width: "100%",
17310
17387
  maxHeight: "90%",
17311
17388
  overflowY: "auto",
17312
17389
  border: "3px solid #3d3d5d",
17313
17390
  borderRadius: "8px",
17314
17391
  boxShadow: "0 0 80px 10px #666",
17315
- width: "auto",
17316
17392
  fontFamily: "sans-serif",
17393
+ boxSizing: "border-box"
17317
17394
  },
17318
17395
  Input: {
17319
17396
  height: "35px",
17320
- width: "17em",
17397
+ width: "100%",
17398
+ maxWidth: "100%",
17321
17399
  borderColor: "#ccf4",
17322
17400
  outline: "none",
17323
- fontSize: "17pt",
17324
- padding: "8px"
17401
+ fontSize: "16px",
17402
+ padding: "8px",
17403
+ boxSizing: "border-box"
17404
+ },
17405
+ Button: {
17406
+ padding: "10px 20px",
17407
+ margin: "0 4px",
17408
+ border: "1px solid #d1d5db",
17409
+ borderRadius: "6px",
17410
+ backgroundColor: "#ffffff",
17411
+ cursor: "pointer",
17412
+ fontSize: "14px",
17413
+ fontWeight: "500",
17414
+ color: "#374151",
17415
+ transition: "all 0.2s ease"
17416
+ },
17417
+ PrimaryButton: {
17418
+ padding: "10px 20px",
17419
+ margin: "0 4px",
17420
+ border: "1px solid #3b82f6",
17421
+ borderRadius: "6px",
17422
+ backgroundColor: "#3b82f6",
17423
+ color: "white",
17424
+ cursor: "pointer",
17425
+ fontSize: "14px",
17426
+ fontWeight: "500",
17427
+ transition: "all 0.2s ease"
17428
+ },
17429
+ ButtonsDiv: {
17430
+ display: "flex",
17431
+ justifyContent: "flex-end",
17432
+ gap: "12px",
17433
+ marginTop: "24px",
17434
+ paddingTop: "20px"
17435
+ },
17436
+ Label: {
17437
+ display: "block",
17438
+ marginBottom: "12px",
17439
+ fontSize: "14px",
17440
+ fontWeight: "500",
17441
+ color: "#333"
17442
+ },
17443
+ WindowHeader: {
17444
+ margin: "0 0 20px 0",
17445
+ fontSize: "18px",
17446
+ fontWeight: "600",
17447
+ color: "#333",
17448
+ borderBottom: "1px solid #eee",
17449
+ paddingBottom: "10px"
17325
17450
  }
17326
17451
  };
17327
17452
 
17328
17453
  function Dialog({ children, className }) {
17329
- return (h("div", { className: className },
17454
+ return (h("div", { className: `dexie-dialog ${className || ''}` },
17330
17455
  h("div", { style: Styles.Darken }),
17331
17456
  h("div", { style: Styles.DialogOuter },
17332
17457
  h("div", { style: Styles.DialogInner }, children))));
@@ -17378,7 +17503,7 @@
17378
17503
  } })))))),
17379
17504
  h("div", { style: Styles.ButtonsDiv },
17380
17505
  h(p$1, null,
17381
- h("button", { type: "submit", style: Styles.Button, onClick: () => onSubmit(params) }, submitLabel),
17506
+ h("button", { type: "submit", style: Styles.PrimaryButton, onClick: () => onSubmit(params) }, submitLabel),
17382
17507
  cancelLabel && (h("button", { style: Styles.Button, onClick: onCancel }, cancelLabel))))));
17383
17508
  }
17384
17509
  function valueTransformer(type, value) {
@@ -18009,7 +18134,7 @@
18009
18134
  const syncComplete = new rxjs.Subject();
18010
18135
  dexie.cloud = {
18011
18136
  // @ts-ignore
18012
- version: "4.2.0",
18137
+ version: "4.2.2",
18013
18138
  options: Object.assign({}, DEFAULT_OPTIONS),
18014
18139
  schema: null,
18015
18140
  get currentUserId() {
@@ -18326,7 +18451,7 @@
18326
18451
  }
18327
18452
  }
18328
18453
  // @ts-ignore
18329
- dexieCloud.version = "4.2.0";
18454
+ dexieCloud.version = "4.2.2";
18330
18455
  Dexie.Cloud = dexieCloud;
18331
18456
 
18332
18457
  // In case the SW lives for a while, let it reuse already opened connections: