braid-text 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -176,3 +176,28 @@ simpleton = simpleton_client(url, options)
176
176
  - `content_type`: <small style="color:lightgrey">[optional]</small> If set, this value will be sent in the `Accept` and `Content-Type` headers to the server.
177
177
 
178
178
  - `simpleton.changed()`: Call this function to report local updates whenever they occur, e.g., in the `oninput` event handler of a textarea being synchronized.
179
+
180
+ ## Testing
181
+
182
+ ### to run unit tests:
183
+ first run the demo server as usual:
184
+
185
+ npm install
186
+ node server-demo.js
187
+
188
+ then open http://localhost:8888/test.html, and the boxes should turn green as the tests pass.
189
+
190
+ ### to run fuzz tests:
191
+
192
+ npm install
193
+ node test.js
194
+
195
+ if the last output line looks like this, good:
196
+
197
+ t = 9999, seed = 1397019, best_n = Infinity @ NaN
198
+
199
+ but it's bad if it looks like this:
200
+
201
+ t = 9999, seed = 1397019, best_n = 5 @ 1396791
202
+
203
+ the number at the end is the random seed that generated the simplest error example
package/index.js CHANGED
@@ -220,10 +220,14 @@ braid_text.get = async (key, options) => {
220
220
  let doc = resource.doc
221
221
  if (options.version || options.parents) doc = dt_get(doc, options.version || options.parents)
222
222
 
223
- return {
223
+ let ret = {
224
224
  version: doc.getRemoteVersion().map((x) => x.join("-")).sort(),
225
225
  body: doc.get()
226
226
  }
227
+
228
+ if (options.version || options.parents) doc.free()
229
+
230
+ return ret
227
231
  } else {
228
232
  if (options.merge_type != "dt") {
229
233
  let version = resource.doc.getRemoteVersion().map((x) => x.join("-")).sort()
@@ -269,17 +273,12 @@ braid_text.get = async (key, options) => {
269
273
  updates = dt_get_patches(resource.doc, options.parents || options.version)
270
274
  }
271
275
 
272
- for (let u of updates) {
273
- u.version = decode_version(u.version)
274
- u.version[1] += u.end - u.start - 1
275
- u.version = u.version.join("-")
276
-
276
+ for (let u of updates)
277
277
  options.subscribe({
278
278
  version: [u.version],
279
279
  parents: u.parents,
280
280
  patches: [{ unit: u.unit, range: u.range, content: u.content }],
281
281
  })
282
- }
283
282
 
284
283
  // Output at least *some* data, or else chrome gets confused and
285
284
  // thinks the connection failed. This isn't strictly necessary,
@@ -292,9 +291,32 @@ braid_text.get = async (key, options) => {
292
291
  }
293
292
  }
294
293
 
294
+ braid_text.forget = async (key, options) => {
295
+ if (!options) throw new Error('options is required')
296
+
297
+ let resource = (typeof key == 'string') ? await get_resource(key) : key
298
+
299
+ if (options.merge_type != "dt")
300
+ resource.simpleton_clients.delete(options)
301
+ else resource.clients.delete(options)
302
+ }
303
+
295
304
  braid_text.put = async (key, options) => {
296
305
  let { version, patches, body, peer } = options
297
306
 
307
+ // support for json patch puts..
308
+ if (patches?.length && patches.every(x => x.unit === 'json')) {
309
+ let resource = (typeof key == 'string') ? await get_resource(key) : key
310
+
311
+ let x = JSON.parse(resource.doc.get())
312
+ for (let p of patches)
313
+ apply_patch(x, p.range, JSON.parse(p.content))
314
+
315
+ return await braid_text.put(key, {
316
+ body: JSON.stringify(x, null, 4)
317
+ })
318
+ }
319
+
298
320
  if (version) validate_version_array(version)
299
321
 
300
322
  // translate a single parent of "root" to the empty array (same meaning)
@@ -320,9 +342,15 @@ braid_text.put = async (key, options) => {
320
342
  let parents = resource.doc.getRemoteVersion().map((x) => x.join("-")).sort()
321
343
  let og_parents = options_parents || parents
322
344
 
345
+ function get_len() {
346
+ let d = dt_get(resource.doc, og_parents)
347
+ let len = d.len()
348
+ d.free()
349
+ return len
350
+ }
351
+
323
352
  let max_pos = resource.length_cache.get('' + og_parents) ??
324
- (v_eq(parents, og_parents) ? resource.doc.len() :
325
- dt_get(resource.doc, og_parents).len())
353
+ (v_eq(parents, og_parents) ? resource.doc.len() : get_len())
326
354
 
327
355
  if (body != null) {
328
356
  patches = [{
@@ -336,7 +364,7 @@ braid_text.put = async (key, options) => {
336
364
  patches = patches.map((p) => ({
337
365
  ...p,
338
366
  range: p.range.match(/\d+/g).map((x) => parseInt(x)),
339
- content: [...p.content],
367
+ content_codepoints: [...p.content],
340
368
  })).sort((a, b) => a.range[0] - b.range[0])
341
369
 
342
370
  // validate patch positions
@@ -347,7 +375,7 @@ braid_text.put = async (key, options) => {
347
375
  must_be_at_least = p.range[1]
348
376
  }
349
377
 
350
- let change_count = patches.reduce((a, b) => a + b.content.length + (b.range[1] - b.range[0]), 0)
378
+ let change_count = patches.reduce((a, b) => a + b.content_codepoints.length + (b.range[1] - b.range[0]), 0)
351
379
 
352
380
  let og_v = version?.[0] || `${(is_valid_actor(peer) && peer) || Math.random().toString(36).slice(2, 7)}-${change_count - 1}`
353
381
 
@@ -365,7 +393,6 @@ braid_text.put = async (key, options) => {
365
393
  let seen = {}
366
394
  for (let u of updates) {
367
395
  u.version = decode_version(u.version)
368
- u.version[1] += u.end - u.start - 1
369
396
 
370
397
  if (!u.content) {
371
398
  // delete
@@ -401,9 +428,9 @@ braid_text.put = async (key, options) => {
401
428
  v = `${v[0]}-${v[1] + 1}`
402
429
  }
403
430
  // insert
404
- for (let i = 0; i < p.content?.length ?? 0; i++) {
431
+ for (let i = 0; i < p.content_codepoints?.length ?? 0; i++) {
405
432
  let vv = decode_version(v)
406
- let c = p.content[i]
433
+ let c = p.content_codepoints[i]
407
434
 
408
435
  if (!seen[JSON.stringify([vv[0], vv[1], ps, p.range[1] + offset, c])]) throw new Error('invalid update: different from previous update with same version')
409
436
 
@@ -420,7 +447,7 @@ braid_text.put = async (key, options) => {
420
447
  resource.actor_seqs[v[0]] = v[1]
421
448
 
422
449
  resource.length_cache.put(`${v[0]}-${v[1]}`, patches.reduce((a, b) =>
423
- a + (b.content.length ? b.content.length : -(b.range[1] - b.range[0])),
450
+ a + (b.content_codepoints.length ? b.content_codepoints.length : -(b.range[1] - b.range[0])),
424
451
  max_pos))
425
452
 
426
453
  v = `${v[0]}-${v[1] + 1 - change_count}`
@@ -434,21 +461,21 @@ braid_text.put = async (key, options) => {
434
461
  let offset = 0
435
462
  for (let p of patches) {
436
463
  // delete
437
- for (let i = p.range[0]; i < p.range[1]; i++) {
438
- bytes.push(dt_create_bytes(v, ps, p.range[1] - 1 + offset, null))
439
- offset--
440
- ps = [v]
464
+ let del = p.range[1] - p.range[0]
465
+ if (del) {
466
+ bytes.push(dt_create_bytes(v, ps, p.range[0] + offset, del, null))
467
+ offset -= del
441
468
  v = decode_version(v)
442
- v = `${v[0]}-${v[1] + 1}`
469
+ ps = [`${v[0]}-${v[1] + (del - 1)}`]
470
+ v = `${v[0]}-${v[1] + del}`
443
471
  }
444
472
  // insert
445
- for (let i = 0; i < p.content?.length ?? 0; i++) {
446
- let c = p.content[i]
447
- bytes.push(dt_create_bytes(v, ps, p.range[1] + offset, c))
448
- offset++
449
- ps = [v]
473
+ if (p.content?.length) {
474
+ bytes.push(dt_create_bytes(v, ps, p.range[1] + offset, 0, p.content))
475
+ offset += p.content_codepoints.length
450
476
  v = decode_version(v)
451
- v = `${v[0]}-${v[1] + 1}`
477
+ ps = [`${v[0]}-${v[1] + (p.content_codepoints.length - 1)}`]
478
+ v = `${v[0]}-${v[1] + p.content_codepoints.length}`
452
479
  }
453
480
  }
454
481
 
@@ -787,6 +814,8 @@ async function file_sync(key, process_delta, get_init) {
787
814
  //////////////////////////////////////////////////////////////////
788
815
 
789
816
  function dt_get(doc, version, agent = null) {
817
+ if (dt_get.last_doc) dt_get.last_doc.free()
818
+
790
819
  let bytes = doc.toBytes()
791
820
  dt_get.last_doc = doc = Doc.fromBytes(bytes, agent)
792
821
 
@@ -834,6 +863,7 @@ function dt_get(doc, version, agent = null) {
834
863
  op_run.start + (i - base_i) :
835
864
  op_run.start) :
836
865
  op_run.end - 1 - (i - base_i),
866
+ op_run.content?.[i - base_i] != null ? 0 : 1,
837
867
  op_run.content?.[i - base_i]
838
868
  )
839
869
  )
@@ -866,12 +896,16 @@ function dt_get_patches(doc, version = null) {
866
896
 
867
897
  before_doc.mergeBytes(after_bytes)
868
898
  op_runs = before_doc.getOpsSince(before_doc_frontier)
899
+
900
+ before_doc.free()
869
901
  } else op_runs = doc.getOpsSince([])
870
902
 
903
+ doc.free()
904
+
871
905
  let i = 0
872
906
  let patches = []
873
907
  op_runs.forEach((op_run) => {
874
- let version = versions[i].join("-")
908
+ let version = versions[i]
875
909
  let parents = parentss[i].map((x) => x.join("-")).sort()
876
910
  let start = op_run.start
877
911
  let end = start + 1
@@ -899,7 +933,7 @@ function dt_get_patches(doc, version = null) {
899
933
  op_run.start + (end - start)) :
900
934
  (op_run.end - (start - op_run.start))
901
935
  patches.push({
902
- version,
936
+ version: `${version[0]}-${version[1] + e - s - 1}`,
903
937
  parents,
904
938
  unit: "text",
905
939
  range: op_run.content ? `[${s}:${s}]` : `[${s}:${e}]`,
@@ -908,7 +942,7 @@ function dt_get_patches(doc, version = null) {
908
942
  end: e,
909
943
  })
910
944
  if (j == len) break
911
- version = versions[I].join("-")
945
+ version = versions[I]
912
946
  parents = parentss[I].map((x) => x.join("-")).sort()
913
947
  start = op_run.start + j
914
948
  }
@@ -1014,7 +1048,8 @@ function dt_parse(byte_array) {
1014
1048
  return [agents, versions, parentss]
1015
1049
  }
1016
1050
 
1017
- function dt_create_bytes(version, parents, pos, ins) {
1051
+ function dt_create_bytes(version, parents, pos, del, ins) {
1052
+ if (del) pos += del - 1
1018
1053
 
1019
1054
  function write_varint(bytes, value) {
1020
1055
  while (value >= 0x80) {
@@ -1085,6 +1120,8 @@ function dt_create_bytes(version, parents, pos, ins) {
1085
1120
 
1086
1121
  let patches = []
1087
1122
 
1123
+ let unicode_chars = ins ? [...ins] : []
1124
+
1088
1125
  if (ins) {
1089
1126
  let inserted_content_bytes = []
1090
1127
 
@@ -1095,18 +1132,21 @@ function dt_create_bytes(version, parents, pos, ins) {
1095
1132
  let encoder = new TextEncoder()
1096
1133
  let utf8Bytes = encoder.encode(ins)
1097
1134
 
1098
- inserted_content_bytes.push(1 + utf8Bytes.length) // length of content chunk
1135
+ write_varint(inserted_content_bytes, 1 + utf8Bytes.length)
1136
+ // inserted_content_bytes.push(1 + utf8Bytes.length) // length of content chunk
1099
1137
  inserted_content_bytes.push(4) // "plain text" enum
1100
1138
 
1101
1139
  for (let b of utf8Bytes) inserted_content_bytes.push(b) // actual text
1102
1140
 
1103
1141
  inserted_content_bytes.push(25) // "known" enum
1104
- inserted_content_bytes.push(1) // length of "known" chunk
1105
- inserted_content_bytes.push(3) // content of length 1, and we "know" it
1142
+ let known_chunk = []
1143
+ write_varint(known_chunk, unicode_chars.length * 2 + 1)
1144
+ write_varint(inserted_content_bytes, known_chunk.length)
1145
+ inserted_content_bytes.push(...known_chunk)
1106
1146
 
1107
1147
  patches.push(24)
1108
1148
  write_varint(patches, inserted_content_bytes.length)
1109
- patches.push(...inserted_content_bytes)
1149
+ for (let b of inserted_content_bytes) patches.push(b)
1110
1150
  }
1111
1151
 
1112
1152
  // write in the version
@@ -1117,26 +1157,43 @@ function dt_create_bytes(version, parents, pos, ins) {
1117
1157
  let jump = seq
1118
1158
 
1119
1159
  write_varint(version_bytes, ((agent_i + 1) << 1) | (jump != 0 ? 1 : 0))
1120
- write_varint(version_bytes, 1)
1160
+ write_varint(version_bytes, ins ? unicode_chars.length : del)
1121
1161
  if (jump) write_varint(version_bytes, jump << 1)
1122
1162
 
1123
1163
  patches.push(21)
1124
1164
  write_varint(patches, version_bytes.length)
1125
- patches.push(...version_bytes)
1165
+ for (let b of version_bytes) patches.push(b)
1126
1166
 
1127
1167
  // write in "op" bytes (some encoding of position)
1128
1168
  let op_bytes = []
1129
1169
 
1130
- write_varint(op_bytes, (pos << 4) | (pos ? 2 : 0) | (ins ? 0 : 4))
1170
+ if (del) {
1171
+ if (pos == 0) {
1172
+ write_varint(op_bytes, 4)
1173
+ } else if (del == 1) {
1174
+ write_varint(op_bytes, pos * 16 + 6)
1175
+ } else {
1176
+ write_varint(op_bytes, del * 16 + 7)
1177
+ write_varint(op_bytes, pos * 2 + 2)
1178
+ }
1179
+ } else if (unicode_chars.length == 1) {
1180
+ if (pos == 0) write_varint(op_bytes, 0)
1181
+ else write_varint(op_bytes, pos * 16 + 2)
1182
+ } else if (pos == 0) {
1183
+ write_varint(op_bytes, unicode_chars.length * 8 + 1)
1184
+ } else {
1185
+ write_varint(op_bytes, unicode_chars.length * 8 + 3)
1186
+ write_varint(op_bytes, pos * 2)
1187
+ }
1131
1188
 
1132
1189
  patches.push(22)
1133
1190
  write_varint(patches, op_bytes.length)
1134
- patches.push(...op_bytes)
1191
+ for (let b of op_bytes) patches.push(b)
1135
1192
 
1136
1193
  // write in parents
1137
1194
  let parents_bytes = []
1138
1195
 
1139
- write_varint(parents_bytes, 1)
1196
+ write_varint(parents_bytes, ins ? unicode_chars.length : del)
1140
1197
 
1141
1198
  if (parents.length) {
1142
1199
  for (let [i, [agent, seq]] of parents.entries()) {
@@ -1154,14 +1211,16 @@ function dt_create_bytes(version, parents, pos, ins) {
1154
1211
  // write in patches
1155
1212
  bytes.push(20)
1156
1213
  write_varint(bytes, patches.length)
1157
- bytes.push(...patches)
1214
+ for (let b of patches) bytes.push(b)
1158
1215
 
1159
1216
  // console.log(bytes);
1160
1217
  return bytes
1161
1218
  }
1162
1219
 
1163
1220
  function defrag_dt(doc) {
1164
- return Doc.fromBytes(doc.toBytes(), 'server')
1221
+ let bytes = doc.toBytes()
1222
+ doc.free()
1223
+ return Doc.fromBytes(bytes, 'server')
1165
1224
  }
1166
1225
 
1167
1226
  function OpLog_remote_to_local(doc, frontier) {
@@ -1572,6 +1631,84 @@ function createSimpleCache(size) {
1572
1631
  }
1573
1632
  }
1574
1633
 
1634
+ function apply_patch(obj, range, content) {
1635
+
1636
+ // Descend down a bunch of objects until we get to the final object
1637
+ // The final object can be a slice
1638
+ // Set the value in the final object
1639
+
1640
+ var path = range,
1641
+ new_stuff = content
1642
+
1643
+ var path_segment = /^(\.?([^\.\[]+))|(\[((-?\d+):)?(-?\d+)\])|\[("(\\"|[^"])*")\]/
1644
+ var curr_obj = obj,
1645
+ last_obj = null
1646
+
1647
+ // Handle negative indices, like "[-9]" or "[-0]"
1648
+ function de_neg (x) {
1649
+ return x[0] === '-'
1650
+ ? curr_obj.length - parseInt(x.substr(1), 10)
1651
+ : parseInt(x, 10)
1652
+ }
1653
+
1654
+ // Now iterate through each segment of the range e.g. [3].a.b[3][9]
1655
+ while (true) {
1656
+ var match = path_segment.exec(path),
1657
+ subpath = match ? match[0] : '',
1658
+ field = match && match[2],
1659
+ slice_start = match && match[5],
1660
+ slice_end = match && match[6],
1661
+ quoted_field = match && match[7]
1662
+
1663
+ // The field could be expressed as ["nnn"] instead of .nnn
1664
+ if (quoted_field) field = JSON.parse(quoted_field)
1665
+
1666
+ slice_start = slice_start && de_neg(slice_start)
1667
+ slice_end = slice_end && de_neg(slice_end)
1668
+
1669
+ // console.log('Descending', {curr_obj, path, subpath, field, slice_start, slice_end, last_obj})
1670
+
1671
+ // If it's the final item, set it
1672
+ if (path.length === subpath.length) {
1673
+ if (!subpath) return new_stuff
1674
+ else if (field) { // Object
1675
+ if (new_stuff === undefined)
1676
+ delete curr_obj[field] // - Delete a field in object
1677
+ else
1678
+ curr_obj[field] = new_stuff // - Set a field in object
1679
+ } else if (typeof curr_obj === 'string') { // String
1680
+ console.assert(typeof new_stuff === 'string')
1681
+ if (!slice_start) {slice_start = slice_end; slice_end = slice_end+1}
1682
+ if (last_obj) {
1683
+ var s = last_obj[last_field]
1684
+ last_obj[last_field] = (s.slice(0, slice_start)
1685
+ + new_stuff
1686
+ + s.slice(slice_end))
1687
+ } else
1688
+ return obj.slice(0, slice_start) + new_stuff + obj.slice(slice_end)
1689
+ } else // Array
1690
+ if (slice_start) // - Array splice
1691
+ [].splice.apply(curr_obj, [slice_start, slice_end-slice_start]
1692
+ .concat(new_stuff))
1693
+ else { // - Array set
1694
+ console.assert(slice_end >= 0, 'Index '+subpath+' is too small')
1695
+ console.assert(slice_end <= curr_obj.length - 1,
1696
+ 'Index '+subpath+' is too big')
1697
+ curr_obj[slice_end] = new_stuff
1698
+ }
1699
+
1700
+ return obj
1701
+ }
1702
+
1703
+ // Otherwise, descend down the path
1704
+ console.assert(!slice_start, 'No splices allowed in middle of path')
1705
+ last_obj = curr_obj
1706
+ last_field = field || slice_end
1707
+ curr_obj = curr_obj[last_field]
1708
+ path = path.substr(subpath.length)
1709
+ }
1710
+ }
1711
+
1575
1712
  braid_text.encode_filename = encode_filename
1576
1713
  braid_text.decode_filename = decode_filename
1577
1714
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-text",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Library for collaborative text over http using braid.",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braidjs",
package/server-demo.js CHANGED
@@ -34,6 +34,12 @@ var server = require("http").createServer(async (req, res) => {
34
34
  return
35
35
  }
36
36
 
37
+ if (req.url == '/test.html') {
38
+ res.writeHead(200, { "Content-Type": "text/html", "Cache-Control": "no-cache" })
39
+ require("fs").createReadStream("./test.html").pipe(res)
40
+ return
41
+ }
42
+
37
43
  // TODO: uncomment out the code below to add /pages endpoint,
38
44
  // which displays all the currently used keys
39
45
  //
package/test.html ADDED
@@ -0,0 +1,118 @@
1
+ <style>
2
+ body {
3
+ font-family: Arial, sans-serif;
4
+ max-width: 800px;
5
+ margin: 0 auto;
6
+ padding: 10px;
7
+ }
8
+ .test {
9
+ margin-bottom: 3px;
10
+ padding: 3px;
11
+ }
12
+ .running {
13
+ background-color: #fffde7;
14
+ }
15
+ .passed {
16
+ background-color: #e8f5e9;
17
+ }
18
+ .failed {
19
+ background-color: #ffebee;
20
+ }
21
+ </style>
22
+ <script src="https://unpkg.com/braid-http@~1.1/braid-http-client.js"></script>
23
+ <div id="testContainer"></div>
24
+ <script type=module>
25
+
26
+ let delay = 0
27
+
28
+ function createTestDiv(testName) {
29
+ const div = document.createElement("div")
30
+ div.className = "test running"
31
+ div.innerHTML = `<span style="font-weight:bold">${testName}: </span><span class="result">Running...</span>`
32
+ testContainer.appendChild(div)
33
+ return div
34
+ }
35
+
36
+ function updateTestResult(div, passed, message, got, expected) {
37
+ div.className = `test ${passed ? "passed" : "failed"}`
38
+
39
+ if (passed) {
40
+ div.querySelector(".result").textContent = message
41
+ div.querySelector(".result").style.fontSize = message.length > 400 ? 'xx-small' : message.length > 100 ? 'small' : ''
42
+ } else {
43
+ div.querySelector(".result").innerHTML = `${message}<br><strong>Got:</strong> ${got}<br><strong>Expected:</strong> ${expected}`
44
+ }
45
+ }
46
+
47
+ async function runTest(testName, testFunction, expectedResult) {
48
+ delay += 70
49
+
50
+ await new Promise(done => setTimeout(done, delay))
51
+ const div = createTestDiv(testName)
52
+ try {
53
+ let x = await testFunction()
54
+ if (x == expectedResult) {
55
+ updateTestResult(div, true, x)
56
+ } else {
57
+ updateTestResult(div, false, "Mismatch:", x, expectedResult)
58
+ }
59
+ } catch (error) {
60
+ updateTestResult(div, false, "Error:", error.message || error, expectedResult)
61
+ }
62
+ }
63
+
64
+ runTest(
65
+ "test sending a json patch to some json-text",
66
+ async () => {
67
+ let key = 'test-' + Math.random().toString(36).slice(2)
68
+
69
+ await fetch(`/${key}`, {
70
+ method: 'PUT',
71
+ body: JSON.stringify({a: 5, b: 6})
72
+ })
73
+
74
+ await fetch(`/${key}`, {
75
+ method: 'PUT',
76
+ headers: { 'Content-Range': 'json a' },
77
+ body: '67'
78
+ })
79
+
80
+ let r = await fetch(`/${key}`)
81
+
82
+ return await r.text()
83
+ },
84
+ JSON.stringify({a: 67, b: 6}, null, 4)
85
+ )
86
+
87
+ runTest(
88
+ "test sending multiple json patches to some json-text",
89
+ async () => {
90
+ let key = 'test-' + Math.random().toString(36).slice(2)
91
+
92
+ await fetch(`/${key}`, {
93
+ method: 'PUT',
94
+ body: JSON.stringify({a: 5, b: 6, c: 7})
95
+ })
96
+
97
+ await braid_fetch(`/${key}`, {
98
+ method: 'PUT',
99
+ headers: { 'Content-Range': 'json a' },
100
+ patches: [{
101
+ unit: 'json',
102
+ range: 'a',
103
+ content: '55',
104
+ }, {
105
+ unit: 'json',
106
+ range: 'b',
107
+ content: '66',
108
+ }]
109
+ })
110
+
111
+ let r = await fetch(`/${key}`)
112
+
113
+ return await r.text()
114
+ },
115
+ JSON.stringify({a: 55, b: 66, c: 7}, null, 4)
116
+ )
117
+
118
+ </script>
package/test.js CHANGED
@@ -1,9 +1,19 @@
1
1
 
2
2
  let { Doc } = require("diamond-types-node")
3
- let {dt_get, dt_get_patches, dt_parse, dt_create_bytes} = require('./index.js')
3
+ let braid_text = require('./index.js')
4
+ let {dt_get, dt_get_patches, dt_parse, dt_create_bytes} = braid_text
5
+
6
+ process.on("unhandledRejection", (x) =>
7
+ console.log(`unhandledRejection: ${x.stack}`)
8
+ )
9
+ process.on("uncaughtException", (x) =>
10
+ console.log(`uncaughtException: ${x.stack}`)
11
+ )
12
+
13
+ braid_text.db_folder = null
4
14
 
5
15
  async function main() {
6
- let best_seed = 0
16
+ let best_seed = NaN
7
17
  let best_n = Infinity
8
18
  let base = Math.floor(Math.random() * 10000000)
9
19
 
@@ -11,89 +21,89 @@ async function main() {
11
21
  console.log = () => {}
12
22
  for (let t = 0; t < 10000; t++) {
13
23
  let seed = base + t
14
- // for (let t = 0; t < 10; t++) {
15
- // let seed = 1188661 + t
24
+ // for (let t = 0; t < 1; t++) {
25
+ // let seed = 7572861
16
26
 
17
27
  og_log(`t = ${t}, seed = ${seed}, best_n = ${best_n} @ ${best_seed}`)
18
28
  Math.randomSeed(seed)
19
29
 
20
- let n = Math.floor(Math.random() * 15)
30
+ let n = Math.floor(Math.random() * 15) + 5
21
31
  console.log(`n = ${n}`)
22
32
 
23
33
  try {
24
- // 1. create a bunch of edits to a dt
34
+ // create a bunch of edits called doc,
35
+ // and remember a point along the way of adding all these edits,
36
+ // called middle_doc
25
37
  let doc = new Doc('server')
26
-
27
38
  let middle_doc = null
28
39
 
29
- if (!middle_doc && (Math.random() < 1/n || n == 0)) {
40
+ if (!middle_doc && (Math.random() < 1/n || n == 0))
30
41
  middle_doc = Doc.fromBytes(doc.toBytes())
31
- }
32
42
  for (let i = 0; i < n; i++) {
33
- make_random_edit(doc)
43
+ console.log(`edit ${i}`)
34
44
 
35
- if (!middle_doc && (Math.random() < 1/n || i == n - 1)) {
45
+ make_random_edit(doc)
46
+ if (!middle_doc && (Math.random() < 1/n || i == n - 1))
36
47
  middle_doc = Doc.fromBytes(doc.toBytes())
37
- }
38
- }
39
- if (!middle_doc) throw 'bad'
40
-
41
- // 2. let x = the resulting string
42
- let x = doc.get()
43
- console.log('x = ' + x)
44
-
45
- // // 3. use the code for sending these edits over the wire to create a new dt
46
- let updates = dt_get_patches(doc)
47
- console.log(updates)
48
-
49
- let new_doc = new Doc('server')
50
- apply_updates(new_doc, updates)
51
- let y = new_doc.get()
52
- console.log('y = ' + y)
53
-
54
- // 4. is the resulting string == x?
55
- console.log(x == y)
56
- if (x != y && n < best_n) {
57
- best_n = n
58
- best_seed = seed
59
- }
60
-
61
- // 5. test dt_get
62
- let middle_v = middle_doc.getRemoteVersion().map(x => x.join('-'))
63
- let new_middle_doc = dt_get(doc, middle_v)
64
- console.log('new_middle_doc = ' + new_middle_doc.get())
65
- if (middle_doc.get() != new_middle_doc.get() && n < best_n) {
66
- best_n = n
67
- best_seed = seed
68
48
  }
69
-
70
- // 6. test dt_get_patches(doc, version)
71
- if (true) {
72
- let updates = dt_get_patches(doc, middle_v)
73
- console.log(`updates:`, updates)
74
-
75
- apply_updates(middle_doc, updates)
76
- console.log(`middle_doc2:${middle_doc.get()}`)
77
- if (middle_doc.get() != doc.get() && n < best_n) {
78
- best_n = n
79
- best_seed = seed
49
+ if (!middle_doc) throw new Error('bad')
50
+
51
+ // put them into braid-text
52
+ let dt_to_braid = async (doc, key) => {
53
+ await braid_text.get(key, {})
54
+ for (let x of dt_get_patches(doc)) {
55
+ console.log(`x = `, x)
56
+ let y = {
57
+ merge_type: 'dt',
58
+ version: [x.version],
59
+ parents: x.parents,
60
+ patches: [{
61
+ unit: x.unit,
62
+ range: x.range,
63
+ content: x.content
64
+ }]
65
+ }
66
+ await braid_text.put(key, y)
67
+ y.validate_already_seen_versions = true
68
+ await braid_text.put(key, y)
80
69
  }
81
70
  }
82
-
83
- // 7. try applying a patch that's out of range..
84
- // if (true) {
85
- // let agent = Math.random().toString(36).slice(2)
86
- // let parents = doc.getRemoteVersion().map(x => x.join('-'))
87
- // let len = doc.len()
88
- // let args = [`${agent}-0`, parents, len + 1, 'c']
89
- // console.log('ARGS:', args)
90
- // try {
91
- // doc.mergeBytes(dt_create_bytes(...args))
92
- // } catch (e) {
93
- // console.log(`EEEE = ${e}`)
94
- // }
95
- // console.log('did that..')
96
- // }
71
+ await dt_to_braid(doc, 'doc')
72
+ await dt_to_braid(middle_doc, 'middle_doc')
73
+ console.log(`doc dt = ${doc.get()}`)
74
+ console.log(`middle_doc dt = ${middle_doc.get()}`)
75
+ console.log(`doc = ${await braid_text.get('doc')}`)
76
+ console.log(`middle_doc = ${await braid_text.get('middle_doc')}`)
77
+
78
+ // ensure they look right
79
+ if (doc.get() != await braid_text.get('doc')) throw new Error('bad')
80
+ if (middle_doc.get() != await braid_text.get('middle_doc')) throw new Error('bad')
81
+
82
+ // test getting old version
83
+ let middle_v = middle_doc.getRemoteVersion().map(x => x.join('-'))
84
+ console.log(`middle_doc = ${await braid_text.get('middle_doc')}`)
85
+ console.log(`middle_v = `, middle_v)
86
+
87
+ let doc_v = doc.getRemoteVersion().map(x => x.join('-'))
88
+ console.log(`doc_v = `, doc_v)
89
+
90
+ console.log(`doc = `, await braid_text.get('doc', {version: middle_v}))
91
+ if (await braid_text.get('middle_doc') != (await braid_text.get('doc', {version: middle_v})).body) throw new Error('bad')
92
+
93
+ // try getting updates from middle_doc to doc
94
+ let o = {merge_type: 'dt', parents: middle_v, subscribe: update => {
95
+ braid_text.put('middle_doc', update)
96
+ }}
97
+ await braid_text.get('doc', o)
98
+ await braid_text.forget('doc', o)
99
+
100
+ if (await braid_text.get('middle_doc') != await braid_text.get('doc')) throw new Error('bad')
101
+
102
+ doc.free()
103
+ middle_doc.free()
104
+ for (let p of Object.values(braid_text.cache))
105
+ (await p).doc.free()
106
+ braid_text.cache = {}
97
107
  } catch (e) {
98
108
  if (console.log == og_log) throw e
99
109
  if (n < best_n) {
@@ -132,74 +142,30 @@ function make_random_edit(doc) {
132
142
  let len = parent_doc.len()
133
143
  console.log(`len = ${len}`)
134
144
 
145
+ parent_doc.free()
146
+
135
147
  if (len && Math.random() > 0.5) {
136
148
  // delete
137
149
  let start = Math.floor(Math.random() * len)
138
150
  let del_len = Math.floor(Math.random() * (len - start - 1)) + 1
139
151
 
140
- for (let i = 0; i < del_len; i++) {
141
- let v = `${agent}-${base_seq++}`
142
- let args = [v, parents, start + del_len - 1 - i, null]
143
- console.log(args)
144
- doc.mergeBytes(dt_create_bytes(...args))
145
- parents = [v]
146
- }
152
+ let args = [`${agent}-${base_seq}`, parents, start, del_len, null]
153
+ console.log(args)
154
+ doc.mergeBytes(dt_create_bytes(...args))
147
155
  } else {
148
156
  // insert
149
157
  let start = Math.floor(Math.random() * (len + 1))
150
- let ins_len = Math.floor(Math.random() * 10) + 1
151
-
152
- for (let i = 0; i < ins_len; i++) {
153
- let v = `${agent}-${base_seq++}`
154
- let args = [v, parents, start++, getRandomCharacter()]
155
- console.log(args)
156
- doc.mergeBytes(dt_create_bytes(...args))
157
- parents = [v]
158
- }
158
+ let ins = Array(Math.floor(Math.random() * 10) + 1).fill(0).map(() => getRandomCharacter()).join('')
159
+
160
+ let args = [`${agent}-${base_seq}`, parents, start, 0, ins]
161
+ console.log(args)
162
+ doc.mergeBytes(dt_create_bytes(...args))
159
163
  }
160
164
 
161
165
  // work here
162
166
  console.log(`doc => ${doc.get()}`)
163
167
  }
164
168
 
165
- function apply_updates(doc, updates) {
166
- for (let u of updates) {
167
- u.range = u.range.match(/\d+/g).map((x) => parseInt(x))
168
- u.content = [...u.content]
169
-
170
- let v = u.version
171
- let ps = u.parents
172
-
173
- console.log('UPDATE:', u)
174
-
175
- // delete
176
- for (let i = u.range[1] - 1; i >= u.range[0]; i--) {
177
-
178
- // work here
179
- let args = [v, ps, i, null]
180
- console.log(`args`, args)
181
-
182
- doc.mergeBytes(dt_create_bytes(...args))
183
- ps = [v]
184
- v = decode_version(v)
185
- v = `${v[0]}-${v[1] + 1}`
186
- }
187
- // insert
188
- for (let i = 0; i < u.content?.length ?? 0; i++) {
189
- let c = u.content[i]
190
-
191
- // work here
192
- let args = [v, ps, u.range[0] + i, c]
193
- console.log(`args`, args)
194
-
195
- doc.mergeBytes(dt_create_bytes(...args))
196
- ps = [v]
197
- v = decode_version(v)
198
- v = `${v[0]}-${v[1] + 1}`
199
- }
200
- }
201
- }
202
-
203
169
  //////////////////////////////////////////////////////////////////
204
170
  //////////////////////////////////////////////////////////////////
205
171
  //////////////////////////////////////////////////////////////////