braid-blob 0.0.21 → 0.0.23

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 CHANGED
@@ -1,6 +1,5 @@
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'),
4
3
  path = require('path')
5
4
 
6
5
  function create_braid_blob() {
@@ -9,9 +8,8 @@ function create_braid_blob() {
9
8
  meta_folder: './braid-blob-meta',
10
9
  cache: {},
11
10
  key_to_subs: {},
12
- peer: null, // we'll try to load this from a file, if not set by the user
13
- db: null, // url-file-db instance for blob storage
14
- meta_db: null // url-file-db instance for meta storage
11
+ peer: null, // will be auto-generated if not set by the user
12
+ db: null // url-file-db instance with integrated meta storage
15
13
  }
16
14
 
17
15
  braid_blob.init = async () => {
@@ -21,25 +19,21 @@ function create_braid_blob() {
21
19
  await braid_blob.init()
22
20
 
23
21
  async function real_init() {
24
- // Create url-file-db instance for blob storage
25
- braid_blob.db = await url_file_db.create(braid_blob.db_folder, async (key) => {
26
- // File changed externally, notify subscriptions
27
- var body = await braid_blob.db.read(key)
28
- await braid_blob.put(key, body, { skip_write: true })
29
- })
30
-
31
- // Create url-file-db instance for meta storage (in a subfolder)
32
- // This will create both meta_folder and the db subfolder with recursive: true
33
- braid_blob.meta_db = await url_file_db.create(`${braid_blob.meta_folder}/db`)
22
+ // Create url-file-db instance with integrated meta storage
23
+ braid_blob.db = await url_file_db.create(
24
+ braid_blob.db_folder,
25
+ braid_blob.meta_folder,
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
+ }
32
+ )
34
33
 
35
- // establish a peer id (stored at root of meta_folder, sibling to db subfolder)
36
- if (!braid_blob.peer)
37
- try {
38
- braid_blob.peer = await fs.promises.readFile(`${braid_blob.meta_folder}/peer.txt`, 'utf8')
39
- } catch (e) {}
34
+ // establish a peer id if not already set
40
35
  if (!braid_blob.peer)
41
36
  braid_blob.peer = Math.random().toString(36).slice(2)
42
- await fs.promises.writeFile(`${braid_blob.meta_folder}/peer.txt`, braid_blob.peer)
43
37
  }
44
38
  }
45
39
 
@@ -69,11 +63,8 @@ function create_braid_blob() {
69
63
 
70
64
  await braid_blob.init()
71
65
 
72
- // Read the meta data from meta_db
73
- var meta = {}
74
- var meta_content = await braid_blob.meta_db.read(key)
75
- if (meta_content)
76
- meta = JSON.parse(meta_content.toString('utf8'))
66
+ // Read the meta data using new meta API
67
+ var meta = braid_blob.db.get_meta(key) || {}
77
68
 
78
69
  var their_e =
79
70
  !options.version ?
@@ -93,11 +84,12 @@ function create_braid_blob() {
93
84
  if (!options.skip_write)
94
85
  await braid_blob.db.write(key, body)
95
86
 
96
- // Write the meta data
87
+ // Update only the fields we want to change in metadata
88
+ var meta_updates = { event: their_e }
97
89
  if (options.content_type)
98
- meta.content_type = options.content_type
90
+ meta_updates.content_type = options.content_type
99
91
 
100
- await braid_blob.meta_db.write(key, JSON.stringify(meta))
92
+ await braid_blob.db.update_meta(key, meta_updates)
101
93
 
102
94
  // Notify all subscriptions of the update
103
95
  // (except the peer which made the PUT request itself)
@@ -153,11 +145,8 @@ function create_braid_blob() {
153
145
 
154
146
  await braid_blob.init()
155
147
 
156
- // Read the meta data from meta_db
157
- var meta = {}
158
- var meta_content = await braid_blob.meta_db.read(key)
159
- if (meta_content)
160
- meta = JSON.parse(meta_content.toString('utf8'))
148
+ // Read the meta data using new meta API
149
+ var meta = braid_blob.db.get_meta(key) || {}
161
150
  if (meta.event == null) return null
162
151
 
163
152
  var result = {
@@ -238,12 +227,6 @@ function create_braid_blob() {
238
227
  var body = req.method === 'PUT' && await slurp(req)
239
228
 
240
229
  await within_fiber(options.key, async () => {
241
- // Read the meta data from meta_db
242
- var meta = {}
243
- var meta_content = await braid_blob.meta_db.read(options.key)
244
- if (meta_content)
245
- meta = JSON.parse(meta_content.toString('utf8'))
246
-
247
230
  if (req.method === 'GET' || req.method === 'HEAD') {
248
231
  if (!res.hasHeader("editable")) res.setHeader("Editable", "true")
249
232
  if (!req.subscribe) res.setHeader("Accept-Subscribe", "true")
@@ -301,16 +284,15 @@ function create_braid_blob() {
301
284
  }
302
285
  } else if (req.method === 'PUT') {
303
286
  // Handle PUT request to update binary files
304
- meta.event = await braid_blob.put(options.key, body, {
287
+ var event = await braid_blob.put(options.key, body, {
305
288
  version: req.version,
306
289
  content_type: req.headers['content-type'],
307
290
  peer: req.peer
308
291
  })
309
- res.setHeader("Version", version_to_header(meta.event != null ? [meta.event] : []))
292
+ res.setHeader("Version", version_to_header(event != null ? [event] : []))
310
293
  res.end('')
311
294
  } else if (req.method === 'DELETE') {
312
295
  await braid_blob.db.delete(options.key)
313
- await braid_blob.meta_db.delete(options.key)
314
296
  res.statusCode = 204 // No Content
315
297
  res.end('')
316
298
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-blob",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
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.15"
14
+ "url-file-db": "^0.0.21"
15
15
  }
16
16
  }
package/test/tests.js CHANGED
@@ -34,7 +34,7 @@ runTest(
34
34
  )
35
35
 
36
36
  runTest(
37
- "test that peer is same the second time we run from same db folder",
37
+ "test that peer is different each time we create a new instance",
38
38
  async () => {
39
39
  var r1 = await braid_fetch(`/eval`, {
40
40
  method: 'POST',
@@ -62,7 +62,7 @@ runTest(
62
62
  await require('fs').promises.rm(db, { recursive: true, force: true })
63
63
  await require('fs').promises.rm(meta, { recursive: true, force: true })
64
64
 
65
- res.end('' + (bb1.peer === bb2.peer))
65
+ res.end('' + (bb1.peer !== bb2.peer))
66
66
  })()`
67
67
  })
68
68
  return await r1.text()
@@ -100,6 +100,39 @@ runTest(
100
100
  'test_peer'
101
101
  )
102
102
 
103
+ runTest(
104
+ "test that manually set peer persists through initialization",
105
+ async () => {
106
+ var r1 = await braid_fetch(`/eval`, {
107
+ method: 'POST',
108
+ body: `void (async () => {
109
+ var test_id = 'test-db-' + Math.random().toString(36).slice(2)
110
+ var db = __dirname + '/' + test_id + '-db'
111
+ var meta = __dirname + '/' + test_id + '-meta'
112
+
113
+ // Create instance with manually set peer
114
+ var bb1 = braid_blob.create_braid_blob()
115
+ bb1.db_folder = db
116
+ bb1.meta_folder = meta
117
+ bb1.peer = 'custom-peer-id-123'
118
+
119
+ // Initialize (should keep our custom peer)
120
+ await bb1.init()
121
+
122
+ var peer_after_init = bb1.peer
123
+
124
+ // Clean up
125
+ await require('fs').promises.rm(db, { recursive: true, force: true })
126
+ await require('fs').promises.rm(meta, { recursive: true, force: true })
127
+
128
+ res.end(peer_after_init === 'custom-peer-id-123' ? 'true' : 'false: ' + peer_after_init)
129
+ })()`
130
+ })
131
+ return await r1.text()
132
+ },
133
+ 'true'
134
+ )
135
+
103
136
  runTest(
104
137
  "test that PUTing with shorter event id doesn't do anything.",
105
138
  async () => {
@@ -1142,6 +1175,229 @@ runTest(
1142
1175
  '309'
1143
1176
  )
1144
1177
 
1178
+ runTest(
1179
+ "test multiple writes preserve correct mtime across restarts",
1180
+ async () => {
1181
+ var r1 = await braid_fetch(`/eval`, {
1182
+ method: 'POST',
1183
+ body: `void (async () => {
1184
+ var fs = require('fs').promises
1185
+ var test_id = 'test-multi-write-' + Math.random().toString(36).slice(2)
1186
+ var db_folder = __dirname + '/' + test_id + '-db'
1187
+ var meta_folder = __dirname + '/' + test_id + '-meta'
1188
+ var test_key = 'test-file'
1189
+
1190
+ try {
1191
+ // Create first braid_blob instance
1192
+ var bb1 = braid_blob.create_braid_blob()
1193
+ bb1.db_folder = db_folder
1194
+ bb1.meta_folder = meta_folder
1195
+
1196
+ // First write
1197
+ await bb1.put(test_key, Buffer.from('content1'), {
1198
+ version: ['version-1']
1199
+ })
1200
+
1201
+ // Wait a bit to ensure different mtime
1202
+ await new Promise(resolve => setTimeout(resolve, 50))
1203
+
1204
+ // Second write to same file (this is where the bug would occur)
1205
+ await bb1.put(test_key, Buffer.from('content2'), {
1206
+ version: ['version-2']
1207
+ })
1208
+
1209
+ var result1 = await bb1.get(test_key)
1210
+
1211
+ // Now restart and check
1212
+ var bb2 = braid_blob.create_braid_blob()
1213
+ bb2.db_folder = db_folder
1214
+ bb2.meta_folder = meta_folder
1215
+
1216
+ // This should NOT trigger a file change callback
1217
+ var result2 = await bb2.get(test_key)
1218
+
1219
+ // Version should still be version-2, not regenerated
1220
+ var correct_version = (result2.version[0] === 'version-2')
1221
+ var content_correct = (result2.body.toString() === 'content2')
1222
+
1223
+ // Clean up
1224
+ await fs.rm(db_folder, { recursive: true, force: true })
1225
+ await fs.rm(meta_folder, { recursive: true, force: true })
1226
+
1227
+ res.end(correct_version && content_correct ? 'true' :
1228
+ 'false: version=' + result2.version[0] + ', content=' + result2.body.toString())
1229
+ } catch (e) {
1230
+ // Clean up even on error
1231
+ await fs.rm(db_folder, { recursive: true, force: true })
1232
+ await fs.rm(meta_folder, { recursive: true, force: true })
1233
+ res.end('error: ' + e.message)
1234
+ }
1235
+ })()`
1236
+ })
1237
+ return await r1.text()
1238
+ },
1239
+ 'true'
1240
+ )
1241
+
1242
+ runTest(
1243
+ "test that files keep same event ID across restarts when not edited",
1244
+ async () => {
1245
+ var r1 = await braid_fetch(`/eval`, {
1246
+ method: 'POST',
1247
+ body: `void (async () => {
1248
+ var fs = require('fs').promises
1249
+ var test_id = 'test-persist-event-' + Math.random().toString(36).slice(2)
1250
+ var db_folder = __dirname + '/' + test_id + '-db'
1251
+ var meta_folder = __dirname + '/' + test_id + '-meta'
1252
+ var test_key = 'test-file'
1253
+ var test_content = 'test content that should not change'
1254
+
1255
+ try {
1256
+ // Create first braid_blob instance
1257
+ var bb1 = braid_blob.create_braid_blob()
1258
+ bb1.db_folder = db_folder
1259
+ bb1.meta_folder = meta_folder
1260
+
1261
+ // Put a file with specific version
1262
+ var version1 = await bb1.put(test_key, Buffer.from(test_content), {
1263
+ version: ['test-peer-123456']
1264
+ })
1265
+
1266
+ // Get the file to verify it has the expected version
1267
+ var result1 = await bb1.get(test_key)
1268
+
1269
+ // Check what metadata was saved
1270
+ var meta1 = bb1.db.get_meta(test_key)
1271
+ var debug_info = 'meta1: ' + JSON.stringify(meta1) + '; '
1272
+
1273
+ // Wait a bit to ensure file system has settled
1274
+ await new Promise(resolve => setTimeout(resolve, 100))
1275
+
1276
+ // Now create a second braid_blob instance with the same folders
1277
+ // This simulates a restart
1278
+ var bb2 = braid_blob.create_braid_blob()
1279
+ bb2.db_folder = db_folder
1280
+ bb2.meta_folder = meta_folder
1281
+
1282
+ // Initialize bb2 by doing a get (this triggers init)
1283
+ var result2 = await bb2.get(test_key)
1284
+
1285
+ // Check what metadata bb2 sees
1286
+ var meta2 = bb2.db.get_meta(test_key)
1287
+ debug_info += 'meta2: ' + JSON.stringify(meta2) + '; '
1288
+
1289
+ // The version should be the same - no new event ID generated
1290
+ var versions_match = (result1.version[0] === result2.version[0])
1291
+ var both_have_expected = (result1.version[0] === 'test-peer-123456')
1292
+
1293
+ // Clean up
1294
+ await fs.rm(db_folder, { recursive: true, force: true })
1295
+ await fs.rm(meta_folder, { recursive: true, force: true })
1296
+
1297
+ res.end(versions_match && both_have_expected ? 'true' :
1298
+ 'false: v1=' + result1.version[0] + ', v2=' + result2.version[0] + ' | ' + debug_info)
1299
+ } catch (e) {
1300
+ // Clean up even on error
1301
+ await fs.rm(db_folder, { recursive: true, force: true })
1302
+ await fs.rm(meta_folder, { recursive: true, force: true })
1303
+ res.end('error: ' + e.message)
1304
+ }
1305
+ })()`
1306
+ })
1307
+ return await r1.text()
1308
+ },
1309
+ 'true'
1310
+ )
1311
+
1312
+ runTest(
1313
+ "test that callback receives db parameter for use before assignment",
1314
+ async () => {
1315
+ var r1 = await braid_fetch(`/eval`, {
1316
+ method: 'POST',
1317
+ body: `void (async () => {
1318
+ var fs = require('fs').promises
1319
+ var test_id = 'test-callback-' + Math.random().toString(36).slice(2)
1320
+ var db_folder = __dirname + '/' + test_id + '-db'
1321
+ var meta_folder = __dirname + '/' + test_id + '-meta'
1322
+
1323
+ try {
1324
+ // Pre-create files that will trigger callback during init
1325
+ await fs.mkdir(db_folder, { recursive: true })
1326
+ await fs.mkdir(meta_folder, { recursive: true })
1327
+
1328
+ // Write a file that exists before init
1329
+ await fs.writeFile(db_folder + '/pre-existing', 'old content')
1330
+
1331
+ // Create metadata for it with old timestamp to trigger callback
1332
+ await fs.writeFile(meta_folder + '/!pre-existing', JSON.stringify({
1333
+ canonical_path: '/pre-existing',
1334
+ event: 'old-version',
1335
+ last_seen: Date.now() - 10000,
1336
+ mtime_ns: '1000000000000000'
1337
+ }))
1338
+
1339
+ // Wait for files to be written
1340
+ await new Promise(resolve => setTimeout(resolve, 100))
1341
+
1342
+ var callback_error = null
1343
+ var callback_called = false
1344
+ var db_was_null = false
1345
+
1346
+ // Monkey-patch url_file_db.create to intercept callback
1347
+ var url_file_db_module = require('url-file-db').url_file_db
1348
+ var original_create = url_file_db_module.create
1349
+
1350
+ url_file_db_module.create = async function(db_dir, meta_dir, callback) {
1351
+ var wrapped_callback = async function(db, key) {
1352
+ callback_called = true
1353
+ // Check if bb.db is null during callback
1354
+ if (!bb.db) {
1355
+ db_was_null = true
1356
+ }
1357
+ try {
1358
+ await callback(db, key)
1359
+ } catch (e) {
1360
+ callback_error = e.message
1361
+ }
1362
+ }
1363
+ return await original_create.call(this, db_dir, meta_dir, wrapped_callback)
1364
+ }
1365
+
1366
+ var bb = braid_blob.create_braid_blob()
1367
+ bb.db_folder = db_folder
1368
+ bb.meta_folder = meta_folder
1369
+
1370
+ // Init will trigger callback for pre-existing file
1371
+ // Callback tries to use braid_blob.db.read() but db not assigned yet
1372
+ await bb.init()
1373
+
1374
+ // Restore
1375
+ url_file_db_module.create = original_create
1376
+
1377
+ // Clean up
1378
+ await fs.rm(db_folder, { recursive: true, force: true })
1379
+ await fs.rm(meta_folder, { recursive: true, force: true })
1380
+
1381
+ if (!callback_called) {
1382
+ res.end('callback was not called')
1383
+ } else if (callback_error) {
1384
+ res.end('callback error: ' + callback_error)
1385
+ } else {
1386
+ // Success: callback worked even if bb.db was null (using db param)
1387
+ res.end('true')
1388
+ }
1389
+ } catch (e) {
1390
+ await fs.rm(db_folder, { recursive: true, force: true }).catch(() => {})
1391
+ await fs.rm(meta_folder, { recursive: true, force: true }).catch(() => {})
1392
+ res.end('error: ' + e.message)
1393
+ }
1394
+ })()`
1395
+ })
1396
+ return await r1.text()
1397
+ },
1398
+ 'true'
1399
+ )
1400
+
1145
1401
  }
1146
1402
 
1147
1403
  // Export for Node.js (CommonJS)
@@ -1,20 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(node test/test.js:*)",
5
- "Bash(lsof:*)",
6
- "Bash(xargs kill -9)",
7
- "Bash(curl:*)",
8
- "Bash(cat:*)",
9
- "Bash(node test.js:*)",
10
- "Bash(timeout 5 node:*)",
11
- "Bash(git checkout:*)",
12
- "Bash(npm show:*)",
13
- "Bash(npm install:*)",
14
- "Bash(node:*)",
15
- "Bash(git add:*)"
16
- ],
17
- "deny": [],
18
- "ask": []
19
- }
20
- }