braid-text 0.2.77 → 0.2.78

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/index.js CHANGED
@@ -17,25 +17,37 @@ function create_braid_text() {
17
17
  let max_encoded_key_size = 240
18
18
 
19
19
  braid_text.sync = async (a, b, options = {}) => {
20
- var unsync_cbs = []
21
- options.my_unsync = () => unsync_cbs.forEach(cb => cb())
22
-
23
20
  if (!options.merge_type) options.merge_type = 'dt'
24
21
 
25
22
  if ((a instanceof URL) === (b instanceof URL)) {
23
+ // Both are URLs or both are local keys
24
+ var a_first_put, b_first_put
25
+ var a_first_put_promise = new Promise(done => a_first_put = done)
26
+ var b_first_put_promise = new Promise(done => b_first_put = done)
27
+
26
28
  var a_ops = {
27
- subscribe: update => braid_text.put(b, update),
29
+ signal: options.signal,
30
+ subscribe: update => {
31
+ update.signal = options.signal
32
+ braid_text.put(b, update).then(a_first_put)
33
+ },
28
34
  merge_type: options.merge_type,
29
35
  }
30
- braid_text.get(a, a_ops)
31
- unsync_cbs.push(() => braid_text.forget(a, a_ops))
36
+ braid_text.get(a, a_ops).then(x =>
37
+ x || b_first_put_promise.then(() =>
38
+ braid_text.get(a, a_ops)))
32
39
 
33
40
  var b_ops = {
34
- subscribe: update => braid_text.put(a, update),
41
+ signal: options.signal,
42
+ subscribe: update => {
43
+ update.signal = options.signal
44
+ braid_text.put(a, update).then(b_first_put)
45
+ },
35
46
  merge_type: options.merge_type,
36
47
  }
37
- braid_text.get(b, b_ops)
38
- unsync_cbs.push(() => braid_text.forget(b, b_ops))
48
+ braid_text.get(b, b_ops).then(x =>
49
+ x || a_first_put_promise.then(() =>
50
+ braid_text.get(b, b_ops)))
39
51
  } else {
40
52
  // make a=local and b=remote (swap if not)
41
53
  if (a instanceof URL) { let swap = a; a = b; b = swap }
@@ -78,12 +90,15 @@ function create_braid_text() {
78
90
  }
79
91
 
80
92
  var closed
81
- var disconnect
82
- unsync_cbs.push(() => {
93
+ var disconnect = () => {}
94
+ options.signal?.addEventListener('abort', () => {
83
95
  closed = true
84
96
  disconnect()
85
97
  })
86
98
 
99
+ var local_first_put
100
+ var local_first_put_promise = new Promise(done => local_first_put = done)
101
+
87
102
  connect()
88
103
  async function connect() {
89
104
  if (options.on_connect) options.on_connect()
@@ -91,9 +106,7 @@ function create_braid_text() {
91
106
  if (closed) return
92
107
 
93
108
  var ac = new AbortController()
94
- var disconnect_cbs = [() => ac.abort()]
95
-
96
- disconnect = () => disconnect_cbs.forEach(cb => cb())
109
+ disconnect = () => ac.abort()
97
110
 
98
111
  try {
99
112
  // fork-point
@@ -142,9 +155,11 @@ function create_braid_text() {
142
155
 
143
156
  // local -> remote
144
157
  var a_ops = {
158
+ signal: ac.signal,
145
159
  subscribe: update => {
146
160
  update.signal = ac.signal
147
161
  braid_text.put(b, update).then((x) => {
162
+ local_first_put()
148
163
  extend_fork_point(update)
149
164
  }).catch(e => {
150
165
  if (e.name === 'AbortError') {
@@ -155,20 +170,26 @@ function create_braid_text() {
155
170
  }
156
171
  if (resource.meta.fork_point)
157
172
  a_ops.parents = resource.meta.fork_point
158
- disconnect_cbs.push(() => braid_text.forget(a, a_ops))
159
173
  braid_text.get(a, a_ops)
160
174
 
161
175
  // remote -> local
162
176
  var b_ops = {
177
+ signal: ac.signal,
163
178
  dont_retry: true,
164
179
  subscribe: async update => {
165
180
  await braid_text.put(a, update)
166
181
  extend_fork_point(update)
167
182
  },
168
183
  }
169
- disconnect_cbs.push(() => braid_text.forget(b, b_ops))
170
- // NOTE: this should not return, but it might throw
171
- await braid_text.get(b, b_ops)
184
+ // Handle case where remote doesn't exist yet - wait for local to create it
185
+ var remote_result = await braid_text.get(b, b_ops)
186
+ if (remote_result === null) {
187
+ // Remote doesn't exist yet, wait for local to put something
188
+ await local_first_put_promise
189
+ disconnect()
190
+ connect()
191
+ }
192
+ // NOTE: if remote exists, this should not return, but it might throw
172
193
  } catch (e) {
173
194
  if (closed) return
174
195
 
@@ -176,7 +197,7 @@ function create_braid_text() {
176
197
  console.log(`disconnected, retrying in 1 second`)
177
198
  setTimeout(connect, 1000)
178
199
  }
179
- }
200
+ }
180
201
  }
181
202
  }
182
203
 
@@ -468,15 +489,9 @@ function create_braid_text() {
468
489
 
469
490
  // Handle URL - make a DELETE request
470
491
  if (key instanceof URL) {
471
- options.my_abort = new AbortController()
472
- if (options.signal)
473
- options.signal.addEventListener('abort', () =>
474
- options.my_abort.abort())
475
-
476
492
  var params = {
477
493
  method: 'DELETE',
478
- signal: options.my_abort.signal,
479
- retry: () => true,
494
+ signal: options.signal,
480
495
  }
481
496
  for (var x of ['headers', 'peer'])
482
497
  if (options[x] != null) params[x] = options[x]
@@ -502,19 +517,21 @@ function create_braid_text() {
502
517
  if (key instanceof URL) {
503
518
  if (!options) options = {}
504
519
 
505
- options.my_abort = new AbortController()
506
-
507
520
  var params = {
508
- signal: options.my_abort.signal,
521
+ signal: options.signal,
509
522
  subscribe: !!options.subscribe,
510
523
  heartbeats: 120,
511
524
  }
512
- if (!options.dont_retry) params.retry = () => true
525
+ if (!options.dont_retry) {
526
+ params.retry = (res) => res.status !== 404
527
+ }
513
528
  for (var x of ['headers', 'parents', 'version', 'peer'])
514
529
  if (options[x] != null) params[x] = options[x]
515
530
 
516
531
  var res = await braid_fetch(key.href, params)
517
532
 
533
+ if (res.status === 404) return null
534
+
518
535
  if (options.subscribe) {
519
536
  if (options.dont_retry) {
520
537
  var error_happened
@@ -603,6 +620,8 @@ function create_braid_text() {
603
620
 
604
621
  options.my_last_sent_version = x.version
605
622
  resource.simpleton_clients.add(options)
623
+ options.signal?.addEventListener('abort', () =>
624
+ resource.simpleton_clients.delete(options))
606
625
  } else {
607
626
 
608
627
  if (options.accept_encoding?.match(/updates\s*\((.*)\)/)?.[1].split(',').map(x=>x.trim()).includes('dt')) {
@@ -650,22 +669,12 @@ function create_braid_text() {
650
669
  }
651
670
 
652
671
  resource.clients.add(options)
672
+ options.signal?.addEventListener('abort', () =>
673
+ resource.clients.delete(options))
653
674
  }
654
675
  }
655
676
  }
656
677
 
657
- braid_text.forget = async (key, options) => {
658
- if (!options) throw new Error('options is required')
659
-
660
- if (key instanceof URL) return options.my_abort.abort()
661
-
662
- let resource = (typeof key == 'string') ? await get_resource(key) : key
663
-
664
- if (options.merge_type != "dt")
665
- resource.simpleton_clients.delete(options)
666
- else resource.clients.delete(options)
667
- }
668
-
669
678
  braid_text.put = async (key, options) => {
670
679
  if (options.version) {
671
680
  validate_version_array(options.version)
@@ -677,14 +686,9 @@ function create_braid_text() {
677
686
  }
678
687
 
679
688
  if (key instanceof URL) {
680
- options.my_abort = new AbortController()
681
- if (options.signal)
682
- options.signal.addEventListener('abort', () =>
683
- options.my_abort.abort())
684
-
685
689
  var params = {
686
690
  method: 'PUT',
687
- signal: options.my_abort.signal,
691
+ signal: options.signal,
688
692
  retry: () => true,
689
693
  }
690
694
  for (var x of ['headers', 'parents', 'version', 'peer', 'body', 'patches'])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-text",
3
- "version": "0.2.77",
3
+ "version": "0.2.78",
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/fuzz-test.js CHANGED
@@ -270,16 +270,20 @@ async function main() {
270
270
 
271
271
  // try getting updates from middle_doc to doc
272
272
  if (!v_eq(middle_v, doc_v)) {
273
- var o
273
+ var ac = new AbortController()
274
274
  await new Promise(async done => {
275
- o = {merge_type: 'dt', parents: middle_v, subscribe: async update => {
276
- await braid_text.put('middle_doc', update)
277
- middle_v = (await braid_text.get_resource('middle_doc')).version
278
- if (v_eq(doc_v, middle_v)) done()
279
- }}
280
- braid_text.get('doc', o)
275
+ braid_text.get('doc', {
276
+ signal: ac.signal,
277
+ merge_type: 'dt',
278
+ parents: middle_v,
279
+ subscribe: async update => {
280
+ await braid_text.put('middle_doc', update)
281
+ middle_v = (await braid_text.get_resource('middle_doc')).version
282
+ if (v_eq(doc_v, middle_v)) done()
283
+ }
284
+ })
281
285
  })
282
- await braid_text.forget('doc', o)
286
+ ac.abort()
283
287
  }
284
288
 
285
289
  if (await braid_text.get('middle_doc') != await braid_text.get('doc')) throw new Error('bad')
package/test/tests.js CHANGED
@@ -50,13 +50,14 @@ runTest(
50
50
  method: 'PUT',
51
51
  body: `void (async () => {
52
52
  var count = 0
53
- var ops
54
- braid_text.sync('/${key}', new URL('http://localhost:8889/have_error'), ops = {
53
+ var ac = new AbortController()
54
+ braid_text.sync('/${key}', new URL('http://localhost:8889/have_error'), {
55
+ signal: ac.signal,
55
56
  on_connect: () => {
56
57
  count++
57
58
  if (count === 2) {
58
59
  res.end('it reconnected!')
59
- ops.my_unsync()
60
+ ac.abort()
60
61
  }
61
62
  }
62
63
  })
@@ -201,13 +202,14 @@ runTest(
201
202
  method: 'PUT',
202
203
  body: `void (async () => {
203
204
  var count = 0
204
- var ops
205
- braid_text.sync('/${key_a}', new URL('http://localhost:8889/have_error'), ops = {
205
+ var ac = new AbortController()
206
+ braid_text.sync('/${key_a}', new URL('http://localhost:8889/have_error'), {
207
+ signal: ac.signal,
206
208
  on_connect: () => {
207
209
  count++
208
210
  if (count === 2) {
209
211
  res.end('it reconnected!')
210
- ops.my_unsync()
212
+ ac.abort()
211
213
  }
212
214
  }
213
215
  })
@@ -265,10 +267,10 @@ runTest(
265
267
  var r = await braid_fetch(`/eval`, {
266
268
  method: 'PUT',
267
269
  body: `void (async () => {
268
- var ops = {}
269
- braid_text.sync('/${key_a}', '/${key_b}', ops)
270
+ var ac = new AbortController()
271
+ braid_text.sync('/${key_a}', '/${key_b}', {signal: ac.signal})
270
272
  await new Promise(done => setTimeout(done, 100))
271
- ops.my_unsync()
273
+ ac.abort()
272
274
  res.end('')
273
275
  })()`
274
276
  })
@@ -283,10 +285,10 @@ runTest(
283
285
  var r = await braid_fetch(`/eval`, {
284
286
  method: 'PUT',
285
287
  body: `void (async () => {
286
- var ops = {}
287
- braid_text.sync('/${key_a}', new URL('http://localhost:8889/${key_b}'), ops)
288
+ var ac = new AbortController()
289
+ braid_text.sync('/${key_a}', new URL('http://localhost:8889/${key_b}'), {signal: ac.signal})
288
290
  await new Promise(done => setTimeout(done, 100))
289
- ops.my_unsync()
291
+ ac.abort()
290
292
  res.end('')
291
293
  })()`
292
294
  })
@@ -456,14 +458,15 @@ runTest(
456
458
  method: 'PUT',
457
459
  body: `void (async () => {
458
460
  var url = new URL('http://localhost:8889/${key}')
461
+ var ac = new AbortController()
459
462
  var update = await new Promise(done => {
460
- var o = {
463
+ braid_text.get(url, {
464
+ signal: ac.signal,
461
465
  subscribe: update => {
462
- braid_text.forget(url, o)
466
+ ac.abort()
463
467
  done(update)
464
468
  }
465
- }
466
- braid_text.get(url, o)
469
+ })
467
470
  })
468
471
  res.end(update.body)
469
472
  })()`
@@ -1929,6 +1932,62 @@ runTest(
1929
1932
  'got: '
1930
1933
  )
1931
1934
 
1935
+ runTest(
1936
+ "test braid_text.get(url) returns null for 404",
1937
+ async () => {
1938
+ // Use the /404 endpoint that always returns 404
1939
+ var r = await braid_fetch(`/eval`, {
1940
+ method: 'PUT',
1941
+ body: `void (async () => {
1942
+ var result = await braid_text.get(new URL('http://localhost:8889/404'))
1943
+ res.end(result === null ? 'null' : 'not null: ' + result)
1944
+ })()`
1945
+ })
1946
+ return await r.text()
1947
+ },
1948
+ 'null'
1949
+ )
1950
+
1951
+ runTest(
1952
+ "test braid_text.sync handles remote not existing yet",
1953
+ async () => {
1954
+ var local_key = 'test-local-' + Math.random().toString(36).slice(2)
1955
+ var remote_key = 'test-remote-' + Math.random().toString(36).slice(2)
1956
+
1957
+ // Start sync between a local key and a remote URL that doesn't exist yet
1958
+ // The sync should wait for local to create something, then push to remote
1959
+ var r = await braid_fetch(`/eval`, {
1960
+ method: 'PUT',
1961
+ body: `void (async () => {
1962
+ var ac = new AbortController()
1963
+
1964
+ // Start sync - remote doesn't exist yet
1965
+ braid_text.sync('/${local_key}', new URL('http://localhost:8889/${remote_key}'), {
1966
+ signal: ac.signal
1967
+ })
1968
+
1969
+ // Wait a bit then put something locally
1970
+ await new Promise(done => setTimeout(done, 100))
1971
+ await braid_text.put('/${local_key}', { body: 'created locally' })
1972
+
1973
+ // Wait for sync to propagate
1974
+ await new Promise(done => setTimeout(done, 200))
1975
+
1976
+ // Stop sync
1977
+ ac.abort()
1978
+
1979
+ res.end('done')
1980
+ })()`
1981
+ })
1982
+ if (!r.ok) return 'eval failed: ' + r.status
1983
+
1984
+ // Check that remote now has the content
1985
+ var r2 = await braid_fetch(`/${remote_key}`)
1986
+ return await r2.text()
1987
+ },
1988
+ 'created locally'
1989
+ )
1990
+
1932
1991
  runTest(
1933
1992
  "test getting a binary update from a subscription",
1934
1993
  async () => {