braid-text 0.2.22 → 0.2.24

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.
Files changed (3) hide show
  1. package/index.js +159 -59
  2. package/package.json +1 -1
  3. package/test/test.js +112 -34
package/index.js CHANGED
@@ -334,7 +334,8 @@ braid_text.put = async (key, options) => {
334
334
  // make sure we have all these parents
335
335
  for (let p of options_parents) {
336
336
  let P = decode_version(p)
337
- if (P[1] > (resource.actor_seqs[P[0]] ?? -1)) throw new Error(`${MISSING_PARENT_VERSION}: ${p}`)
337
+ if (!resource.actor_seqs[P[0]]?.has(P[1]))
338
+ throw new Error(`${MISSING_PARENT_VERSION}: ${p}`)
338
339
  }
339
340
  }
340
341
 
@@ -378,7 +379,7 @@ braid_text.put = async (key, options) => {
378
379
  max_pos))
379
380
 
380
381
  // validate version: make sure we haven't seen it already
381
- if (v[1] <= (resource.actor_seqs[v[0]] ?? -1)) {
382
+ if (resource.actor_seqs[v[0]]?.has(v[1])) {
382
383
 
383
384
  if (!options.validate_already_seen_versions) return
384
385
 
@@ -439,7 +440,8 @@ braid_text.put = async (key, options) => {
439
440
  // we already have this version, so nothing left to do
440
441
  return
441
442
  }
442
- resource.actor_seqs[v[0]] = v[1]
443
+ if (!resource.actor_seqs[v[0]]) resource.actor_seqs[v[0]] = new RangeSet()
444
+ resource.actor_seqs[v[0]].add_range(v[1] + 1 - change_count, v[1])
443
445
 
444
446
  // reduce the version sequence by the number of char-edits
445
447
  v = `${v[0]}-${v[1] + 1 - change_count}`
@@ -580,28 +582,6 @@ braid_text.put = async (key, options) => {
580
582
  await resource.db_delta(resource.doc.getPatchSince(v_before))
581
583
  }
582
584
 
583
- braid_text.revert = async (key, version) => {
584
- var resource = (typeof key == 'string') ? await get_resource(key) : key
585
-
586
- // revert dt
587
- var old_doc = resource.doc
588
- resource.doc = dt_get(resource.doc, null, null, version)
589
- old_doc.free()
590
-
591
- for (let v of version) {
592
- var [actor, seq] = decode_version(v)
593
- if ((resource.actor_seqs[actor] ?? -1) > seq - 1) {
594
- if (seq > 0) resource.actor_seqs[actor] = seq - 1
595
- else delete resource.actor_seqs[actor]
596
- }
597
- }
598
-
599
- resource.val = resource.doc.get()
600
-
601
- // save it
602
- await resource.db_delta()
603
- }
604
-
605
585
  braid_text.list = async () => {
606
586
  try {
607
587
  if (braid_text.db_folder) {
@@ -645,7 +625,8 @@ async function get_resource(key) {
645
625
  let max_version = resource.doc.getLocalVersion().reduce((a, b) => Math.max(a, b), -1)
646
626
  for (let i = 0; i <= max_version; i++) {
647
627
  let v = resource.doc.localToRemoteVersion([i])[0]
648
- resource.actor_seqs[v[0]] = Math.max(v[1], resource.actor_seqs[v[0]] ?? -1)
628
+ if (!resource.actor_seqs[v[0]]) resource.actor_seqs[v[0]] = new RangeSet()
629
+ resource.actor_seqs[v[0]].add_range(v[1], v[1])
649
630
  }
650
631
 
651
632
  resource.val = resource.doc.get()
@@ -819,20 +800,8 @@ function dt_len(doc, version) {
819
800
  function dt_get_string(doc, version) {
820
801
  var bytes = doc.toBytes()
821
802
  var oplog = OpLog.fromBytes(bytes)
822
- var [_agents, versions, _parentss] = dt_parse([...bytes])
823
-
824
- var frontier = new Set(version)
825
803
 
826
- var local_version = []
827
- for (var i = 0; i < versions.length; i++) {
828
- var v = versions[i].join("-")
829
- if (frontier.has(v)) {
830
- local_version.push(i)
831
- frontier.delete(v)
832
- }
833
- }
834
-
835
- if (frontier.size) throw new Error(`version not found: ${version}`)
804
+ var local_version = dt_get_local_version(bytes, version)
836
805
 
837
806
  var b = new Branch()
838
807
  b.merge(oplog, new Uint32Array(local_version))
@@ -1027,25 +996,25 @@ function dt_parse(byte_array) {
1027
996
 
1028
997
  while (byte_array.length) {
1029
998
  let id = byte_array.shift()
1030
- let len = read_varint(byte_array)
999
+ let len = dt_read_varint(byte_array)
1031
1000
  if (id == 1) {
1032
1001
  } else if (id == 3) {
1033
1002
  let goal = byte_array.length - len
1034
1003
  while (byte_array.length > goal) {
1035
- agents.push(read_string(byte_array))
1004
+ agents.push(dt_read_string(byte_array))
1036
1005
  }
1037
1006
  } else if (id == 20) {
1038
1007
  } else if (id == 21) {
1039
1008
  let seqs = {}
1040
1009
  let goal = byte_array.length - len
1041
1010
  while (byte_array.length > goal) {
1042
- let part0 = read_varint(byte_array)
1011
+ let part0 = dt_read_varint(byte_array)
1043
1012
  let has_jump = part0 & 1
1044
1013
  let agent_i = (part0 >> 1) - 1
1045
- let run_length = read_varint(byte_array)
1014
+ let run_length = dt_read_varint(byte_array)
1046
1015
  let jump = 0
1047
1016
  if (has_jump) {
1048
- let part2 = read_varint(byte_array)
1017
+ let part2 = dt_read_varint(byte_array)
1049
1018
  jump = part2 >> 1
1050
1019
  if (part2 & 1) jump *= -1
1051
1020
  }
@@ -1060,12 +1029,12 @@ function dt_parse(byte_array) {
1060
1029
  let count = 0
1061
1030
  let goal = byte_array.length - len
1062
1031
  while (byte_array.length > goal) {
1063
- let run_len = read_varint(byte_array)
1032
+ let run_len = dt_read_varint(byte_array)
1064
1033
 
1065
1034
  let parents = []
1066
1035
  let has_more = 1
1067
1036
  while (has_more) {
1068
- let x = read_varint(byte_array)
1037
+ let x = dt_read_varint(byte_array)
1069
1038
  let is_foreign = 0x1 & x
1070
1039
  has_more = 0x2 & x
1071
1040
  let num = x >> 2
@@ -1075,7 +1044,7 @@ function dt_parse(byte_array) {
1075
1044
  } else if (!is_foreign) {
1076
1045
  parents.push(versions[count - num])
1077
1046
  } else {
1078
- parents.push([agents[num - 1], read_varint(byte_array)])
1047
+ parents.push([agents[num - 1], dt_read_varint(byte_array)])
1079
1048
  }
1080
1049
  }
1081
1050
  parentss.push(parents)
@@ -1091,24 +1060,107 @@ function dt_parse(byte_array) {
1091
1060
  }
1092
1061
  }
1093
1062
 
1094
- function read_string(byte_array) {
1095
- return new TextDecoder().decode(new Uint8Array(byte_array.splice(0, read_varint(byte_array))))
1063
+ return [agents, versions, parentss]
1064
+ }
1065
+
1066
+ function dt_get_local_version(bytes, version) {
1067
+ var looking_for = new Map()
1068
+ for (var event of version) {
1069
+ var [agent, seq] = decode_version(event)
1070
+ if (!looking_for.has(agent)) looking_for.set(agent, [])
1071
+ looking_for.get(agent).push(seq)
1096
1072
  }
1073
+ for (var seqs of looking_for.values())
1074
+ seqs.sort((a, b) => a - b)
1097
1075
 
1098
- function read_varint(byte_array) {
1099
- let result = 0
1100
- let shift = 0
1101
- while (true) {
1102
- if (byte_array.length === 0) throw new Error("byte array does not contain varint")
1076
+ var byte_array = [...bytes]
1077
+ var local_version = []
1078
+ var local_version_base = 0
1079
+
1080
+ if (new TextDecoder().decode(new Uint8Array(byte_array.splice(0, 8))) !== "DMNDTYPS") throw new Error("dt parse error, expected DMNDTYPS")
1081
+
1082
+ if (byte_array.shift() != 0) throw new Error("dt parse error, expected version 0")
1083
+
1084
+ let agents = []
1085
+
1086
+ while (byte_array.length && looking_for.size) {
1087
+ let id = byte_array.shift()
1088
+ let len = dt_read_varint(byte_array)
1089
+ if (id == 1) {
1090
+ } else if (id == 3) {
1091
+ let goal = byte_array.length - len
1092
+ while (byte_array.length > goal) {
1093
+ agents.push(dt_read_string(byte_array))
1094
+ }
1095
+ } else if (id == 20) {
1096
+ } else if (id == 21) {
1097
+ let seqs = {}
1098
+ let goal = byte_array.length - len
1099
+ while (byte_array.length > goal && looking_for.size) {
1100
+ let part0 = dt_read_varint(byte_array)
1101
+ let has_jump = part0 & 1
1102
+ let agent_i = (part0 >> 1) - 1
1103
+ let run_length = dt_read_varint(byte_array)
1104
+ let jump = 0
1105
+ if (has_jump) {
1106
+ let part2 = dt_read_varint(byte_array)
1107
+ jump = part2 >> 1
1108
+ if (part2 & 1) jump *= -1
1109
+ }
1110
+ let base = (seqs[agent_i] || 0) + jump
1111
+
1112
+ var agent = agents[agent_i]
1113
+ looking_for_seqs = looking_for.get(agent)
1114
+ if (looking_for_seqs) {
1115
+ for (var seq of splice_out_range(
1116
+ looking_for_seqs, base, base + run_length - 1))
1117
+ local_version.push(local_version_base + (seq - base))
1118
+ if (!looking_for_seqs.length) looking_for.delete(agent)
1119
+ }
1120
+ local_version_base += run_length
1121
+
1122
+ seqs[agent_i] = base + run_length
1123
+ }
1124
+ } else {
1125
+ byte_array.splice(0, len)
1126
+ }
1127
+ }
1128
+
1129
+ if (looking_for.size) throw new Error(`version not found: ${version}`)
1130
+ return local_version
1103
1131
 
1104
- let byte_val = byte_array.shift()
1105
- result |= (byte_val & 0x7f) << shift
1106
- if ((byte_val & 0x80) == 0) return result
1107
- shift += 7
1132
+ function splice_out_range(a, s, e) {
1133
+ if (!a?.length) return [];
1134
+ let l = 0, r = a.length;
1135
+ while (l < r) {
1136
+ const m = Math.floor((l + r) / 2);
1137
+ if (a[m] < s) l = m + 1; else r = m;
1108
1138
  }
1139
+ const i = l;
1140
+ l = i; r = a.length;
1141
+ while (l < r) {
1142
+ const m = Math.floor((l + r) / 2);
1143
+ if (a[m] <= e) l = m + 1; else r = m;
1144
+ }
1145
+ return a.splice(i, l - i);
1109
1146
  }
1147
+ }
1110
1148
 
1111
- return [agents, versions, parentss]
1149
+ function dt_read_string(byte_array) {
1150
+ return new TextDecoder().decode(new Uint8Array(byte_array.splice(0, dt_read_varint(byte_array))))
1151
+ }
1152
+
1153
+ function dt_read_varint(byte_array) {
1154
+ let result = 0
1155
+ let shift = 0
1156
+ while (true) {
1157
+ if (byte_array.length === 0) throw new Error("byte array does not contain varint")
1158
+
1159
+ let byte_val = byte_array.shift()
1160
+ result |= (byte_val & 0x7f) << shift
1161
+ if ((byte_val & 0x80) == 0) return result
1162
+ shift += 7
1163
+ }
1112
1164
  }
1113
1165
 
1114
1166
  function dt_create_bytes(version, parents, pos, del, ins) {
@@ -1772,6 +1824,52 @@ function apply_patch(obj, range, content) {
1772
1824
  }
1773
1825
  }
1774
1826
 
1827
+ class RangeSet {
1828
+ constructor() {
1829
+ this.ranges = []
1830
+ }
1831
+
1832
+ add_range(low_inclusive, high_inclusive) {
1833
+ if (low_inclusive > high_inclusive) return
1834
+
1835
+ const startIndex = this._bs(mid => this.ranges[mid][1] >= low_inclusive - 1, this.ranges.length, true)
1836
+ const endIndex = this._bs(mid => this.ranges[mid][0] <= high_inclusive + 1, -1, false)
1837
+
1838
+ if (startIndex > endIndex) {
1839
+ this.ranges.splice(startIndex, 0, [low_inclusive, high_inclusive])
1840
+ } else {
1841
+ const mergedLow = Math.min(low_inclusive, this.ranges[startIndex][0])
1842
+ const mergedHigh = Math.max(high_inclusive, this.ranges[endIndex][1])
1843
+ const removeCount = endIndex - startIndex + 1
1844
+ this.ranges.splice(startIndex, removeCount, [mergedLow, mergedHigh])
1845
+ }
1846
+ }
1847
+
1848
+ has(x) {
1849
+ var index = this._bs(mid => this.ranges[mid][0] <= x, -1, false)
1850
+ return index !== -1 && x <= this.ranges[index][1]
1851
+ }
1852
+
1853
+ _bs(condition, defaultR, moveLeft) {
1854
+ let low = 0
1855
+ let high = this.ranges.length - 1
1856
+ let result = defaultR
1857
+
1858
+ while (low <= high) {
1859
+ const mid = Math.floor((low + high) / 2)
1860
+ if (condition(mid)) {
1861
+ result = mid
1862
+ if (moveLeft) high = mid - 1
1863
+ else low = mid + 1
1864
+ } else {
1865
+ if (moveLeft) low = mid + 1
1866
+ else high = mid - 1
1867
+ }
1868
+ }
1869
+ return result
1870
+ }
1871
+ }
1872
+
1775
1873
  braid_text.get_resource = get_resource
1776
1874
 
1777
1875
  braid_text.encode_filename = encode_filename
@@ -1781,8 +1879,10 @@ braid_text.get_files_for_key = get_files_for_key
1781
1879
  braid_text.dt_get = dt_get
1782
1880
  braid_text.dt_get_patches = dt_get_patches
1783
1881
  braid_text.dt_parse = dt_parse
1882
+ braid_text.dt_get_local_version = dt_get_local_version
1784
1883
  braid_text.dt_create_bytes = dt_create_bytes
1785
1884
 
1786
1885
  braid_text.decode_version = decode_version
1886
+ braid_text.RangeSet = RangeSet
1787
1887
 
1788
1888
  module.exports = braid_text
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-text",
3
- "version": "0.2.22",
3
+ "version": "0.2.24",
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/test.js CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  let { Doc } = require("diamond-types-node")
3
3
  let braid_text = require('../index.js')
4
- let {dt_get, dt_get_patches, dt_parse, dt_create_bytes} = braid_text
4
+ let {dt_get, dt_get_patches, dt_parse, dt_create_bytes, dt_get_local_version, RangeSet} = braid_text
5
5
 
6
6
  process.on("unhandledRejection", (x) =>
7
7
  console.log(`unhandledRejection: ${x.stack ?? x}`)
@@ -12,43 +12,138 @@ process.on("uncaughtException", (x) =>
12
12
 
13
13
  braid_text.db_folder = null
14
14
 
15
- async function test_revert() {
15
+ async function test_db() {
16
16
  braid_text.db_folder = './braid-text-db'
17
17
  var key = Math.random().toString(36).slice(2)
18
18
  await braid_text.put(key, {version: ['a-0'], body: 'A'})
19
- await braid_text.put(key, {version: ['a-1'], parents: ['a-0'], patches: [{range: '[1:1]', content: 'B'}]})
20
- await braid_text.revert(key, ['a-1'])
19
+ await braid_text.put(key, {version: ['a-2'], parents: ['a-0'], patches: [{range: '[1:1]', content: 'B'}]})
21
20
  delete braid_text.cache[key]
22
21
  var {version, body} = await braid_text.get(key, {})
23
- if (version[0] != 'a-0') throw new Error('revert error: wrong version')
24
- if (body != 'A') throw new Error('revert error: wrong text')
25
22
 
26
- await braid_text.put(key, {version: ['b-0'], parents: ['a-0'], patches: [{range: '[1:1]', content: 'C'}]})
27
- if ('AC' !== await braid_text.get(key)) throw new Error('revert error: wrong text')
28
- await braid_text.revert(key, ['b-0'])
29
- if ('A' !== await braid_text.get(key)) throw new Error('revert error: wrong text')
30
- delete braid_text.cache[key]
31
- var {version, body} = await braid_text.get(key, {})
32
- if (version[0] != 'a-0') throw new Error('revert error: wrong version')
33
- if (body != 'A') throw new Error('revert error: wrong text')
23
+ if (body != 'AB') throw new Error('db error')
34
24
 
35
25
  braid_text.db_folder = null
36
26
  }
37
27
 
28
+ async function test_dt_get_local_version() {
29
+ for (var tt = 0; tt < 100; tt++) {
30
+ var st = Date.now()
31
+
32
+ var doc = new Doc('x')
33
+ var n = 100
34
+ var v_array = []
35
+ var p_array = []
36
+ var agents = ['hi']
37
+ var agent_rangesets = {}
38
+
39
+ for (var i = 0; i < n; i++) {
40
+ // console.log(`i = ${i}`)
41
+
42
+ // Math.randomSeed('seed::::' + i)
43
+
44
+ var agent = agents[0]
45
+
46
+ var seq = null
47
+ if (!agent_rangesets[agent]) agent_rangesets[agent] = new RangeSet()
48
+ var ranges = agent_rangesets[agent].ranges
49
+ if (!ranges.length) {
50
+ seq = Math.floor(Math.random() * 10)
51
+ } else {
52
+ var ii
53
+ if (ranges[0]?.[0] === 0)
54
+ ii = Math.floor(Math.random() * ranges.length)
55
+ else
56
+ ii = Math.floor(Math.random() * (ranges.length + 1)) - 1
57
+ var low = ii < 0 ? 0 : ranges[ii][1] + 1
58
+ var high = ii + 1 < ranges.length ? ranges[ii + 1][0] - 1 : ranges[ii][1] + 10
59
+ seq = Math.random() < 0.8 ? low : low + Math.floor(Math.random() * (high - low + 1))
60
+ }
61
+ agent_rangesets[agent].add_range(seq, seq)
62
+ v_array.push(`${agent}-${seq}`)
63
+
64
+ var parents = []
65
+ var shadow = new Set()
66
+ if (p_array.length) {
67
+ var starting_point = Math.floor(Math.random() * p_array.length)
68
+ parents.push(starting_point)
69
+ for (var p of p_array[starting_point]) shadow.add(p)
70
+ for (var ii = starting_point - 1; ii >= 0 && Math.random() < 0.9; ii--) {
71
+ if (!shadow.has(ii) && Math.random() < 0.5) {
72
+ parents.push(ii)
73
+ shadow.add(ii)
74
+ }
75
+ if (shadow.has(ii))
76
+ for (var p of p_array[ii])
77
+ shadow.add(p)
78
+ }
79
+ }
80
+ p_array.push(parents)
81
+ parents = parents.map(p => v_array[p])
82
+
83
+ let args = [`${agent}-${seq}`, parents, 0, 0, 'x']
84
+ // console.log(args)
85
+
86
+ doc.mergeBytes(dt_create_bytes(...args))
87
+ }
88
+
89
+ var bytes = doc.toBytes()
90
+
91
+ for (var t = 0; t < 100; t++) {
92
+ // if (t % 10 === 0) console.log(` t = ${t}`)
93
+
94
+ var version = []
95
+ var vv_array = [...v_array]
96
+ while (!version.length || (vv_array.length && Math.random() < 0.9)) {
97
+ version.push(vv_array.splice(Math.floor(Math.random() * vv_array.length), 1)[0])
98
+ }
99
+
100
+ var local_new = dt_get_local_version(bytes, version)
101
+ var local_old = old_dt_get_local_version(bytes, version)
102
+
103
+ if (local_new.some((x, i) => x != local_old[i])) {
104
+ throw new Error('bad!')
105
+ }
106
+ }
107
+
108
+ console.log(`[tt=${tt}] total T = ${Date.now() - st}`)
109
+ }
110
+ }
111
+
112
+ function old_dt_get_local_version(bytes, version) {
113
+ var [_agents, versions, _parentss] = dt_parse([...bytes])
114
+
115
+ var frontier = new Set(version)
116
+
117
+ var local_version = []
118
+ for (var i = 0; i < versions.length; i++) {
119
+ var v = versions[i].join("-")
120
+ if (frontier.has(v)) {
121
+ local_version.push(i)
122
+ frontier.delete(v)
123
+ }
124
+ }
125
+
126
+ if (frontier.size) throw new Error(`version not found: ${version}`)
127
+
128
+ return local_version
129
+ }
130
+
38
131
  async function main() {
39
132
  let best_seed = NaN
40
133
  let best_n = Infinity
41
134
  let base = Math.floor(Math.random() * 10000000)
42
135
  let st = Date.now()
43
136
 
44
- await test_revert()
137
+ await test_dt_get_local_version()
138
+
139
+ await test_db()
45
140
 
46
141
  let og_log = console.log
47
142
  console.log = () => {}
48
143
  for (let t = 0; t < 10000; t++) {
49
144
  let seed = base + t
50
145
  // for (let t = 0; t < 1; t++) {
51
- // let seed = 2746153
146
+ // let seed = 403279
52
147
 
53
148
  og_log(`t = ${t}, seed = ${seed}, best_n = ${best_n} @ ${best_seed}`)
54
149
  Math.randomSeed(seed)
@@ -96,14 +191,6 @@ async function main() {
96
191
  await braid_text.put(key, y)
97
192
  y.validate_already_seen_versions = true
98
193
  await braid_text.put(key, y)
99
-
100
- // test revert
101
- var range = x.range.match(/\d+/g).map((x) => parseInt(x))
102
- var change_count = [...x.content].length + range[1] - range[0]
103
- await braid_text.revert(key, [`${actor}-${seq + 1 - change_count}`])
104
- var new_text = await braid_text.get(key)
105
- if (old_text !== new_text) throw new Error('revert failed!')
106
- await braid_text.put(key, y)
107
194
  }
108
195
  }
109
196
  await dt_to_braid(doc, 'doc')
@@ -134,16 +221,7 @@ async function main() {
134
221
  await braid_text.get('middle_doc', {peer: 'sim', subscribe: async update => {
135
222
  if (first_time) {
136
223
  first_time = false
137
- if (update.body !== await braid_text.get('middle_doc')) fail(new Error('test 2'))
138
-
139
- var x = await braid_text.get('middle_doc', {})
140
- await braid_text.put('middle_doc', {peer: 'sim', version: ['sim-0'], parents: x.version, patches: [{content: 'A', range: '[0:0]'}]})
141
- await braid_text.put('middle_doc', {peer: 'other', merge_type: 'dt', version: ['other-0'], parents: [], patches: [{content: 'B', range: '[0:0]'}]})
142
- await braid_text.put('middle_doc', {peer: 'sim', version: ['sim-1'], parents: ['sim-0'], patches: [{content: 'b', range: '[0:0]'}]})
143
- await braid_text.put('middle_doc', {peer: 'sim', version: ['sim-2'], parents: ['sim-1'], patches: [{content: 'c', range: '[0:0]'}]})
144
- await braid_text.revert('middle_doc', ['sim-0'])
145
- await braid_text.revert('middle_doc', ['other-0'])
146
-
224
+ if (update.body !== await braid_text.get('middle_doc')) fail(new Error('simpleton fail'))
147
225
  done()
148
226
  }
149
227
  }})