braid-blob 0.0.40 → 0.0.42

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/AI-README.md CHANGED
@@ -54,11 +54,6 @@ DEPENDENCY_UPDATES:
54
54
  - Format: "updates to {package}@{version}"
55
55
  - Example: "0.0.18 - ... updates to url-file-db 0.0.8"
56
56
 
57
- RESOLVED_ISSUES:
58
- - url-file-db < 0.0.13: Bug where reading non-existent files returned "index" file contents
59
- - Fixed in url-file-db 0.0.13+ (properly returns null for non-existent files)
60
- - Caused "test sync local to remote" to fail with unexpected "shared content"
61
- - url-file-db 0.0.15+ also relaxed path requirements (no longer requires leading "/")
62
57
  ```
63
58
 
64
59
  ## MODULE_STRUCTURE
@@ -67,7 +62,7 @@ RESOLVED_ISSUES:
67
62
  EXPORT: create_braid_blob() -> braid_blob_instance
68
63
  MODULE_TYPE: CommonJS
69
64
  MAIN_ENTRY: index.js
70
- DEPENDENCIES: [braid-http, url-file-db, fs, path]
65
+ DEPENDENCIES: [braid-http, fs]
71
66
  ```
72
67
 
73
68
  ## DATA_MODEL
@@ -81,9 +76,9 @@ braid_blob_instance = {
81
76
 
82
77
  // Runtime state
83
78
  cache: object // internal cache
79
+ meta_cache: object // metadata cache
84
80
  key_to_subs: Map<key, Map<peer, subscription>> // subscription tracking
85
- db: url_file_db_instance // blob storage backend
86
- meta_db: url_file_db_instance // metadata storage backend
81
+ db: {read, write, delete} // blob storage backend (auto-created or custom)
87
82
 
88
83
  // Methods
89
84
  init: async () -> void
@@ -130,6 +125,7 @@ INPUT:
130
125
  content_type?: string // MIME type
131
126
  peer?: string // peer identifier
132
127
  skip_write?: boolean // skip disk write (for external changes)
128
+ db?: {read, write, delete} // custom db backend (overrides braid_blob.db)
133
129
  signal?: AbortSignal // for URL mode
134
130
  headers?: object // for URL mode
135
131
  }
@@ -137,8 +133,8 @@ INPUT:
137
133
  OUTPUT: version_string
138
134
 
139
135
  SIDE_EFFECTS:
140
- - Writes blob to db_folder via url-file-db
141
- - Writes metadata to meta_folder/db
136
+ - Writes blob to db (options.db or braid_blob.db)
137
+ - Writes metadata to meta_folder
142
138
  - Notifies active subscriptions (except originating peer)
143
139
  - If key instanceof URL: makes remote HTTP PUT via braid_fetch
144
140
 
@@ -163,6 +159,7 @@ INPUT:
163
159
  parents?: [version] // fork-point for subscriptions
164
160
  version?: [version] // request specific version
165
161
  peer?: string // peer identifier
162
+ db?: {read, write, delete} // custom db backend (overrides braid_blob.db)
166
163
  signal?: AbortSignal // for URL mode
167
164
  dont_retry?: boolean // for URL mode subscriptions
168
165
  }
@@ -294,14 +291,11 @@ isAcceptable(contentType, acceptHeader) -> boolean
294
291
 
295
292
  ```
296
293
  db_folder/
297
- {url_file_db structure}
298
- - Blob data stored via url-file-db
299
- - Key mapping: URL-safe encoding of keys
294
+ {encoded_key} # Blob data files
295
+ - Key encoding: encode_filename() escapes special chars
300
296
 
301
297
  meta_folder/
302
- peer.txt # Peer ID (auto-generated if missing)
303
- db/ # url-file-db for metadata
304
- {encoded_key}.txt # JSON: {event: version, content_type: mime}
298
+ {encoded_key} # JSON metadata files: {event: version, content_type: mime}
305
299
  ```
306
300
 
307
301
  ## PROTOCOL_DETAILS
@@ -344,8 +338,8 @@ BRAID_UPDATE_FORMAT:
344
338
  INITIALIZATION:
345
339
  - init() called lazily by put/get/serve
346
340
  - init() runs once (subsequent calls return same promise)
347
- - Creates db and meta_db url-file-db instances
348
- - Loads or generates peer ID
341
+ - Creates db object with read/write/delete methods (or uses provided db_folder object)
342
+ - Generates peer ID if not set
349
343
 
350
344
  SUBSCRIPTION_MANAGEMENT:
351
345
  - key_to_subs: Map<string, Map<string, {sendUpdate}>>
@@ -354,11 +348,6 @@ SUBSCRIPTION_MANAGEMENT:
354
348
  - Prevents echo: put() doesn't notify originating peer
355
349
  - Serialized updates: subscribe_chain ensures sequential callback execution
356
350
 
357
- FILE_WATCHING:
358
- - url-file-db monitors db_folder for external changes
359
- - External changes trigger put() with skip_write: true
360
- - Subscriptions notified of external changes
361
-
362
351
  CONCURRENCY_CONTROL:
363
352
  - within_fiber(key, fn) serializes operations per key
364
353
  - Uses promise chain stored in within_fiber.chains[key]
@@ -413,14 +402,6 @@ braid-http:
413
402
  - http_server (braidify): Adds Braid protocol support to Node.js HTTP
414
403
  - fetch (braid_fetch): Braid-aware fetch implementation
415
404
  - Handles: Subscribe headers, Version headers, streaming updates
416
-
417
- url-file-db (^0.0.15):
418
- - Bidirectional URL ↔ filesystem mapping
419
- - Collision-resistant encoding (case-insensitive filesystem safe)
420
- - File watching for external changes
421
- - Separate instances for blobs (db) and metadata (meta_db)
422
- - API change in 0.0.15: use get_canonical_path() instead of url_path_to_canonical_path()
423
- - Fixed in 0.0.13+: properly returns null for non-existent files (not "index" content)
424
405
  ```
425
406
 
426
407
  ## ERROR_CONDITIONS
@@ -455,7 +436,7 @@ TEST_RUNNER: test/test.js
455
436
  - Browser mode: Opens puppeteer, loads test.html
456
437
 
457
438
  TEST_SUITE: test/tests.js
458
- - 40+ test cases covering:
439
+ - 50+ test cases covering:
459
440
  - Basic put/get operations
460
441
  - Subscriptions and updates
461
442
  - Version conflict resolution
package/index.js CHANGED
@@ -11,6 +11,8 @@ function create_braid_blob() {
11
11
  db: null // object with read/write/delete methods
12
12
  }
13
13
 
14
+ var temp_folder = null // will be set in init
15
+
14
16
  braid_blob.init = async () => {
15
17
  // We only want to initialize once
16
18
  var init_p = real_init()
@@ -21,6 +23,15 @@ function create_braid_blob() {
21
23
  // Ensure our meta folder exists
22
24
  await require('fs').promises.mkdir(braid_blob.meta_folder, { recursive: true })
23
25
 
26
+ // Create a temp folder inside the meta folder for writing temp files,
27
+ // for atomic writing.
28
+ // The temp folder is called "temp",
29
+ // And this is guaranteed not to conflict with any other files,
30
+ // because other files are the result of encode_filename,
31
+ // which always ends with a ".XX" (for handling insensitive filesystems)
32
+ temp_folder = `${braid_blob.meta_folder}/temp`
33
+ await require('fs').promises.mkdir(temp_folder, { recursive: true })
34
+
24
35
  // Set up db - either use provided object or create file-based storage
25
36
  if (typeof braid_blob.db_folder === 'string') {
26
37
  await require('fs').promises.mkdir(braid_blob.db_folder, { recursive: true })
@@ -36,7 +47,7 @@ function create_braid_blob() {
36
47
  },
37
48
  write: async (key, data) => {
38
49
  var file_path = `${braid_blob.db_folder}/${encode_filename(key)}`
39
- await require('fs').promises.writeFile(file_path, data)
50
+ await atomic_write(file_path, data, temp_folder)
40
51
  },
41
52
  delete: async (key) => {
42
53
  var file_path = `${braid_blob.db_folder}/${encode_filename(key)}`
@@ -74,9 +85,8 @@ function create_braid_blob() {
74
85
  }
75
86
 
76
87
  async function save_meta(key) {
77
- await require('fs').promises.writeFile(
78
- `${braid_blob.meta_folder}/${encode_filename(key)}`,
79
- JSON.stringify(braid_blob.meta_cache[key]))
88
+ await atomic_write(`${braid_blob.meta_folder}/${encode_filename(key)}`,
89
+ JSON.stringify(braid_blob.meta_cache[key]), temp_folder)
80
90
  }
81
91
 
82
92
  async function delete_meta(key) {
@@ -126,7 +136,7 @@ function create_braid_blob() {
126
136
  meta.event = their_e
127
137
 
128
138
  if (!options.skip_write)
129
- await braid_blob.db.write(key, body)
139
+ await (options.db || braid_blob.db).write(key, body)
130
140
  if (options.signal?.aborted) return
131
141
 
132
142
  if (options.content_type)
@@ -254,12 +264,12 @@ function create_braid_blob() {
254
264
  // Send an immediate update if needed
255
265
  if (compare_events(result.version?.[0], options.parents?.[0]) > 0) {
256
266
  result.sent = true
257
- result.body = await braid_blob.db.read(key)
267
+ result.body = await (options.db || braid_blob.db).read(key)
258
268
  options.my_subscribe(result)
259
269
  }
260
270
  } else {
261
271
  // If not subscribe, send the body now
262
- result.body = await braid_blob.db.read(key)
272
+ result.body = await (options.db || braid_blob.db).read(key)
263
273
  }
264
274
 
265
275
  return result
@@ -294,7 +304,7 @@ function create_braid_blob() {
294
304
  var meta = await get_meta(key)
295
305
  if (options.signal?.aborted) return
296
306
 
297
- await braid_blob.db.delete(key)
307
+ await (options.db || braid_blob.db).delete(key)
298
308
  await delete_meta(key)
299
309
 
300
310
  // Notify all subscriptions of the delete
@@ -733,6 +743,12 @@ function create_braid_blob() {
733
743
  return normalized
734
744
  }
735
745
 
746
+ async function atomic_write(final_destination, data, temp_folder) {
747
+ var temp = `${temp_folder}/${Math.random().toString(36).slice(2)}`
748
+ await require('fs').promises.writeFile(temp, data)
749
+ await require('fs').promises.rename(temp, final_destination)
750
+ }
751
+
736
752
  braid_blob.create_braid_blob = create_braid_blob
737
753
 
738
754
  return braid_blob
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-blob",
3
- "version": "0.0.40",
3
+ "version": "0.0.42",
4
4
  "description": "Library for collaborative blobs over http using braid.",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braid-blob",
package/test/tests.js CHANGED
@@ -1635,6 +1635,376 @@ runTest(
1635
1635
  'stopped'
1636
1636
  )
1637
1637
 
1638
+ runTest(
1639
+ "test options.db in put writes to custom db",
1640
+ async () => {
1641
+ var r1 = await braid_fetch(`/eval`, {
1642
+ method: 'POST',
1643
+ body: `void (async () => {
1644
+ var test_key = '/test-custom-db-put-' + Math.random().toString(36).slice(2)
1645
+
1646
+ // Create a simple in-memory db
1647
+ var custom_storage = {}
1648
+ var custom_db = {
1649
+ read: async (key) => custom_storage[key] || null,
1650
+ write: async (key, data) => { custom_storage[key] = data },
1651
+ delete: async (key) => { delete custom_storage[key] }
1652
+ }
1653
+
1654
+ // Put using the custom db
1655
+ await braid_blob.put(test_key, Buffer.from('custom db content'), {
1656
+ version: ['100'],
1657
+ db: custom_db
1658
+ })
1659
+
1660
+ // Verify content is in custom db
1661
+ var custom_content = await custom_db.read(test_key)
1662
+ var custom_ok = custom_content && custom_content.toString() === 'custom db content'
1663
+
1664
+ // Verify content is NOT in the default db
1665
+ var default_content = await braid_blob.db.read(test_key)
1666
+ var default_empty = default_content === null
1667
+
1668
+ res.end(custom_ok && default_empty ? 'true' :
1669
+ 'custom_ok=' + custom_ok + ', default_empty=' + default_empty)
1670
+ })()`
1671
+ })
1672
+ return await r1.text()
1673
+ },
1674
+ 'true'
1675
+ )
1676
+
1677
+ runTest(
1678
+ "test options.db in get reads from custom db",
1679
+ async () => {
1680
+ var r1 = await braid_fetch(`/eval`, {
1681
+ method: 'POST',
1682
+ body: `void (async () => {
1683
+ var test_key = '/test-custom-db-get-' + Math.random().toString(36).slice(2)
1684
+
1685
+ // Create a simple in-memory db with some content
1686
+ var custom_storage = {}
1687
+ custom_storage[test_key] = Buffer.from('from custom db')
1688
+ var custom_db = {
1689
+ read: async (key) => custom_storage[key] || null,
1690
+ write: async (key, data) => { custom_storage[key] = data },
1691
+ delete: async (key) => { delete custom_storage[key] }
1692
+ }
1693
+
1694
+ // Put with skip_write to just create meta
1695
+ await braid_blob.put(test_key, Buffer.from('ignored'), {
1696
+ version: ['200'],
1697
+ skip_write: true
1698
+ })
1699
+
1700
+ // Get using the custom db - should read from custom db
1701
+ var result = await braid_blob.get(test_key, { db: custom_db })
1702
+
1703
+ res.end(result && result.body.toString() === 'from custom db' ? 'true' :
1704
+ 'got: ' + (result ? result.body.toString() : 'null'))
1705
+ })()`
1706
+ })
1707
+ return await r1.text()
1708
+ },
1709
+ 'true'
1710
+ )
1711
+
1712
+ runTest(
1713
+ "test options.db in delete deletes from custom db",
1714
+ async () => {
1715
+ var r1 = await braid_fetch(`/eval`, {
1716
+ method: 'POST',
1717
+ body: `void (async () => {
1718
+ var test_key = '/test-custom-db-delete-' + Math.random().toString(36).slice(2)
1719
+
1720
+ // Create a simple in-memory db
1721
+ var custom_storage = {}
1722
+ custom_storage[test_key] = Buffer.from('custom content')
1723
+ var custom_db = {
1724
+ read: async (key) => custom_storage[key] || null,
1725
+ write: async (key, data) => { custom_storage[key] = data },
1726
+ delete: async (key) => { delete custom_storage[key] }
1727
+ }
1728
+
1729
+ // Also put to default db
1730
+ await braid_blob.put(test_key, Buffer.from('default content'), {
1731
+ version: ['300']
1732
+ })
1733
+
1734
+ // Delete using custom db - should only delete from custom db
1735
+ await braid_blob.delete(test_key, { db: custom_db })
1736
+
1737
+ // Verify custom db content is gone
1738
+ var custom_content = await custom_db.read(test_key)
1739
+ var custom_deleted = custom_content === null
1740
+
1741
+ // Verify default db content still exists
1742
+ var default_content = await braid_blob.db.read(test_key)
1743
+ var default_exists = default_content && default_content.toString() === 'default content'
1744
+
1745
+ res.end(custom_deleted && default_exists ? 'true' :
1746
+ 'custom_deleted=' + custom_deleted + ', default_exists=' + default_exists)
1747
+ })()`
1748
+ })
1749
+ return await r1.text()
1750
+ },
1751
+ 'true'
1752
+ )
1753
+
1754
+ runTest(
1755
+ "test options.db in get subscribe uses custom db for initial update",
1756
+ async () => {
1757
+ var r1 = await braid_fetch(`/eval`, {
1758
+ method: 'POST',
1759
+ body: `void (async () => {
1760
+ var test_key = '/test-custom-db-sub-' + Math.random().toString(36).slice(2)
1761
+
1762
+ // Create a simple in-memory db with content
1763
+ var custom_storage = {}
1764
+ custom_storage[test_key] = Buffer.from('subscribe custom content')
1765
+ var custom_db = {
1766
+ read: async (key) => custom_storage[key] || null,
1767
+ write: async (key, data) => { custom_storage[key] = data },
1768
+ delete: async (key) => { delete custom_storage[key] }
1769
+ }
1770
+
1771
+ // Create meta with version using skip_write
1772
+ await braid_blob.put(test_key, Buffer.from('ignored'), {
1773
+ version: ['400'],
1774
+ skip_write: true
1775
+ })
1776
+
1777
+ // Subscribe using custom db - initial update should come from custom db
1778
+ var ac = new AbortController()
1779
+ var received_content = null
1780
+
1781
+ await braid_blob.get(test_key, {
1782
+ db: custom_db,
1783
+ signal: ac.signal,
1784
+ subscribe: (update) => {
1785
+ received_content = update.body.toString()
1786
+ }
1787
+ })
1788
+
1789
+ // Wait for update
1790
+ await new Promise(done => setTimeout(done, 50))
1791
+ ac.abort()
1792
+
1793
+ res.end(received_content === 'subscribe custom content' ? 'true' :
1794
+ 'got: ' + received_content)
1795
+ })()`
1796
+ })
1797
+ return await r1.text()
1798
+ },
1799
+ 'true'
1800
+ )
1801
+
1802
+ runTest(
1803
+ "test atomic write creates temp folder on init",
1804
+ async () => {
1805
+ var r1 = await braid_fetch(`/eval`, {
1806
+ method: 'POST',
1807
+ body: `void (async () => {
1808
+ var fs = require('fs').promises
1809
+ var test_id = 'test-atomic-init-' + Math.random().toString(36).slice(2)
1810
+ var db_folder = __dirname + '/' + test_id + '-db'
1811
+ var meta_folder = __dirname + '/' + test_id + '-meta'
1812
+
1813
+ try {
1814
+ var bb = braid_blob.create_braid_blob()
1815
+ bb.db_folder = db_folder
1816
+ bb.meta_folder = meta_folder
1817
+
1818
+ // Initialize
1819
+ await bb.init()
1820
+
1821
+ // Check that temp folder exists inside meta folder
1822
+ var temp_folder = meta_folder + '/temp'
1823
+ var stat = await fs.stat(temp_folder)
1824
+ var is_dir = stat.isDirectory()
1825
+
1826
+ res.end(is_dir ? 'true' : 'not a directory')
1827
+ } catch (e) {
1828
+ res.end('error: ' + e.message)
1829
+ } finally {
1830
+ await fs.rm(db_folder, { recursive: true, force: true })
1831
+ await fs.rm(meta_folder, { recursive: true, force: true })
1832
+ }
1833
+ })()`
1834
+ })
1835
+ return await r1.text()
1836
+ },
1837
+ 'true'
1838
+ )
1839
+
1840
+ runTest(
1841
+ "test atomic write leaves no temp files after successful write",
1842
+ async () => {
1843
+ var r1 = await braid_fetch(`/eval`, {
1844
+ method: 'POST',
1845
+ body: `void (async () => {
1846
+ var fs = require('fs').promises
1847
+ var test_id = 'test-atomic-cleanup-' + Math.random().toString(36).slice(2)
1848
+ var db_folder = __dirname + '/' + test_id + '-db'
1849
+ var meta_folder = __dirname + '/' + test_id + '-meta'
1850
+
1851
+ try {
1852
+ var bb = braid_blob.create_braid_blob()
1853
+ bb.db_folder = db_folder
1854
+ bb.meta_folder = meta_folder
1855
+
1856
+ // Do a write
1857
+ await bb.put('/test-file', Buffer.from('hello'), { version: ['1'] })
1858
+
1859
+ // Check that temp folder is empty (no leftover temp files)
1860
+ var temp_folder = meta_folder + '/temp'
1861
+ var files = await fs.readdir(temp_folder)
1862
+
1863
+ res.end(files.length === 0 ? 'true' : 'leftover files: ' + files.join(', '))
1864
+ } catch (e) {
1865
+ res.end('error: ' + e.message)
1866
+ } finally {
1867
+ await fs.rm(db_folder, { recursive: true, force: true })
1868
+ await fs.rm(meta_folder, { recursive: true, force: true })
1869
+ }
1870
+ })()`
1871
+ })
1872
+ return await r1.text()
1873
+ },
1874
+ 'true'
1875
+ )
1876
+
1877
+ runTest(
1878
+ "test atomic write data file integrity",
1879
+ async () => {
1880
+ var r1 = await braid_fetch(`/eval`, {
1881
+ method: 'POST',
1882
+ body: `void (async () => {
1883
+ var fs = require('fs').promises
1884
+ var test_id = 'test-atomic-integrity-' + Math.random().toString(36).slice(2)
1885
+ var db_folder = __dirname + '/' + test_id + '-db'
1886
+ var meta_folder = __dirname + '/' + test_id + '-meta'
1887
+
1888
+ try {
1889
+ var bb = braid_blob.create_braid_blob()
1890
+ bb.db_folder = db_folder
1891
+ bb.meta_folder = meta_folder
1892
+
1893
+ // Write initial content
1894
+ await bb.put('/test-file', Buffer.from('initial content'), { version: ['1'] })
1895
+
1896
+ // Verify we can read it back correctly
1897
+ var result = await bb.get('/test-file')
1898
+ var content = result.body.toString()
1899
+
1900
+ res.end(content === 'initial content' ? 'true' : 'wrong content: ' + content)
1901
+ } catch (e) {
1902
+ res.end('error: ' + e.message)
1903
+ } finally {
1904
+ await fs.rm(db_folder, { recursive: true, force: true })
1905
+ await fs.rm(meta_folder, { recursive: true, force: true })
1906
+ }
1907
+ })()`
1908
+ })
1909
+ return await r1.text()
1910
+ },
1911
+ 'true'
1912
+ )
1913
+
1914
+ runTest(
1915
+ "test atomic write - multiple rapid writes preserve last value",
1916
+ async () => {
1917
+ var r1 = await braid_fetch(`/eval`, {
1918
+ method: 'POST',
1919
+ body: `void (async () => {
1920
+ var fs = require('fs').promises
1921
+ var test_id = 'test-atomic-rapid-' + Math.random().toString(36).slice(2)
1922
+ var db_folder = __dirname + '/' + test_id + '-db'
1923
+ var meta_folder = __dirname + '/' + test_id + '-meta'
1924
+
1925
+ try {
1926
+ var bb = braid_blob.create_braid_blob()
1927
+ bb.db_folder = db_folder
1928
+ bb.meta_folder = meta_folder
1929
+
1930
+ // Do multiple rapid writes
1931
+ await bb.put('/test-file', Buffer.from('write1'), { version: ['1'] })
1932
+ await bb.put('/test-file', Buffer.from('write2'), { version: ['2'] })
1933
+ await bb.put('/test-file', Buffer.from('write3'), { version: ['3'] })
1934
+
1935
+ // Verify last write won
1936
+ var result = await bb.get('/test-file')
1937
+ var content = result.body.toString()
1938
+ var version = result.version[0]
1939
+
1940
+ // Also verify temp folder is clean
1941
+ var temp_folder = meta_folder + '/temp'
1942
+ var files = await fs.readdir(temp_folder)
1943
+
1944
+ res.end(content === 'write3' && version === '3' && files.length === 0 ? 'true' :
1945
+ 'content=' + content + ', version=' + version + ', temp_files=' + files.length)
1946
+ } catch (e) {
1947
+ res.end('error: ' + e.message)
1948
+ } finally {
1949
+ await fs.rm(db_folder, { recursive: true, force: true })
1950
+ await fs.rm(meta_folder, { recursive: true, force: true })
1951
+ }
1952
+ })()`
1953
+ })
1954
+ return await r1.text()
1955
+ },
1956
+ 'true'
1957
+ )
1958
+
1959
+ runTest(
1960
+ "test atomic write - meta file is also written atomically",
1961
+ async () => {
1962
+ var r1 = await braid_fetch(`/eval`, {
1963
+ method: 'POST',
1964
+ body: `void (async () => {
1965
+ var fs = require('fs').promises
1966
+ var test_id = 'test-atomic-meta-' + Math.random().toString(36).slice(2)
1967
+ var db_folder = __dirname + '/' + test_id + '-db'
1968
+ var meta_folder = __dirname + '/' + test_id + '-meta'
1969
+
1970
+ try {
1971
+ var bb = braid_blob.create_braid_blob()
1972
+ bb.db_folder = db_folder
1973
+ bb.meta_folder = meta_folder
1974
+
1975
+ // Write with content_type to test meta file
1976
+ await bb.put('/test-file', Buffer.from('content'), {
1977
+ version: ['test-version'],
1978
+ content_type: 'text/plain'
1979
+ })
1980
+
1981
+ // Create new instance to read from disk (not cache)
1982
+ var bb2 = braid_blob.create_braid_blob()
1983
+ bb2.db_folder = db_folder
1984
+ bb2.meta_folder = meta_folder
1985
+
1986
+ var result = await bb2.get('/test-file')
1987
+
1988
+ // Verify both version and content_type are correctly persisted
1989
+ var version_ok = result.version[0] === 'test-version'
1990
+ var ct_ok = result.content_type === 'text/plain'
1991
+
1992
+ res.end(version_ok && ct_ok ? 'true' :
1993
+ 'version_ok=' + version_ok + ', ct_ok=' + ct_ok +
1994
+ ', version=' + result.version[0] + ', ct=' + result.content_type)
1995
+ } catch (e) {
1996
+ res.end('error: ' + e.message)
1997
+ } finally {
1998
+ await fs.rm(db_folder, { recursive: true, force: true })
1999
+ await fs.rm(meta_folder, { recursive: true, force: true })
2000
+ }
2001
+ })()`
2002
+ })
2003
+ return await r1.text()
2004
+ },
2005
+ 'true'
2006
+ )
2007
+
1638
2008
  }
1639
2009
 
1640
2010
  // Export for Node.js (CommonJS)