braid-text 0.1.1 → 0.1.3
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 +128 -68
- package/package.json +2 -2
- package/test.js +88 -122
package/index.js
CHANGED
|
@@ -3,6 +3,8 @@ let { Doc } = require("diamond-types-node")
|
|
|
3
3
|
let braidify = require("braid-http").http_server
|
|
4
4
|
let fs = require("fs")
|
|
5
5
|
|
|
6
|
+
let MISSING_PARENT_VERSION = 'missing parent version'
|
|
7
|
+
|
|
6
8
|
let braid_text = {
|
|
7
9
|
verbose: false,
|
|
8
10
|
db_folder: './braid-text-db',
|
|
@@ -164,32 +166,36 @@ braid_text.serve = async (req, res, options = {}) => {
|
|
|
164
166
|
|
|
165
167
|
options.put_cb(options.key, resource.val)
|
|
166
168
|
} catch (e) {
|
|
167
|
-
console.log(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
169
|
+
console.log(`${req.method} ERROR: ${e.stack}`)
|
|
170
|
+
if (e.message?.startsWith(MISSING_PARENT_VERSION)) {
|
|
171
|
+
// we couldn't apply the version, because we're missing its parents,
|
|
172
|
+
// we want to send a 4XX error, so the client will resend this request later,
|
|
173
|
+
// hopefully after we've received the necessary parents.
|
|
174
|
+
|
|
175
|
+
// here are some 4XX error code options..
|
|
176
|
+
//
|
|
177
|
+
// - 425 Too Early
|
|
178
|
+
// - pros: our message is too early
|
|
179
|
+
// - cons: associated with some "Early-Data" http thing, which we're not using
|
|
180
|
+
// - 400 Bad Request
|
|
181
|
+
// - pros: pretty generic
|
|
182
|
+
// - cons: implies client shouldn't resend as-is
|
|
183
|
+
// - 409 Conflict
|
|
184
|
+
// - pros: doesn't imply modifications needed
|
|
185
|
+
// - cons: the message is not conflicting with anything
|
|
186
|
+
// - 412 Precondition Failed
|
|
187
|
+
// - pros: kindof true.. the precondition of having another version has failed..
|
|
188
|
+
// - cons: not strictly true, as this code is associated with http's If-Unmodified-Since stuff
|
|
189
|
+
// - 422 Unprocessable Content
|
|
190
|
+
// - pros: it's true
|
|
191
|
+
// - cons: implies client shouldn't resend as-is (at least, it says that here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422)
|
|
192
|
+
// - 428 Precondition Required
|
|
193
|
+
// - pros: the name sounds right
|
|
194
|
+
// - cons: typically implies that the request was missing an http conditional field like If-Match. that is to say, it implies that the request is missing a precondition, not that the server is missing a precondition
|
|
195
|
+
return done_my_turn(425, e.message)
|
|
196
|
+
} else {
|
|
197
|
+
return done_my_turn(400, "The server failed to apply this version. The error generated was: " + e)
|
|
198
|
+
}
|
|
193
199
|
}
|
|
194
200
|
|
|
195
201
|
return done_my_turn(200)
|
|
@@ -214,10 +220,14 @@ braid_text.get = async (key, options) => {
|
|
|
214
220
|
let doc = resource.doc
|
|
215
221
|
if (options.version || options.parents) doc = dt_get(doc, options.version || options.parents)
|
|
216
222
|
|
|
217
|
-
|
|
223
|
+
let ret = {
|
|
218
224
|
version: doc.getRemoteVersion().map((x) => x.join("-")).sort(),
|
|
219
225
|
body: doc.get()
|
|
220
226
|
}
|
|
227
|
+
|
|
228
|
+
if (options.version || options.parents) doc.free()
|
|
229
|
+
|
|
230
|
+
return ret
|
|
221
231
|
} else {
|
|
222
232
|
if (options.merge_type != "dt") {
|
|
223
233
|
let version = resource.doc.getRemoteVersion().map((x) => x.join("-")).sort()
|
|
@@ -263,17 +273,12 @@ braid_text.get = async (key, options) => {
|
|
|
263
273
|
updates = dt_get_patches(resource.doc, options.parents || options.version)
|
|
264
274
|
}
|
|
265
275
|
|
|
266
|
-
for (let u of updates)
|
|
267
|
-
u.version = decode_version(u.version)
|
|
268
|
-
u.version[1] += u.end - u.start - 1
|
|
269
|
-
u.version = u.version.join("-")
|
|
270
|
-
|
|
276
|
+
for (let u of updates)
|
|
271
277
|
options.subscribe({
|
|
272
278
|
version: [u.version],
|
|
273
279
|
parents: u.parents,
|
|
274
280
|
patches: [{ unit: u.unit, range: u.range, content: u.content }],
|
|
275
281
|
})
|
|
276
|
-
}
|
|
277
282
|
|
|
278
283
|
// Output at least *some* data, or else chrome gets confused and
|
|
279
284
|
// thinks the connection failed. This isn't strictly necessary,
|
|
@@ -286,6 +291,16 @@ braid_text.get = async (key, options) => {
|
|
|
286
291
|
}
|
|
287
292
|
}
|
|
288
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
|
+
|
|
289
304
|
braid_text.put = async (key, options) => {
|
|
290
305
|
let { version, patches, body, peer } = options
|
|
291
306
|
|
|
@@ -303,12 +318,26 @@ braid_text.put = async (key, options) => {
|
|
|
303
318
|
|
|
304
319
|
let resource = (typeof key == 'string') ? await get_resource(key) : key
|
|
305
320
|
|
|
321
|
+
if (options_parents) {
|
|
322
|
+
// make sure we have all these parents
|
|
323
|
+
for (let p of options_parents) {
|
|
324
|
+
let P = decode_version(p)
|
|
325
|
+
if (P[1] > (resource.actor_seqs[P[0]] ?? -1)) throw new Error(`${MISSING_PARENT_VERSION}: ${p}`)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
306
329
|
let parents = resource.doc.getRemoteVersion().map((x) => x.join("-")).sort()
|
|
307
330
|
let og_parents = options_parents || parents
|
|
308
331
|
|
|
332
|
+
function get_len() {
|
|
333
|
+
let d = dt_get(resource.doc, og_parents)
|
|
334
|
+
let len = d.len()
|
|
335
|
+
d.free()
|
|
336
|
+
return len
|
|
337
|
+
}
|
|
338
|
+
|
|
309
339
|
let max_pos = resource.length_cache.get('' + og_parents) ??
|
|
310
|
-
(v_eq(parents, og_parents) ? resource.doc.len() :
|
|
311
|
-
dt_get(resource.doc, og_parents).len())
|
|
340
|
+
(v_eq(parents, og_parents) ? resource.doc.len() : get_len())
|
|
312
341
|
|
|
313
342
|
if (body != null) {
|
|
314
343
|
patches = [{
|
|
@@ -322,7 +351,7 @@ braid_text.put = async (key, options) => {
|
|
|
322
351
|
patches = patches.map((p) => ({
|
|
323
352
|
...p,
|
|
324
353
|
range: p.range.match(/\d+/g).map((x) => parseInt(x)),
|
|
325
|
-
|
|
354
|
+
content_codepoints: [...p.content],
|
|
326
355
|
})).sort((a, b) => a.range[0] - b.range[0])
|
|
327
356
|
|
|
328
357
|
// validate patch positions
|
|
@@ -333,7 +362,7 @@ braid_text.put = async (key, options) => {
|
|
|
333
362
|
must_be_at_least = p.range[1]
|
|
334
363
|
}
|
|
335
364
|
|
|
336
|
-
let change_count = patches.reduce((a, b) => a + b.
|
|
365
|
+
let change_count = patches.reduce((a, b) => a + b.content_codepoints.length + (b.range[1] - b.range[0]), 0)
|
|
337
366
|
|
|
338
367
|
let og_v = version?.[0] || `${(is_valid_actor(peer) && peer) || Math.random().toString(36).slice(2, 7)}-${change_count - 1}`
|
|
339
368
|
|
|
@@ -351,7 +380,6 @@ braid_text.put = async (key, options) => {
|
|
|
351
380
|
let seen = {}
|
|
352
381
|
for (let u of updates) {
|
|
353
382
|
u.version = decode_version(u.version)
|
|
354
|
-
u.version[1] += u.end - u.start - 1
|
|
355
383
|
|
|
356
384
|
if (!u.content) {
|
|
357
385
|
// delete
|
|
@@ -387,9 +415,9 @@ braid_text.put = async (key, options) => {
|
|
|
387
415
|
v = `${v[0]}-${v[1] + 1}`
|
|
388
416
|
}
|
|
389
417
|
// insert
|
|
390
|
-
for (let i = 0; i < p.
|
|
418
|
+
for (let i = 0; i < p.content_codepoints?.length ?? 0; i++) {
|
|
391
419
|
let vv = decode_version(v)
|
|
392
|
-
let c = p.
|
|
420
|
+
let c = p.content_codepoints[i]
|
|
393
421
|
|
|
394
422
|
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')
|
|
395
423
|
|
|
@@ -406,7 +434,7 @@ braid_text.put = async (key, options) => {
|
|
|
406
434
|
resource.actor_seqs[v[0]] = v[1]
|
|
407
435
|
|
|
408
436
|
resource.length_cache.put(`${v[0]}-${v[1]}`, patches.reduce((a, b) =>
|
|
409
|
-
a + (b.
|
|
437
|
+
a + (b.content_codepoints.length ? b.content_codepoints.length : -(b.range[1] - b.range[0])),
|
|
410
438
|
max_pos))
|
|
411
439
|
|
|
412
440
|
v = `${v[0]}-${v[1] + 1 - change_count}`
|
|
@@ -420,21 +448,21 @@ braid_text.put = async (key, options) => {
|
|
|
420
448
|
let offset = 0
|
|
421
449
|
for (let p of patches) {
|
|
422
450
|
// delete
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
offset
|
|
426
|
-
|
|
451
|
+
let del = p.range[1] - p.range[0]
|
|
452
|
+
if (del) {
|
|
453
|
+
bytes.push(dt_create_bytes(v, ps, p.range[0] + offset, del, null))
|
|
454
|
+
offset -= del
|
|
427
455
|
v = decode_version(v)
|
|
428
|
-
|
|
456
|
+
ps = [`${v[0]}-${v[1] + (del - 1)}`]
|
|
457
|
+
v = `${v[0]}-${v[1] + del}`
|
|
429
458
|
}
|
|
430
459
|
// insert
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
offset++
|
|
435
|
-
ps = [v]
|
|
460
|
+
if (p.content?.length) {
|
|
461
|
+
bytes.push(dt_create_bytes(v, ps, p.range[1] + offset, 0, p.content))
|
|
462
|
+
offset += p.content_codepoints.length
|
|
436
463
|
v = decode_version(v)
|
|
437
|
-
|
|
464
|
+
ps = [`${v[0]}-${v[1] + (p.content_codepoints.length - 1)}`]
|
|
465
|
+
v = `${v[0]}-${v[1] + p.content_codepoints.length}`
|
|
438
466
|
}
|
|
439
467
|
}
|
|
440
468
|
|
|
@@ -577,7 +605,7 @@ async function get_resource(key) {
|
|
|
577
605
|
resource.need_defrag = false
|
|
578
606
|
|
|
579
607
|
resource.actor_seqs = {}
|
|
580
|
-
let max_version = resource.doc.getLocalVersion()
|
|
608
|
+
let max_version = Math.max(...resource.doc.getLocalVersion()) ?? -1
|
|
581
609
|
for (let i = 0; i <= max_version; i++) {
|
|
582
610
|
let v = resource.doc.localToRemoteVersion([i])[0]
|
|
583
611
|
resource.actor_seqs[v[0]] = Math.max(v[1], resource.actor_seqs[v[0]] ?? -1)
|
|
@@ -773,6 +801,8 @@ async function file_sync(key, process_delta, get_init) {
|
|
|
773
801
|
//////////////////////////////////////////////////////////////////
|
|
774
802
|
|
|
775
803
|
function dt_get(doc, version, agent = null) {
|
|
804
|
+
if (dt_get.last_doc) dt_get.last_doc.free()
|
|
805
|
+
|
|
776
806
|
let bytes = doc.toBytes()
|
|
777
807
|
dt_get.last_doc = doc = Doc.fromBytes(bytes, agent)
|
|
778
808
|
|
|
@@ -820,6 +850,7 @@ function dt_get(doc, version, agent = null) {
|
|
|
820
850
|
op_run.start + (i - base_i) :
|
|
821
851
|
op_run.start) :
|
|
822
852
|
op_run.end - 1 - (i - base_i),
|
|
853
|
+
op_run.content?.[i - base_i] != null ? 0 : 1,
|
|
823
854
|
op_run.content?.[i - base_i]
|
|
824
855
|
)
|
|
825
856
|
)
|
|
@@ -852,12 +883,16 @@ function dt_get_patches(doc, version = null) {
|
|
|
852
883
|
|
|
853
884
|
before_doc.mergeBytes(after_bytes)
|
|
854
885
|
op_runs = before_doc.getOpsSince(before_doc_frontier)
|
|
886
|
+
|
|
887
|
+
before_doc.free()
|
|
855
888
|
} else op_runs = doc.getOpsSince([])
|
|
856
889
|
|
|
890
|
+
doc.free()
|
|
891
|
+
|
|
857
892
|
let i = 0
|
|
858
893
|
let patches = []
|
|
859
894
|
op_runs.forEach((op_run) => {
|
|
860
|
-
let version = versions[i]
|
|
895
|
+
let version = versions[i]
|
|
861
896
|
let parents = parentss[i].map((x) => x.join("-")).sort()
|
|
862
897
|
let start = op_run.start
|
|
863
898
|
let end = start + 1
|
|
@@ -885,7 +920,7 @@ function dt_get_patches(doc, version = null) {
|
|
|
885
920
|
op_run.start + (end - start)) :
|
|
886
921
|
(op_run.end - (start - op_run.start))
|
|
887
922
|
patches.push({
|
|
888
|
-
version
|
|
923
|
+
version: `${version[0]}-${version[1] + e - s - 1}`,
|
|
889
924
|
parents,
|
|
890
925
|
unit: "text",
|
|
891
926
|
range: op_run.content ? `[${s}:${s}]` : `[${s}:${e}]`,
|
|
@@ -894,7 +929,7 @@ function dt_get_patches(doc, version = null) {
|
|
|
894
929
|
end: e,
|
|
895
930
|
})
|
|
896
931
|
if (j == len) break
|
|
897
|
-
version = versions[I]
|
|
932
|
+
version = versions[I]
|
|
898
933
|
parents = parentss[I].map((x) => x.join("-")).sort()
|
|
899
934
|
start = op_run.start + j
|
|
900
935
|
}
|
|
@@ -1000,7 +1035,8 @@ function dt_parse(byte_array) {
|
|
|
1000
1035
|
return [agents, versions, parentss]
|
|
1001
1036
|
}
|
|
1002
1037
|
|
|
1003
|
-
function dt_create_bytes(version, parents, pos, ins) {
|
|
1038
|
+
function dt_create_bytes(version, parents, pos, del, ins) {
|
|
1039
|
+
if (del) pos += del - 1
|
|
1004
1040
|
|
|
1005
1041
|
function write_varint(bytes, value) {
|
|
1006
1042
|
while (value >= 0x80) {
|
|
@@ -1071,6 +1107,8 @@ function dt_create_bytes(version, parents, pos, ins) {
|
|
|
1071
1107
|
|
|
1072
1108
|
let patches = []
|
|
1073
1109
|
|
|
1110
|
+
let unicode_chars = ins ? [...ins] : []
|
|
1111
|
+
|
|
1074
1112
|
if (ins) {
|
|
1075
1113
|
let inserted_content_bytes = []
|
|
1076
1114
|
|
|
@@ -1081,18 +1119,21 @@ function dt_create_bytes(version, parents, pos, ins) {
|
|
|
1081
1119
|
let encoder = new TextEncoder()
|
|
1082
1120
|
let utf8Bytes = encoder.encode(ins)
|
|
1083
1121
|
|
|
1084
|
-
inserted_content_bytes
|
|
1122
|
+
write_varint(inserted_content_bytes, 1 + utf8Bytes.length)
|
|
1123
|
+
// inserted_content_bytes.push(1 + utf8Bytes.length) // length of content chunk
|
|
1085
1124
|
inserted_content_bytes.push(4) // "plain text" enum
|
|
1086
1125
|
|
|
1087
1126
|
for (let b of utf8Bytes) inserted_content_bytes.push(b) // actual text
|
|
1088
1127
|
|
|
1089
1128
|
inserted_content_bytes.push(25) // "known" enum
|
|
1090
|
-
|
|
1091
|
-
|
|
1129
|
+
let known_chunk = []
|
|
1130
|
+
write_varint(known_chunk, unicode_chars.length * 2 + 1)
|
|
1131
|
+
write_varint(inserted_content_bytes, known_chunk.length)
|
|
1132
|
+
inserted_content_bytes.push(...known_chunk)
|
|
1092
1133
|
|
|
1093
1134
|
patches.push(24)
|
|
1094
1135
|
write_varint(patches, inserted_content_bytes.length)
|
|
1095
|
-
patches.push(
|
|
1136
|
+
for (let b of inserted_content_bytes) patches.push(b)
|
|
1096
1137
|
}
|
|
1097
1138
|
|
|
1098
1139
|
// write in the version
|
|
@@ -1103,26 +1144,43 @@ function dt_create_bytes(version, parents, pos, ins) {
|
|
|
1103
1144
|
let jump = seq
|
|
1104
1145
|
|
|
1105
1146
|
write_varint(version_bytes, ((agent_i + 1) << 1) | (jump != 0 ? 1 : 0))
|
|
1106
|
-
write_varint(version_bytes,
|
|
1147
|
+
write_varint(version_bytes, ins ? unicode_chars.length : del)
|
|
1107
1148
|
if (jump) write_varint(version_bytes, jump << 1)
|
|
1108
1149
|
|
|
1109
1150
|
patches.push(21)
|
|
1110
1151
|
write_varint(patches, version_bytes.length)
|
|
1111
|
-
patches.push(
|
|
1152
|
+
for (let b of version_bytes) patches.push(b)
|
|
1112
1153
|
|
|
1113
1154
|
// write in "op" bytes (some encoding of position)
|
|
1114
1155
|
let op_bytes = []
|
|
1115
1156
|
|
|
1116
|
-
|
|
1157
|
+
if (del) {
|
|
1158
|
+
if (pos == 0) {
|
|
1159
|
+
write_varint(op_bytes, 4)
|
|
1160
|
+
} else if (del == 1) {
|
|
1161
|
+
write_varint(op_bytes, pos * 16 + 6)
|
|
1162
|
+
} else {
|
|
1163
|
+
write_varint(op_bytes, del * 16 + 7)
|
|
1164
|
+
write_varint(op_bytes, pos * 2 + 2)
|
|
1165
|
+
}
|
|
1166
|
+
} else if (unicode_chars.length == 1) {
|
|
1167
|
+
if (pos == 0) write_varint(op_bytes, 0)
|
|
1168
|
+
else write_varint(op_bytes, pos * 16 + 2)
|
|
1169
|
+
} else if (pos == 0) {
|
|
1170
|
+
write_varint(op_bytes, unicode_chars.length * 8 + 1)
|
|
1171
|
+
} else {
|
|
1172
|
+
write_varint(op_bytes, unicode_chars.length * 8 + 3)
|
|
1173
|
+
write_varint(op_bytes, pos * 2)
|
|
1174
|
+
}
|
|
1117
1175
|
|
|
1118
1176
|
patches.push(22)
|
|
1119
1177
|
write_varint(patches, op_bytes.length)
|
|
1120
|
-
patches.push(
|
|
1178
|
+
for (let b of op_bytes) patches.push(b)
|
|
1121
1179
|
|
|
1122
1180
|
// write in parents
|
|
1123
1181
|
let parents_bytes = []
|
|
1124
1182
|
|
|
1125
|
-
write_varint(parents_bytes,
|
|
1183
|
+
write_varint(parents_bytes, ins ? unicode_chars.length : del)
|
|
1126
1184
|
|
|
1127
1185
|
if (parents.length) {
|
|
1128
1186
|
for (let [i, [agent, seq]] of parents.entries()) {
|
|
@@ -1140,14 +1198,16 @@ function dt_create_bytes(version, parents, pos, ins) {
|
|
|
1140
1198
|
// write in patches
|
|
1141
1199
|
bytes.push(20)
|
|
1142
1200
|
write_varint(bytes, patches.length)
|
|
1143
|
-
bytes.push(
|
|
1201
|
+
for (let b of patches) bytes.push(b)
|
|
1144
1202
|
|
|
1145
1203
|
// console.log(bytes);
|
|
1146
1204
|
return bytes
|
|
1147
1205
|
}
|
|
1148
1206
|
|
|
1149
1207
|
function defrag_dt(doc) {
|
|
1150
|
-
|
|
1208
|
+
let bytes = doc.toBytes()
|
|
1209
|
+
doc.free()
|
|
1210
|
+
return Doc.fromBytes(bytes, 'server')
|
|
1151
1211
|
}
|
|
1152
1212
|
|
|
1153
1213
|
function OpLog_remote_to_local(doc, frontier) {
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "braid-text",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Library for collaborative text over http using braid.",
|
|
5
5
|
"author": "Braid Working Group",
|
|
6
6
|
"repository": "braid-org/braidjs",
|
|
7
7
|
"homepage": "https://braid.org",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"diamond-types-node": "^1.0.2",
|
|
10
|
-
"braid-http": "^0.
|
|
10
|
+
"braid-http": "^1.0.7"
|
|
11
11
|
}
|
|
12
12
|
}
|
package/test.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
|
|
2
2
|
let { Doc } = require("diamond-types-node")
|
|
3
|
-
let
|
|
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 =
|
|
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 <
|
|
15
|
-
// let seed =
|
|
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
|
-
//
|
|
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
|
-
|
|
43
|
+
console.log(`edit ${i}`)
|
|
34
44
|
|
|
35
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
//////////////////////////////////////////////////////////////////
|