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.
@@ -0,0 +1,6 @@
1
+ {
2
+ "demoUsers": {
3
+ "foo@demo.local": {},
4
+ "bar@demo.local": {}
5
+ }
6
+ }
@@ -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
  *
@@ -1653,17 +1653,14 @@ function listClientChanges(mutationTables_1, db_1) {
1653
1653
  : mutationTable;
1654
1654
  if (limit < Infinity)
1655
1655
  query = query.limit(limit);
1656
- const muts = yield query.toArray();
1657
- //const objTable = db.table(tableName);
1658
- /*for (const mut of muts) {
1659
- if (mut.type === "insert" || mut.type === "upsert") {
1660
- mut.values = await objTable.bulkGet(mut.keys);
1661
- }
1662
- }*/
1663
- return muts.map((mut) => ({
1656
+ let muts = yield query.toArray();
1657
+ muts = canonicalizeToUpdateOps(muts);
1658
+ muts = removeRedundantUpdateOps(muts);
1659
+ const rv = muts.map((mut) => ({
1664
1660
  table: tableName,
1665
1661
  mut,
1666
1662
  }));
1663
+ return rv;
1667
1664
  })));
1668
1665
  // Sort by time to get a true order of the operations (between tables)
1669
1666
  const sorted = flatten(allMutsOnTables).sort((a, b) => a.mut.txid === b.mut.txid
@@ -1692,6 +1689,69 @@ function listClientChanges(mutationTables_1, db_1) {
1692
1689
  return result;
1693
1690
  });
1694
1691
  }
1692
+ function removeRedundantUpdateOps(muts) {
1693
+ const updateCoverage = new Map();
1694
+ for (const mut of muts) {
1695
+ if (mut.type === 'update') {
1696
+ if (mut.keys.length !== 1 || mut.changeSpecs.length !== 1) {
1697
+ continue; // Don't optimize multi-key updates
1698
+ }
1699
+ const strKey = '' + mut.keys[0];
1700
+ const changeSpecs = mut.changeSpecs[0];
1701
+ if (Object.values(changeSpecs).some(v => typeof v === "object" && v && "@@propmod" in v)) {
1702
+ continue; // Cannot optimize if any PropModification is present
1703
+ }
1704
+ let keyCoverage = updateCoverage.get(strKey);
1705
+ if (keyCoverage) {
1706
+ keyCoverage.push({ txid: mut.txid, updateSpec: changeSpecs });
1707
+ }
1708
+ else {
1709
+ updateCoverage.set(strKey, [{ txid: mut.txid, updateSpec: changeSpecs }]);
1710
+ }
1711
+ }
1712
+ }
1713
+ muts = muts.filter(mut => {
1714
+ // Only apply optimization to update mutations that are single-key
1715
+ if (mut.type !== 'update')
1716
+ return true;
1717
+ if (mut.keys.length !== 1 || mut.changeSpecs.length !== 1)
1718
+ return true;
1719
+ // Keep track of properties that aren't overlapped by later transactions
1720
+ const unoverlappedProps = new Set(Object.keys(mut.changeSpecs[0]));
1721
+ const strKey = '' + mut.keys[0];
1722
+ const keyCoverage = updateCoverage.get(strKey);
1723
+ for (let i = keyCoverage.length - 1; i >= 0; --i) {
1724
+ const { txid, updateSpec } = keyCoverage[i];
1725
+ if (txid === mut.txid)
1726
+ break; // Stop when reaching own txid
1727
+ // If all changes in updateSpec are covered by all props on all mut.changeSpecs then
1728
+ // txid is redundant and can be removed.
1729
+ for (const keyPath of Object.keys(updateSpec)) {
1730
+ unoverlappedProps.delete(keyPath);
1731
+ }
1732
+ }
1733
+ if (unoverlappedProps.size === 0) {
1734
+ // This operation is completely overlapped by later operations. It can be removed.
1735
+ return false;
1736
+ }
1737
+ return true;
1738
+ });
1739
+ return muts;
1740
+ }
1741
+ function canonicalizeToUpdateOps(muts) {
1742
+ muts = muts.map(mut => {
1743
+ if (mut.type === 'modify' && mut.criteria.index === null) {
1744
+ // The criteria is on primary key. Convert to an update operation instead.
1745
+ // It is simpler for the server to handle and also more efficient.
1746
+ const updateMut = Object.assign(Object.assign({}, mut), { criteria: undefined, changeSpec: undefined, type: 'update', keys: mut.keys, changeSpecs: [mut.changeSpec] });
1747
+ delete updateMut.criteria;
1748
+ delete updateMut.changeSpec;
1749
+ return updateMut;
1750
+ }
1751
+ return mut;
1752
+ });
1753
+ return muts;
1754
+ }
1695
1755
 
1696
1756
  function randomString(bytes) {
1697
1757
  const buf = new Uint8Array(bytes);
@@ -4224,6 +4284,7 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
4224
4284
  let values = 'values' in req ? req.values : [];
4225
4285
  let changeSpec = 'changeSpec' in req ? req.changeSpec : undefined;
4226
4286
  let updates = 'updates' in req ? req.updates : undefined;
4287
+ let upsert = updates && 'upsert' in req ? req.upsert : false;
4227
4288
  if (hasFailures) {
4228
4289
  keys = keys.filter((_, idx) => !failures[idx]);
4229
4290
  values = values.filter((_, idx) => !failures[idx]);
@@ -4255,29 +4316,32 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
4255
4316
  };
4256
4317
  const validKeys = new RangeSet();
4257
4318
  let anyChangeSpecBecameEmpty = false;
4258
- for (let i = 0, l = strippedChangeSpecs.length; i < l; ++i) {
4259
- if (Object.keys(strippedChangeSpecs[i]).length > 0) {
4260
- newUpdates.keys.push(updates.keys[i]);
4261
- newUpdates.changeSpecs.push(strippedChangeSpecs[i]);
4262
- validKeys.addKey(updates.keys[i]);
4263
- }
4264
- else {
4265
- anyChangeSpecBecameEmpty = true;
4319
+ if (!upsert) {
4320
+ for (let i = 0, l = strippedChangeSpecs.length; i < l; ++i) {
4321
+ if (Object.keys(strippedChangeSpecs[i]).length > 0) {
4322
+ newUpdates.keys.push(updates.keys[i]);
4323
+ newUpdates.changeSpecs.push(strippedChangeSpecs[i]);
4324
+ validKeys.addKey(updates.keys[i]);
4325
+ }
4326
+ else {
4327
+ anyChangeSpecBecameEmpty = true;
4328
+ }
4266
4329
  }
4267
- }
4268
- updates = newUpdates;
4269
- if (anyChangeSpecBecameEmpty) {
4270
- // Some keys were stripped. We must also strip them from keys and values
4271
- let newKeys = [];
4272
- let newValues = [];
4273
- for (let i = 0, l = keys.length; i < l; ++i) {
4274
- if (validKeys.hasKey(keys[i])) {
4275
- newKeys.push(keys[i]);
4276
- newValues.push(values[i]);
4330
+ updates = newUpdates;
4331
+ if (anyChangeSpecBecameEmpty) {
4332
+ // Some keys were stripped. We must also strip them from keys and values
4333
+ // unless this is an upsert operation in which case we want to send them all.
4334
+ let newKeys = [];
4335
+ let newValues = [];
4336
+ for (let i = 0, l = keys.length; i < l; ++i) {
4337
+ if (validKeys.hasKey(keys[i])) {
4338
+ newKeys.push(keys[i]);
4339
+ newValues.push(values[i]);
4340
+ }
4277
4341
  }
4342
+ keys = newKeys;
4343
+ values = newValues;
4278
4344
  }
4279
- keys = newKeys;
4280
- values = newValues;
4281
4345
  }
4282
4346
  }
4283
4347
  }
@@ -4319,49 +4383,59 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
4319
4383
  userId,
4320
4384
  values,
4321
4385
  }
4322
- : criteria && changeSpec
4323
- ? {
4324
- // Common changeSpec for all keys
4325
- type: 'modify',
4326
- ts,
4327
- opNo,
4328
- keys,
4329
- criteria,
4330
- changeSpec,
4331
- txid,
4332
- userId,
4333
- }
4334
- : changeSpec
4386
+ : upsert ? {
4387
+ type: 'upsert',
4388
+ ts,
4389
+ opNo,
4390
+ keys,
4391
+ values,
4392
+ changeSpecs: updates.changeSpecs.filter((_, idx) => !failures[idx]),
4393
+ txid,
4394
+ userId,
4395
+ }
4396
+ : criteria && changeSpec
4335
4397
  ? {
4336
- // In case criteria involved an unsynced property, we go for keys instead.
4337
- type: 'update',
4398
+ // Common changeSpec for all keys
4399
+ type: 'modify',
4338
4400
  ts,
4339
4401
  opNo,
4340
4402
  keys,
4341
- changeSpecs: keys.map(() => changeSpec),
4403
+ criteria,
4404
+ changeSpec,
4342
4405
  txid,
4343
4406
  userId,
4344
4407
  }
4345
- : updates
4408
+ : changeSpec
4346
4409
  ? {
4347
- // One changeSpec per key
4410
+ // In case criteria involved an unsynced property, we go for keys instead.
4348
4411
  type: 'update',
4349
4412
  ts,
4350
4413
  opNo,
4351
- keys: updates.keys,
4352
- changeSpecs: updates.changeSpecs,
4353
- txid,
4354
- userId,
4355
- }
4356
- : {
4357
- type: 'upsert',
4358
- ts,
4359
- opNo,
4360
4414
  keys,
4361
- values,
4415
+ changeSpecs: keys.map(() => changeSpec),
4362
4416
  txid,
4363
4417
  userId,
4364
- };
4418
+ }
4419
+ : updates
4420
+ ? {
4421
+ // One changeSpec per key
4422
+ type: 'update',
4423
+ ts,
4424
+ opNo,
4425
+ keys: updates.keys,
4426
+ changeSpecs: updates.changeSpecs,
4427
+ txid,
4428
+ userId,
4429
+ }
4430
+ : {
4431
+ type: 'upsert',
4432
+ ts,
4433
+ opNo,
4434
+ keys,
4435
+ values,
4436
+ txid,
4437
+ userId,
4438
+ };
4365
4439
  if ('isAdditionalChunk' in req && req.isAdditionalChunk) {
4366
4440
  mut.isAdditionalChunk = true;
4367
4441
  }
@@ -5256,34 +5330,85 @@ const Styles = {
5256
5330
  alignItems: "center",
5257
5331
  display: "flex",
5258
5332
  justifyContent: "center",
5333
+ padding: "16px",
5334
+ boxSizing: "border-box"
5259
5335
  },
5260
5336
  DialogInner: {
5261
5337
  position: "relative",
5262
5338
  color: "#222",
5263
5339
  backgroundColor: "#fff",
5264
- padding: "30px",
5340
+ padding: "24px",
5265
5341
  marginBottom: "2em",
5266
- maxWidth: "90%",
5342
+ maxWidth: "400px",
5343
+ width: "100%",
5267
5344
  maxHeight: "90%",
5268
5345
  overflowY: "auto",
5269
5346
  border: "3px solid #3d3d5d",
5270
5347
  borderRadius: "8px",
5271
5348
  boxShadow: "0 0 80px 10px #666",
5272
- width: "auto",
5273
5349
  fontFamily: "sans-serif",
5350
+ boxSizing: "border-box"
5274
5351
  },
5275
5352
  Input: {
5276
5353
  height: "35px",
5277
- width: "17em",
5354
+ width: "100%",
5355
+ maxWidth: "100%",
5278
5356
  borderColor: "#ccf4",
5279
5357
  outline: "none",
5280
- fontSize: "17pt",
5281
- padding: "8px"
5358
+ fontSize: "16px",
5359
+ padding: "8px",
5360
+ boxSizing: "border-box"
5361
+ },
5362
+ Button: {
5363
+ padding: "10px 20px",
5364
+ margin: "0 4px",
5365
+ border: "1px solid #d1d5db",
5366
+ borderRadius: "6px",
5367
+ backgroundColor: "#ffffff",
5368
+ cursor: "pointer",
5369
+ fontSize: "14px",
5370
+ fontWeight: "500",
5371
+ color: "#374151",
5372
+ transition: "all 0.2s ease"
5373
+ },
5374
+ PrimaryButton: {
5375
+ padding: "10px 20px",
5376
+ margin: "0 4px",
5377
+ border: "1px solid #3b82f6",
5378
+ borderRadius: "6px",
5379
+ backgroundColor: "#3b82f6",
5380
+ color: "white",
5381
+ cursor: "pointer",
5382
+ fontSize: "14px",
5383
+ fontWeight: "500",
5384
+ transition: "all 0.2s ease"
5385
+ },
5386
+ ButtonsDiv: {
5387
+ display: "flex",
5388
+ justifyContent: "flex-end",
5389
+ gap: "12px",
5390
+ marginTop: "24px",
5391
+ paddingTop: "20px"
5392
+ },
5393
+ Label: {
5394
+ display: "block",
5395
+ marginBottom: "12px",
5396
+ fontSize: "14px",
5397
+ fontWeight: "500",
5398
+ color: "#333"
5399
+ },
5400
+ WindowHeader: {
5401
+ margin: "0 0 20px 0",
5402
+ fontSize: "18px",
5403
+ fontWeight: "600",
5404
+ color: "#333",
5405
+ borderBottom: "1px solid #eee",
5406
+ paddingBottom: "10px"
5282
5407
  }
5283
5408
  };
5284
5409
 
5285
5410
  function Dialog({ children, className }) {
5286
- return (h("div", { className: className },
5411
+ return (h("div", { className: `dexie-dialog ${className || ''}` },
5287
5412
  h("div", { style: Styles.Darken }),
5288
5413
  h("div", { style: Styles.DialogOuter },
5289
5414
  h("div", { style: Styles.DialogInner }, children))));
@@ -5335,7 +5460,7 @@ function LoginDialog({ title, type, alerts, fields, submitLabel, cancelLabel, on
5335
5460
  } })))))),
5336
5461
  h("div", { style: Styles.ButtonsDiv },
5337
5462
  h(p$1, null,
5338
- h("button", { type: "submit", style: Styles.Button, onClick: () => onSubmit(params) }, submitLabel),
5463
+ h("button", { type: "submit", style: Styles.PrimaryButton, onClick: () => onSubmit(params) }, submitLabel),
5339
5464
  cancelLabel && (h("button", { style: Styles.Button, onClick: onCancel }, cancelLabel))))));
5340
5465
  }
5341
5466
  function valueTransformer(type, value) {
@@ -6137,7 +6262,7 @@ function dexieCloud(dexie) {
6137
6262
  const syncComplete = new Subject();
6138
6263
  dexie.cloud = {
6139
6264
  // @ts-ignore
6140
- version: "4.2.0",
6265
+ version: "4.2.2",
6141
6266
  options: Object.assign({}, DEFAULT_OPTIONS),
6142
6267
  schema: null,
6143
6268
  get currentUserId() {
@@ -6454,7 +6579,7 @@ function dexieCloud(dexie) {
6454
6579
  }
6455
6580
  }
6456
6581
  // @ts-ignore
6457
- dexieCloud.version = "4.2.0";
6582
+ dexieCloud.version = "4.2.2";
6458
6583
  Dexie.Cloud = dexieCloud;
6459
6584
 
6460
6585
  export { dexieCloud as default, defineYDocTrigger, dexieCloud, getTiedObjectId, getTiedRealmId, resolveText };