braid-blob 0.0.41 → 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/index.js +20 -4
- package/package.json +1 -1
- package/test/tests.js +206 -0
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
|
|
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
|
|
78
|
-
|
|
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) {
|
|
@@ -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
package/test/tests.js
CHANGED
|
@@ -1799,6 +1799,212 @@ runTest(
|
|
|
1799
1799
|
'true'
|
|
1800
1800
|
)
|
|
1801
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
|
+
|
|
1802
2008
|
}
|
|
1803
2009
|
|
|
1804
2010
|
// Export for Node.js (CommonJS)
|