braid-text 0.2.68 → 0.2.69
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 +500 -306
- package/package.json +1 -1
- package/test/test.html +223 -50
package/index.js
CHANGED
|
@@ -16,13 +16,168 @@ function create_braid_text() {
|
|
|
16
16
|
|
|
17
17
|
let max_encoded_key_size = 240
|
|
18
18
|
|
|
19
|
-
braid_text.sync = async (a, b, options) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
braid_text.sync = async (a, b, options = {}) => {
|
|
20
|
+
var unsync_cbs = []
|
|
21
|
+
options.my_unsync = () => unsync_cbs.forEach(cb => cb())
|
|
22
|
+
|
|
23
|
+
if (!options.merge_type) options.merge_type = 'dt'
|
|
24
|
+
|
|
25
|
+
if ((a instanceof URL) === (b instanceof URL)) {
|
|
26
|
+
var a_ops = {
|
|
27
|
+
subscribe: update => braid_text.put(b, update),
|
|
28
|
+
merge_type: options.merge_type,
|
|
29
|
+
}
|
|
30
|
+
braid_text.get(a, a_ops)
|
|
31
|
+
unsync_cbs.push(() => braid_text.forget(a, a_ops))
|
|
32
|
+
|
|
33
|
+
var b_ops = {
|
|
34
|
+
subscribe: update => braid_text.put(a, update),
|
|
35
|
+
merge_type: options.merge_type,
|
|
36
|
+
}
|
|
37
|
+
braid_text.get(b, b_ops)
|
|
38
|
+
unsync_cbs.push(() => braid_text.forget(b, b_ops))
|
|
39
|
+
} else {
|
|
40
|
+
// make a=local and b=remote (swap if not)
|
|
41
|
+
if (a instanceof URL) { let swap = a; a = b; b = swap }
|
|
42
|
+
|
|
43
|
+
var resource = (typeof a == 'string') ? await get_resource(a) : a
|
|
44
|
+
|
|
45
|
+
function extend_frontier(frontier, version, parents) {
|
|
46
|
+
// special case:
|
|
47
|
+
// if current frontier has all parents,
|
|
48
|
+
// then we can just remove those
|
|
49
|
+
// and add version
|
|
50
|
+
var frontier_set = new Set(frontier)
|
|
51
|
+
if (parents.length &&
|
|
52
|
+
parents.every(p => frontier_set.has(p))) {
|
|
53
|
+
parents.forEach(p => frontier_set.delete(p))
|
|
54
|
+
for (var event of version) frontier_set.add(event)
|
|
55
|
+
frontier = [...frontier_set.values()]
|
|
56
|
+
} else {
|
|
57
|
+
// full-proof approach..
|
|
58
|
+
var looking_for = frontier_set
|
|
59
|
+
for (var event of version) looking_for.add(event)
|
|
60
|
+
|
|
61
|
+
frontier = []
|
|
62
|
+
var shadow = new Set()
|
|
63
|
+
|
|
64
|
+
var bytes = resource.doc.toBytes()
|
|
65
|
+
var [_, events, parentss] = braid_text.dt_parse([...bytes])
|
|
66
|
+
for (var i = events.length - 1; i >= 0 && looking_for.size; i--) {
|
|
67
|
+
var e = events[i].join('-')
|
|
68
|
+
if (looking_for.has(e)) {
|
|
69
|
+
looking_for.delete(e)
|
|
70
|
+
if (!shadow.has(e)) frontier.push(e)
|
|
71
|
+
shadow.add(e)
|
|
72
|
+
}
|
|
73
|
+
if (shadow.has(e))
|
|
74
|
+
parentss[i].forEach(p => shadow.add(p.join('-')))
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return frontier.sort()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
var closed
|
|
81
|
+
var disconnect
|
|
82
|
+
unsync_cbs.push(() => {
|
|
83
|
+
closed = true
|
|
84
|
+
disconnect()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
connect()
|
|
88
|
+
async function connect() {
|
|
89
|
+
if (options.on_connect) options.on_connect()
|
|
90
|
+
|
|
91
|
+
if (closed) return
|
|
92
|
+
|
|
93
|
+
var ac = new AbortController()
|
|
94
|
+
var disconnect_cbs = [() => ac.abort()]
|
|
95
|
+
|
|
96
|
+
disconnect = () => disconnect_cbs.forEach(cb => cb())
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// fork-point
|
|
100
|
+
async function check_version(version) {
|
|
101
|
+
var r = await braid_fetch(b.href, {
|
|
102
|
+
signal: ac.signal,
|
|
103
|
+
method: "HEAD",
|
|
104
|
+
version
|
|
105
|
+
})
|
|
106
|
+
if (!r.ok && r.status !== 309 && r.status !== 500)
|
|
107
|
+
throw new Error(`unexpected HEAD status: ${r.status}`)
|
|
108
|
+
return r.ok
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function extend_fork_point(update) {
|
|
112
|
+
resource.meta.fork_point =
|
|
113
|
+
extend_frontier(resource.meta.fork_point,
|
|
114
|
+
update.version, update.parents)
|
|
115
|
+
resource.change_meta()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// see if remote has the fork point
|
|
119
|
+
if (resource.meta.fork_point &&
|
|
120
|
+
!(await check_version(resource.meta.fork_point))) {
|
|
121
|
+
resource.meta.fork_point = null
|
|
122
|
+
resource.change_meta()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// otherwise let's binary search for new fork point..
|
|
126
|
+
if (!resource.meta.fork_point) {
|
|
127
|
+
var bytes = resource.doc.toBytes()
|
|
128
|
+
var [_, events, __] = braid_text.dt_parse([...bytes])
|
|
129
|
+
events = events.map(x => x.join('-'))
|
|
130
|
+
|
|
131
|
+
var min = -1
|
|
132
|
+
var max = events.length
|
|
133
|
+
while (min + 1 < max) {
|
|
134
|
+
var i = Math.floor((min + max)/2)
|
|
135
|
+
var version = [events[i]]
|
|
136
|
+
if (await check_version(version)) {
|
|
137
|
+
min = i
|
|
138
|
+
resource.meta.fork_point = version
|
|
139
|
+
} else max = i
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// local -> remote
|
|
144
|
+
var a_ops = {
|
|
145
|
+
subscribe: update => {
|
|
146
|
+
update.signal = ac.signal
|
|
147
|
+
braid_text.put(b, update).then((x) => {
|
|
148
|
+
extend_fork_point(update)
|
|
149
|
+
}).catch(e => {
|
|
150
|
+
if (e.name === 'AbortError') {
|
|
151
|
+
// ignore
|
|
152
|
+
} else throw e
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (resource.meta.fork_point)
|
|
157
|
+
a_ops.parents = resource.meta.fork_point
|
|
158
|
+
disconnect_cbs.push(() => braid_text.forget(a, a_ops))
|
|
159
|
+
braid_text.get(a, a_ops)
|
|
160
|
+
|
|
161
|
+
// remote -> local
|
|
162
|
+
var b_ops = {
|
|
163
|
+
dont_retry: true,
|
|
164
|
+
subscribe: async update => {
|
|
165
|
+
await braid_text.put(a, update)
|
|
166
|
+
extend_fork_point(update)
|
|
167
|
+
},
|
|
168
|
+
}
|
|
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)
|
|
172
|
+
} catch (e) {
|
|
173
|
+
if (closed) return
|
|
174
|
+
|
|
175
|
+
disconnect()
|
|
176
|
+
console.log(`disconnected, retrying in 1 second`)
|
|
177
|
+
setTimeout(connect, 1000)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
26
181
|
}
|
|
27
182
|
|
|
28
183
|
braid_text.serve = async (req, res, options = {}) => {
|
|
@@ -45,6 +200,10 @@ function create_braid_text() {
|
|
|
45
200
|
|
|
46
201
|
braidify(req, res)
|
|
47
202
|
if (res.is_multiplexer) return
|
|
203
|
+
|
|
204
|
+
// Sort version arrays from external sources
|
|
205
|
+
if (req.version) req.version.sort()
|
|
206
|
+
if (req.parents) req.parents.sort()
|
|
48
207
|
} catch (e) {
|
|
49
208
|
return my_end(500, "The server failed to process this request. The error generated was: " + e)
|
|
50
209
|
}
|
|
@@ -301,6 +460,15 @@ function create_braid_text() {
|
|
|
301
460
|
}
|
|
302
461
|
|
|
303
462
|
braid_text.get = async (key, options) => {
|
|
463
|
+
if (options && options.version) {
|
|
464
|
+
validate_version_array(options.version)
|
|
465
|
+
options.version.sort()
|
|
466
|
+
}
|
|
467
|
+
if (options && options.parents) {
|
|
468
|
+
validate_version_array(options.parents)
|
|
469
|
+
options.parents.sort()
|
|
470
|
+
}
|
|
471
|
+
|
|
304
472
|
if (key instanceof URL) {
|
|
305
473
|
if (!options) options = {}
|
|
306
474
|
|
|
@@ -308,22 +476,29 @@ function create_braid_text() {
|
|
|
308
476
|
|
|
309
477
|
var params = {
|
|
310
478
|
signal: options.my_abort.signal,
|
|
311
|
-
retry: () => true,
|
|
312
479
|
subscribe: !!options.subscribe,
|
|
313
480
|
heartbeats: 120,
|
|
314
481
|
}
|
|
482
|
+
if (!options.dont_retry) params.retry = () => true
|
|
315
483
|
for (var x of ['headers', 'parents', 'version', 'peer'])
|
|
316
484
|
if (options[x] != null) params[x] = options[x]
|
|
317
485
|
|
|
318
486
|
var res = await braid_fetch(key.href, params)
|
|
319
487
|
|
|
320
488
|
if (options.subscribe) {
|
|
321
|
-
|
|
489
|
+
if (options.dont_retry) {
|
|
490
|
+
var error_happened
|
|
491
|
+
var error_promise = new Promise((_, fail) => error_happened = fail)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
res.subscribe(async update => {
|
|
322
495
|
update.body = update.body_text
|
|
323
496
|
if (update.patches)
|
|
324
497
|
for (var p of update.patches) p.content = p.content_text
|
|
325
|
-
options.subscribe(update)
|
|
326
|
-
})
|
|
498
|
+
await options.subscribe(update)
|
|
499
|
+
}, e => options.dont_retry && error_happened(e))
|
|
500
|
+
|
|
501
|
+
if (options.dont_retry) return await error_promise
|
|
327
502
|
return res
|
|
328
503
|
} else return await res.text()
|
|
329
504
|
}
|
|
@@ -334,9 +509,6 @@ function create_braid_text() {
|
|
|
334
509
|
return (await get_resource(key)).val
|
|
335
510
|
}
|
|
336
511
|
|
|
337
|
-
if (options.version) validate_version_array(options.version)
|
|
338
|
-
if (options.parents) validate_version_array(options.parents)
|
|
339
|
-
|
|
340
512
|
let resource = (typeof key == 'string') ? await get_resource(key) : key
|
|
341
513
|
var version = resource.version
|
|
342
514
|
|
|
@@ -374,13 +546,19 @@ function create_braid_text() {
|
|
|
374
546
|
body: resource.doc.get()
|
|
375
547
|
}
|
|
376
548
|
} else {
|
|
549
|
+
options.my_subscribe_chain = Promise.resolve()
|
|
550
|
+
options.my_subscribe = (x) =>
|
|
551
|
+
options.my_subscribe_chain =
|
|
552
|
+
options.my_subscribe_chain.then(() =>
|
|
553
|
+
options.subscribe(x))
|
|
554
|
+
|
|
377
555
|
if (options.merge_type != "dt") {
|
|
378
556
|
let x = { version }
|
|
379
557
|
|
|
380
558
|
if (!options.parents && !options.version) {
|
|
381
559
|
x.parents = []
|
|
382
560
|
x.body = resource.doc.get()
|
|
383
|
-
options.
|
|
561
|
+
options.my_subscribe(x)
|
|
384
562
|
} else {
|
|
385
563
|
x.parents = options.version ? options.version : options.parents
|
|
386
564
|
options.my_last_seen_version = x.parents
|
|
@@ -389,7 +567,7 @@ function create_braid_text() {
|
|
|
389
567
|
let local_version = OpLog_remote_to_local(resource.doc, x.parents)
|
|
390
568
|
if (local_version) {
|
|
391
569
|
x.patches = get_xf_patches(resource.doc, local_version)
|
|
392
|
-
options.
|
|
570
|
+
options.my_subscribe(x)
|
|
393
571
|
}
|
|
394
572
|
}
|
|
395
573
|
|
|
@@ -401,7 +579,7 @@ function create_braid_text() {
|
|
|
401
579
|
// optimization: if client wants past current version,
|
|
402
580
|
// send empty dt
|
|
403
581
|
if (options.parents && v_eq(options.parents, version)) {
|
|
404
|
-
options.
|
|
582
|
+
options.my_subscribe({ encoding: 'dt', body: new Doc().toBytes() })
|
|
405
583
|
} else {
|
|
406
584
|
var bytes = resource.doc.toBytes()
|
|
407
585
|
if (options.parents) {
|
|
@@ -410,12 +588,12 @@ function create_braid_text() {
|
|
|
410
588
|
dt_get_local_version(bytes, options.parents))
|
|
411
589
|
doc.free()
|
|
412
590
|
}
|
|
413
|
-
options.
|
|
591
|
+
options.my_subscribe({ encoding: 'dt', body: bytes })
|
|
414
592
|
}
|
|
415
593
|
} else {
|
|
416
594
|
var updates = null
|
|
417
595
|
if (!options.parents && !options.version) {
|
|
418
|
-
options.
|
|
596
|
+
options.my_subscribe({
|
|
419
597
|
version: [],
|
|
420
598
|
parents: [],
|
|
421
599
|
body: "",
|
|
@@ -428,7 +606,7 @@ function create_braid_text() {
|
|
|
428
606
|
}
|
|
429
607
|
|
|
430
608
|
for (let u of updates)
|
|
431
|
-
options.
|
|
609
|
+
options.my_subscribe({
|
|
432
610
|
version: [u.version],
|
|
433
611
|
parents: u.parents,
|
|
434
612
|
patches: [{ unit: u.unit, range: u.range, content: u.content }],
|
|
@@ -459,8 +637,20 @@ function create_braid_text() {
|
|
|
459
637
|
}
|
|
460
638
|
|
|
461
639
|
braid_text.put = async (key, options) => {
|
|
640
|
+
if (options.version) {
|
|
641
|
+
validate_version_array(options.version)
|
|
642
|
+
options.version.sort()
|
|
643
|
+
}
|
|
644
|
+
if (options.parents) {
|
|
645
|
+
validate_version_array(options.parents)
|
|
646
|
+
options.parents.sort()
|
|
647
|
+
}
|
|
648
|
+
|
|
462
649
|
if (key instanceof URL) {
|
|
463
650
|
options.my_abort = new AbortController()
|
|
651
|
+
if (options.signal)
|
|
652
|
+
options.signal.addEventListener('abort', () =>
|
|
653
|
+
options.my_abort.abort())
|
|
464
654
|
|
|
465
655
|
var params = {
|
|
466
656
|
method: 'PUT',
|
|
@@ -473,315 +663,311 @@ function create_braid_text() {
|
|
|
473
663
|
return await braid_fetch(key.href, params)
|
|
474
664
|
}
|
|
475
665
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
// support for json patch puts..
|
|
479
|
-
if (patches?.length && patches.every(x => x.unit === 'json')) {
|
|
666
|
+
return await within_fiber('put:' + key, async () => {
|
|
480
667
|
let resource = (typeof key == 'string') ? await get_resource(key) : key
|
|
481
|
-
|
|
482
|
-
let x = JSON.parse(resource.doc.get())
|
|
483
|
-
for (let p of patches)
|
|
484
|
-
apply_patch(x, p.range, p.content === '' ? undefined : JSON.parse(p.content))
|
|
485
668
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
669
|
+
// support for json patch puts..
|
|
670
|
+
if (options.patches && options.patches.length &&
|
|
671
|
+
options.patches.every(x => x.unit === 'json')) {
|
|
672
|
+
let x = JSON.parse(resource.doc.get())
|
|
673
|
+
for (let p of options.patches)
|
|
674
|
+
apply_patch(x, p.range, p.content === '' ? undefined : JSON.parse(p.content))
|
|
675
|
+
options = { body: JSON.stringify(x, null, 4) }
|
|
676
|
+
}
|
|
490
677
|
|
|
491
|
-
|
|
678
|
+
let { version, patches, body, peer } = options
|
|
492
679
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
680
|
+
if (options.transfer_encoding === 'dt') {
|
|
681
|
+
var start_i = 1 + resource.doc.getLocalVersion().reduce((a, b) => Math.max(a, b), -1)
|
|
682
|
+
|
|
683
|
+
resource.doc.mergeBytes(body)
|
|
497
684
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
685
|
+
var end_i = resource.doc.getLocalVersion().reduce((a, b) => Math.max(a, b), -1)
|
|
686
|
+
for (var i = start_i; i <= end_i; i++) {
|
|
687
|
+
let v = resource.doc.localToRemoteVersion([i])[0]
|
|
688
|
+
if (!resource.actor_seqs[v[0]]) resource.actor_seqs[v[0]] = new braid_text.RangeSet()
|
|
689
|
+
resource.actor_seqs[v[0]].add_range(v[1], v[1])
|
|
690
|
+
}
|
|
691
|
+
resource.val = resource.doc.get()
|
|
692
|
+
resource.version = resource.doc.getRemoteVersion().map(x => x.join("-")).sort()
|
|
506
693
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
694
|
+
await resource.db_delta(body)
|
|
695
|
+
return { change_count: end_i - start_i + 1 }
|
|
696
|
+
}
|
|
510
697
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
throw new Error(`missing parent version: ${p}`)
|
|
698
|
+
if (version && !version.length) {
|
|
699
|
+
console.log(`warning: ignoring put with empty version`)
|
|
700
|
+
return { change_count: 0 }
|
|
701
|
+
}
|
|
702
|
+
if (version && version.length > 1)
|
|
703
|
+
throw new Error(`cannot put a version with multiple ids`)
|
|
704
|
+
|
|
705
|
+
// translate a single parent of "root" to the empty array (same meaning)
|
|
706
|
+
let options_parents = options.parents
|
|
707
|
+
if (options_parents?.length === 1 && options_parents[0] === 'root')
|
|
708
|
+
options_parents = []
|
|
709
|
+
|
|
710
|
+
if (body != null && patches) throw new Error(`cannot have a body and patches`)
|
|
711
|
+
if (body != null && (typeof body !== 'string')) throw new Error(`body must be a string`)
|
|
712
|
+
if (patches) validate_patches(patches)
|
|
713
|
+
|
|
714
|
+
if (options_parents) {
|
|
715
|
+
// make sure we have all these parents
|
|
716
|
+
for (let p of options_parents) {
|
|
717
|
+
let P = decode_version(p)
|
|
718
|
+
if (!resource.actor_seqs[P[0]]?.has(P[1]))
|
|
719
|
+
throw new Error(`missing parent version: ${p}`)
|
|
720
|
+
}
|
|
535
721
|
}
|
|
536
|
-
}
|
|
537
722
|
|
|
538
|
-
|
|
539
|
-
|
|
723
|
+
let parents = resource.version
|
|
724
|
+
let og_parents = options_parents || parents
|
|
540
725
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
726
|
+
let max_pos = resource.length_cache.get('' + og_parents) ??
|
|
727
|
+
(v_eq(parents, og_parents) ? resource.doc.len() : dt_len(resource.doc, og_parents))
|
|
728
|
+
|
|
729
|
+
if (body != null) {
|
|
730
|
+
patches = [{
|
|
731
|
+
unit: 'text',
|
|
732
|
+
range: `[0:${max_pos}]`,
|
|
733
|
+
content: body
|
|
734
|
+
}]
|
|
735
|
+
}
|
|
551
736
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
737
|
+
let og_patches = patches
|
|
738
|
+
patches = patches.map((p) => ({
|
|
739
|
+
...p,
|
|
740
|
+
range: p.range.match(/\d+/g).map((x) => parseInt(x)),
|
|
741
|
+
content_codepoints: [...p.content],
|
|
742
|
+
})).sort((a, b) => a.range[0] - b.range[0])
|
|
558
743
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
744
|
+
// validate patch positions
|
|
745
|
+
let must_be_at_least = 0
|
|
746
|
+
for (let p of patches) {
|
|
747
|
+
if (p.range[0] < must_be_at_least || p.range[0] > max_pos) throw new Error(`invalid patch range position: ${p.range[0]}`)
|
|
748
|
+
if (p.range[1] < p.range[0] || p.range[1] > max_pos) throw new Error(`invalid patch range position: ${p.range[1]}`)
|
|
749
|
+
must_be_at_least = p.range[1]
|
|
750
|
+
}
|
|
566
751
|
|
|
567
|
-
|
|
752
|
+
let change_count = patches.reduce((a, b) => a + b.content_codepoints.length + (b.range[1] - b.range[0]), 0)
|
|
568
753
|
|
|
569
|
-
|
|
754
|
+
let og_v = version?.[0] || `${(is_valid_actor(peer) && peer) || Math.random().toString(36).slice(2, 7)}-${change_count - 1}`
|
|
570
755
|
|
|
571
|
-
|
|
756
|
+
let v = decode_version(og_v)
|
|
572
757
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
758
|
+
resource.length_cache.put(`${v[0]}-${v[1]}`, patches.reduce((a, b) =>
|
|
759
|
+
a + (b.content_codepoints?.length ?? 0) - (b.range[1] - b.range[0]),
|
|
760
|
+
max_pos))
|
|
576
761
|
|
|
577
|
-
|
|
578
|
-
|
|
762
|
+
// validate version: make sure we haven't seen it already
|
|
763
|
+
if (resource.actor_seqs[v[0]]?.has(v[1])) {
|
|
579
764
|
|
|
580
|
-
|
|
765
|
+
if (!options.validate_already_seen_versions) return { change_count }
|
|
581
766
|
|
|
582
|
-
|
|
583
|
-
|
|
767
|
+
// if we have seen it already, make sure it's the same as before
|
|
768
|
+
let updates = dt_get_patches(resource.doc, og_parents)
|
|
584
769
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
770
|
+
let seen = {}
|
|
771
|
+
for (let u of updates) {
|
|
772
|
+
u.version = decode_version(u.version)
|
|
773
|
+
|
|
774
|
+
if (!u.content) {
|
|
775
|
+
// delete
|
|
776
|
+
let v = u.version
|
|
777
|
+
for (let i = 0; i < u.end - u.start; i++) {
|
|
778
|
+
let ps = (i < u.end - u.start - 1) ? [`${v[0]}-${v[1] - i - 1}`] : u.parents
|
|
779
|
+
seen[JSON.stringify([v[0], v[1] - i, ps, u.start + i])] = true
|
|
780
|
+
}
|
|
781
|
+
} else {
|
|
782
|
+
// insert
|
|
783
|
+
let v = u.version
|
|
784
|
+
let content = [...u.content]
|
|
785
|
+
for (let i = 0; i < content.length; i++) {
|
|
786
|
+
let ps = (i > 0) ? [`${v[0]}-${v[1] - content.length + i}`] : u.parents
|
|
787
|
+
seen[JSON.stringify([v[0], v[1] + 1 - content.length + i, ps, u.start + i, content[i]])] = true
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
588
791
|
|
|
589
|
-
|
|
792
|
+
v = `${v[0]}-${v[1] + 1 - change_count}`
|
|
793
|
+
let ps = og_parents
|
|
794
|
+
let offset = 0
|
|
795
|
+
for (let p of patches) {
|
|
590
796
|
// delete
|
|
591
|
-
let
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
seen[JSON.stringify([
|
|
797
|
+
for (let i = p.range[0]; i < p.range[1]; i++) {
|
|
798
|
+
let vv = decode_version(v)
|
|
799
|
+
|
|
800
|
+
if (!seen[JSON.stringify([vv[0], vv[1], ps, p.range[1] - 1 + offset])]) throw new Error('invalid update: different from previous update with same version')
|
|
801
|
+
|
|
802
|
+
offset--
|
|
803
|
+
ps = [v]
|
|
804
|
+
v = vv
|
|
805
|
+
v = `${v[0]}-${v[1] + 1}`
|
|
595
806
|
}
|
|
596
|
-
} else {
|
|
597
807
|
// insert
|
|
598
|
-
let
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
seen[JSON.stringify([
|
|
808
|
+
for (let i = 0; i < p.content_codepoints?.length ?? 0; i++) {
|
|
809
|
+
let vv = decode_version(v)
|
|
810
|
+
let c = p.content_codepoints[i]
|
|
811
|
+
|
|
812
|
+
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')
|
|
813
|
+
|
|
814
|
+
offset++
|
|
815
|
+
ps = [v]
|
|
816
|
+
v = vv
|
|
817
|
+
v = `${v[0]}-${v[1] + 1}`
|
|
603
818
|
}
|
|
604
819
|
}
|
|
820
|
+
|
|
821
|
+
// we already have this version, so nothing left to do
|
|
822
|
+
return { change_count: change_count }
|
|
605
823
|
}
|
|
824
|
+
if (!resource.actor_seqs[v[0]]) resource.actor_seqs[v[0]] = new RangeSet()
|
|
825
|
+
resource.actor_seqs[v[0]].add_range(v[1] + 1 - change_count, v[1])
|
|
606
826
|
|
|
827
|
+
// reduce the version sequence by the number of char-edits
|
|
607
828
|
v = `${v[0]}-${v[1] + 1 - change_count}`
|
|
829
|
+
|
|
608
830
|
let ps = og_parents
|
|
831
|
+
|
|
832
|
+
let v_before = resource.doc.getLocalVersion()
|
|
833
|
+
|
|
834
|
+
let bytes = []
|
|
835
|
+
|
|
609
836
|
let offset = 0
|
|
610
837
|
for (let p of patches) {
|
|
611
838
|
// delete
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
v = vv
|
|
620
|
-
v = `${v[0]}-${v[1] + 1}`
|
|
839
|
+
let del = p.range[1] - p.range[0]
|
|
840
|
+
if (del) {
|
|
841
|
+
bytes.push(dt_create_bytes(v, ps, p.range[0] + offset, del, null))
|
|
842
|
+
offset -= del
|
|
843
|
+
v = decode_version(v)
|
|
844
|
+
ps = [`${v[0]}-${v[1] + (del - 1)}`]
|
|
845
|
+
v = `${v[0]}-${v[1] + del}`
|
|
621
846
|
}
|
|
622
847
|
// insert
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
offset++
|
|
630
|
-
ps = [v]
|
|
631
|
-
v = vv
|
|
632
|
-
v = `${v[0]}-${v[1] + 1}`
|
|
848
|
+
if (p.content?.length) {
|
|
849
|
+
bytes.push(dt_create_bytes(v, ps, p.range[1] + offset, 0, p.content))
|
|
850
|
+
offset += p.content_codepoints.length
|
|
851
|
+
v = decode_version(v)
|
|
852
|
+
ps = [`${v[0]}-${v[1] + (p.content_codepoints.length - 1)}`]
|
|
853
|
+
v = `${v[0]}-${v[1] + p.content_codepoints.length}`
|
|
633
854
|
}
|
|
634
855
|
}
|
|
635
856
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
if (!resource.actor_seqs[v[0]]) resource.actor_seqs[v[0]] = new RangeSet()
|
|
640
|
-
resource.actor_seqs[v[0]].add_range(v[1] + 1 - change_count, v[1])
|
|
641
|
-
|
|
642
|
-
// reduce the version sequence by the number of char-edits
|
|
643
|
-
v = `${v[0]}-${v[1] + 1 - change_count}`
|
|
644
|
-
|
|
645
|
-
let ps = og_parents
|
|
646
|
-
|
|
647
|
-
let v_before = resource.doc.getLocalVersion()
|
|
648
|
-
|
|
649
|
-
let bytes = []
|
|
650
|
-
|
|
651
|
-
let offset = 0
|
|
652
|
-
for (let p of patches) {
|
|
653
|
-
// delete
|
|
654
|
-
let del = p.range[1] - p.range[0]
|
|
655
|
-
if (del) {
|
|
656
|
-
bytes.push(dt_create_bytes(v, ps, p.range[0] + offset, del, null))
|
|
657
|
-
offset -= del
|
|
658
|
-
v = decode_version(v)
|
|
659
|
-
ps = [`${v[0]}-${v[1] + (del - 1)}`]
|
|
660
|
-
v = `${v[0]}-${v[1] + del}`
|
|
661
|
-
}
|
|
662
|
-
// insert
|
|
663
|
-
if (p.content?.length) {
|
|
664
|
-
bytes.push(dt_create_bytes(v, ps, p.range[1] + offset, 0, p.content))
|
|
665
|
-
offset += p.content_codepoints.length
|
|
666
|
-
v = decode_version(v)
|
|
667
|
-
ps = [`${v[0]}-${v[1] + (p.content_codepoints.length - 1)}`]
|
|
668
|
-
v = `${v[0]}-${v[1] + p.content_codepoints.length}`
|
|
669
|
-
}
|
|
670
|
-
}
|
|
857
|
+
for (let b of bytes) resource.doc.mergeBytes(b)
|
|
858
|
+
resource.val = resource.doc.get()
|
|
859
|
+
resource.version = resource.doc.getRemoteVersion().map(x => x.join("-")).sort()
|
|
671
860
|
|
|
672
|
-
|
|
673
|
-
resource.val = resource.doc.get()
|
|
674
|
-
resource.version = resource.doc.getRemoteVersion().map(x => x.join("-")).sort()
|
|
861
|
+
var post_commit_updates = []
|
|
675
862
|
|
|
676
|
-
|
|
863
|
+
if (options.merge_type != "dt") {
|
|
864
|
+
patches = get_xf_patches(resource.doc, v_before)
|
|
865
|
+
if (braid_text.verbose) console.log(JSON.stringify({ patches }))
|
|
677
866
|
|
|
678
|
-
|
|
679
|
-
patches = get_xf_patches(resource.doc, v_before)
|
|
680
|
-
if (braid_text.verbose) console.log(JSON.stringify({ patches }))
|
|
867
|
+
let version = resource.version
|
|
681
868
|
|
|
682
|
-
|
|
869
|
+
for (let client of resource.simpleton_clients) {
|
|
870
|
+
if (peer && client.peer === peer) {
|
|
871
|
+
client.my_last_seen_version = [og_v]
|
|
872
|
+
}
|
|
683
873
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
874
|
+
function set_timeout(time_override) {
|
|
875
|
+
if (client.my_timeout) clearTimeout(client.my_timeout)
|
|
876
|
+
client.my_timeout = setTimeout(() => {
|
|
877
|
+
// if the doc has been freed, exit early
|
|
878
|
+
if (resource.doc.__wbg_ptr === 0) return
|
|
688
879
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
// if the doc has been freed, exit early
|
|
693
|
-
if (resource.doc.__wbg_ptr === 0) return
|
|
880
|
+
let version = resource.version
|
|
881
|
+
let x = { version }
|
|
882
|
+
x.parents = client.my_last_seen_version
|
|
694
883
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
884
|
+
if (braid_text.verbose) console.log("rebasing after timeout.. ")
|
|
885
|
+
if (braid_text.verbose) console.log(" client.my_unused_version_count = " + client.my_unused_version_count)
|
|
886
|
+
x.patches = get_xf_patches(resource.doc, OpLog_remote_to_local(resource.doc, client.my_last_seen_version))
|
|
698
887
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
888
|
+
if (braid_text.verbose) console.log(`sending from rebase: ${JSON.stringify(x)}`)
|
|
889
|
+
client.my_subscribe(x)
|
|
890
|
+
client.my_last_sent_version = x.version
|
|
702
891
|
|
|
703
|
-
|
|
704
|
-
client.
|
|
705
|
-
|
|
892
|
+
delete client.my_timeout
|
|
893
|
+
}, time_override ?? Math.min(3000, 23 * Math.pow(1.5, client.my_unused_version_count - 1)))
|
|
894
|
+
}
|
|
706
895
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
896
|
+
if (client.my_timeout) {
|
|
897
|
+
if (peer && client.peer === peer) {
|
|
898
|
+
if (!v_eq(client.my_last_sent_version, og_parents)) {
|
|
899
|
+
// note: we don't add to client.my_unused_version_count,
|
|
900
|
+
// because we're already in a timeout;
|
|
901
|
+
// we'll just extend it here..
|
|
902
|
+
set_timeout()
|
|
903
|
+
} else {
|
|
904
|
+
// hm.. it appears we got a correctly parented version,
|
|
905
|
+
// which suggests that maybe we can stop the timeout early
|
|
906
|
+
set_timeout(0)
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
continue
|
|
910
|
+
}
|
|
710
911
|
|
|
711
|
-
|
|
912
|
+
let x = { version }
|
|
712
913
|
if (peer && client.peer === peer) {
|
|
713
914
|
if (!v_eq(client.my_last_sent_version, og_parents)) {
|
|
714
|
-
|
|
715
|
-
// because we're already in a timeout;
|
|
716
|
-
// we'll just extend it here..
|
|
915
|
+
client.my_unused_version_count = (client.my_unused_version_count ?? 0) + 1
|
|
717
916
|
set_timeout()
|
|
917
|
+
continue
|
|
718
918
|
} else {
|
|
719
|
-
|
|
720
|
-
// which suggests that maybe we can stop the timeout early
|
|
721
|
-
set_timeout(0)
|
|
919
|
+
delete client.my_unused_version_count
|
|
722
920
|
}
|
|
723
|
-
}
|
|
724
|
-
continue
|
|
725
|
-
}
|
|
726
921
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
922
|
+
x.parents = options.version
|
|
923
|
+
if (!v_eq(version, options.version)) {
|
|
924
|
+
if (braid_text.verbose) console.log("rebasing..")
|
|
925
|
+
x.patches = get_xf_patches(resource.doc, OpLog_remote_to_local(resource.doc, [og_v]))
|
|
926
|
+
} else {
|
|
927
|
+
// this client already has this version,
|
|
928
|
+
// so let's pretend to send it back, but not
|
|
929
|
+
if (braid_text.verbose) console.log(`not reflecting back to simpleton`)
|
|
930
|
+
client.my_last_sent_version = x.version
|
|
931
|
+
continue
|
|
932
|
+
}
|
|
733
933
|
} else {
|
|
734
|
-
|
|
934
|
+
x.parents = parents
|
|
935
|
+
x.patches = patches
|
|
735
936
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
937
|
+
if (braid_text.verbose) console.log(`sending: ${JSON.stringify(x)}`)
|
|
938
|
+
post_commit_updates.push([client, x])
|
|
939
|
+
client.my_last_sent_version = x.version
|
|
940
|
+
}
|
|
941
|
+
} else {
|
|
942
|
+
if (resource.simpleton_clients.size) {
|
|
943
|
+
let version = resource.version
|
|
944
|
+
patches = get_xf_patches(resource.doc, v_before)
|
|
945
|
+
let x = { version, parents, patches }
|
|
946
|
+
if (braid_text.verbose) console.log(`sending: ${JSON.stringify(x)}`)
|
|
947
|
+
for (let client of resource.simpleton_clients) {
|
|
948
|
+
if (client.my_timeout) continue
|
|
949
|
+
post_commit_updates.push([client, x])
|
|
745
950
|
client.my_last_sent_version = x.version
|
|
746
|
-
continue
|
|
747
951
|
}
|
|
748
|
-
} else {
|
|
749
|
-
x.parents = parents
|
|
750
|
-
x.patches = patches
|
|
751
952
|
}
|
|
752
|
-
if (braid_text.verbose) console.log(`sending: ${JSON.stringify(x)}`)
|
|
753
|
-
post_commit_updates.push([client, x])
|
|
754
|
-
client.my_last_sent_version = x.version
|
|
755
953
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
954
|
+
|
|
955
|
+
var x = {
|
|
956
|
+
version: [og_v],
|
|
957
|
+
parents: og_parents,
|
|
958
|
+
patches: og_patches,
|
|
959
|
+
}
|
|
960
|
+
for (let client of resource.clients) {
|
|
961
|
+
if (!peer || client.peer !== peer)
|
|
764
962
|
post_commit_updates.push([client, x])
|
|
765
|
-
client.my_last_sent_version = x.version
|
|
766
|
-
}
|
|
767
963
|
}
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
var x = {
|
|
771
|
-
version: [og_v],
|
|
772
|
-
parents: og_parents,
|
|
773
|
-
patches: og_patches,
|
|
774
|
-
}
|
|
775
|
-
for (let client of resource.clients) {
|
|
776
|
-
if (!peer || client.peer !== peer)
|
|
777
|
-
post_commit_updates.push([client, x])
|
|
778
|
-
}
|
|
779
964
|
|
|
780
|
-
|
|
965
|
+
await resource.db_delta(resource.doc.getPatchSince(v_before))
|
|
781
966
|
|
|
782
|
-
|
|
967
|
+
for (var [client, x] of post_commit_updates) client.my_subscribe(x)
|
|
783
968
|
|
|
784
|
-
|
|
969
|
+
return { change_count }
|
|
970
|
+
})
|
|
785
971
|
}
|
|
786
972
|
|
|
787
973
|
braid_text.list = async () => {
|
|
@@ -821,10 +1007,7 @@ function create_braid_text() {
|
|
|
821
1007
|
: { change: () => { }, change_meta: () => {} }
|
|
822
1008
|
|
|
823
1009
|
resource.db_delta = change
|
|
824
|
-
resource.
|
|
825
|
-
Object.assign(resource.meta, meta)
|
|
826
|
-
change_meta()
|
|
827
|
-
}
|
|
1010
|
+
resource.change_meta = change_meta
|
|
828
1011
|
|
|
829
1012
|
resource.actor_seqs = {}
|
|
830
1013
|
|
|
@@ -959,48 +1142,45 @@ function create_braid_text() {
|
|
|
959
1142
|
} catch (e) {}
|
|
960
1143
|
set_meta(JSON.parse(meta_file_content))
|
|
961
1144
|
|
|
962
|
-
let chain = Promise.resolve()
|
|
963
1145
|
return {
|
|
964
|
-
change: async (
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
if (
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1146
|
+
change: (bytes) => within_fiber('file:' + key, async () => {
|
|
1147
|
+
if (!bytes) currentSize = threshold
|
|
1148
|
+
else currentSize += bytes.length + 4 // we account for the extra 4 bytes for uint32
|
|
1149
|
+
const filename = `${braid_text.db_folder}/${encoded}.${currentNumber}`
|
|
1150
|
+
if (currentSize < threshold) {
|
|
1151
|
+
if (braid_text.verbose) console.log(`appending to db..`)
|
|
1152
|
+
|
|
1153
|
+
let buffer = Buffer.allocUnsafe(4)
|
|
1154
|
+
buffer.writeUInt32LE(bytes.length, 0)
|
|
1155
|
+
await fs.promises.appendFile(filename, buffer)
|
|
1156
|
+
await fs.promises.appendFile(filename, bytes)
|
|
1157
|
+
|
|
1158
|
+
if (braid_text.verbose) console.log("wrote to : " + filename)
|
|
1159
|
+
} else {
|
|
1160
|
+
try {
|
|
1161
|
+
if (braid_text.verbose) console.log(`starting new db..`)
|
|
1162
|
+
|
|
1163
|
+
currentNumber++
|
|
1164
|
+
const init = get_init()
|
|
1165
|
+
const buffer = Buffer.allocUnsafe(4)
|
|
1166
|
+
buffer.writeUInt32LE(init.length, 0)
|
|
1167
|
+
|
|
1168
|
+
const newFilename = `${braid_text.db_folder}/${encoded}.${currentNumber}`
|
|
1169
|
+
await fs.promises.writeFile(newFilename, buffer)
|
|
1170
|
+
await fs.promises.appendFile(newFilename, init)
|
|
1171
|
+
|
|
1172
|
+
if (braid_text.verbose) console.log("wrote to : " + newFilename)
|
|
1173
|
+
|
|
1174
|
+
currentSize = 4 + init.length
|
|
1175
|
+
threshold = currentSize * 10
|
|
979
1176
|
try {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
const buffer = Buffer.allocUnsafe(4)
|
|
985
|
-
buffer.writeUInt32LE(init.length, 0)
|
|
986
|
-
|
|
987
|
-
const newFilename = `${braid_text.db_folder}/${encoded}.${currentNumber}`
|
|
988
|
-
await fs.promises.writeFile(newFilename, buffer)
|
|
989
|
-
await fs.promises.appendFile(newFilename, init)
|
|
990
|
-
|
|
991
|
-
if (braid_text.verbose) console.log("wrote to : " + newFilename)
|
|
992
|
-
|
|
993
|
-
currentSize = 4 + init.length
|
|
994
|
-
threshold = currentSize * 10
|
|
995
|
-
try {
|
|
996
|
-
await fs.promises.unlink(filename)
|
|
997
|
-
} catch (e) { }
|
|
998
|
-
} catch (e) {
|
|
999
|
-
if (braid_text.verbose) console.log(`e = ${e.stack}`)
|
|
1000
|
-
}
|
|
1177
|
+
await fs.promises.unlink(filename)
|
|
1178
|
+
} catch (e) { }
|
|
1179
|
+
} catch (e) {
|
|
1180
|
+
if (braid_text.verbose) console.log(`e = ${e.stack}`)
|
|
1001
1181
|
}
|
|
1002
|
-
}
|
|
1003
|
-
},
|
|
1182
|
+
}
|
|
1183
|
+
}),
|
|
1004
1184
|
change_meta: async () => {
|
|
1005
1185
|
meta_dirty = true
|
|
1006
1186
|
if (meta_saving) return
|
|
@@ -2271,6 +2451,20 @@ function create_braid_text() {
|
|
|
2271
2451
|
return `sha-256=:${require('crypto').createHash('sha256').update(s).digest('base64')}:`
|
|
2272
2452
|
}
|
|
2273
2453
|
|
|
2454
|
+
function within_fiber(id, func) {
|
|
2455
|
+
if (!within_fiber.chains) within_fiber.chains = {}
|
|
2456
|
+
var prev = within_fiber.chains[id] || Promise.resolve()
|
|
2457
|
+
var curr = prev.then(async () => {
|
|
2458
|
+
try {
|
|
2459
|
+
return await func()
|
|
2460
|
+
} finally {
|
|
2461
|
+
if (within_fiber.chains[id] === curr)
|
|
2462
|
+
delete within_fiber.chains[id]
|
|
2463
|
+
}
|
|
2464
|
+
})
|
|
2465
|
+
return within_fiber.chains[id] = curr
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2274
2468
|
braid_text.get_resource = get_resource
|
|
2275
2469
|
|
|
2276
2470
|
braid_text.encode_filename = encode_filename
|