braid-blob 0.0.23 → 0.0.25
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 +9 -0
- package/index.js +95 -37
- package/package.json +1 -1
- package/test/tests.js +315 -8
package/index.js
CHANGED
|
@@ -40,15 +40,10 @@ function create_braid_blob() {
|
|
|
40
40
|
braid_blob.put = async (key, body, options = {}) => {
|
|
41
41
|
// Handle URL case - make a remote PUT request
|
|
42
42
|
if (key instanceof URL) {
|
|
43
|
-
options.my_abort = new AbortController()
|
|
44
|
-
if (options.signal) {
|
|
45
|
-
options.signal.addEventListener('abort', () =>
|
|
46
|
-
options.my_abort.abort())
|
|
47
|
-
}
|
|
48
43
|
|
|
49
44
|
var params = {
|
|
50
45
|
method: 'PUT',
|
|
51
|
-
signal: options.
|
|
46
|
+
signal: options.signal,
|
|
52
47
|
retry: () => true,
|
|
53
48
|
body: body
|
|
54
49
|
}
|
|
@@ -62,6 +57,7 @@ function create_braid_blob() {
|
|
|
62
57
|
}
|
|
63
58
|
|
|
64
59
|
await braid_blob.init()
|
|
60
|
+
if (options.signal?.aborted) return
|
|
65
61
|
|
|
66
62
|
// Read the meta data using new meta API
|
|
67
63
|
var meta = braid_blob.db.get_meta(key) || {}
|
|
@@ -83,6 +79,7 @@ function create_braid_blob() {
|
|
|
83
79
|
// Write the file using url-file-db (unless skip_write is set)
|
|
84
80
|
if (!options.skip_write)
|
|
85
81
|
await braid_blob.db.write(key, body)
|
|
82
|
+
if (options.signal?.aborted) return
|
|
86
83
|
|
|
87
84
|
// Update only the fields we want to change in metadata
|
|
88
85
|
var meta_updates = { event: their_e }
|
|
@@ -90,6 +87,7 @@ function create_braid_blob() {
|
|
|
90
87
|
meta_updates.content_type = options.content_type
|
|
91
88
|
|
|
92
89
|
await braid_blob.db.update_meta(key, meta_updates)
|
|
90
|
+
if (options.signal?.aborted) return
|
|
93
91
|
|
|
94
92
|
// Notify all subscriptions of the update
|
|
95
93
|
// (except the peer which made the PUT request itself)
|
|
@@ -109,21 +107,21 @@ function create_braid_blob() {
|
|
|
109
107
|
braid_blob.get = async (key, options = {}) => {
|
|
110
108
|
// Handle URL case - make a remote GET request
|
|
111
109
|
if (key instanceof URL) {
|
|
112
|
-
options.my_abort = new AbortController()
|
|
113
|
-
|
|
114
110
|
var params = {
|
|
115
|
-
signal: options.
|
|
111
|
+
signal: options.signal,
|
|
116
112
|
subscribe: !!options.subscribe,
|
|
117
113
|
heartbeats: 120,
|
|
118
114
|
}
|
|
119
115
|
if (!options.dont_retry) {
|
|
120
|
-
params.retry = () =>
|
|
116
|
+
params.retry = (res) => res.status !== 404
|
|
121
117
|
}
|
|
122
118
|
for (var x of ['headers', 'parents', 'version', 'peer'])
|
|
123
119
|
if (options[x] != null) params[x] = options[x]
|
|
124
120
|
|
|
125
121
|
var res = await braid_fetch(key.href, params)
|
|
126
122
|
|
|
123
|
+
if (res.status === 404) return null
|
|
124
|
+
|
|
127
125
|
if (options.subscribe) {
|
|
128
126
|
if (options.dont_retry) {
|
|
129
127
|
var error_happened
|
|
@@ -154,6 +152,7 @@ function create_braid_blob() {
|
|
|
154
152
|
content_type: meta.content_type
|
|
155
153
|
}
|
|
156
154
|
if (options.header_cb) await options.header_cb(result)
|
|
155
|
+
if (options.signal?.aborted) return
|
|
157
156
|
// Check if requested version/parents is newer than what we have - if so, we don't have it
|
|
158
157
|
if (options.version && options.version.length && compare_events(options.version[0], meta.event) > 0)
|
|
159
158
|
throw new Error('unkown version: ' + options.version)
|
|
@@ -164,7 +163,8 @@ function create_braid_blob() {
|
|
|
164
163
|
if (options.subscribe) {
|
|
165
164
|
var subscribe_chain = Promise.resolve()
|
|
166
165
|
options.my_subscribe = (x) => subscribe_chain =
|
|
167
|
-
subscribe_chain.then(() =>
|
|
166
|
+
subscribe_chain.then(() =>
|
|
167
|
+
!options.signal?.aborted && options.subscribe(x))
|
|
168
168
|
|
|
169
169
|
// Start a subscription for future updates
|
|
170
170
|
if (!braid_blob.key_to_subs[key])
|
|
@@ -181,14 +181,14 @@ function create_braid_blob() {
|
|
|
181
181
|
}
|
|
182
182
|
})
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
result.unsubscribe = () => {
|
|
184
|
+
options.signal?.addEventListener('abort', () => {
|
|
186
185
|
braid_blob.key_to_subs[key].delete(peer)
|
|
187
186
|
if (!braid_blob.key_to_subs[key].size)
|
|
188
187
|
delete braid_blob.key_to_subs[key]
|
|
189
|
-
}
|
|
188
|
+
})
|
|
190
189
|
|
|
191
190
|
if (options.before_send_cb) await options.before_send_cb(result)
|
|
191
|
+
if (options.signal?.aborted) return
|
|
192
192
|
|
|
193
193
|
// Send an immediate update if needed
|
|
194
194
|
if (!options.parents ||
|
|
@@ -209,6 +209,32 @@ function create_braid_blob() {
|
|
|
209
209
|
return result
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
braid_blob.delete = async (key, options = {}) => {
|
|
213
|
+
// Handle URL case - make a remote DELETE request
|
|
214
|
+
if (key instanceof URL) {
|
|
215
|
+
|
|
216
|
+
var params = {
|
|
217
|
+
method: 'DELETE',
|
|
218
|
+
signal: options.signal
|
|
219
|
+
}
|
|
220
|
+
for (var x of ['headers', 'peer'])
|
|
221
|
+
if (options[x] != null) params[x] = options[x]
|
|
222
|
+
|
|
223
|
+
return await braid_fetch(key.href, params)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
await braid_blob.init()
|
|
227
|
+
if (options.signal?.aborted) return
|
|
228
|
+
|
|
229
|
+
// Delete the file from the database
|
|
230
|
+
await braid_blob.db.delete(key)
|
|
231
|
+
|
|
232
|
+
// TODO: notify subscribers of deletion once we have a protocol for that
|
|
233
|
+
// For now, just clean up the subscriptions
|
|
234
|
+
if (braid_blob.key_to_subs[key])
|
|
235
|
+
delete braid_blob.key_to_subs[key]
|
|
236
|
+
}
|
|
237
|
+
|
|
212
238
|
braid_blob.serve = async (req, res, options = {}) => {
|
|
213
239
|
await braid_blob.init()
|
|
214
240
|
|
|
@@ -292,34 +318,47 @@ function create_braid_blob() {
|
|
|
292
318
|
res.setHeader("Version", version_to_header(event != null ? [event] : []))
|
|
293
319
|
res.end('')
|
|
294
320
|
} else if (req.method === 'DELETE') {
|
|
295
|
-
await braid_blob.
|
|
321
|
+
await braid_blob.delete(options.key)
|
|
296
322
|
res.statusCode = 204 // No Content
|
|
297
323
|
res.end('')
|
|
298
324
|
}
|
|
299
325
|
})
|
|
300
326
|
}
|
|
301
327
|
|
|
302
|
-
braid_blob.sync =
|
|
303
|
-
var unsync_cbs = []
|
|
304
|
-
options.my_unsync = () => unsync_cbs.forEach(cb => cb())
|
|
305
|
-
|
|
328
|
+
braid_blob.sync = (a, b, options = {}) => {
|
|
306
329
|
if ((a instanceof URL) === (b instanceof URL)) {
|
|
307
330
|
// Both are URLs or both are local keys
|
|
331
|
+
var a_first_put, b_first_put
|
|
332
|
+
var a_first_put_promise = new Promise(done => a_first_put = done)
|
|
333
|
+
var b_first_put_promise = new Promise(done => b_first_put = done)
|
|
334
|
+
|
|
308
335
|
var a_ops = {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
336
|
+
signal: options.signal,
|
|
337
|
+
subscribe: update => {
|
|
338
|
+
braid_blob.put(b, update.body, {
|
|
339
|
+
signal: options.signal,
|
|
340
|
+
version: update.version,
|
|
341
|
+
content_type: update.headers?.['content-type']
|
|
342
|
+
}).then(a_first_put)
|
|
343
|
+
}
|
|
313
344
|
}
|
|
314
|
-
braid_blob.get(a, a_ops)
|
|
345
|
+
braid_blob.get(a, a_ops).then(x =>
|
|
346
|
+
x || b_first_put_promise.then(() =>
|
|
347
|
+
braid_blob.get(a, a_ops)))
|
|
315
348
|
|
|
316
349
|
var b_ops = {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
350
|
+
signal: options.signal,
|
|
351
|
+
subscribe: update => {
|
|
352
|
+
braid_blob.put(a, update.body, {
|
|
353
|
+
signal: options.signal,
|
|
354
|
+
version: update.version,
|
|
355
|
+
content_type: update.headers?.['content-type']
|
|
356
|
+
}).then(b_first_put)
|
|
357
|
+
}
|
|
321
358
|
}
|
|
322
|
-
braid_blob.get(b, b_ops)
|
|
359
|
+
braid_blob.get(b, b_ops).then(x =>
|
|
360
|
+
x || a_first_put_promise.then(() =>
|
|
361
|
+
braid_blob.get(b, b_ops)))
|
|
323
362
|
} else {
|
|
324
363
|
// One is local, one is remote - make a=local and b=remote (swap if not)
|
|
325
364
|
if (a instanceof URL) {
|
|
@@ -327,17 +366,23 @@ function create_braid_blob() {
|
|
|
327
366
|
}
|
|
328
367
|
|
|
329
368
|
var closed = false
|
|
330
|
-
options.my_unsync = () => { closed = true; disconnect() }
|
|
331
|
-
|
|
332
369
|
var disconnect = () => { }
|
|
370
|
+
options.signal?.addEventListener('abort', () =>
|
|
371
|
+
{ closed = true; disconnect() })
|
|
372
|
+
|
|
373
|
+
var local_first_put, remote_first_put
|
|
374
|
+
var local_first_put_promise = new Promise(done => local_first_put = done)
|
|
375
|
+
var remote_first_put_promise = new Promise(done => remote_first_put = done)
|
|
376
|
+
|
|
333
377
|
async function connect() {
|
|
334
378
|
var ac = new AbortController()
|
|
335
|
-
|
|
336
|
-
disconnect = () => disconnect_cbs.forEach(cb => cb())
|
|
379
|
+
disconnect = () => ac.abort()
|
|
337
380
|
|
|
338
381
|
try {
|
|
339
382
|
// Check if remote has our current version (simple fork-point check)
|
|
340
|
-
var local_result = await braid_blob.get(a
|
|
383
|
+
var local_result = await braid_blob.get(a, {
|
|
384
|
+
signal: ac.signal
|
|
385
|
+
})
|
|
341
386
|
var local_version = local_result ? local_result.version : null
|
|
342
387
|
var server_has_our_version = false
|
|
343
388
|
|
|
@@ -353,12 +398,13 @@ function create_braid_blob() {
|
|
|
353
398
|
|
|
354
399
|
// Local -> remote: subscribe to future local changes
|
|
355
400
|
var a_ops = {
|
|
401
|
+
signal: ac.signal,
|
|
356
402
|
subscribe: update => {
|
|
357
|
-
update.signal = ac.signal
|
|
358
403
|
braid_blob.put(b, update.body, {
|
|
404
|
+
signal: ac.signal,
|
|
359
405
|
version: update.version,
|
|
360
406
|
content_type: update.content_type
|
|
361
|
-
}).catch(e => {
|
|
407
|
+
}).then(local_first_put).catch(e => {
|
|
362
408
|
if (e.name === 'AbortError') {
|
|
363
409
|
// ignore
|
|
364
410
|
} else throw e
|
|
@@ -370,24 +416,36 @@ function create_braid_blob() {
|
|
|
370
416
|
if (server_has_our_version) {
|
|
371
417
|
a_ops.parents = local_version
|
|
372
418
|
}
|
|
373
|
-
braid_blob.get(a, a_ops)
|
|
374
419
|
|
|
375
420
|
// Remote -> local: subscribe to remote updates
|
|
376
421
|
var b_ops = {
|
|
422
|
+
signal: ac.signal,
|
|
377
423
|
dont_retry: true,
|
|
378
424
|
subscribe: async update => {
|
|
379
425
|
await braid_blob.put(a, update.body, {
|
|
380
426
|
version: update.version,
|
|
381
427
|
content_type: update.headers?.['content-type']
|
|
382
428
|
})
|
|
429
|
+
remote_first_put()
|
|
383
430
|
},
|
|
384
431
|
}
|
|
385
432
|
// Use fork-point (parents) to avoid receiving data we already have
|
|
386
433
|
if (local_version) {
|
|
387
434
|
b_ops.parents = local_version
|
|
388
435
|
}
|
|
436
|
+
|
|
437
|
+
// Set up both subscriptions, handling cases where one doesn't exist yet
|
|
438
|
+
braid_blob.get(a, a_ops).then(x =>
|
|
439
|
+
x || remote_first_put_promise.then(() =>
|
|
440
|
+
braid_blob.get(a, a_ops)))
|
|
441
|
+
|
|
389
442
|
// NOTE: this should not return, but it might throw
|
|
390
443
|
await braid_blob.get(b, b_ops)
|
|
444
|
+
|
|
445
|
+
// this will only return if it couldn't find the key
|
|
446
|
+
await local_first_put_promise
|
|
447
|
+
disconnect()
|
|
448
|
+
connect()
|
|
391
449
|
} catch (e) {
|
|
392
450
|
if (closed) {
|
|
393
451
|
return
|
package/package.json
CHANGED
package/test/tests.js
CHANGED
|
@@ -381,6 +381,102 @@ runTest(
|
|
|
381
381
|
'204'
|
|
382
382
|
)
|
|
383
383
|
|
|
384
|
+
runTest(
|
|
385
|
+
"test braid_blob.delete() directly",
|
|
386
|
+
async () => {
|
|
387
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
388
|
+
method: 'POST',
|
|
389
|
+
body: `void (async () => {
|
|
390
|
+
var test_id = 'test-db-' + Math.random().toString(36).slice(2)
|
|
391
|
+
var db_folder = __dirname + '/' + test_id + '-db'
|
|
392
|
+
var meta_folder = __dirname + '/' + test_id + '-meta'
|
|
393
|
+
|
|
394
|
+
var bb = braid_blob.create_braid_blob()
|
|
395
|
+
bb.db_folder = db_folder
|
|
396
|
+
bb.meta_folder = meta_folder
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
// Put a file
|
|
400
|
+
await bb.put('/test-file', Buffer.from('hello'))
|
|
401
|
+
|
|
402
|
+
// Verify it exists
|
|
403
|
+
var result = await bb.get('/test-file')
|
|
404
|
+
if (!result || !result.body) {
|
|
405
|
+
res.end('error: file not found after put')
|
|
406
|
+
return
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Delete it
|
|
410
|
+
await bb.delete('/test-file')
|
|
411
|
+
|
|
412
|
+
// Verify it's gone
|
|
413
|
+
var result2 = await bb.get('/test-file')
|
|
414
|
+
if (result2) {
|
|
415
|
+
res.end('error: file still exists after delete')
|
|
416
|
+
return
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
res.end('true')
|
|
420
|
+
} catch (e) {
|
|
421
|
+
res.end('error: ' + e.message)
|
|
422
|
+
} finally {
|
|
423
|
+
await require('fs').promises.rm(db_folder, { recursive: true, force: true })
|
|
424
|
+
await require('fs').promises.rm(meta_folder, { recursive: true, force: true })
|
|
425
|
+
}
|
|
426
|
+
})()`
|
|
427
|
+
})
|
|
428
|
+
return await r1.text()
|
|
429
|
+
},
|
|
430
|
+
'true'
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
runTest(
|
|
434
|
+
"test braid_blob.delete() cleans up subscriptions",
|
|
435
|
+
async () => {
|
|
436
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
437
|
+
method: 'POST',
|
|
438
|
+
body: `void (async () => {
|
|
439
|
+
var test_id = 'test-db-' + Math.random().toString(36).slice(2)
|
|
440
|
+
var db_folder = __dirname + '/' + test_id + '-db'
|
|
441
|
+
var meta_folder = __dirname + '/' + test_id + '-meta'
|
|
442
|
+
|
|
443
|
+
var bb = braid_blob.create_braid_blob()
|
|
444
|
+
bb.db_folder = db_folder
|
|
445
|
+
bb.meta_folder = meta_folder
|
|
446
|
+
|
|
447
|
+
try {
|
|
448
|
+
// Put a file
|
|
449
|
+
await bb.put('/test-file', Buffer.from('hello'))
|
|
450
|
+
|
|
451
|
+
// Subscribe to it
|
|
452
|
+
var got_update = false
|
|
453
|
+
await bb.get('/test-file', {
|
|
454
|
+
subscribe: (update) => { got_update = true }
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
// Verify subscription exists
|
|
458
|
+
var has_sub_before = !!bb.key_to_subs['/test-file']
|
|
459
|
+
|
|
460
|
+
// Delete it
|
|
461
|
+
await bb.delete('/test-file')
|
|
462
|
+
|
|
463
|
+
// Verify subscription is cleaned up
|
|
464
|
+
var has_sub_after = !!bb.key_to_subs['/test-file']
|
|
465
|
+
|
|
466
|
+
res.end('' + (has_sub_before && !has_sub_after))
|
|
467
|
+
} catch (e) {
|
|
468
|
+
res.end('error: ' + e.message)
|
|
469
|
+
} finally {
|
|
470
|
+
await require('fs').promises.rm(db_folder, { recursive: true, force: true })
|
|
471
|
+
await require('fs').promises.rm(meta_folder, { recursive: true, force: true })
|
|
472
|
+
}
|
|
473
|
+
})()`
|
|
474
|
+
})
|
|
475
|
+
return await r1.text()
|
|
476
|
+
},
|
|
477
|
+
'true'
|
|
478
|
+
)
|
|
479
|
+
|
|
384
480
|
runTest(
|
|
385
481
|
"test that subscribe returns current-version header",
|
|
386
482
|
async () => {
|
|
@@ -1066,12 +1162,14 @@ runTest(
|
|
|
1066
1162
|
// Use an invalid/unreachable URL to trigger an error
|
|
1067
1163
|
var remote_url = new URL('http://localhost:9999/${remote_key}')
|
|
1068
1164
|
|
|
1069
|
-
//
|
|
1070
|
-
var
|
|
1071
|
-
|
|
1165
|
+
// Create an AbortController to stop the sync
|
|
1166
|
+
var ac = new AbortController()
|
|
1167
|
+
|
|
1168
|
+
// Start sync with signal
|
|
1169
|
+
braid_blob.sync('${local_key}', remote_url, { signal: ac.signal })
|
|
1072
1170
|
|
|
1073
1171
|
// Close the sync immediately to trigger the closed path when error occurs
|
|
1074
|
-
|
|
1172
|
+
ac.abort()
|
|
1075
1173
|
|
|
1076
1174
|
res.end('sync started and closed')
|
|
1077
1175
|
} catch (e) {
|
|
@@ -1105,15 +1203,17 @@ runTest(
|
|
|
1105
1203
|
// Use an invalid/unreachable URL to trigger an error
|
|
1106
1204
|
var remote_url = new URL('http://localhost:9999/${remote_key}')
|
|
1107
1205
|
|
|
1108
|
-
//
|
|
1109
|
-
var
|
|
1110
|
-
|
|
1206
|
+
// Create an AbortController to stop the sync
|
|
1207
|
+
var ac = new AbortController()
|
|
1208
|
+
|
|
1209
|
+
// Start sync with signal - should trigger retry on error
|
|
1210
|
+
braid_blob.sync('${local_key}', remote_url, { signal: ac.signal })
|
|
1111
1211
|
|
|
1112
1212
|
// Wait a bit for the error to occur and retry message to print
|
|
1113
1213
|
await new Promise(done => setTimeout(done, 200))
|
|
1114
1214
|
|
|
1115
1215
|
// Now close it to stop retrying
|
|
1116
|
-
|
|
1216
|
+
ac.abort()
|
|
1117
1217
|
|
|
1118
1218
|
res.end('sync error occurred')
|
|
1119
1219
|
} catch (e) {
|
|
@@ -1398,6 +1498,213 @@ runTest(
|
|
|
1398
1498
|
'true'
|
|
1399
1499
|
)
|
|
1400
1500
|
|
|
1501
|
+
runTest(
|
|
1502
|
+
"test get with URL returns null on 404",
|
|
1503
|
+
async () => {
|
|
1504
|
+
var key = 'test-url-get-404-' + Math.random().toString(36).slice(2)
|
|
1505
|
+
|
|
1506
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
1507
|
+
method: 'POST',
|
|
1508
|
+
body: `void (async () => {
|
|
1509
|
+
var braid_blob = require(\`\${__dirname}/../index.js\`)
|
|
1510
|
+
var url = new URL('http://localhost:' + req.socket.localPort + '/${key}')
|
|
1511
|
+
var result = await braid_blob.get(url)
|
|
1512
|
+
res.end(result === null ? 'null' : 'not null: ' + JSON.stringify(result))
|
|
1513
|
+
})()`
|
|
1514
|
+
})
|
|
1515
|
+
|
|
1516
|
+
return await r1.text()
|
|
1517
|
+
},
|
|
1518
|
+
'null'
|
|
1519
|
+
)
|
|
1520
|
+
|
|
1521
|
+
runTest(
|
|
1522
|
+
"test signal abort stops local put operation",
|
|
1523
|
+
async () => {
|
|
1524
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
1525
|
+
method: 'POST',
|
|
1526
|
+
body: `void (async () => {
|
|
1527
|
+
var fs = require('fs').promises
|
|
1528
|
+
var test_id = 'test-abort-put-' + Math.random().toString(36).slice(2)
|
|
1529
|
+
var db_folder = __dirname + '/' + test_id + '-db'
|
|
1530
|
+
var meta_folder = __dirname + '/' + test_id + '-meta'
|
|
1531
|
+
|
|
1532
|
+
try {
|
|
1533
|
+
var bb = braid_blob.create_braid_blob()
|
|
1534
|
+
bb.db_folder = db_folder
|
|
1535
|
+
bb.meta_folder = meta_folder
|
|
1536
|
+
|
|
1537
|
+
// Create an already-aborted signal
|
|
1538
|
+
var ac = new AbortController()
|
|
1539
|
+
ac.abort()
|
|
1540
|
+
|
|
1541
|
+
// Try to put with aborted signal
|
|
1542
|
+
var result = await bb.put('/test-file', Buffer.from('hello'), {
|
|
1543
|
+
signal: ac.signal
|
|
1544
|
+
})
|
|
1545
|
+
|
|
1546
|
+
// Result should be undefined since operation was aborted
|
|
1547
|
+
res.end(result === undefined ? 'aborted' : 'not aborted: ' + result)
|
|
1548
|
+
} catch (e) {
|
|
1549
|
+
res.end('error: ' + e.message)
|
|
1550
|
+
} finally {
|
|
1551
|
+
await fs.rm(db_folder, { recursive: true, force: true })
|
|
1552
|
+
await fs.rm(meta_folder, { recursive: true, force: true })
|
|
1553
|
+
}
|
|
1554
|
+
})()`
|
|
1555
|
+
})
|
|
1556
|
+
return await r1.text()
|
|
1557
|
+
},
|
|
1558
|
+
'aborted'
|
|
1559
|
+
)
|
|
1560
|
+
|
|
1561
|
+
runTest(
|
|
1562
|
+
"test signal abort stops local get operation",
|
|
1563
|
+
async () => {
|
|
1564
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
1565
|
+
method: 'POST',
|
|
1566
|
+
body: `void (async () => {
|
|
1567
|
+
var fs = require('fs').promises
|
|
1568
|
+
var test_id = 'test-abort-get-' + Math.random().toString(36).slice(2)
|
|
1569
|
+
var db_folder = __dirname + '/' + test_id + '-db'
|
|
1570
|
+
var meta_folder = __dirname + '/' + test_id + '-meta'
|
|
1571
|
+
|
|
1572
|
+
try {
|
|
1573
|
+
var bb = braid_blob.create_braid_blob()
|
|
1574
|
+
bb.db_folder = db_folder
|
|
1575
|
+
bb.meta_folder = meta_folder
|
|
1576
|
+
|
|
1577
|
+
// Put a file first
|
|
1578
|
+
await bb.put('/test-file', Buffer.from('hello'), { version: ['1'] })
|
|
1579
|
+
|
|
1580
|
+
// Create an already-aborted signal
|
|
1581
|
+
var ac = new AbortController()
|
|
1582
|
+
ac.abort()
|
|
1583
|
+
|
|
1584
|
+
// Try to get with aborted signal (after header_cb)
|
|
1585
|
+
var header_called = false
|
|
1586
|
+
var result = await bb.get('/test-file', {
|
|
1587
|
+
signal: ac.signal,
|
|
1588
|
+
header_cb: () => { header_called = true }
|
|
1589
|
+
})
|
|
1590
|
+
|
|
1591
|
+
// Result should be undefined since operation was aborted after header_cb
|
|
1592
|
+
res.end(header_called && result === undefined ? 'aborted' : 'not aborted: header=' + header_called + ' result=' + JSON.stringify(result))
|
|
1593
|
+
} catch (e) {
|
|
1594
|
+
res.end('error: ' + e.message)
|
|
1595
|
+
} finally {
|
|
1596
|
+
await fs.rm(db_folder, { recursive: true, force: true })
|
|
1597
|
+
await fs.rm(meta_folder, { recursive: true, force: true })
|
|
1598
|
+
}
|
|
1599
|
+
})()`
|
|
1600
|
+
})
|
|
1601
|
+
return await r1.text()
|
|
1602
|
+
},
|
|
1603
|
+
'aborted'
|
|
1604
|
+
)
|
|
1605
|
+
|
|
1606
|
+
runTest(
|
|
1607
|
+
"test signal abort stops local delete operation",
|
|
1608
|
+
async () => {
|
|
1609
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
1610
|
+
method: 'POST',
|
|
1611
|
+
body: `void (async () => {
|
|
1612
|
+
var fs = require('fs').promises
|
|
1613
|
+
var test_id = 'test-abort-delete-' + Math.random().toString(36).slice(2)
|
|
1614
|
+
var db_folder = __dirname + '/' + test_id + '-db'
|
|
1615
|
+
var meta_folder = __dirname + '/' + test_id + '-meta'
|
|
1616
|
+
|
|
1617
|
+
try {
|
|
1618
|
+
var bb = braid_blob.create_braid_blob()
|
|
1619
|
+
bb.db_folder = db_folder
|
|
1620
|
+
bb.meta_folder = meta_folder
|
|
1621
|
+
|
|
1622
|
+
// Put a file first
|
|
1623
|
+
await bb.put('/test-file', Buffer.from('hello'), { version: ['1'] })
|
|
1624
|
+
|
|
1625
|
+
// Create an already-aborted signal
|
|
1626
|
+
var ac = new AbortController()
|
|
1627
|
+
ac.abort()
|
|
1628
|
+
|
|
1629
|
+
// Try to delete with aborted signal
|
|
1630
|
+
await bb.delete('/test-file', { signal: ac.signal })
|
|
1631
|
+
|
|
1632
|
+
// File should still exist since delete was aborted
|
|
1633
|
+
var result = await bb.get('/test-file')
|
|
1634
|
+
res.end(result && result.body ? 'still exists' : 'deleted')
|
|
1635
|
+
} catch (e) {
|
|
1636
|
+
res.end('error: ' + e.message)
|
|
1637
|
+
} finally {
|
|
1638
|
+
await fs.rm(db_folder, { recursive: true, force: true })
|
|
1639
|
+
await fs.rm(meta_folder, { recursive: true, force: true })
|
|
1640
|
+
}
|
|
1641
|
+
})()`
|
|
1642
|
+
})
|
|
1643
|
+
return await r1.text()
|
|
1644
|
+
},
|
|
1645
|
+
'still exists'
|
|
1646
|
+
)
|
|
1647
|
+
|
|
1648
|
+
runTest(
|
|
1649
|
+
"test signal abort stops subscription updates",
|
|
1650
|
+
async () => {
|
|
1651
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
1652
|
+
method: 'POST',
|
|
1653
|
+
body: `void (async () => {
|
|
1654
|
+
var fs = require('fs').promises
|
|
1655
|
+
var test_id = 'test-abort-sub-' + Math.random().toString(36).slice(2)
|
|
1656
|
+
var db_folder = __dirname + '/' + test_id + '-db'
|
|
1657
|
+
var meta_folder = __dirname + '/' + test_id + '-meta'
|
|
1658
|
+
|
|
1659
|
+
try {
|
|
1660
|
+
var bb = braid_blob.create_braid_blob()
|
|
1661
|
+
bb.db_folder = db_folder
|
|
1662
|
+
bb.meta_folder = meta_folder
|
|
1663
|
+
|
|
1664
|
+
// Put a file first
|
|
1665
|
+
await bb.put('/test-file', Buffer.from('v1'), { version: ['1'] })
|
|
1666
|
+
|
|
1667
|
+
// Subscribe with an AbortController
|
|
1668
|
+
var ac = new AbortController()
|
|
1669
|
+
var updates = []
|
|
1670
|
+
|
|
1671
|
+
await bb.get('/test-file', {
|
|
1672
|
+
signal: ac.signal,
|
|
1673
|
+
subscribe: (update) => {
|
|
1674
|
+
updates.push(update.body.toString())
|
|
1675
|
+
}
|
|
1676
|
+
})
|
|
1677
|
+
|
|
1678
|
+
// Should have received initial update
|
|
1679
|
+
if (updates.length !== 1 || updates[0] !== 'v1') {
|
|
1680
|
+
res.end('initial update wrong: ' + JSON.stringify(updates))
|
|
1681
|
+
return
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
// Abort the subscription
|
|
1685
|
+
ac.abort()
|
|
1686
|
+
|
|
1687
|
+
// Put another update
|
|
1688
|
+
await bb.put('/test-file', Buffer.from('v2'), { version: ['2'] })
|
|
1689
|
+
|
|
1690
|
+
// Wait a bit for any updates to propagate
|
|
1691
|
+
await new Promise(done => setTimeout(done, 50))
|
|
1692
|
+
|
|
1693
|
+
// Should still only have the initial update
|
|
1694
|
+
res.end(updates.length === 1 ? 'stopped' : 'got extra: ' + JSON.stringify(updates))
|
|
1695
|
+
} catch (e) {
|
|
1696
|
+
res.end('error: ' + e.message)
|
|
1697
|
+
} finally {
|
|
1698
|
+
await fs.rm(db_folder, { recursive: true, force: true })
|
|
1699
|
+
await fs.rm(meta_folder, { recursive: true, force: true })
|
|
1700
|
+
}
|
|
1701
|
+
})()`
|
|
1702
|
+
})
|
|
1703
|
+
return await r1.text()
|
|
1704
|
+
},
|
|
1705
|
+
'stopped'
|
|
1706
|
+
)
|
|
1707
|
+
|
|
1401
1708
|
}
|
|
1402
1709
|
|
|
1403
1710
|
// Export for Node.js (CommonJS)
|