braid-text 0.2.36 → 0.2.38

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
@@ -13,7 +13,6 @@ let braid_text = {
13
13
  }
14
14
 
15
15
  let waiting_puts = 0
16
- let prev_put_p = null
17
16
 
18
17
  let max_encoded_key_size = 240
19
18
 
@@ -152,28 +151,15 @@ braid_text.serve = async (req, res, options = {}) => {
152
151
 
153
152
  waiting_puts++
154
153
  if (braid_text.verbose) console.log(`waiting_puts(after++) = ${waiting_puts}`)
154
+ let done_my_turn = (statusCode, x, statusText, headers) => {
155
+ waiting_puts--
156
+ if (braid_text.verbose) console.log(`waiting_puts(after--) = ${waiting_puts}`)
157
+ my_end(statusCode, x, statusText, headers)
158
+ }
155
159
 
156
- let my_prev_put_p = prev_put_p
157
- let done_my_turn = null
158
- prev_put_p = new Promise(
159
- (done) =>
160
- (done_my_turn = (statusCode, x, statusText, headers) => {
161
- waiting_puts--
162
- if (braid_text.verbose) console.log(`waiting_puts(after--) = ${waiting_puts}`)
163
- my_end(statusCode, x, statusText, headers)
164
- done()
165
- })
166
- )
167
- let patches = null
168
- let ee = null
169
160
  try {
170
- patches = await req.patches()
161
+ var patches = await req.patches()
171
162
  for (let p of patches) p.content = p.content_text
172
- } catch (e) { ee = e }
173
- await my_prev_put_p
174
-
175
- try {
176
- if (ee) throw ee
177
163
 
178
164
  let body = null
179
165
  if (patches[0]?.unit === 'everything') {
@@ -181,8 +167,20 @@ braid_text.serve = async (req, res, options = {}) => {
181
167
  patches = null
182
168
  }
183
169
 
184
- await braid_text.put(resource, { peer, version: req.version, parents: req.parents, patches, body, merge_type })
185
-
170
+ if (req.parents) await wait_for_events(
171
+ options.key,
172
+ req.parents,
173
+ resource.actor_seqs,
174
+ // approximation of memory usage for this update
175
+ body ? body.length :
176
+ patches.reduce((a, b) => a + b.range.length + b.content.length, 0),
177
+ options.put_buffer_max_time,
178
+ options.put_buffer_max_space)
179
+
180
+ var {change_count} = await braid_text.put(resource, { peer, version: req.version, parents: req.parents, patches, body, merge_type })
181
+
182
+ if (req.version) got_event(options.key, req.version[0], change_count)
183
+
186
184
  res.setHeader("Version", get_current_version())
187
185
 
188
186
  options.put_cb(options.key, resource.val)
@@ -509,7 +507,7 @@ braid_text.put = async (key, options) => {
509
507
 
510
508
  resource.need_defrag = true
511
509
 
512
- await resource.db_delta(resource.doc.getPatchSince(v_before))
510
+ var post_commit_updates = []
513
511
 
514
512
  if (options.merge_type != "dt") {
515
513
  patches = get_xf_patches(resource.doc, v_before)
@@ -586,7 +584,7 @@ braid_text.put = async (key, options) => {
586
584
  x.patches = patches
587
585
  }
588
586
  if (braid_text.verbose) console.log(`sending: ${JSON.stringify(x)}`)
589
- client.subscribe(x)
587
+ post_commit_updates.push([client, x])
590
588
  client.my_last_sent_version = x.version
591
589
  }
592
590
  } else {
@@ -597,20 +595,27 @@ braid_text.put = async (key, options) => {
597
595
  if (braid_text.verbose) console.log(`sending: ${JSON.stringify(x)}`)
598
596
  for (let client of resource.simpleton_clients) {
599
597
  if (client.my_timeout) continue
600
- client.subscribe(x)
598
+ post_commit_updates.push([client, x])
601
599
  client.my_last_sent_version = x.version
602
600
  }
603
601
  }
604
602
  }
605
603
 
606
- let x = {
604
+ var x = {
607
605
  version: [og_v],
608
606
  parents: og_parents,
609
607
  patches: og_patches,
610
608
  }
611
609
  for (let client of resource.clients) {
612
- if (!peer || client.peer !== peer) client.subscribe(x)
610
+ if (!peer || client.peer !== peer)
611
+ post_commit_updates.push([client, x])
613
612
  }
613
+
614
+ await resource.db_delta(resource.doc.getPatchSince(v_before))
615
+
616
+ for (var [client, x] of post_commit_updates) client.subscribe(x)
617
+
618
+ return { change_count }
614
619
  }
615
620
 
616
621
  braid_text.list = async () => {
@@ -820,6 +825,106 @@ async function file_sync(key, process_delta, get_init) {
820
825
  }
821
826
  }
822
827
 
828
+ async function wait_for_events(
829
+ key,
830
+ events,
831
+ actor_seqs,
832
+ my_space,
833
+ max_time = 3000,
834
+ max_space = 5 * 1024 * 1024) {
835
+
836
+ if (!wait_for_events.namespaces) wait_for_events.namespaces = {}
837
+ if (!wait_for_events.namespaces[key]) wait_for_events.namespaces[key] = {}
838
+ var ns = wait_for_events.namespaces[key]
839
+
840
+ if (!wait_for_events.space_used) wait_for_events.space_used = 0
841
+ if (wait_for_events.space_used + my_space > max_space) return
842
+ wait_for_events.space_used += my_space
843
+
844
+ var p_done = null
845
+ var p = new Promise(done => p_done = done)
846
+
847
+ var missing = 0
848
+ var on_find = () => {
849
+ missing--
850
+ if (!missing) p_done()
851
+ }
852
+
853
+ for (let event of events) {
854
+ var [actor, seq] = decode_version(event)
855
+ if (actor_seqs?.[actor]?.has(seq)) continue
856
+ missing++
857
+
858
+ if (!ns.actor_seqs) ns.actor_seqs = {}
859
+ if (!ns.actor_seqs[actor]) ns.actor_seqs[actor] = []
860
+ sorted_set_insert(ns.actor_seqs[actor], seq)
861
+
862
+ if (!ns.events) ns.events = {}
863
+ if (!ns.events[event]) ns.events[event] = new Set()
864
+ ns.events[event].add(on_find)
865
+ }
866
+
867
+ if (missing) {
868
+ var t = setTimeout(() => {
869
+ for (let event of events) {
870
+ var [actor, seq] = decode_version(event)
871
+
872
+ var cbs = ns.events[event]
873
+ if (!cbs) continue
874
+
875
+ cbs.delete(on_find)
876
+ if (cbs.size) continue
877
+
878
+ delete ns.events[event]
879
+
880
+ var seqs = ns.actor_seqs[actor]
881
+ if (!seqs) continue
882
+
883
+ sorted_set_delete(seqs, seq)
884
+ if (seqs.length) continue
885
+
886
+ delete ns.actor_seqs[actor]
887
+ }
888
+ p_done()
889
+ }, max_time)
890
+
891
+ await p
892
+
893
+ clearTimeout(t)
894
+ }
895
+ wait_for_events.space_used -= my_space
896
+ }
897
+
898
+ async function got_event(key, event, change_count) {
899
+ var ns = wait_for_events.namespaces?.[key]
900
+ if (!ns) return
901
+
902
+ var [actor, seq] = decode_version(event)
903
+ var base_seq = seq + 1 - change_count
904
+
905
+ var seqs = ns.actor_seqs?.[actor]
906
+ if (!seqs) return
907
+
908
+ // binary search to find the first i >= base_seq
909
+ var i = 0, end = seqs.length
910
+ while (i < end) {
911
+ var mid = (i + end) >> 1
912
+ seqs[mid] < base_seq ? i = mid + 1 : end = mid
913
+ }
914
+ var start = i
915
+
916
+ // iterate up through seq
917
+ while (i < seqs.length && seqs[i] <= seq) {
918
+ var e = actor + "-" + seqs[i]
919
+ ns.events?.[e]?.forEach(cb => cb())
920
+ delete ns.events?.[e]
921
+ i++
922
+ }
923
+
924
+ seqs.splice(start, i - start)
925
+ if (!seqs.length) delete ns.actor_seqs[actor]
926
+ }
927
+
823
928
  //////////////////////////////////////////////////////////////////
824
929
  //////////////////////////////////////////////////////////////////
825
930
  //////////////////////////////////////////////////////////////////
@@ -1908,6 +2013,25 @@ function ascii_ify(s) {
1908
2013
  return s.replace(/[^\x20-\x7E]/g, c => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0'))
1909
2014
  }
1910
2015
 
2016
+ function sorted_set_find(arr, val) {
2017
+ var left = 0, right = arr.length
2018
+ while (left < right) {
2019
+ var mid = (left + right) >> 1
2020
+ arr[mid] < val ? left = mid + 1 : right = mid
2021
+ }
2022
+ return left
2023
+ }
2024
+
2025
+ function sorted_set_insert(arr, val) {
2026
+ var i = sorted_set_find(arr, val)
2027
+ if (arr[i] !== val) arr.splice(i, 0, val)
2028
+ }
2029
+
2030
+ function sorted_set_delete(arr, val) {
2031
+ var i = sorted_set_find(arr, val)
2032
+ if (arr[i] === val) arr.splice(i, 1)
2033
+ }
2034
+
1911
2035
  braid_text.get_resource = get_resource
1912
2036
 
1913
2037
  braid_text.encode_filename = encode_filename
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-text",
3
- "version": "0.2.36",
3
+ "version": "0.2.38",
4
4
  "description": "Library for collaborative text over http using braid.",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braid-text",
@@ -115,7 +115,7 @@ function simpleton_client(url, { apply_remote_update, generate_local_diff_update
115
115
 
116
116
  outstanding_changes++
117
117
  try {
118
- await braid_fetch(url, {
118
+ var r = await braid_fetch(url, {
119
119
  headers: { "Merge-Type": "simpleton",
120
120
  ...(content_type ? {"Content-Type": content_type} : {}) },
121
121
  method: "PUT",
@@ -123,6 +123,7 @@ function simpleton_client(url, { apply_remote_update, generate_local_diff_update
123
123
  version, parents, patches,
124
124
  peer
125
125
  })
126
+ if (!r.ok) throw new Error(`bad http status: ${r.status}${(r.status === 401 || r.status === 403) ? ` (access denied)` : ''}`)
126
127
  } catch (e) {
127
128
  on_error(e)
128
129
  throw e
package/test/test.html CHANGED
@@ -68,6 +68,192 @@ async function runTest(testName, testFunction, expectedResult) {
68
68
  }
69
69
  }
70
70
 
71
+ runTest(
72
+ "test when PUT cache/buffer size fails",
73
+ async () => {
74
+ var key = 'test-' + Math.random().toString(36).slice(2)
75
+
76
+ var f1 = braid_fetch(`/${key}`, {
77
+ method: 'PUT',
78
+ version: ['hi-3000000'],
79
+ parents: ['yo-0'],
80
+ body: 'A'.repeat(3000000)
81
+ })
82
+
83
+ await new Promise(done => setTimeout(done, 300))
84
+
85
+ var f2 = braid_fetch(`/${key}`, {
86
+ method: 'PUT',
87
+ version: ['ih-3000000'],
88
+ parents: ['yo-0'],
89
+ body: 'B'.repeat(3000000)
90
+ })
91
+
92
+ await new Promise(done => setTimeout(done, 300))
93
+
94
+ var r = await braid_fetch(`/${key}`, {
95
+ method: 'PUT',
96
+ version: ['yo-0'],
97
+ parents: [],
98
+ body: 'x'
99
+ })
100
+ if (!r.ok) throw 'got: ' + r.statusCode
101
+
102
+ return `f1: ${(await f1).status}, f2: ${(await f2).status}`
103
+ },
104
+ 'f1: 200, f2: 309'
105
+ )
106
+
107
+ runTest(
108
+ "test multiple patches",
109
+ async () => {
110
+ var key = 'test-' + Math.random().toString(36).slice(2)
111
+
112
+ var r = await braid_fetch(`/${key}`, {
113
+ method: 'PUT',
114
+ version: ['hi-0'],
115
+ parents: [],
116
+ body: 'A'
117
+ })
118
+ if (!r.ok) throw 'got: ' + r.statusCode
119
+
120
+ var r = await braid_fetch(`/${key}`, {
121
+ method: 'PUT',
122
+ version: ['yo-1'],
123
+ parents: ['hi-0'],
124
+ patches: [
125
+ {unit: 'text', range: '[0:0]', content: 'C'},
126
+ {unit: 'text', range: '[1:1]', content: 'T'}
127
+ ]
128
+ })
129
+ if (!r.ok) throw 'got: ' + r.statusCode
130
+
131
+ var r2 = await braid_fetch(`/${key}`)
132
+ return await r2.text()
133
+ },
134
+ 'CAT'
135
+ )
136
+
137
+ runTest(
138
+ "test PUT after subscribing",
139
+ async () => {
140
+ var key = 'test-' + Math.random().toString(36).slice(2)
141
+
142
+ var p_done
143
+ var p = new Promise(done => p_done = done)
144
+
145
+ var r = await braid_fetch(`/${key}`, {
146
+ subscribe: true
147
+ })
148
+ r.subscribe(update => {
149
+ if (update.version[0] === 'hi-0')
150
+ p_done(update.patches[0].content_text)
151
+ })
152
+
153
+ var r = await braid_fetch(`/${key}`, {
154
+ method: 'PUT',
155
+ version: ['hi-0'],
156
+ parents: [],
157
+ body: 'x'
158
+ })
159
+ if (!r.ok) throw 'got: ' + r.statusCode
160
+
161
+ return await p
162
+ },
163
+ 'x'
164
+ )
165
+
166
+ runTest(
167
+ "test out-of-order PUTs",
168
+ async () => {
169
+ var key = 'test-' + Math.random().toString(36).slice(2)
170
+
171
+ var f = braid_fetch(`/${key}`, {
172
+ method: 'PUT',
173
+ version: ['hi-1'],
174
+ parents: ['hi-0'],
175
+ patches: [{unit: 'text', range: '[1:1]', content: 'y'}]
176
+ })
177
+
178
+ await new Promise(done => setTimeout(done, 500))
179
+
180
+ var r = await braid_fetch(`/${key}`, {
181
+ method: 'PUT',
182
+ version: ['hi-0'],
183
+ parents: [],
184
+ body: 'x'
185
+ })
186
+
187
+ if (!r.ok) throw 'got: ' + r.statusCode
188
+
189
+ r = await f
190
+ if (!r.ok) throw 'got: ' + r.statusCode
191
+
192
+ var r2 = await braid_fetch(`/${key}`)
193
+ return await r2.text()
194
+ },
195
+ 'xy'
196
+ )
197
+
198
+ runTest(
199
+ "test out-of-order PUTs (trial two)",
200
+ async () => {
201
+ var key = 'test-' + Math.random().toString(36).slice(2)
202
+
203
+ var f = braid_fetch(`/${key}`, {
204
+ method: 'PUT',
205
+ version: ['ab-1'],
206
+ parents: ['hi-0'],
207
+ patches: [{unit: 'text', range: '[1:1]', content: 'y'}]
208
+ })
209
+
210
+ await new Promise(done => setTimeout(done, 500))
211
+
212
+ var r = await braid_fetch(`/${key}`, {
213
+ method: 'PUT',
214
+ version: ['hi-1'],
215
+ parents: [],
216
+ body: 'xz'
217
+ })
218
+
219
+ if (!r.ok) throw 'got: ' + r.statusCode
220
+
221
+ r = await f
222
+ if (!r.ok) throw 'got: ' + r.statusCode
223
+
224
+ var r2 = await braid_fetch(`/${key}`)
225
+ return await r2.text()
226
+ },
227
+ 'xyz'
228
+ )
229
+
230
+ runTest(
231
+ "test in-order PUTs",
232
+ async () => {
233
+ var key = 'test-' + Math.random().toString(36).slice(2)
234
+
235
+ var r = await braid_fetch(`/${key}`, {
236
+ method: 'PUT',
237
+ version: ['hi-0'],
238
+ parents: [],
239
+ body: 'x'
240
+ })
241
+ if (!r.ok) throw 'got: ' + r.statusCode
242
+
243
+ var r = await braid_fetch(`/${key}`, {
244
+ method: 'PUT',
245
+ version: ['hi-1'],
246
+ parents: ['hi-0'],
247
+ patches: [{unit: 'text', range: '[1:1]', content: 'y'}]
248
+ })
249
+ if (!r.ok) throw 'got: ' + r.statusCode
250
+
251
+ var r2 = await braid_fetch(`/${key}`)
252
+ return await r2.text()
253
+ },
254
+ 'xy'
255
+ )
256
+
71
257
  runTest(
72
258
  "test transfer-encoding dt (with parents)",
73
259
  async () => {