@procedures/store 0.41.0 → 0.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@procedures/store",
4
- "version": "0.41.0",
4
+ "version": "0.41.1",
5
5
  "description": "A program store for procedure-as-data: save the programs that procedure.define makes, load them back after a restart, in memory, a JSON file, IndexedDB or a remote collection storage.",
6
6
  "keywords": [
7
7
  "program-store",
@@ -78,12 +78,12 @@
78
78
  },
79
79
  "dependencies": {
80
80
  "@mark1russell7/service": "^0.5.8",
81
- "@procedures/client": "0.41.0",
82
- "@procedures/collections": "0.41.0",
81
+ "@procedures/client": "0.41.1",
82
+ "@procedures/collections": "0.41.1",
83
83
  "zod": "^4.6.5"
84
84
  },
85
85
  "devDependencies": {
86
- "@procedures/fs": "0.41.0",
86
+ "@procedures/fs": "0.41.1",
87
87
  "@mark1russell7/cue": "^1.0.0",
88
88
  "@types/node": "^22.0.0",
89
89
  "fake-indexeddb": "^6.2.5",
@@ -2893,7 +2893,14 @@ export class HybridStorage<T> implements CollectionStorage<T> {
2893
2893
  if (conflicts !== undefined && conflicts.length > 0 && !resolved.has(op)) {
2894
2894
  resolved.add(op);
2895
2895
  this.isOnline = true;
2896
- await this.resolvePushConflicts(op, conflicts);
2896
+ if (await this.resolvePushConflicts(op, conflicts)) {
2897
+ // Each id is the echo of an earlier send: the operation leaves the queue as sent (roadmap O69)
2898
+ if (this.offlineOps[0] === op) {
2899
+ this.offlineOps.shift();
2900
+ sent++;
2901
+ this.storeDirty = true;
2902
+ }
2903
+ }
2897
2904
  continue;
2898
2905
  }
2899
2906
  if (
@@ -3357,16 +3364,21 @@ export class HybridStorage<T> implements CollectionStorage<T> {
3357
3364
  * This method applies the conflicts of a push that the remote refused (`VERSION_CONFLICT`). Each
3358
3365
  * conflict goes to `conflictResolution`, as a conflict of a pull does:
3359
3366
  *
3360
- * - The remote item is the value of the operation itself: an earlier send of it reached the
3361
- * remote, and its answer was lost. The base becomes the current version, and the push sends
3362
- * the operation again.
3367
+ * - The remote item is the value of the operation itself, or a delete finds no remote item. An
3368
+ * earlier send of it reached the remote, and its answer was lost. The base becomes the current
3369
+ * version. When each id of the operation is such an echo, the operation leaves the queue with
3370
+ * no further send (roadmap O69). Else the policy decides for the other ids.
3363
3371
  * - `remote`: the remote item replaces the local item, and the queued writes of the id go.
3364
3372
  * - `merge`: the merged item replaces the local item, and a new queued write sends it.
3365
3373
  * - `local`: the base becomes the current version, so the next send replaces the remote item.
3366
3374
  * - `error`: the queue keeps the operation, and the push throws `ConflictError` with the ids.
3375
+ *
3376
+ * The method gives true when each id of the operation is an echo. An id without a conflict is
3377
+ * not an echo: the remote did not show its value.
3367
3378
  */
3368
- private async resolvePushConflicts(op: QueuedOperation, conflicts: readonly VersionConflict[]): Promise<void> {
3379
+ private async resolvePushConflicts(op: QueuedOperation, conflicts: readonly VersionConflict[]): Promise<boolean> {
3369
3380
  const ids: string[] = [];
3381
+ const echoes = new Set<string>();
3370
3382
  let queueChanged = false;
3371
3383
  for (const conflict of conflicts) {
3372
3384
  const { id } = conflict;
@@ -3376,9 +3388,11 @@ export class HybridStorage<T> implements CollectionStorage<T> {
3376
3388
  const remoteItem = conflict.value as T;
3377
3389
  const remoteVersion = conflict.version ?? this.versionOfItem(remoteItem);
3378
3390
  // The echo of this operation, or a remote item without a version: the queued write wins
3379
- if (remoteVersion === undefined || (written !== null && sameJson(written.value, remoteItem))) {
3391
+ const echo = written !== null && sameJson(written.value, remoteItem);
3392
+ if (remoteVersion === undefined || echo) {
3380
3393
  if (remoteVersion === undefined) this.bases.delete(id);
3381
3394
  else this.bases.set(id, remoteVersion);
3395
+ if (echo) echoes.add(id);
3382
3396
  continue;
3383
3397
  }
3384
3398
  const localItem = await this.readLocal(id);
@@ -3388,6 +3402,7 @@ export class HybridStorage<T> implements CollectionStorage<T> {
3388
3402
  // The remote has no item: the echo of a delete, or a delete of another writer
3389
3403
  if (written === null) {
3390
3404
  this.bases.set(id, null);
3405
+ echoes.add(id);
3391
3406
  continue;
3392
3407
  }
3393
3408
  if (await this.resolveRemoteDelete(id, ids)) queueChanged = true;
@@ -3398,6 +3413,9 @@ export class HybridStorage<T> implements CollectionStorage<T> {
3398
3413
  await this.persistAll().catch(() => undefined);
3399
3414
  }
3400
3415
  if (ids.length > 0) throw new ConflictError(ids);
3416
+ // A clear and a delete of a batch write no value that a conflict shows: they are never echoes
3417
+ const written = idsOf(op);
3418
+ return !queueChanged && written.length > 0 && written.every((id) => echoes.has(id));
3401
3419
  }
3402
3420
 
3403
3421
  /**