braid-text 0.2.86 → 0.2.88

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,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(node test/test.js:*)"
5
+ ],
6
+ "deny": [],
7
+ "ask": []
8
+ }
9
+ }
package/index.js CHANGED
@@ -160,24 +160,86 @@ function create_braid_text() {
160
160
  }
161
161
  }
162
162
 
163
- // local -> remote
163
+ // local -> remote (with in_flight queue for concurrency control)
164
+ var q = []
165
+ var in_flight = new Map()
166
+ var max_in_flight = 10
167
+ var send_pump_lock = 0
168
+ var temp_acs = new Set()
169
+ ac.signal.addEventListener('abort', () => {
170
+ for (var t of temp_acs) t.abort()
171
+ })
172
+
173
+ async function send_out(update) {
174
+ update.signal = ac.signal
175
+ update.dont_retry = true
176
+ var x = await braid_text.put(b, update)
177
+ if (x.ok) {
178
+ local_first_put()
179
+ extend_fork_point(update)
180
+ } else if (x.status === 401 || x.status === 403) {
181
+ options.on_unauthorized?.()
182
+ } else throw new Error('failed to PUT: ' + x.status)
183
+ }
184
+
185
+ async function send_pump() {
186
+ send_pump_lock++
187
+ if (send_pump_lock > 1) return
188
+ try {
189
+ if (closed) return
190
+ if (in_flight.size >= max_in_flight) return
191
+ if (!q.length) {
192
+ // Extend frontier based on in-flight updates
193
+ var frontier = resource.meta.fork_point || []
194
+ for (var u of in_flight.values())
195
+ frontier = extend_frontier(frontier, u.version, u.parents)
196
+
197
+ var temp_ac = new AbortController()
198
+ temp_acs.add(temp_ac)
199
+ var temp_ops = {
200
+ signal: temp_ac.signal,
201
+ parents: frontier,
202
+ merge_type: 'dt',
203
+ subscribe: u => u.version?.length && q.push(u)
204
+ }
205
+ await braid_text.get(a, temp_ops)
206
+ temp_ac.abort()
207
+ temp_acs.delete(temp_ac)
208
+ }
209
+ while (q.length && in_flight.size < max_in_flight) {
210
+ let u = q.shift()
211
+ if (!u.version?.length) continue
212
+ in_flight.set(u.version[0], u);
213
+ (async () => {
214
+ try {
215
+ if (closed) return
216
+ await send_out(u)
217
+ if (closed) return
218
+ in_flight.delete(u.version[0])
219
+ setTimeout(send_pump, 0)
220
+ } catch (e) {
221
+ if (e.name === 'AbortError') {
222
+ // ignore
223
+ } else handle_error(e)
224
+ }
225
+ })()
226
+ }
227
+ } finally {
228
+ var retry = send_pump_lock > 1
229
+ send_pump_lock = 0
230
+ if (retry) setTimeout(send_pump, 0)
231
+ }
232
+ }
233
+
164
234
  var a_ops = {
165
235
  signal: ac.signal,
236
+ merge_type: 'dt',
166
237
  subscribe: update => {
167
- update.signal = ac.signal
168
- update.dont_retry = true
169
- braid_text.put(b, update).then((x) => {
170
- if (x.ok) {
171
- local_first_put()
172
- extend_fork_point(update)
173
- } else if (x.status === 401 || x.status === 403) {
174
- options.on_unauthorized?.()
175
- } else throw new Error('failed to PUT: ' + x.status)
176
- }).catch(e => {
177
- if (e.name === 'AbortError') {
178
- // ignore
179
- } else handle_error(e)
180
- })
238
+ if (closed) return
239
+ if (update.version?.length) {
240
+ q.push(update)
241
+ send_pump()
242
+ }
181
243
  }
182
244
  }
183
245
  if (resource.meta.fork_point)
@@ -185,12 +247,30 @@ function create_braid_text() {
185
247
  braid_text.get(a, a_ops)
186
248
 
187
249
  // remote -> local
250
+ var remote_res_done
251
+ var remote_res_promise = new Promise(done => remote_res_done = done)
252
+ var remote_res = null
253
+
188
254
  var b_ops = {
189
255
  signal: ac.signal,
190
256
  dont_retry: true,
257
+ headers: { 'Merge-Type': 'dt', 'accept-encoding': 'updates(dt)' },
191
258
  subscribe: async update => {
192
- await braid_text.put(a, update)
193
- extend_fork_point(update)
259
+ // Wait for remote_res to be available
260
+ await remote_res_promise
261
+
262
+ // Check if this is a dt-encoded update (initial body without status)
263
+ if (!update.status) {
264
+ var cv = remote_res.headers.get('current-version')
265
+ await braid_text.put(a, {
266
+ body: update.body,
267
+ transfer_encoding: 'dt'
268
+ })
269
+ if (cv) extend_fork_point({ version: JSON.parse(`[${cv}]`), parents: resource.meta.fork_point || [] })
270
+ } else {
271
+ await braid_text.put(a, update)
272
+ if (update.version) extend_fork_point(update)
273
+ }
194
274
  },
195
275
  on_error: e => {
196
276
  options.on_disconnect?.()
@@ -198,15 +278,16 @@ function create_braid_text() {
198
278
  }
199
279
  }
200
280
  // Handle case where remote doesn't exist yet - wait for local to create it
201
- var remote_result = await braid_text.get(b, b_ops)
202
- if (remote_result === null) {
281
+ remote_res = await braid_text.get(b, b_ops)
282
+ remote_res_done()
283
+ if (remote_res === null) {
203
284
  // Remote doesn't exist yet, wait for local to put something
204
285
  await local_first_put_promise
205
286
  disconnect()
206
287
  connect()
207
288
  return
208
289
  }
209
- options.on_res?.(remote_result)
290
+ options.on_res?.(remote_res)
210
291
  // on_error will call handle_error when connection drops
211
292
  } catch (e) {
212
293
  handle_error(e)
@@ -548,9 +629,12 @@ function create_braid_text() {
548
629
 
549
630
  if (options.subscribe) {
550
631
  res.subscribe(async update => {
551
- update.body = update.body_text
552
- if (update.patches)
553
- for (var p of update.patches) p.content = p.content_text
632
+ // Don't convert to text for initial dt-encoded body (no status)
633
+ if (update.status) {
634
+ update.body = update.body_text
635
+ if (update.patches)
636
+ for (var p of update.patches) p.content = p.content_text
637
+ }
554
638
  await options.subscribe(update)
555
639
  }, e => options.on_error?.(e))
556
640
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-text",
3
- "version": "0.2.86",
3
+ "version": "0.2.88",
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/test/tests.js CHANGED
@@ -2066,6 +2066,39 @@ runTest(
2066
2066
  'on_res called'
2067
2067
  )
2068
2068
 
2069
+ runTest(
2070
+ "test braid_text.sync uses accept-encoding updates(dt)",
2071
+ async () => {
2072
+ var remote_key = 'test-remote-' + Math.random().toString(36).slice(2)
2073
+ var local_key = 'test-local-' + Math.random().toString(36).slice(2)
2074
+
2075
+ // Create the remote resource with some content
2076
+ var r = await braid_fetch(`/${remote_key}`, {
2077
+ method: 'PUT',
2078
+ body: 'remote content here'
2079
+ })
2080
+ if (!r.ok) return 'put failed: ' + r.status
2081
+
2082
+ // Start sync with URL first (like the passing "url to key" test)
2083
+ var r = await braid_fetch(`/eval`, {
2084
+ method: 'PUT',
2085
+ body: `void (async () => {
2086
+ braid_text.sync(new URL('http://localhost:8889/${remote_key}'), '/${local_key}')
2087
+ res.end('')
2088
+ })()`
2089
+ })
2090
+ if (!r.ok) return 'eval failed: ' + r.status
2091
+
2092
+ // Wait for sync to complete
2093
+ await new Promise(done => setTimeout(done, 100))
2094
+
2095
+ // Check local has remote content
2096
+ var r = await braid_fetch(`/${local_key}`)
2097
+ return await r.text()
2098
+ },
2099
+ 'remote content here'
2100
+ )
2101
+
2069
2102
  runTest(
2070
2103
  "test braid_text.sync reconnects when inner put fails with non-200 status",
2071
2104
  async () => {