braid-text 0.3.16 → 0.3.18

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.
@@ -118,7 +118,7 @@ function simpleton_client(url, {
118
118
  var outstanding_changes = 0 // PUTs sent, not yet ACKed
119
119
  var max_outstanding_changes = 10 // throttle limit
120
120
  var throttled = false
121
- var throttled_update = null
121
+ var throttled_updates = []
122
122
  var ac = new AbortController()
123
123
 
124
124
  // ── Subscription (GET) ──────────────────────────────────────────────
@@ -150,8 +150,11 @@ function simpleton_client(url, {
150
150
  // parents match our client_version exactly. This ensures
151
151
  // we stay on a single line of time.
152
152
  update.parents.sort()
153
- if (versions_eq(client_version, update.parents))
154
- if (throttled) throttled_update = update
153
+ var last_queued = throttled_updates.length
154
+ ? throttled_updates[throttled_updates.length - 1].version
155
+ : client_version
156
+ if (versions_eq(last_queued, update.parents))
157
+ if (throttled) throttled_updates.push(update)
155
158
  else await apply_update(update)
156
159
  }, on_error)
157
160
  }).catch(on_error)
@@ -199,11 +202,31 @@ function simpleton_client(url, {
199
202
  client_state = apply_patches(client_state, patches)
200
203
  }
201
204
 
205
+ // ── Advance version ─────────────────────────────────────────
206
+ // IMPORTANT: This must happen synchronously (before any await)
207
+ // to prevent the changed() accumulation loop from interleaving
208
+ // and capturing a stale client_version during a yield point.
209
+ client_version = update.version
210
+
211
+ // ── Notify listener ─────────────────────────────────────────
212
+ // IMPORTANT: No changed() / flush is called here.
213
+ // The JS does NOT send edits after receiving a server
214
+ // update. The PUT response handler's async accumulation
215
+ // loop handles flushing accumulated edits.
216
+ if (on_state) on_state(client_state)
217
+
202
218
  // ── Digest verification ─────────────────────────────────────
203
219
  // If the server sent a repr-digest, verify our state
204
220
  // matches. On mismatch, THROW — this halts the
205
221
  // subscription handler. The document is corrupted and
206
222
  // continuing would compound the problem.
223
+ //
224
+ // This is placed after advancing client_version and calling
225
+ // on_state so that the await does not create a yield point
226
+ // between applying patches and advancing client_version.
227
+ // That yield point previously allowed the changed() PUT loop
228
+ // to interleave, capturing a stale client_version and causing
229
+ // edit loss.
207
230
  if (update.extra_headers &&
208
231
  update.extra_headers["repr-digest"] &&
209
232
  update.extra_headers["repr-digest"].startsWith('sha-256=') &&
@@ -213,16 +236,6 @@ function simpleton_client(url, {
213
236
  console.log('state: ' + client_state)
214
237
  throw new Error('repr-digest mismatch')
215
238
  }
216
-
217
- // ── Advance version ─────────────────────────────────────────
218
- client_version = update.version
219
-
220
- // ── Notify listener ─────────────────────────────────────────
221
- // IMPORTANT: No changed() / flush is called here.
222
- // The JS does NOT send edits after receiving a server
223
- // update. The PUT response handler's async accumulation
224
- // loop handles flushing accumulated edits.
225
- if (on_state) on_state(client_state)
226
239
  }
227
240
 
228
241
  // ── Public interface ────────────────────────────────────────────────
@@ -255,10 +268,10 @@ function simpleton_client(url, {
255
268
  if (!change) {
256
269
  if (throttled) {
257
270
  throttled = false
258
- if (throttled_update &&
259
- versions_eq(client_version, throttled_update.parents))
260
- apply_update(throttled_update).catch(on_error)
261
- throttled_update = null
271
+ for (var update of throttled_updates)
272
+ if (versions_eq(client_version, update.parents))
273
+ apply_update(update).catch(on_error)
274
+ throttled_updates = []
262
275
  }
263
276
  return
264
277
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-text",
3
- "version": "0.3.16",
3
+ "version": "0.3.18",
4
4
  "description": "Library for collaborative text over http using braid.",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braid-text",
package/server.js CHANGED
@@ -1023,16 +1023,10 @@ function create_braid_text() {
1023
1023
 
1024
1024
  if (client.my_timeout) {
1025
1025
  if (peer && client.peer === peer) {
1026
- if (!v_eq(client.my_last_sent_version, parents)) {
1027
- // note: we don't add to client.my_unused_version_count,
1028
- // because we're already in a timeout;
1029
- // we'll just extend it here..
1030
- set_timeout()
1031
- } else {
1032
- // hm.. it appears we got a correctly parented version,
1033
- // which suggests that maybe we can stop the timeout early
1034
- set_timeout(0)
1035
- }
1026
+ // note: we don't add to client.my_unused_version_count,
1027
+ // because we're already in a timeout;
1028
+ // we'll just extend it here..
1029
+ set_timeout()
1036
1030
  }
1037
1031
  continue
1038
1032
  }
@@ -1049,6 +1043,19 @@ function create_braid_text() {
1049
1043
 
1050
1044
  x.parents = [version]
1051
1045
  if (!v_eq(x.version, x.parents)) {
1046
+ // Note: this rebase will never trigger in the
1047
+ // presence of the timeout logic above, because
1048
+ // any version that would cause x.version to
1049
+ // differ from x.parents would have already
1050
+ // updated my_last_sent_version when forwarding
1051
+ // other peers' changes to this client, causing
1052
+ // a mismatch and triggering a timeout instead.
1053
+ //
1054
+ // However, this is the standard rebase path and
1055
+ // is left here for implementations that omit the
1056
+ // timeout optimization — without it, this branch
1057
+ // is needed to rebase the client's version when
1058
+ // concurrent edits have been merged.
1052
1059
  if (braid_text.verbose) console.log("rebasing..")
1053
1060
  x.patches = get_xf_patches(resource.doc, OpLog_remote_to_local(resource.doc, x.parents))
1054
1061
  } else {