braid-text 0.5.7 → 0.5.8

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.
@@ -1,19 +1,9 @@
1
- // ************************************************************************
2
- // ******* Reference Implementation of Simpleton Client Algorithm *********
3
- // ************************************************************************
4
- //
5
- // This is the canonical JS reference for implementing a simpleton client.
6
- // Other language implementations should mirror this logic exactly, with
7
- // adaptations only for language-specific details (e.g., string encoding).
8
- //
9
- // requires braid-http@~1.3/braid-http-client.js
1
+ // Simpleton Javascript Client
10
2
  //
3
+ // requires braid-http@~1.3/braid-http-client.js
4
+
11
5
  // --- API ---
12
6
  //
13
- // url: resource endpoint
14
- //
15
- // headers: custom headers that get forwarded through into the fetch
16
- //
17
7
  // on_patches?: (patches) => void
18
8
  // processes incoming patches by applying them to the UI/textarea.
19
9
  // Patches are guaranteed to be in-order and non-overlapping.
@@ -74,26 +64,6 @@
74
64
  // get_patches is optional.)
75
65
  // call abort() to abort the subscription.
76
66
  //
77
- // --- Retry and Reconnection Behavior ---
78
- //
79
- // Simpleton relies on braid_fetch for retry/reconnection:
80
- //
81
- // Subscription (GET):
82
- // retry: () => true — always reconnect on any error (network failure,
83
- // HTTP error, etc.). Reconnection backs off:
84
- // delay = Math.min(retry_count + 1, 3) * 1000 ms
85
- // i.e., 1s, 2s, 3s, 3s, 3s, ...
86
- // On reconnect, sends Parents via the parents callback to resume
87
- // from where the client left off.
88
- //
89
- // PUT requests:
90
- // retry: (res) => res.status !== 550 — retry all errors EXCEPT
91
- // HTTP 550 (Repr-Digest mismatch, meaning client is out of sync).
92
- // This means:
93
- // - Connection failure: retried with backoff
94
- // - HTTP 401, 403, 408, 429, 500, 502, 503, 504, etc.: retried
95
- // - HTTP 550: out of sync — stop retrying, throw error. The
96
- // client must be torn down and restarted from scratch.
97
67
  //
98
68
  // --- Local Edit Absorption ---
99
69
  //
@@ -113,26 +83,27 @@
113
83
  // update; otherwise your UI will not reflect remote changes.
114
84
  //
115
85
  function simpleton_client(url, {
116
- on_patches,
117
- on_state,
118
86
  get_patches,
119
87
  get_state,
120
- content_type,
121
- headers, // The user can pass in custom headers
122
- // that are forwarded into fetches
88
+
89
+ on_patches,
90
+ on_state,
123
91
  on_error,
124
92
  on_online,
125
93
  on_ack,
94
+
95
+ headers, // The user can pass in custom headers
96
+ // that are forwarded into fetches
97
+ content_type,
126
98
  send_digests
127
99
  }) {
128
100
  var peer = Math.random().toString(36).slice(2)
129
101
  var client_version = [] // sorted version strings
130
102
  var client_state = "" // text as of client_version
131
103
  var char_counter = -1 // char-delta for version IDs
132
- var outstanding_changes = 0 // PUTs sent, not yet ACKed
133
- var max_outstanding_changes = 10 // throttle limit
134
- var throttled = false
135
- var throttled_updates = []
104
+ var dirty = false // true when local edits exist but haven't been sent
105
+ var is_online = false
106
+ var outstanding_puts = 0
136
107
 
137
108
  // extend the headers with merge-type and peer
138
109
  headers = {
@@ -145,31 +116,26 @@ function simpleton_client(url, {
145
116
  // channel with automatic reconnection and PUT queuing.
146
117
  var channel = reliable_update_channel(url, {
147
118
  reconnect_from_parents: () => client_version.length ? client_version : null,
148
- get_headers: {
149
- ...headers,
150
- ...content_type && {Accept: content_type}
151
- },
152
- put_headers: {
153
- ...headers,
154
- ...content_type && {"Content-Type": content_type}
155
- },
119
+ get_headers: { ...headers, ...content_type && {Accept: content_type} },
120
+ put_headers: { ...headers, ...content_type && {"Content-Type": content_type} },
156
121
  on_update: async update => {
157
122
  // ── Parent check ────────────────────────────────────────
158
123
  // Core simpleton invariant: only accept updates whose
159
- // parents form a continuous chain. We compare against the
160
- // last queued update's version (if throttled) or
161
- // client_version. When throttled, matching updates are
162
- // queued but not applied — they'll be applied later when
163
- // the throttle clears (see changed()).
124
+ // parents match our current version. If we're dirty
125
+ // (have unsent local edits), skip we'll reconnect
126
+ // once the edits are flushed.
164
127
  update.parents.sort()
165
- var last_queued = throttled_updates.length
166
- ? throttled_updates[throttled_updates.length - 1].version
167
- : client_version
168
- if (versions_eq(last_queued, update.parents))
169
- if (throttled) throttled_updates.push(update)
170
- else await apply_update(update)
128
+ if (!dirty && versions_eq(client_version, update.parents))
129
+ await apply_update(update)
130
+ },
131
+ on_status: status => {
132
+ is_online = status.online
133
+ outstanding_puts = status.outstanding_puts
134
+ if (on_online) on_online(status.online)
135
+ if (on_ack && outstanding_puts === 0) on_ack()
136
+ if (dirty && is_online && outstanding_puts < 10)
137
+ try_send()
171
138
  },
172
- on_status: status => on_online && on_online(status.online),
173
139
  on_error: err => on_error && on_error(err),
174
140
 
175
141
  // this api is preliminary and undocumented;
@@ -200,12 +166,12 @@ function simpleton_client(url, {
200
166
  // are natively indexed by code points (e.g., Emacs Lisp,
201
167
  // Python, Rust's char iterator).
202
168
  convert_ranges_codepoints_to_utf16(patches, client_state)
203
- } else {
169
+ } else
204
170
  // Initial snapshot: convert body to a patch replacing
205
171
  // [0,0] so it follows the same code path as incremental
206
172
  // patches.
207
173
  patches = [{range: [0, 0], content: update.body_text}]
208
- }
174
+
209
175
 
210
176
  // ── Apply the update ────────────────────────────────────────
211
177
  if (on_patches) {
@@ -216,11 +182,11 @@ function simpleton_client(url, {
216
182
  // changed() after every local edit to avoid this.
217
183
  on_patches(patches)
218
184
  client_state = get_state()
219
- } else {
185
+ } else
220
186
  // Apply patches to our internal state; the
221
187
  // result is delivered via on_state below.
222
188
  client_state = apply_patches(client_state, patches)
223
- }
189
+
224
190
 
225
191
  // ── Advance version ─────────────────────────────────────────
226
192
  // IMPORTANT: This must happen synchronously (before any await)
@@ -235,135 +201,83 @@ function simpleton_client(url, {
235
201
  // loop handles flushing accumulated edits.
236
202
  if (on_state) on_state(client_state)
237
203
 
238
- // ── Digest verification ─────────────────────────────────────
239
- // If the server sent a repr-digest, verify our state
240
- // matches. On mismatch, THROW — this halts the
241
- // subscription handler. The document is corrupted and
242
- // continuing would compound the problem.
243
- //
244
- // This is placed after advancing client_version and calling
245
- // on_state so that the await does not create a yield point
246
- // between applying patches and advancing client_version.
247
- // That yield point previously allowed the changed() PUT loop
248
- // to interleave, capturing a stale client_version and causing
249
- // edit loss.
250
- if (update.extra_headers &&
251
- update.extra_headers["repr-digest"] &&
252
- update.extra_headers["repr-digest"].startsWith('sha-256=') &&
253
- update.extra_headers["repr-digest"] !== await get_digest(client_state)) {
254
- console.log('repr-digest mismatch!')
255
- console.log('repr-digest: ' + update.extra_headers["repr-digest"])
256
- console.log('state: ' + client_state)
257
- throw new Error('repr-digest mismatch')
258
- }
204
+ // Now verify that we did this correct, and are in sync. We do this
205
+ // at the end, so the prior updating is atomic.
206
+ await check_digest(update, client_state)
259
207
  }
260
208
 
261
- // ── Public interface ────────────────────────────────────────────────
262
- return {
263
- // ── abort() cancel the subscription ─────────────────────────
264
- abort: () => channel.close(),
265
-
266
- // ── changed() — call when local edits occur ───────────────────
267
- // This is the entry point for sending local edits. It:
268
- // 1. Diffs client_state vs current state
269
- // 2. Checks the throttle (outstanding_changes >= max)
270
- // 3. Sends a PUT with the diff
271
- // 4. After the PUT completes, loops to check for MORE accumulated
272
- // edits (the async accumulation loop), sending them too.
273
- //
274
- // The async accumulation loop (while(true) {...}) is equivalent
275
- // to a callback-driven flush: after each PUT ACK, re-diff and
276
- // send again if changed. This ensures edits that accumulate
277
- // during a PUT round-trip are eventually sent.
278
- changed: () => {
279
- function get_change() {
280
- var new_state = get_state()
281
- if (new_state === client_state) return null
282
- var patches = get_patches ? get_patches(client_state) :
283
- [simple_diff(client_state, new_state)]
284
- return {patches, new_state}
285
- }
286
-
287
- var change = get_change()
288
- if (!change) {
289
- if (throttled) {
290
- throttled = false
291
- for (var update of throttled_updates)
292
- if (versions_eq(client_version, update.parents))
293
- apply_update(update).catch(on_error)
294
- throttled_updates = []
295
- }
296
- return
297
- }
298
-
299
- if (outstanding_changes >= max_outstanding_changes) {
300
- throttled = true
209
+ // ── try_send attempt to flush local edits ───────────────────────
210
+ // Called from changed() and on_status. Diffs client_state vs current
211
+ // state and sends a PUT if there's a change. If dirty but no diff,
212
+ // we may have missed updates while dirty, so reconnect to re-sync.
213
+ function try_send() {
214
+ var new_state = get_state()
215
+ if (new_state === client_state) {
216
+ // No local diff but we were dirty, meaning we may have
217
+ // skipped incoming updates. Reconnect to catch up.
218
+ dirty = false
219
+ channel.reconnect()
301
220
  return
302
221
  }
303
222
 
304
- var {patches, new_state} = change
223
+ var patches = get_patches ? get_patches(client_state) :
224
+ [simple_diff(client_state, new_state)]
305
225
 
306
226
  // Save JS-index patches before code-point conversion mutates them
307
227
  var js_patches = patches.map(p => ({range: [...p.range], content: p.content}))
308
228
 
309
- ;(async () => {
310
- while (true) {
311
- // ── JS-SPECIFIC: Convert JS UTF-16 ranges to code-points ──
312
- // The wire protocol uses code-point offsets. See the
313
- // inverse conversion in the receive path above.
314
- //
315
- // OTHER LANGUAGES: Skip this if your strings are
316
- // natively code-point indexed.
317
- convert_ranges_utf16_to_codepoints(patches, client_state)
318
-
319
- for (let patch of patches) {
320
- // ── Update char_counter ─────────────────────────
321
- // Increment by deleted chars + inserted chars
322
- char_counter += patch.range[1] - patch.range[0]
323
- char_counter += count_code_points(patch.content)
324
-
325
- patch.unit = "text"
326
- patch.range = `[${patch.range[0]}:${patch.range[1]}]`
327
- }
328
-
329
- // ── Compute version and advance optimistically ──────
330
- var version = [peer + "-" + char_counter]
331
-
332
- var parents = client_version
333
- client_version = version // optimistic advance
334
- client_state = new_state // update client_state
335
-
336
- // ── Send PUT ────────────────────────────────────────
337
- // Uses braid_fetch with retry: (res) => res.status !== 550
338
- // This means:
339
- // - Network failures: retried with backoff
340
- // - HTTP 401, 403, 408, 429, 500, 502, 503, 504: retried
341
- // - HTTP 550 (Repr-Digest mismatch / out of sync):
342
- // give up, throw — client must be re-created
343
- outstanding_changes++
344
-
345
- await channel.put({
346
- version, parents, patches,
347
- headers: {
348
- ...send_digests && {
349
- "Repr-Digest": await get_digest(client_state) }
350
- }
351
- })
352
-
353
- throttled = false
354
- outstanding_changes--
355
- if (on_ack && !outstanding_changes) on_ack()
356
-
357
- // ── Check for accumulated edits ─────────────────────
358
- // While the PUT was in flight, more local edits may
359
- // have occurred. Diff again and loop if changed.
360
- var more = get_change()
361
- if (!more) return
362
- ;({patches, new_state} = more)
363
- }
364
- })()
229
+ // ── JS-SPECIFIC: Convert JS UTF-16 ranges to code-points ────
230
+ // The wire protocol uses code-point offsets. See the
231
+ // inverse conversion in the receive path above.
232
+ //
233
+ // OTHER LANGUAGES: Skip this if your strings are
234
+ // natively code-point indexed.
235
+ convert_ranges_utf16_to_codepoints(patches, client_state)
236
+
237
+ for (let patch of patches) {
238
+ // ── Update char_counter ─────────────────────────────────
239
+ // Increment by deleted chars + inserted chars
240
+ char_counter += patch.range[1] - patch.range[0]
241
+ char_counter += count_code_points(patch.content)
242
+
243
+ patch.unit = "text"
244
+ patch.range = `[${patch.range[0]}:${patch.range[1]}]`
245
+ }
246
+
247
+ // ── Compute version and advance optimistically ──────────────
248
+ var version = [peer + "-" + char_counter]
249
+
250
+ var parents = client_version
251
+ client_version = version // optimistic advance
252
+ client_state = new_state // update client_state
253
+ dirty = false
254
+
255
+ // Send Update — when the PUT completes, on_status fires
256
+ // with updated outstanding_puts, which will call try_send
257
+ // again if dirty.
258
+ if (send_digests)
259
+ get_digest(client_state).then(digest =>
260
+ channel.put({ version, parents, patches,
261
+ headers: { "Repr-Digest": digest } }))
262
+ else
263
+ channel.put({ version, parents, patches })
365
264
 
366
265
  return js_patches
266
+ }
267
+
268
+ // ── Public interface ────────────────────────────────────────────────
269
+ return {
270
+ // ── abort() — cancel the subscription ─────────────────────────
271
+ abort: () => channel.close(),
272
+
273
+ // ── changed() — call when local edits occur ───────────────────
274
+ // If online and under the PUT limit, sends immediately.
275
+ // Otherwise marks dirty — on_status will flush later.
276
+ changed: () => {
277
+ if (is_online && outstanding_puts < 10)
278
+ return try_send()
279
+ else
280
+ dirty = true
367
281
  }
368
282
  }
369
283
 
@@ -497,12 +411,24 @@ function simpleton_client(url, {
497
411
  return state
498
412
  }
499
413
 
500
- // ── get_digest ──────────────────────────────────────────────────────
501
- // Computes SHA-256 of the UTF-8 encoding of the state string,
502
- // formatted as the Repr-Digest header value:
503
- // sha-256=:<base64-encoded-hash>:
414
+ // get_digest():
415
+ // - Computes SHA-256 of the UTF-8 encoding of the string
416
+ // - Formatted as Repr-Digest: sha-256=:<base64-encoded-hash>:
504
417
  async function get_digest(str) {
505
418
  var bytes = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str))
506
419
  return `sha-256=:${btoa(String.fromCharCode(...new Uint8Array(bytes)))}:`
507
420
  }
421
+ // check_digest():
422
+ // - Makes sure the current state matches the digest in the update
423
+ async function check_digest(update, client_state) {
424
+ // If the server sent a repr-digest, verify our state matches. Throw
425
+ // exception if it fails.
426
+ if (update.extra_headers?.["repr-digest"]?.startsWith('sha-256=')
427
+ && update.extra_headers["repr-digest"] !== await get_digest(client_state)) {
428
+ console.log('repr-digest mismatch!')
429
+ console.log('repr-digest: ' + update.extra_headers["repr-digest"])
430
+ console.log('state: ' + client_state)
431
+ throw new Error('repr-digest mismatch')
432
+ }
433
+ }
508
434
  }
@@ -1,16 +1,15 @@
1
1
 
2
- function set_acked_state(textarea, binary = true) {
3
- if (!binary) {
2
+ function set_acked_state(textarea, on = true) {
3
+ if (on)
4
+ textarea.style.caretColor = textarea.old_caretColor
5
+ else {
4
6
  textarea.old_caretColor = textarea.style.caretColor
5
-
6
7
  textarea.style.caretColor = 'red'
7
- } else {
8
- textarea.style.caretColor = textarea.old_caretColor
9
8
  }
10
9
  }
11
10
 
12
- function set_error_state(textarea, binary = true) {
13
- if (binary) {
11
+ function set_error_state(textarea, on = true) {
12
+ if (on) {
14
13
  textarea.old_disabled = textarea.disabled
15
14
  textarea.old_background = textarea.style.background
16
15
  textarea.old_border = textarea.style.border
@@ -25,6 +24,8 @@ function set_error_state(textarea, binary = true) {
25
24
  }
26
25
  }
27
26
 
27
+ // A convenient wrapper around the myers-diff.js library's "diff_main()" function,
28
+ // which is defined in https://braid.org/code/myers-diff1.js.
28
29
  function diff(before, after) {
29
30
  let diff = diff_main(before, after)
30
31
  let patches = []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-text",
3
- "version": "0.5.7",
3
+ "version": "0.5.8",
4
4
  "description": "Library for collaborative text over http using braid.",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braid-text",
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "dependencies": {
27
27
  "@braid.org/diamond-types-node": "^2.0.1",
28
- "braid-http": "~1.3.123"
28
+ "braid-http": "~1.3.124"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "yjs": "^13.6.0"
package/server-demo.js CHANGED
@@ -14,7 +14,7 @@ var server = require("http").createServer(async (req, res) => {
14
14
  if (req.method === 'OPTIONS') return res.end()
15
15
 
16
16
  var q = req.url.split('?').slice(-1)[0]
17
- if (q === 'editor' || q === 'markdown-editor' || q === 'yjs-editor' || q === 'demo') {
17
+ if (q === 'editor' || q === 'markdown-editor' || q === 'yjs-editor') {
18
18
  res.writeHead(200, { "Content-Type": "text/html", "Cache-Control": "no-cache" })
19
19
  require("fs").createReadStream(`./client/${q}.html`).pipe(res)
20
20
  return
package/server.js CHANGED
@@ -93,45 +93,55 @@ function create_braid_text() {
93
93
  resource.save_meta()
94
94
  }
95
95
 
96
- // Given a version frontier, incorporate a new update (version + parents)
97
- // to compute the new frontier. Walks the DT version DAG if needed.
98
- function extend_frontier(frontier, version, parents) {
99
- var frontier_set = new Set(frontier)
100
- // Fast path: if the frontier contains all the update's parents,
101
- // just swap them out for the new version
102
- if (parents.length &&
103
- parents.every(p => frontier_set.has(p))) {
104
- parents.forEach(p => frontier_set.delete(p))
105
- for (var event of version) frontier_set.add(event)
106
- frontier = [...frontier_set.values()]
107
- } else {
108
- // Slow path: walk the full DT history to compute the frontier
109
- var looking_for = frontier_set
110
- for (var event of version) looking_for.add(event)
111
-
112
- frontier = []
113
- var shadow = new Set()
114
-
115
- var bytes = resource.dt.doc.toBytes()
116
- var [_, events, parentss] = braid_text.dt_parse([...bytes])
117
- for (var i = events.length - 1; i >= 0 && looking_for.size; i--) {
118
- var e = events[i].join('-')
119
- if (looking_for.has(e)) {
120
- looking_for.delete(e)
121
- if (!shadow.has(e)) frontier.push(e)
122
- shadow.add(e)
96
+ // When we get an ackowledgement that a remote server has a version
97
+ // that we have:
98
+ //
99
+ // - In a PUT acknowledgement
100
+ // - Or a GET response
101
+ //
102
+ // ...then we extend our known fork point "frontier" to include that
103
+ // version.
104
+ function extend_fork_point(update) {
105
+
106
+ // Given a version frontier, incorporate a new update (version +
107
+ // parents) to compute the new frontier. Walks the DT version DAG
108
+ // if needed.
109
+ function extend_frontier(frontier, version, parents) {
110
+ var frontier_set = new Set(frontier)
111
+ // Fast path: if the frontier contains all the update's parents,
112
+ // just swap them out for the new version
113
+ if (parents.length &&
114
+ parents.every(p => frontier_set.has(p))) {
115
+ parents.forEach(p => frontier_set.delete(p))
116
+ for (var event of version) frontier_set.add(event)
117
+ frontier = [...frontier_set.values()]
118
+ } else {
119
+ // Slow path: walk the full DT history to compute the frontier
120
+ var looking_for = frontier_set
121
+ for (var event of version) looking_for.add(event)
122
+
123
+ frontier = []
124
+ var shadow = new Set()
125
+
126
+ var bytes = resource.dt.doc.toBytes()
127
+ var [_, events, parentss] = braid_text.dt_parse([...bytes])
128
+ for (var i = events.length - 1; i >= 0 && looking_for.size; i--) {
129
+ var e = events[i].join('-')
130
+ if (looking_for.has(e)) {
131
+ looking_for.delete(e)
132
+ if (!shadow.has(e)) frontier.push(e)
133
+ shadow.add(e)
134
+ }
135
+ if (shadow.has(e))
136
+ parentss[i].forEach(p => shadow.add(p.join('-')))
123
137
  }
124
- if (shadow.has(e))
125
- parentss[i].forEach(p => shadow.add(p.join('-')))
126
138
  }
139
+ return frontier.sort()
127
140
  }
128
- return frontier.sort()
129
- }
130
141
 
131
- function extend_fork_point(update) {
132
- resource.meta.fork_point =
133
- extend_frontier(resource.meta.fork_point,
134
- update.version, update.parents)
142
+ resource.meta.fork_point = extend_frontier(resource.meta.fork_point,
143
+ update.version,
144
+ update.parents)
135
145
  resource.save_meta()
136
146
  }
137
147
 
@@ -370,8 +380,8 @@ function create_braid_text() {
370
380
  res.setHeader('Content-Type', `${ct}; charset=utf-8`)
371
381
  else if (charset.toLowerCase() !== 'charset=utf-8')
372
382
  res.setHeader('Content-Type', ct_parts
373
- .map(p => p.toLowerCase().startsWith('charset=') ? 'charset=utf-8' : p)
374
- .join('; '))
383
+ .map(p => p.toLowerCase().startsWith('charset=') ? 'charset=utf-8' : p)
384
+ .join('; '))
375
385
 
376
386
  // ── Handle simple methods that don't need further processing ──
377
387
 
@@ -407,9 +417,9 @@ function create_braid_text() {
407
417
  var getting = {
408
418
  subscribe: !!req.subscribe,
409
419
  history: (has_parents && v_eq(req.parents, resource.version)) ? false
410
- : has_parents ? 'since-parents'
411
- : (req.subscribe || req.parents || req.headers['accept-transfer-encoding']) ? 'up-to-version'
412
- : false,
420
+ : has_parents ? 'since-parents'
421
+ : (req.subscribe || req.parents || req.headers['accept-transfer-encoding']) ? 'up-to-version'
422
+ : false,
413
423
  transfer_encoding: req.headers['accept-transfer-encoding'],
414
424
  }
415
425
  getting.single_snapshot = !getting.subscribe && !getting.history
@@ -493,8 +503,7 @@ function create_braid_text() {
493
503
  merge_type,
494
504
  signal: aborter.signal,
495
505
  accept_encoding:
496
- req.headers['x-accept-encoding'] ??
497
- req.headers['accept-encoding'],
506
+ req.headers['x-accept-encoding'] ?? req.headers['accept-encoding'],
498
507
  subscribe: update => {
499
508
  // Add digest for integrity checking on the client
500
509
  if (update.version && v_eq(update.version, resource.version))
@@ -688,9 +697,9 @@ function create_braid_text() {
688
697
  // 'up-to-version' = bring client up to current (from scratch)
689
698
  // false = no history needed
690
699
  history: (has_parents && v_eq(options.parents, version)) ? false
691
- : has_parents ? 'since-parents'
692
- : (options.subscribe || options.parents || options.transfer_encoding) ? 'up-to-version'
693
- : false,
700
+ : has_parents ? 'since-parents'
701
+ : (options.subscribe || options.parents || options.transfer_encoding) ? 'up-to-version'
702
+ : false,
694
703
  transfer_encoding: options.transfer_encoding,
695
704
  }
696
705
  getting.single_snapshot = !getting.subscribe && !getting.history
@@ -733,6 +742,7 @@ function create_braid_text() {
733
742
  return { body: bytes }
734
743
  }
735
744
 
745
+ // Each merge-type has a different way of getting history
736
746
  switch (merge_type) {
737
747
 
738
748
  case 'yjs':
@@ -774,7 +784,7 @@ function create_braid_text() {
774
784
 
775
785
  if (getting.history && !getting.subscribe)
776
786
  return dt_get_patches(resource.dt.doc,
777
- getting.history === 'since-parents' ? options.parents : undefined)
787
+ getting.history === 'since-parents' ? options.parents : undefined)
778
788
 
779
789
  if (getting.subscribe) {
780
790
  var client = {
@@ -807,7 +817,7 @@ function create_braid_text() {
807
817
 
808
818
  if (getting.history && !getting.subscribe)
809
819
  return dt_get_patches(resource.dt.doc,
810
- getting.history === 'since-parents' ? options.parents : undefined)
820
+ getting.history === 'since-parents' ? options.parents : undefined)
811
821
 
812
822
  if (getting.subscribe) {
813
823
  var client = {