braid-blob 0.0.22 → 0.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +9 -0
- package/index.js +34 -3
- package/package.json +2 -2
- package/test/tests.js +185 -0
package/index.js
CHANGED
|
@@ -23,9 +23,10 @@ function create_braid_blob() {
|
|
|
23
23
|
braid_blob.db = await url_file_db.create(
|
|
24
24
|
braid_blob.db_folder,
|
|
25
25
|
braid_blob.meta_folder,
|
|
26
|
-
async (key) => {
|
|
26
|
+
async (db, key) => {
|
|
27
27
|
// File changed externally, notify subscriptions
|
|
28
|
-
|
|
28
|
+
// Use db parameter instead of braid_blob.db to avoid race condition
|
|
29
|
+
var body = await db.read(key)
|
|
29
30
|
await braid_blob.put(key, body, { skip_write: true })
|
|
30
31
|
}
|
|
31
32
|
)
|
|
@@ -208,6 +209,36 @@ function create_braid_blob() {
|
|
|
208
209
|
return result
|
|
209
210
|
}
|
|
210
211
|
|
|
212
|
+
braid_blob.delete = async (key, options = {}) => {
|
|
213
|
+
// Handle URL case - make a remote DELETE request
|
|
214
|
+
if (key instanceof URL) {
|
|
215
|
+
options.my_abort = new AbortController()
|
|
216
|
+
if (options.signal) {
|
|
217
|
+
options.signal.addEventListener('abort', () =>
|
|
218
|
+
options.my_abort.abort())
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
var params = {
|
|
222
|
+
method: 'DELETE',
|
|
223
|
+
signal: options.my_abort.signal
|
|
224
|
+
}
|
|
225
|
+
for (var x of ['headers', 'peer'])
|
|
226
|
+
if (options[x] != null) params[x] = options[x]
|
|
227
|
+
|
|
228
|
+
return await braid_fetch(key.href, params)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
await braid_blob.init()
|
|
232
|
+
|
|
233
|
+
// Delete the file from the database
|
|
234
|
+
await braid_blob.db.delete(key)
|
|
235
|
+
|
|
236
|
+
// TODO: notify subscribers of deletion once we have a protocol for that
|
|
237
|
+
// For now, just clean up the subscriptions
|
|
238
|
+
if (braid_blob.key_to_subs[key])
|
|
239
|
+
delete braid_blob.key_to_subs[key]
|
|
240
|
+
}
|
|
241
|
+
|
|
211
242
|
braid_blob.serve = async (req, res, options = {}) => {
|
|
212
243
|
await braid_blob.init()
|
|
213
244
|
|
|
@@ -291,7 +322,7 @@ function create_braid_blob() {
|
|
|
291
322
|
res.setHeader("Version", version_to_header(event != null ? [event] : []))
|
|
292
323
|
res.end('')
|
|
293
324
|
} else if (req.method === 'DELETE') {
|
|
294
|
-
await braid_blob.
|
|
325
|
+
await braid_blob.delete(options.key)
|
|
295
326
|
res.statusCode = 204 // No Content
|
|
296
327
|
res.end('')
|
|
297
328
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "braid-blob",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"description": "Library for collaborative blobs over http using braid.",
|
|
5
5
|
"author": "Braid Working Group",
|
|
6
6
|
"repository": "braid-org/braid-blob",
|
|
@@ -11,6 +11,6 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"braid-http": "~1.3.82",
|
|
14
|
-
"url-file-db": "^0.0.
|
|
14
|
+
"url-file-db": "^0.0.21"
|
|
15
15
|
}
|
|
16
16
|
}
|
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 () => {
|
|
@@ -1309,6 +1405,95 @@ runTest(
|
|
|
1309
1405
|
'true'
|
|
1310
1406
|
)
|
|
1311
1407
|
|
|
1408
|
+
runTest(
|
|
1409
|
+
"test that callback receives db parameter for use before assignment",
|
|
1410
|
+
async () => {
|
|
1411
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
1412
|
+
method: 'POST',
|
|
1413
|
+
body: `void (async () => {
|
|
1414
|
+
var fs = require('fs').promises
|
|
1415
|
+
var test_id = 'test-callback-' + Math.random().toString(36).slice(2)
|
|
1416
|
+
var db_folder = __dirname + '/' + test_id + '-db'
|
|
1417
|
+
var meta_folder = __dirname + '/' + test_id + '-meta'
|
|
1418
|
+
|
|
1419
|
+
try {
|
|
1420
|
+
// Pre-create files that will trigger callback during init
|
|
1421
|
+
await fs.mkdir(db_folder, { recursive: true })
|
|
1422
|
+
await fs.mkdir(meta_folder, { recursive: true })
|
|
1423
|
+
|
|
1424
|
+
// Write a file that exists before init
|
|
1425
|
+
await fs.writeFile(db_folder + '/pre-existing', 'old content')
|
|
1426
|
+
|
|
1427
|
+
// Create metadata for it with old timestamp to trigger callback
|
|
1428
|
+
await fs.writeFile(meta_folder + '/!pre-existing', JSON.stringify({
|
|
1429
|
+
canonical_path: '/pre-existing',
|
|
1430
|
+
event: 'old-version',
|
|
1431
|
+
last_seen: Date.now() - 10000,
|
|
1432
|
+
mtime_ns: '1000000000000000'
|
|
1433
|
+
}))
|
|
1434
|
+
|
|
1435
|
+
// Wait for files to be written
|
|
1436
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
1437
|
+
|
|
1438
|
+
var callback_error = null
|
|
1439
|
+
var callback_called = false
|
|
1440
|
+
var db_was_null = false
|
|
1441
|
+
|
|
1442
|
+
// Monkey-patch url_file_db.create to intercept callback
|
|
1443
|
+
var url_file_db_module = require('url-file-db').url_file_db
|
|
1444
|
+
var original_create = url_file_db_module.create
|
|
1445
|
+
|
|
1446
|
+
url_file_db_module.create = async function(db_dir, meta_dir, callback) {
|
|
1447
|
+
var wrapped_callback = async function(db, key) {
|
|
1448
|
+
callback_called = true
|
|
1449
|
+
// Check if bb.db is null during callback
|
|
1450
|
+
if (!bb.db) {
|
|
1451
|
+
db_was_null = true
|
|
1452
|
+
}
|
|
1453
|
+
try {
|
|
1454
|
+
await callback(db, key)
|
|
1455
|
+
} catch (e) {
|
|
1456
|
+
callback_error = e.message
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
return await original_create.call(this, db_dir, meta_dir, wrapped_callback)
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
var bb = braid_blob.create_braid_blob()
|
|
1463
|
+
bb.db_folder = db_folder
|
|
1464
|
+
bb.meta_folder = meta_folder
|
|
1465
|
+
|
|
1466
|
+
// Init will trigger callback for pre-existing file
|
|
1467
|
+
// Callback tries to use braid_blob.db.read() but db not assigned yet
|
|
1468
|
+
await bb.init()
|
|
1469
|
+
|
|
1470
|
+
// Restore
|
|
1471
|
+
url_file_db_module.create = original_create
|
|
1472
|
+
|
|
1473
|
+
// Clean up
|
|
1474
|
+
await fs.rm(db_folder, { recursive: true, force: true })
|
|
1475
|
+
await fs.rm(meta_folder, { recursive: true, force: true })
|
|
1476
|
+
|
|
1477
|
+
if (!callback_called) {
|
|
1478
|
+
res.end('callback was not called')
|
|
1479
|
+
} else if (callback_error) {
|
|
1480
|
+
res.end('callback error: ' + callback_error)
|
|
1481
|
+
} else {
|
|
1482
|
+
// Success: callback worked even if bb.db was null (using db param)
|
|
1483
|
+
res.end('true')
|
|
1484
|
+
}
|
|
1485
|
+
} catch (e) {
|
|
1486
|
+
await fs.rm(db_folder, { recursive: true, force: true }).catch(() => {})
|
|
1487
|
+
await fs.rm(meta_folder, { recursive: true, force: true }).catch(() => {})
|
|
1488
|
+
res.end('error: ' + e.message)
|
|
1489
|
+
}
|
|
1490
|
+
})()`
|
|
1491
|
+
})
|
|
1492
|
+
return await r1.text()
|
|
1493
|
+
},
|
|
1494
|
+
'true'
|
|
1495
|
+
)
|
|
1496
|
+
|
|
1312
1497
|
}
|
|
1313
1498
|
|
|
1314
1499
|
// Export for Node.js (CommonJS)
|