braid-blob 0.0.32 → 0.0.34
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 +77 -15
- package/package.json +1 -1
- package/test/tests.js +9 -98
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
var {http_server: braidify, fetch: braid_fetch} = require('braid-http'),
|
|
2
2
|
{url_file_db} = require('url-file-db'),
|
|
3
|
+
fs = require('fs'),
|
|
3
4
|
path = require('path')
|
|
4
5
|
|
|
5
6
|
function create_braid_blob() {
|
|
@@ -7,9 +8,10 @@ function create_braid_blob() {
|
|
|
7
8
|
db_folder: './braid-blob-db',
|
|
8
9
|
meta_folder: './braid-blob-meta',
|
|
9
10
|
cache: {},
|
|
11
|
+
meta_cache: {},
|
|
10
12
|
key_to_subs: {},
|
|
11
13
|
peer: null, // will be auto-generated if not set by the user
|
|
12
|
-
db: null // url-file-db instance
|
|
14
|
+
db: null // url-file-db instance
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
braid_blob.init = async () => {
|
|
@@ -19,16 +21,17 @@ function create_braid_blob() {
|
|
|
19
21
|
await braid_blob.init()
|
|
20
22
|
|
|
21
23
|
async function real_init() {
|
|
22
|
-
//
|
|
24
|
+
// Ensure our meta folder exists
|
|
25
|
+
await fs.promises.mkdir(braid_blob.meta_folder, { recursive: true })
|
|
26
|
+
|
|
27
|
+
// Create a fake meta folder for url-file-db (we manage our own meta)
|
|
28
|
+
var fake_meta_folder = braid_blob.meta_folder + '-fake'
|
|
29
|
+
await fs.promises.mkdir(fake_meta_folder, { recursive: true })
|
|
30
|
+
|
|
31
|
+
// Create url-file-db instance (with fake meta folder - we manage our own)
|
|
23
32
|
braid_blob.db = await url_file_db.create(
|
|
24
33
|
braid_blob.db_folder,
|
|
25
|
-
|
|
26
|
-
async (db, key) => {
|
|
27
|
-
// File changed externally, notify subscriptions
|
|
28
|
-
// Use db parameter instead of braid_blob.db to avoid race condition
|
|
29
|
-
var body = await db.read(key)
|
|
30
|
-
await braid_blob.put(key, body, { skip_write: true })
|
|
31
|
-
}
|
|
34
|
+
fake_meta_folder
|
|
32
35
|
)
|
|
33
36
|
|
|
34
37
|
// establish a peer id if not already set
|
|
@@ -37,6 +40,37 @@ function create_braid_blob() {
|
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
function get_meta(key) {
|
|
44
|
+
if (braid_blob.meta_cache[key]) return braid_blob.meta_cache[key]
|
|
45
|
+
var meta_path = path.join(braid_blob.meta_folder, encode_filename(key))
|
|
46
|
+
try {
|
|
47
|
+
var data = fs.readFileSync(meta_path, 'utf8')
|
|
48
|
+
braid_blob.meta_cache[key] = JSON.parse(data)
|
|
49
|
+
return braid_blob.meta_cache[key]
|
|
50
|
+
} catch (e) {
|
|
51
|
+
if (e.code === 'ENOENT') return null
|
|
52
|
+
throw e
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function update_meta(key, updates) {
|
|
57
|
+
var meta = get_meta(key) || {}
|
|
58
|
+
Object.assign(meta, updates)
|
|
59
|
+
braid_blob.meta_cache[key] = meta
|
|
60
|
+
var meta_path = path.join(braid_blob.meta_folder, encode_filename(key))
|
|
61
|
+
await fs.promises.writeFile(meta_path, JSON.stringify(meta))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function delete_meta(key) {
|
|
65
|
+
delete braid_blob.meta_cache[key]
|
|
66
|
+
var meta_path = path.join(braid_blob.meta_folder, encode_filename(key))
|
|
67
|
+
try {
|
|
68
|
+
await fs.promises.unlink(meta_path)
|
|
69
|
+
} catch (e) {
|
|
70
|
+
if (e.code !== 'ENOENT') throw e
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
40
74
|
braid_blob.put = async (key, body, options = {}) => {
|
|
41
75
|
// Handle URL case - make a remote PUT request
|
|
42
76
|
if (key instanceof URL) {
|
|
@@ -59,8 +93,7 @@ function create_braid_blob() {
|
|
|
59
93
|
await braid_blob.init()
|
|
60
94
|
if (options.signal?.aborted) return
|
|
61
95
|
|
|
62
|
-
|
|
63
|
-
var meta = braid_blob.db.get_meta(key) || {}
|
|
96
|
+
var meta = get_meta(key) || {}
|
|
64
97
|
|
|
65
98
|
var their_e =
|
|
66
99
|
!options.version ?
|
|
@@ -86,7 +119,7 @@ function create_braid_blob() {
|
|
|
86
119
|
if (options.content_type)
|
|
87
120
|
meta_updates.content_type = options.content_type
|
|
88
121
|
|
|
89
|
-
await
|
|
122
|
+
await update_meta(key, meta_updates)
|
|
90
123
|
if (options.signal?.aborted) return
|
|
91
124
|
|
|
92
125
|
// Notify all subscriptions of the update
|
|
@@ -134,8 +167,7 @@ function create_braid_blob() {
|
|
|
134
167
|
|
|
135
168
|
await braid_blob.init()
|
|
136
169
|
|
|
137
|
-
|
|
138
|
-
var meta = braid_blob.db.get_meta(key) || {}
|
|
170
|
+
var meta = get_meta(key) || {}
|
|
139
171
|
if (meta.event == null) return null
|
|
140
172
|
|
|
141
173
|
var result = {
|
|
@@ -217,8 +249,9 @@ function create_braid_blob() {
|
|
|
217
249
|
await braid_blob.init()
|
|
218
250
|
if (options.signal?.aborted) return
|
|
219
251
|
|
|
220
|
-
// Delete the file
|
|
252
|
+
// Delete the file and its metadata
|
|
221
253
|
await braid_blob.db.delete(key)
|
|
254
|
+
await delete_meta(key)
|
|
222
255
|
|
|
223
256
|
// TODO: notify subscribers of deletion once we have a protocol for that
|
|
224
257
|
// For now, just clean up the subscriptions
|
|
@@ -534,6 +567,35 @@ function create_braid_blob() {
|
|
|
534
567
|
return false;
|
|
535
568
|
}
|
|
536
569
|
|
|
570
|
+
function encode_filename(s) {
|
|
571
|
+
// Deal with case insensitivity
|
|
572
|
+
var bits = s.match(/\p{L}/ug).
|
|
573
|
+
map(c => +(c === c.toUpperCase())).join('')
|
|
574
|
+
var postfix = BigInt('0b0' + bits).toString(16)
|
|
575
|
+
|
|
576
|
+
// Swap ! and /
|
|
577
|
+
s = s.replace(/[\/!]/g, x => x === '/' ? '!' : '/')
|
|
578
|
+
|
|
579
|
+
// Encode characters that are unsafe on various filesystems:
|
|
580
|
+
// < > : " / \ | ? * - Windows restrictions
|
|
581
|
+
// % - Reserved for encoding
|
|
582
|
+
// \x00-\x1f, \x7f - Control characters
|
|
583
|
+
s = s.replace(/[<>:"/|\\?*%\x00-\x1f\x7f]/g, encode_char)
|
|
584
|
+
|
|
585
|
+
// Deal with windows reserved words
|
|
586
|
+
if (s.match(/^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\..*)?$/i))
|
|
587
|
+
s = s.slice(0, 2) + encode_char(s[2]) + s.slice(3)
|
|
588
|
+
|
|
589
|
+
// Deal with case insensitivity
|
|
590
|
+
s += '.' + postfix
|
|
591
|
+
|
|
592
|
+
return s
|
|
593
|
+
|
|
594
|
+
function encode_char(char) {
|
|
595
|
+
return '%' + char.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0')
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
537
599
|
braid_blob.create_braid_blob = create_braid_blob
|
|
538
600
|
|
|
539
601
|
return braid_blob
|
package/package.json
CHANGED
package/test/tests.js
CHANGED
|
@@ -1021,8 +1021,8 @@ runTest(
|
|
|
1021
1021
|
runTest(
|
|
1022
1022
|
"test sync two local keys",
|
|
1023
1023
|
async () => {
|
|
1024
|
-
var key1 = 'test-sync-local1-' + Math.random().toString(36).slice(2)
|
|
1025
|
-
var key2 = 'test-sync-local2-' + Math.random().toString(36).slice(2)
|
|
1024
|
+
var key1 = '/test-sync-local1-' + Math.random().toString(36).slice(2)
|
|
1025
|
+
var key2 = '/test-sync-local2-' + Math.random().toString(36).slice(2)
|
|
1026
1026
|
|
|
1027
1027
|
var r1 = await braid_fetch(`/eval`, {
|
|
1028
1028
|
method: 'POST',
|
|
@@ -1049,7 +1049,7 @@ runTest(
|
|
|
1049
1049
|
await new Promise(done => setTimeout(done, 100))
|
|
1050
1050
|
|
|
1051
1051
|
// Check second key has the content
|
|
1052
|
-
var r = await braid_fetch(
|
|
1052
|
+
var r = await braid_fetch(`${key2}`)
|
|
1053
1053
|
return await r.text()
|
|
1054
1054
|
},
|
|
1055
1055
|
'sync local content'
|
|
@@ -1058,11 +1058,11 @@ runTest(
|
|
|
1058
1058
|
runTest(
|
|
1059
1059
|
"test sync remote to local (swap)",
|
|
1060
1060
|
async () => {
|
|
1061
|
-
var local_key = 'test-sync-swap-local-' + Math.random().toString(36).slice(2)
|
|
1062
|
-
var remote_key = 'test-sync-swap-remote-' + Math.random().toString(36).slice(2)
|
|
1061
|
+
var local_key = '/test-sync-swap-local-' + Math.random().toString(36).slice(2)
|
|
1062
|
+
var remote_key = '/test-sync-swap-remote-' + Math.random().toString(36).slice(2)
|
|
1063
1063
|
|
|
1064
1064
|
// Put something on the server first
|
|
1065
|
-
await braid_fetch(
|
|
1065
|
+
await braid_fetch(`${remote_key}`, {
|
|
1066
1066
|
method: 'PUT',
|
|
1067
1067
|
version: ['800'],
|
|
1068
1068
|
body: 'remote content'
|
|
@@ -1073,7 +1073,7 @@ runTest(
|
|
|
1073
1073
|
body: `void (async () => {
|
|
1074
1074
|
try {
|
|
1075
1075
|
var braid_blob = require(\`\${__dirname}/../index.js\`)
|
|
1076
|
-
var remote_url = new URL('http://localhost:' + req.socket.localPort + '
|
|
1076
|
+
var remote_url = new URL('http://localhost:' + req.socket.localPort + '${remote_key}')
|
|
1077
1077
|
|
|
1078
1078
|
// Start sync with URL as first argument (should swap internally)
|
|
1079
1079
|
braid_blob.sync(remote_url, '${local_key}')
|
|
@@ -1091,7 +1091,7 @@ runTest(
|
|
|
1091
1091
|
await new Promise(done => setTimeout(done, 100))
|
|
1092
1092
|
|
|
1093
1093
|
// Check local key has the remote content
|
|
1094
|
-
var r = await braid_fetch(
|
|
1094
|
+
var r = await braid_fetch(`${local_key}`)
|
|
1095
1095
|
return await r.text()
|
|
1096
1096
|
},
|
|
1097
1097
|
'remote content'
|
|
@@ -1365,7 +1365,7 @@ runTest(
|
|
|
1365
1365
|
bb2.db_folder = db_folder
|
|
1366
1366
|
bb2.meta_folder = meta_folder
|
|
1367
1367
|
|
|
1368
|
-
//
|
|
1368
|
+
// Get the file from the new instance
|
|
1369
1369
|
var result2 = await bb2.get(test_key)
|
|
1370
1370
|
|
|
1371
1371
|
// Version should still be version-2, not regenerated
|
|
@@ -1461,95 +1461,6 @@ runTest(
|
|
|
1461
1461
|
'true'
|
|
1462
1462
|
)
|
|
1463
1463
|
|
|
1464
|
-
runTest(
|
|
1465
|
-
"test that callback receives db parameter for use before assignment",
|
|
1466
|
-
async () => {
|
|
1467
|
-
var r1 = await braid_fetch(`/eval`, {
|
|
1468
|
-
method: 'POST',
|
|
1469
|
-
body: `void (async () => {
|
|
1470
|
-
var fs = require('fs').promises
|
|
1471
|
-
var test_id = 'test-callback-' + Math.random().toString(36).slice(2)
|
|
1472
|
-
var db_folder = __dirname + '/' + test_id + '-db'
|
|
1473
|
-
var meta_folder = __dirname + '/' + test_id + '-meta'
|
|
1474
|
-
|
|
1475
|
-
try {
|
|
1476
|
-
// Pre-create files that will trigger callback during init
|
|
1477
|
-
await fs.mkdir(db_folder, { recursive: true })
|
|
1478
|
-
await fs.mkdir(meta_folder, { recursive: true })
|
|
1479
|
-
|
|
1480
|
-
// Write a file that exists before init
|
|
1481
|
-
await fs.writeFile(db_folder + '/pre-existing', 'old content')
|
|
1482
|
-
|
|
1483
|
-
// Create metadata for it with old timestamp to trigger callback
|
|
1484
|
-
await fs.writeFile(meta_folder + '/!pre-existing', JSON.stringify({
|
|
1485
|
-
canonical_path: '/pre-existing',
|
|
1486
|
-
event: 'old-version',
|
|
1487
|
-
last_seen: Date.now() - 10000,
|
|
1488
|
-
mtime_ns: '1000000000000000'
|
|
1489
|
-
}))
|
|
1490
|
-
|
|
1491
|
-
// Wait for files to be written
|
|
1492
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
1493
|
-
|
|
1494
|
-
var callback_error = null
|
|
1495
|
-
var callback_called = false
|
|
1496
|
-
var db_was_null = false
|
|
1497
|
-
|
|
1498
|
-
// Monkey-patch url_file_db.create to intercept callback
|
|
1499
|
-
var url_file_db_module = require('url-file-db').url_file_db
|
|
1500
|
-
var original_create = url_file_db_module.create
|
|
1501
|
-
|
|
1502
|
-
url_file_db_module.create = async function(db_dir, meta_dir, callback) {
|
|
1503
|
-
var wrapped_callback = async function(db, key) {
|
|
1504
|
-
callback_called = true
|
|
1505
|
-
// Check if bb.db is null during callback
|
|
1506
|
-
if (!bb.db) {
|
|
1507
|
-
db_was_null = true
|
|
1508
|
-
}
|
|
1509
|
-
try {
|
|
1510
|
-
await callback(db, key)
|
|
1511
|
-
} catch (e) {
|
|
1512
|
-
callback_error = e.message
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
return await original_create.call(this, db_dir, meta_dir, wrapped_callback)
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
var bb = braid_blob.create_braid_blob()
|
|
1519
|
-
bb.db_folder = db_folder
|
|
1520
|
-
bb.meta_folder = meta_folder
|
|
1521
|
-
|
|
1522
|
-
// Init will trigger callback for pre-existing file
|
|
1523
|
-
// Callback tries to use braid_blob.db.read() but db not assigned yet
|
|
1524
|
-
await bb.init()
|
|
1525
|
-
|
|
1526
|
-
// Restore
|
|
1527
|
-
url_file_db_module.create = original_create
|
|
1528
|
-
|
|
1529
|
-
// Clean up
|
|
1530
|
-
await fs.rm(db_folder, { recursive: true, force: true })
|
|
1531
|
-
await fs.rm(meta_folder, { recursive: true, force: true })
|
|
1532
|
-
|
|
1533
|
-
if (!callback_called) {
|
|
1534
|
-
res.end('callback was not called')
|
|
1535
|
-
} else if (callback_error) {
|
|
1536
|
-
res.end('callback error: ' + callback_error)
|
|
1537
|
-
} else {
|
|
1538
|
-
// Success: callback worked even if bb.db was null (using db param)
|
|
1539
|
-
res.end('true')
|
|
1540
|
-
}
|
|
1541
|
-
} catch (e) {
|
|
1542
|
-
await fs.rm(db_folder, { recursive: true, force: true }).catch(() => {})
|
|
1543
|
-
await fs.rm(meta_folder, { recursive: true, force: true }).catch(() => {})
|
|
1544
|
-
res.end('error: ' + e.message)
|
|
1545
|
-
}
|
|
1546
|
-
})()`
|
|
1547
|
-
})
|
|
1548
|
-
return await r1.text()
|
|
1549
|
-
},
|
|
1550
|
-
'true'
|
|
1551
|
-
)
|
|
1552
|
-
|
|
1553
1464
|
runTest(
|
|
1554
1465
|
"test get with URL returns null on 404",
|
|
1555
1466
|
async () => {
|