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
  *
@@ -412,17 +412,14 @@ function listClientChanges(mutationTables_1, db_1) {
412
412
  : mutationTable;
413
413
  if (limit < Infinity)
414
414
  query = query.limit(limit);
415
- const muts = yield query.toArray();
416
- //const objTable = db.table(tableName);
417
- /*for (const mut of muts) {
418
- if (mut.type === "insert" || mut.type === "upsert") {
419
- mut.values = await objTable.bulkGet(mut.keys);
420
- }
421
- }*/
422
- return muts.map((mut) => ({
415
+ let muts = yield query.toArray();
416
+ muts = canonicalizeToUpdateOps(muts);
417
+ muts = removeRedundantUpdateOps(muts);
418
+ const rv = muts.map((mut) => ({
423
419
  table: tableName,
424
420
  mut,
425
421
  }));
422
+ return rv;
426
423
  })));
427
424
  // Sort by time to get a true order of the operations (between tables)
428
425
  const sorted = flatten(allMutsOnTables).sort((a, b) => a.mut.txid === b.mut.txid
@@ -451,6 +448,69 @@ function listClientChanges(mutationTables_1, db_1) {
451
448
  return result;
452
449
  });
453
450
  }
451
+ function removeRedundantUpdateOps(muts) {
452
+ const updateCoverage = new Map();
453
+ for (const mut of muts) {
454
+ if (mut.type === 'update') {
455
+ if (mut.keys.length !== 1 || mut.changeSpecs.length !== 1) {
456
+ continue; // Don't optimize multi-key updates
457
+ }
458
+ const strKey = '' + mut.keys[0];
459
+ const changeSpecs = mut.changeSpecs[0];
460
+ if (Object.values(changeSpecs).some(v => typeof v === "object" && v && "@@propmod" in v)) {
461
+ continue; // Cannot optimize if any PropModification is present
462
+ }
463
+ let keyCoverage = updateCoverage.get(strKey);
464
+ if (keyCoverage) {
465
+ keyCoverage.push({ txid: mut.txid, updateSpec: changeSpecs });
466
+ }
467
+ else {
468
+ updateCoverage.set(strKey, [{ txid: mut.txid, updateSpec: changeSpecs }]);
469
+ }
470
+ }
471
+ }
472
+ muts = muts.filter(mut => {
473
+ // Only apply optimization to update mutations that are single-key
474
+ if (mut.type !== 'update')
475
+ return true;
476
+ if (mut.keys.length !== 1 || mut.changeSpecs.length !== 1)
477
+ return true;
478
+ // Keep track of properties that aren't overlapped by later transactions
479
+ const unoverlappedProps = new Set(Object.keys(mut.changeSpecs[0]));
480
+ const strKey = '' + mut.keys[0];
481
+ const keyCoverage = updateCoverage.get(strKey);
482
+ for (let i = keyCoverage.length - 1; i >= 0; --i) {
483
+ const { txid, updateSpec } = keyCoverage[i];
484
+ if (txid === mut.txid)
485
+ break; // Stop when reaching own txid
486
+ // If all changes in updateSpec are covered by all props on all mut.changeSpecs then
487
+ // txid is redundant and can be removed.
488
+ for (const keyPath of Object.keys(updateSpec)) {
489
+ unoverlappedProps.delete(keyPath);
490
+ }
491
+ }
492
+ if (unoverlappedProps.size === 0) {
493
+ // This operation is completely overlapped by later operations. It can be removed.
494
+ return false;
495
+ }
496
+ return true;
497
+ });
498
+ return muts;
499
+ }
500
+ function canonicalizeToUpdateOps(muts) {
501
+ muts = muts.map(mut => {
502
+ if (mut.type === 'modify' && mut.criteria.index === null) {
503
+ // The criteria is on primary key. Convert to an update operation instead.
504
+ // It is simpler for the server to handle and also more efficient.
505
+ const updateMut = Object.assign(Object.assign({}, mut), { criteria: undefined, changeSpec: undefined, type: 'update', keys: mut.keys, changeSpecs: [mut.changeSpec] });
506
+ delete updateMut.criteria;
507
+ delete updateMut.changeSpec;
508
+ return updateMut;
509
+ }
510
+ return mut;
511
+ });
512
+ return muts;
513
+ }
454
514
 
455
515
  function randomString$1(bytes) {
456
516
  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) {
@@ -5966,7 +6091,7 @@ function dexieCloud(dexie) {
5966
6091
  const syncComplete = new Subject();
5967
6092
  dexie.cloud = {
5968
6093
  // @ts-ignore
5969
- version: "4.2.0",
6094
+ version: "4.2.2",
5970
6095
  options: Object.assign({}, DEFAULT_OPTIONS),
5971
6096
  schema: null,
5972
6097
  get currentUserId() {
@@ -6283,7 +6408,7 @@ function dexieCloud(dexie) {
6283
6408
  }
6284
6409
  }
6285
6410
  // @ts-ignore
6286
- dexieCloud.version = "4.2.0";
6411
+ dexieCloud.version = "4.2.2";
6287
6412
  Dexie.Cloud = dexieCloud;
6288
6413
 
6289
6414
  // In case the SW lives for a while, let it reuse already opened connections: