braid-blob 0.0.18 → 0.0.20
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/.claude/settings.local.json +3 -1
- package/index.js +200 -23
- package/package.json +1 -1
- package/test/tests.js +417 -0
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var {http_server: braidify} = require('braid-http'),
|
|
1
|
+
var {http_server: braidify, fetch: braid_fetch} = require('braid-http'),
|
|
2
2
|
{url_file_db} = require('url-file-db'),
|
|
3
3
|
fs = require('fs'),
|
|
4
4
|
path = require('path')
|
|
@@ -44,6 +44,29 @@ function create_braid_blob() {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
braid_blob.put = async (key, body, options = {}) => {
|
|
47
|
+
// Handle URL case - make a remote PUT request
|
|
48
|
+
if (key instanceof URL) {
|
|
49
|
+
options.my_abort = new AbortController()
|
|
50
|
+
if (options.signal) {
|
|
51
|
+
options.signal.addEventListener('abort', () =>
|
|
52
|
+
options.my_abort.abort())
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
var params = {
|
|
56
|
+
method: 'PUT',
|
|
57
|
+
signal: options.my_abort.signal,
|
|
58
|
+
retry: () => true,
|
|
59
|
+
body: body
|
|
60
|
+
}
|
|
61
|
+
for (var x of ['headers', 'version', 'peer'])
|
|
62
|
+
if (options[x] != null) params[x] = options[x]
|
|
63
|
+
if (options.content_type) {
|
|
64
|
+
params.headers = { ...params.headers, 'Content-Type': options.content_type }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return await braid_fetch(key.href, params)
|
|
68
|
+
}
|
|
69
|
+
|
|
47
70
|
await braid_blob.init()
|
|
48
71
|
|
|
49
72
|
// Read the meta data from meta_db
|
|
@@ -92,6 +115,42 @@ function create_braid_blob() {
|
|
|
92
115
|
}
|
|
93
116
|
|
|
94
117
|
braid_blob.get = async (key, options = {}) => {
|
|
118
|
+
// Handle URL case - make a remote GET request
|
|
119
|
+
if (key instanceof URL) {
|
|
120
|
+
options.my_abort = new AbortController()
|
|
121
|
+
|
|
122
|
+
var params = {
|
|
123
|
+
signal: options.my_abort.signal,
|
|
124
|
+
subscribe: !!options.subscribe,
|
|
125
|
+
heartbeats: 120,
|
|
126
|
+
}
|
|
127
|
+
if (!options.dont_retry) {
|
|
128
|
+
params.retry = () => true
|
|
129
|
+
}
|
|
130
|
+
for (var x of ['headers', 'parents', 'version', 'peer'])
|
|
131
|
+
if (options[x] != null) params[x] = options[x]
|
|
132
|
+
|
|
133
|
+
var res = await braid_fetch(key.href, params)
|
|
134
|
+
|
|
135
|
+
if (options.subscribe) {
|
|
136
|
+
if (options.dont_retry) {
|
|
137
|
+
var error_happened
|
|
138
|
+
var error_promise = new Promise((_, fail) => error_happened = fail)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
res.subscribe(async update => {
|
|
142
|
+
await options.subscribe(update)
|
|
143
|
+
}, e => options.dont_retry && error_happened(e))
|
|
144
|
+
|
|
145
|
+
if (options.dont_retry) {
|
|
146
|
+
return await error_promise
|
|
147
|
+
}
|
|
148
|
+
return res
|
|
149
|
+
} else {
|
|
150
|
+
return await res.arrayBuffer()
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
95
154
|
await braid_blob.init()
|
|
96
155
|
|
|
97
156
|
// Read the meta data from meta_db
|
|
@@ -106,6 +165,11 @@ function create_braid_blob() {
|
|
|
106
165
|
content_type: meta.content_type
|
|
107
166
|
}
|
|
108
167
|
if (options.header_cb) await options.header_cb(result)
|
|
168
|
+
// Check if requested version/parents is newer than what we have - if so, we don't have it
|
|
169
|
+
if (options.version && options.version.length && compare_events(options.version[0], meta.event) > 0)
|
|
170
|
+
throw new Error('unkown version: ' + options.version)
|
|
171
|
+
if (options.parents && options.parents.length && compare_events(options.parents[0], meta.event) > 0)
|
|
172
|
+
throw new Error('unkown version: ' + options.parents)
|
|
109
173
|
if (options.head) return
|
|
110
174
|
|
|
111
175
|
if (options.subscribe) {
|
|
@@ -177,32 +241,42 @@ function create_braid_blob() {
|
|
|
177
241
|
if (meta_content)
|
|
178
242
|
meta = JSON.parse(meta_content.toString('utf8'))
|
|
179
243
|
|
|
180
|
-
if (req.method === 'GET') {
|
|
244
|
+
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
181
245
|
if (!res.hasHeader("editable")) res.setHeader("Editable", "true")
|
|
182
246
|
if (!req.subscribe) res.setHeader("Accept-Subscribe", "true")
|
|
183
247
|
res.setHeader("Merge-Type", "lww")
|
|
184
248
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
249
|
+
try {
|
|
250
|
+
var result = await braid_blob.get(options.key, {
|
|
251
|
+
peer: req.peer,
|
|
252
|
+
head: req.method == "HEAD",
|
|
253
|
+
version: req.version || null,
|
|
254
|
+
parents: req.parents || null,
|
|
255
|
+
header_cb: (result) => {
|
|
256
|
+
res.setHeader((req.subscribe ? "Current-" : "") +
|
|
257
|
+
"Version", ascii_ify(result.version.map((x) =>
|
|
258
|
+
JSON.stringify(x)).join(", ")))
|
|
259
|
+
if (result.content_type)
|
|
260
|
+
res.setHeader('Content-Type', result.content_type)
|
|
261
|
+
},
|
|
262
|
+
before_send_cb: (result) =>
|
|
263
|
+
res.startSubscription({ onClose: result.unsubscribe }),
|
|
264
|
+
subscribe: req.subscribe ? (update) => {
|
|
265
|
+
res.sendUpdate({
|
|
266
|
+
version: update.version,
|
|
267
|
+
'Merge-Type': 'lww',
|
|
268
|
+
body: update.body
|
|
269
|
+
})
|
|
270
|
+
} : null
|
|
271
|
+
})
|
|
272
|
+
} catch (e) {
|
|
273
|
+
if (e.message && e.message.startsWith('unkown version')) {
|
|
274
|
+
// Server doesn't have this version
|
|
275
|
+
res.statusCode = 309
|
|
276
|
+
res.statusMessage = 'Version Unknown Here'
|
|
277
|
+
return res.end('')
|
|
278
|
+
} else throw e
|
|
279
|
+
}
|
|
206
280
|
|
|
207
281
|
if (!result) {
|
|
208
282
|
res.statusCode = 404
|
|
@@ -240,6 +314,109 @@ function create_braid_blob() {
|
|
|
240
314
|
})
|
|
241
315
|
}
|
|
242
316
|
|
|
317
|
+
braid_blob.sync = async (a, b, options = {}) => {
|
|
318
|
+
var unsync_cbs = []
|
|
319
|
+
options.my_unsync = () => unsync_cbs.forEach(cb => cb())
|
|
320
|
+
|
|
321
|
+
if ((a instanceof URL) === (b instanceof URL)) {
|
|
322
|
+
// Both are URLs or both are local keys
|
|
323
|
+
var a_ops = {
|
|
324
|
+
subscribe: update => braid_blob.put(b, update.body, {
|
|
325
|
+
version: update.version,
|
|
326
|
+
content_type: update.headers?.['content-type']
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
braid_blob.get(a, a_ops)
|
|
330
|
+
|
|
331
|
+
var b_ops = {
|
|
332
|
+
subscribe: update => braid_blob.put(a, update.body, {
|
|
333
|
+
version: update.version,
|
|
334
|
+
content_type: update.headers?.['content-type']
|
|
335
|
+
})
|
|
336
|
+
}
|
|
337
|
+
braid_blob.get(b, b_ops)
|
|
338
|
+
} else {
|
|
339
|
+
// One is local, one is remote - make a=local and b=remote (swap if not)
|
|
340
|
+
if (a instanceof URL) {
|
|
341
|
+
let swap = a; a = b; b = swap
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
var closed = false
|
|
345
|
+
options.my_unsync = () => { closed = true; disconnect() }
|
|
346
|
+
|
|
347
|
+
var disconnect = () => { }
|
|
348
|
+
async function connect() {
|
|
349
|
+
var ac = new AbortController()
|
|
350
|
+
var disconnect_cbs = [() => ac.abort()]
|
|
351
|
+
disconnect = () => disconnect_cbs.forEach(cb => cb())
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
// Check if remote has our current version (simple fork-point check)
|
|
355
|
+
var local_result = await braid_blob.get(a)
|
|
356
|
+
var local_version = local_result ? local_result.version : null
|
|
357
|
+
var server_has_our_version = false
|
|
358
|
+
|
|
359
|
+
if (local_version) {
|
|
360
|
+
// Check if server has our version
|
|
361
|
+
var r = await braid_fetch(b.href, {
|
|
362
|
+
signal: ac.signal,
|
|
363
|
+
method: "HEAD",
|
|
364
|
+
version: local_version
|
|
365
|
+
})
|
|
366
|
+
server_has_our_version = r.ok
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Local -> remote: subscribe to future local changes
|
|
370
|
+
var a_ops = {
|
|
371
|
+
subscribe: update => {
|
|
372
|
+
update.signal = ac.signal
|
|
373
|
+
braid_blob.put(b, update.body, {
|
|
374
|
+
version: update.version,
|
|
375
|
+
content_type: update.content_type
|
|
376
|
+
}).catch(e => {
|
|
377
|
+
if (e.name === 'AbortError') {
|
|
378
|
+
// ignore
|
|
379
|
+
} else throw e
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Only set parents if server already has our version
|
|
384
|
+
// If server doesn't have it, omit parents so subscription sends everything
|
|
385
|
+
if (server_has_our_version) {
|
|
386
|
+
a_ops.parents = local_version
|
|
387
|
+
}
|
|
388
|
+
braid_blob.get(a, a_ops)
|
|
389
|
+
|
|
390
|
+
// Remote -> local: subscribe to remote updates
|
|
391
|
+
var b_ops = {
|
|
392
|
+
dont_retry: true,
|
|
393
|
+
subscribe: async update => {
|
|
394
|
+
await braid_blob.put(a, update.body, {
|
|
395
|
+
version: update.version,
|
|
396
|
+
content_type: update.headers?.['content-type']
|
|
397
|
+
})
|
|
398
|
+
},
|
|
399
|
+
}
|
|
400
|
+
// Use fork-point (parents) to avoid receiving data we already have
|
|
401
|
+
if (local_version) {
|
|
402
|
+
b_ops.parents = local_version
|
|
403
|
+
}
|
|
404
|
+
// NOTE: this should not return, but it might throw
|
|
405
|
+
await braid_blob.get(b, b_ops)
|
|
406
|
+
} catch (e) {
|
|
407
|
+
if (closed) {
|
|
408
|
+
return
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
disconnect()
|
|
412
|
+
console.log(`disconnected, retrying in 1 second`)
|
|
413
|
+
setTimeout(connect, 1000)
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
connect()
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
243
420
|
function compare_events(a, b) {
|
|
244
421
|
var a_num = get_event_seq(a)
|
|
245
422
|
var b_num = get_event_seq(b)
|
package/package.json
CHANGED
package/test/tests.js
CHANGED
|
@@ -725,6 +725,423 @@ runTest(
|
|
|
725
725
|
'"100"|"200"'
|
|
726
726
|
)
|
|
727
727
|
|
|
728
|
+
runTest(
|
|
729
|
+
"test put with URL (no content_type)",
|
|
730
|
+
async () => {
|
|
731
|
+
var key = 'test-url-put-' + Math.random().toString(36).slice(2)
|
|
732
|
+
|
|
733
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
734
|
+
method: 'POST',
|
|
735
|
+
body: `void (async () => {
|
|
736
|
+
var braid_blob = require(\`\${__dirname}/../index.js\`)
|
|
737
|
+
var url = new URL('http://localhost:' + req.socket.localPort + '/${key}')
|
|
738
|
+
await braid_blob.put(url, Buffer.from('url put test'), { version: ['100'] })
|
|
739
|
+
res.end('done')
|
|
740
|
+
})()`
|
|
741
|
+
})
|
|
742
|
+
await r1.text()
|
|
743
|
+
|
|
744
|
+
var r = await braid_fetch(`/${key}`)
|
|
745
|
+
return await r.text()
|
|
746
|
+
},
|
|
747
|
+
'url put test'
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
runTest(
|
|
751
|
+
"test put with URL (with content_type)",
|
|
752
|
+
async () => {
|
|
753
|
+
var key = 'test-url-put-ct-' + Math.random().toString(36).slice(2)
|
|
754
|
+
|
|
755
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
756
|
+
method: 'POST',
|
|
757
|
+
body: `void (async () => {
|
|
758
|
+
var braid_blob = require(\`\${__dirname}/../index.js\`)
|
|
759
|
+
var url = new URL('http://localhost:' + req.socket.localPort + '/${key}')
|
|
760
|
+
await braid_blob.put(url, Buffer.from('url put with ct'), {
|
|
761
|
+
version: ['200'],
|
|
762
|
+
content_type: 'text/plain'
|
|
763
|
+
})
|
|
764
|
+
res.end('done')
|
|
765
|
+
})()`
|
|
766
|
+
})
|
|
767
|
+
await r1.text()
|
|
768
|
+
|
|
769
|
+
var r = await braid_fetch(`/${key}`)
|
|
770
|
+
return r.headers.get('content-type') + '|' + await r.text()
|
|
771
|
+
},
|
|
772
|
+
'text/plain|url put with ct'
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
runTest(
|
|
776
|
+
"test get with URL (no subscribe)",
|
|
777
|
+
async () => {
|
|
778
|
+
var key = 'test-url-get-' + Math.random().toString(36).slice(2)
|
|
779
|
+
|
|
780
|
+
await braid_fetch(`/${key}`, {
|
|
781
|
+
method: 'PUT',
|
|
782
|
+
version: ['300'],
|
|
783
|
+
body: 'url get test'
|
|
784
|
+
})
|
|
785
|
+
|
|
786
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
787
|
+
method: 'POST',
|
|
788
|
+
body: `void (async () => {
|
|
789
|
+
var braid_blob = require(\`\${__dirname}/../index.js\`)
|
|
790
|
+
var url = new URL('http://localhost:' + req.socket.localPort + '/${key}')
|
|
791
|
+
var result = await braid_blob.get(url)
|
|
792
|
+
res.end(Buffer.from(result).toString('utf8'))
|
|
793
|
+
})()`
|
|
794
|
+
})
|
|
795
|
+
|
|
796
|
+
return await r1.text()
|
|
797
|
+
},
|
|
798
|
+
'url get test'
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
runTest(
|
|
802
|
+
"test get with URL (with subscribe)",
|
|
803
|
+
async () => {
|
|
804
|
+
var key = 'test-url-get-sub-' + Math.random().toString(36).slice(2)
|
|
805
|
+
|
|
806
|
+
await braid_fetch(`/${key}`, {
|
|
807
|
+
method: 'PUT',
|
|
808
|
+
version: ['400'],
|
|
809
|
+
body: 'initial'
|
|
810
|
+
})
|
|
811
|
+
|
|
812
|
+
// Use a promise to wait for the eval to complete
|
|
813
|
+
var evalPromise = braid_fetch(`/eval`, {
|
|
814
|
+
method: 'POST',
|
|
815
|
+
body: `void (async () => {
|
|
816
|
+
var braid_blob = require(\`\${__dirname}/../index.js\`)
|
|
817
|
+
var url = new URL('http://localhost:' + req.socket.localPort + '/${key}')
|
|
818
|
+
|
|
819
|
+
var updates = []
|
|
820
|
+
var a = new AbortController()
|
|
821
|
+
|
|
822
|
+
// Don't await - braid_blob.get returns immediately when subscribe is used
|
|
823
|
+
braid_blob.get(url, {
|
|
824
|
+
subscribe: update => {
|
|
825
|
+
updates.push(Buffer.from(update.body).toString('utf8'))
|
|
826
|
+
if (updates.length === 2) {
|
|
827
|
+
a.abort()
|
|
828
|
+
res.end(updates.join('|'))
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
signal: a.signal
|
|
832
|
+
})
|
|
833
|
+
})()`
|
|
834
|
+
})
|
|
835
|
+
|
|
836
|
+
// Wait a bit for subscription to be established
|
|
837
|
+
await new Promise(done => setTimeout(done, 100))
|
|
838
|
+
|
|
839
|
+
// Send update
|
|
840
|
+
await braid_fetch(`/${key}`, {
|
|
841
|
+
method: 'PUT',
|
|
842
|
+
version: ['500'],
|
|
843
|
+
body: 'updated'
|
|
844
|
+
})
|
|
845
|
+
|
|
846
|
+
// Wait for the eval to complete
|
|
847
|
+
var r1 = await evalPromise
|
|
848
|
+
return await r1.text()
|
|
849
|
+
},
|
|
850
|
+
'initial|updated'
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
runTest(
|
|
854
|
+
"test sync local to remote",
|
|
855
|
+
async () => {
|
|
856
|
+
var local_key = 'test-sync-local-' + Math.random().toString(36).slice(2)
|
|
857
|
+
var remote_key = 'test-sync-remote-' + Math.random().toString(36).slice(2)
|
|
858
|
+
|
|
859
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
860
|
+
method: 'POST',
|
|
861
|
+
body: `void (async () => {
|
|
862
|
+
try {
|
|
863
|
+
var braid_blob = require(\`\${__dirname}/../index.js\`)
|
|
864
|
+
|
|
865
|
+
// Put something locally first
|
|
866
|
+
await braid_blob.put('${local_key}', Buffer.from('local content'), { version: ['600'] })
|
|
867
|
+
|
|
868
|
+
var remote_url = new URL('http://localhost:' + req.socket.localPort + '/${remote_key}')
|
|
869
|
+
|
|
870
|
+
// Start sync
|
|
871
|
+
braid_blob.sync('${local_key}', remote_url)
|
|
872
|
+
|
|
873
|
+
res.end('syncing')
|
|
874
|
+
} catch (e) {
|
|
875
|
+
res.end('error: ' + e.message + ' ' + e.stack)
|
|
876
|
+
}
|
|
877
|
+
})()`
|
|
878
|
+
})
|
|
879
|
+
var result = await r1.text()
|
|
880
|
+
if (result.startsWith('error:')) return result
|
|
881
|
+
|
|
882
|
+
// Wait a bit for sync to happen
|
|
883
|
+
await new Promise(done => setTimeout(done, 100))
|
|
884
|
+
|
|
885
|
+
// Check remote has the content
|
|
886
|
+
var r = await braid_fetch(`/${remote_key}`)
|
|
887
|
+
return await r.text()
|
|
888
|
+
},
|
|
889
|
+
'local content'
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
runTest(
|
|
893
|
+
"test sync two local keys",
|
|
894
|
+
async () => {
|
|
895
|
+
var key1 = 'test-sync-local1-' + Math.random().toString(36).slice(2)
|
|
896
|
+
var key2 = 'test-sync-local2-' + Math.random().toString(36).slice(2)
|
|
897
|
+
|
|
898
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
899
|
+
method: 'POST',
|
|
900
|
+
body: `void (async () => {
|
|
901
|
+
try {
|
|
902
|
+
var braid_blob = require(\`\${__dirname}/../index.js\`)
|
|
903
|
+
|
|
904
|
+
// Put something to first key
|
|
905
|
+
await braid_blob.put('${key1}', Buffer.from('sync local content'), { version: ['700'] })
|
|
906
|
+
|
|
907
|
+
// Start sync between two local keys
|
|
908
|
+
braid_blob.sync('${key1}', '${key2}')
|
|
909
|
+
|
|
910
|
+
res.end('syncing')
|
|
911
|
+
} catch (e) {
|
|
912
|
+
res.end('error: ' + e.message + ' ' + e.stack)
|
|
913
|
+
}
|
|
914
|
+
})()`
|
|
915
|
+
})
|
|
916
|
+
var result = await r1.text()
|
|
917
|
+
if (result.startsWith('error:')) return result
|
|
918
|
+
|
|
919
|
+
// Wait a bit for sync to happen
|
|
920
|
+
await new Promise(done => setTimeout(done, 100))
|
|
921
|
+
|
|
922
|
+
// Check second key has the content
|
|
923
|
+
var r = await braid_fetch(`/${key2}`)
|
|
924
|
+
return await r.text()
|
|
925
|
+
},
|
|
926
|
+
'sync local content'
|
|
927
|
+
)
|
|
928
|
+
|
|
929
|
+
runTest(
|
|
930
|
+
"test sync remote to local (swap)",
|
|
931
|
+
async () => {
|
|
932
|
+
var local_key = 'test-sync-swap-local-' + Math.random().toString(36).slice(2)
|
|
933
|
+
var remote_key = 'test-sync-swap-remote-' + Math.random().toString(36).slice(2)
|
|
934
|
+
|
|
935
|
+
// Put something on the server first
|
|
936
|
+
await braid_fetch(`/${remote_key}`, {
|
|
937
|
+
method: 'PUT',
|
|
938
|
+
version: ['800'],
|
|
939
|
+
body: 'remote content'
|
|
940
|
+
})
|
|
941
|
+
|
|
942
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
943
|
+
method: 'POST',
|
|
944
|
+
body: `void (async () => {
|
|
945
|
+
try {
|
|
946
|
+
var braid_blob = require(\`\${__dirname}/../index.js\`)
|
|
947
|
+
var remote_url = new URL('http://localhost:' + req.socket.localPort + '/${remote_key}')
|
|
948
|
+
|
|
949
|
+
// Start sync with URL as first argument (should swap internally)
|
|
950
|
+
braid_blob.sync(remote_url, '${local_key}')
|
|
951
|
+
|
|
952
|
+
res.end('syncing')
|
|
953
|
+
} catch (e) {
|
|
954
|
+
res.end('error: ' + e.message + ' ' + e.stack)
|
|
955
|
+
}
|
|
956
|
+
})()`
|
|
957
|
+
})
|
|
958
|
+
var result = await r1.text()
|
|
959
|
+
if (result.startsWith('error:')) return result
|
|
960
|
+
|
|
961
|
+
// Wait a bit for sync to happen
|
|
962
|
+
await new Promise(done => setTimeout(done, 100))
|
|
963
|
+
|
|
964
|
+
// Check local key has the remote content
|
|
965
|
+
var r = await braid_fetch(`/${local_key}`)
|
|
966
|
+
return await r.text()
|
|
967
|
+
},
|
|
968
|
+
'remote content'
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
runTest(
|
|
972
|
+
"test sync when server already has our version",
|
|
973
|
+
async () => {
|
|
974
|
+
var local_key = 'test-sync-has-version-local-' + Math.random().toString(36).slice(2)
|
|
975
|
+
var remote_key = 'test-sync-has-version-remote-' + Math.random().toString(36).slice(2)
|
|
976
|
+
|
|
977
|
+
// Put the same content on both local and remote with the same version
|
|
978
|
+
var version = ['900']
|
|
979
|
+
var content = 'shared content'
|
|
980
|
+
|
|
981
|
+
// Put on remote first
|
|
982
|
+
await braid_fetch(`/${remote_key}`, {
|
|
983
|
+
method: 'PUT',
|
|
984
|
+
version: version,
|
|
985
|
+
body: content
|
|
986
|
+
})
|
|
987
|
+
|
|
988
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
989
|
+
method: 'POST',
|
|
990
|
+
body: `void (async () => {
|
|
991
|
+
try {
|
|
992
|
+
var braid_blob = require(\`\${__dirname}/../index.js\`)
|
|
993
|
+
|
|
994
|
+
// Put the same content locally with the same version
|
|
995
|
+
await braid_blob.put('${local_key}', Buffer.from('${content}'), { version: ${JSON.stringify(version)} })
|
|
996
|
+
|
|
997
|
+
var remote_url = new URL('http://localhost:' + req.socket.localPort + '/${remote_key}')
|
|
998
|
+
|
|
999
|
+
// Start sync - this should trigger the "server already has our version" path
|
|
1000
|
+
braid_blob.sync('${local_key}', remote_url)
|
|
1001
|
+
|
|
1002
|
+
res.end('syncing')
|
|
1003
|
+
} catch (e) {
|
|
1004
|
+
res.end('error: ' + e.message + ' ' + e.stack)
|
|
1005
|
+
}
|
|
1006
|
+
})()`
|
|
1007
|
+
})
|
|
1008
|
+
var result = await r1.text()
|
|
1009
|
+
if (result.startsWith('error:')) return result
|
|
1010
|
+
|
|
1011
|
+
// Wait a bit for sync to initialize (the console.log should happen quickly)
|
|
1012
|
+
await new Promise(done => setTimeout(done, 100))
|
|
1013
|
+
|
|
1014
|
+
// Verify that both still have the same content
|
|
1015
|
+
var r = await braid_fetch(`/${remote_key}`)
|
|
1016
|
+
return await r.text()
|
|
1017
|
+
},
|
|
1018
|
+
'shared content'
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
runTest(
|
|
1022
|
+
"test sync closed during error",
|
|
1023
|
+
async () => {
|
|
1024
|
+
var local_key = 'test-sync-closed-local-' + Math.random().toString(36).slice(2)
|
|
1025
|
+
var remote_key = 'test-sync-closed-remote-' + Math.random().toString(36).slice(2)
|
|
1026
|
+
|
|
1027
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
1028
|
+
method: 'POST',
|
|
1029
|
+
body: `void (async () => {
|
|
1030
|
+
try {
|
|
1031
|
+
var braid_blob = require(\`\${__dirname}/../index.js\`)
|
|
1032
|
+
|
|
1033
|
+
// Use an invalid/unreachable URL to trigger an error
|
|
1034
|
+
var remote_url = new URL('http://localhost:9999/${remote_key}')
|
|
1035
|
+
|
|
1036
|
+
// Start sync
|
|
1037
|
+
var sync_options = {}
|
|
1038
|
+
braid_blob.sync('${local_key}', remote_url, sync_options)
|
|
1039
|
+
|
|
1040
|
+
// Close the sync immediately to trigger the closed path when error occurs
|
|
1041
|
+
sync_options.my_unsync()
|
|
1042
|
+
|
|
1043
|
+
res.end('sync started and closed')
|
|
1044
|
+
} catch (e) {
|
|
1045
|
+
res.end('error: ' + e.message + ' ' + e.stack)
|
|
1046
|
+
}
|
|
1047
|
+
})()`
|
|
1048
|
+
})
|
|
1049
|
+
var result = await r1.text()
|
|
1050
|
+
if (result.startsWith('error:')) return result
|
|
1051
|
+
|
|
1052
|
+
// Wait for the connection error and closed message
|
|
1053
|
+
await new Promise(done => setTimeout(done, 200))
|
|
1054
|
+
|
|
1055
|
+
return result
|
|
1056
|
+
},
|
|
1057
|
+
'sync started and closed'
|
|
1058
|
+
)
|
|
1059
|
+
|
|
1060
|
+
runTest(
|
|
1061
|
+
"test sync error with retry",
|
|
1062
|
+
async () => {
|
|
1063
|
+
var local_key = 'test-sync-retry-local-' + Math.random().toString(36).slice(2)
|
|
1064
|
+
var remote_key = 'test-sync-retry-remote-' + Math.random().toString(36).slice(2)
|
|
1065
|
+
|
|
1066
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
1067
|
+
method: 'POST',
|
|
1068
|
+
body: `void (async () => {
|
|
1069
|
+
try {
|
|
1070
|
+
var braid_blob = require(\`\${__dirname}/../index.js\`)
|
|
1071
|
+
|
|
1072
|
+
// Use an invalid/unreachable URL to trigger an error
|
|
1073
|
+
var remote_url = new URL('http://localhost:9999/${remote_key}')
|
|
1074
|
+
|
|
1075
|
+
// Start sync without closing it - should trigger retry
|
|
1076
|
+
var sync_options = {}
|
|
1077
|
+
braid_blob.sync('${local_key}', remote_url, sync_options)
|
|
1078
|
+
|
|
1079
|
+
// Wait a bit for the error to occur and retry message to print
|
|
1080
|
+
await new Promise(done => setTimeout(done, 200))
|
|
1081
|
+
|
|
1082
|
+
// Now close it to stop retrying
|
|
1083
|
+
sync_options.my_unsync()
|
|
1084
|
+
|
|
1085
|
+
res.end('sync error occurred')
|
|
1086
|
+
} catch (e) {
|
|
1087
|
+
res.end('error: ' + e.message + ' ' + e.stack)
|
|
1088
|
+
}
|
|
1089
|
+
})()`
|
|
1090
|
+
})
|
|
1091
|
+
var result = await r1.text()
|
|
1092
|
+
|
|
1093
|
+
return result
|
|
1094
|
+
},
|
|
1095
|
+
'sync error occurred'
|
|
1096
|
+
)
|
|
1097
|
+
|
|
1098
|
+
runTest(
|
|
1099
|
+
"test requesting with version/parents server doesn't have",
|
|
1100
|
+
async () => {
|
|
1101
|
+
var key = 'test-parents-unknown-' + Math.random().toString(36).slice(2)
|
|
1102
|
+
|
|
1103
|
+
// Put with version 100
|
|
1104
|
+
await braid_fetch(`/${key}`, {
|
|
1105
|
+
method: 'PUT',
|
|
1106
|
+
version: ['100'],
|
|
1107
|
+
body: 'content v100'
|
|
1108
|
+
})
|
|
1109
|
+
|
|
1110
|
+
// Try to subscribe with parents 200 (newer than what server has)
|
|
1111
|
+
// This triggers the "unkown version" error which gets caught and returns 309
|
|
1112
|
+
var r = await braid_fetch(`/${key}`, {
|
|
1113
|
+
subscribe: true,
|
|
1114
|
+
parents: ['200']
|
|
1115
|
+
})
|
|
1116
|
+
|
|
1117
|
+
return r.status
|
|
1118
|
+
},
|
|
1119
|
+
'309'
|
|
1120
|
+
)
|
|
1121
|
+
|
|
1122
|
+
runTest(
|
|
1123
|
+
"test requesting specific version server doesn't have",
|
|
1124
|
+
async () => {
|
|
1125
|
+
var key = 'test-version-unknown-' + Math.random().toString(36).slice(2)
|
|
1126
|
+
|
|
1127
|
+
// Put with version 100
|
|
1128
|
+
await braid_fetch(`/${key}`, {
|
|
1129
|
+
method: 'PUT',
|
|
1130
|
+
version: ['100'],
|
|
1131
|
+
body: 'content v100'
|
|
1132
|
+
})
|
|
1133
|
+
|
|
1134
|
+
// Try to GET with version 200 (newer than what server has)
|
|
1135
|
+
// This should trigger line 269 when req.version is checked
|
|
1136
|
+
var r = await braid_fetch(`/${key}`, {
|
|
1137
|
+
version: ['200']
|
|
1138
|
+
})
|
|
1139
|
+
|
|
1140
|
+
return r.status
|
|
1141
|
+
},
|
|
1142
|
+
'309'
|
|
1143
|
+
)
|
|
1144
|
+
|
|
728
1145
|
}
|
|
729
1146
|
|
|
730
1147
|
// Export for Node.js (CommonJS)
|