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.
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm test:*)"
5
+ ],
6
+ "deny": [],
7
+ "ask": []
8
+ }
9
+ }
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
- var body = await braid_blob.db.read(key)
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.db.delete(options.key)
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.22",
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.19"
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)