braid-blob 0.0.15 → 0.0.17
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/.claude/settings.local.json +9 -0
- package/LICENSE.txt +11 -0
- package/README.md +88 -1
- package/blob.png +0 -0
- package/index.js +198 -99
- package/package.json +7 -2
- package/plop.png +0 -0
- package/server-demo.js +4 -3
- package/test/test.html +15 -662
- package/test/tests.js +701 -0
- package/test/server.js +0 -43
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Invisible Property License
|
|
2
|
+
|
|
3
|
+
1. You have the right to use this IP for any purpose.
|
|
4
|
+
2. If you make profit, you agree to give back a fair share of the profit to
|
|
5
|
+
the creators of this IP.
|
|
6
|
+
3. The creators will tell you how much they think is a fair share, if your
|
|
7
|
+
usage matters to them, and promise not to take you to court to enforce
|
|
8
|
+
the agreement.
|
|
9
|
+
|
|
10
|
+
(In other words, this license thus runs on the honor system. You are invited
|
|
11
|
+
to participate in our community with honor.)
|
package/README.md
CHANGED
|
@@ -1,5 +1,92 @@
|
|
|
1
1
|
# braid-blob
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
A simple, self-contained library for synchronizing binary blobs (files, images, etc.) over HTTP using [Braid-HTTP](https://braid.org). It provides real-time synchronization with last-write-wins (LWW) conflict resolution and persistent storage.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install braid-blob
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Basic Server
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
var braid_blob = require('braid-blob')
|
|
17
|
+
|
|
18
|
+
require('http').createServer((req, res) => {
|
|
19
|
+
braid_blob.serve(req, res)
|
|
20
|
+
}).listen(8888)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
That's it! You now have a blob synchronization server.
|
|
24
|
+
|
|
25
|
+
### Usage Examples
|
|
26
|
+
|
|
27
|
+
First let's upload a file:
|
|
28
|
+
```bash
|
|
29
|
+
curl -X PUT -H "Content-Type: image/png" -T blob.png http://localhost:8888/image.png
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
View image in browser at http://localhost:8888/image.png
|
|
33
|
+
|
|
34
|
+
To see updates, let's do a textual example for easy viewing:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
curl -X PUT -H "Content-Type: text/plain" -d "hello" http://localhost:8888/text
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Next, subscribe for updates:
|
|
41
|
+
```
|
|
42
|
+
curl -H "Subscribe: true" http://localhost:8888/text
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Now, in another terminal, write over the file:
|
|
46
|
+
```bash
|
|
47
|
+
curl -X PUT -H "Content-Type: text/plain" -d "world" http://localhost:8888/text
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Should see activity in the first terminal showing the update.
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
# Delete a file
|
|
54
|
+
curl -X DELETE http://localhost:8888/text
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## API
|
|
58
|
+
|
|
59
|
+
### Configuration
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
var braid_blob = require('braid-blob')
|
|
63
|
+
|
|
64
|
+
// Set custom blob storage location (default: './braid-blob-db')
|
|
65
|
+
// This uses url-file-db for efficient URL-to-file mapping
|
|
66
|
+
braid_blob.db_folder = './custom_files_folder'
|
|
67
|
+
|
|
68
|
+
// Set custom metadata storage location (default: './braid-blob-meta')
|
|
69
|
+
// Stores version metadata and peer information
|
|
70
|
+
braid_blob.meta_folder = './custom_meta_folder'
|
|
71
|
+
|
|
72
|
+
// Set custom peer ID (default: auto-generated and persisted)
|
|
73
|
+
braid_blob.peer = 'my-server-id'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `braid_blob.serve(req, res, options)`
|
|
77
|
+
|
|
78
|
+
Handles HTTP requests for blob storage and synchronization.
|
|
79
|
+
|
|
80
|
+
**Parameters:**
|
|
81
|
+
- `req` - HTTP request object
|
|
82
|
+
- `res` - HTTP response object
|
|
83
|
+
- `options` - Optional configuration object
|
|
84
|
+
- `key` - Override the resource key (default: URL path)
|
|
85
|
+
|
|
86
|
+
**Supported HTTP Methods:**
|
|
87
|
+
- `GET` - Retrieve a blob (with optional `Subscribe: true` header)
|
|
88
|
+
- `PUT` - Store/update a blob
|
|
89
|
+
- `DELETE` - Remove a blob
|
|
3
90
|
|
|
4
91
|
## Testing
|
|
5
92
|
|
package/blob.png
ADDED
|
Binary file
|
package/index.js
CHANGED
|
@@ -1,35 +1,165 @@
|
|
|
1
1
|
var {http_server: braidify, free_cors} = require('braid-http'),
|
|
2
|
+
{url_file_db} = require('url-file-db'),
|
|
2
3
|
fs = require('fs'),
|
|
3
4
|
path = require('path')
|
|
4
5
|
|
|
5
6
|
function create_braid_blob() {
|
|
6
7
|
var braid_blob = {
|
|
7
8
|
db_folder: './braid-blob-db',
|
|
9
|
+
meta_folder: './braid-blob-meta',
|
|
8
10
|
cache: {},
|
|
9
11
|
key_to_subs: {},
|
|
10
|
-
peer: null // we'll try to load this from a file, if not set by the user
|
|
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
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
braid_blob.init = async () => {
|
|
14
|
-
|
|
17
|
+
// We only want to initialize once
|
|
18
|
+
var init_p = real_init()
|
|
19
|
+
braid_blob.init = () => init_p
|
|
20
|
+
await braid_blob.init()
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
async function real_init() {
|
|
23
|
+
// Create url-file-db instance for blob storage
|
|
24
|
+
braid_blob.db = await url_file_db.create(braid_blob.db_folder, async (key) => {
|
|
25
|
+
// File changed externally, notify subscriptions
|
|
26
|
+
var body = await braid_blob.db.read(key)
|
|
27
|
+
await braid_blob.put(key, body, { skip_write: true })
|
|
28
|
+
})
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
// Create meta folder
|
|
31
|
+
await fs.promises.mkdir(braid_blob.meta_folder, { recursive: true })
|
|
32
|
+
|
|
33
|
+
// establish a peer id
|
|
34
|
+
if (!braid_blob.peer)
|
|
35
|
+
try {
|
|
36
|
+
braid_blob.peer = await fs.promises.readFile(`${braid_blob.meta_folder}/peer.txt`, 'utf8')
|
|
37
|
+
} catch (e) {}
|
|
38
|
+
if (!braid_blob.peer)
|
|
39
|
+
braid_blob.peer = Math.random().toString(36).slice(2)
|
|
40
|
+
await fs.promises.writeFile(`${braid_blob.meta_folder}/peer.txt`, braid_blob.peer)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
braid_blob.put = async (key, body, options = {}) => {
|
|
45
|
+
await braid_blob.init()
|
|
46
|
+
|
|
47
|
+
// Read the meta file
|
|
48
|
+
const metaname = `${braid_blob.meta_folder}/${encode_filename(key)}`
|
|
49
|
+
var meta = {}
|
|
50
|
+
try {
|
|
51
|
+
meta = JSON.parse(await fs.promises.readFile(metaname, 'utf8'))
|
|
52
|
+
} catch (e) {}
|
|
53
|
+
|
|
54
|
+
var their_e =
|
|
55
|
+
!options.version ?
|
|
56
|
+
// we'll give them a event id in this case
|
|
57
|
+
`${braid_blob.peer}-${Math.max(Date.now(),
|
|
58
|
+
meta.event ? 1*get_event_seq(meta.event) + 1 : -Infinity)}` :
|
|
59
|
+
!options.version.length ?
|
|
60
|
+
null :
|
|
61
|
+
options.version[0]
|
|
62
|
+
|
|
63
|
+
if (their_e != null &&
|
|
64
|
+
(meta.event == null ||
|
|
65
|
+
compare_events(their_e, meta.event) > 0)) {
|
|
66
|
+
meta.event = their_e
|
|
67
|
+
|
|
68
|
+
// Write the file using url-file-db (unless skip_write is set)
|
|
69
|
+
if (!options.skip_write)
|
|
70
|
+
await braid_blob.db.write(key, body)
|
|
71
|
+
|
|
72
|
+
// Write the meta file
|
|
73
|
+
if (options.content_type)
|
|
74
|
+
meta.content_type = options.content_type
|
|
75
|
+
|
|
76
|
+
await fs.promises.writeFile(metaname, JSON.stringify(meta))
|
|
77
|
+
|
|
78
|
+
// Notify all subscriptions of the update
|
|
79
|
+
// (except the peer which made the PUT request itself)
|
|
80
|
+
if (braid_blob.key_to_subs[key])
|
|
81
|
+
for (var [peer, sub] of braid_blob.key_to_subs[key].entries())
|
|
82
|
+
if (peer !== options.peer)
|
|
83
|
+
sub.sendUpdate({
|
|
84
|
+
version: [meta.event],
|
|
85
|
+
'Merge-Type': 'lww',
|
|
86
|
+
body
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return meta.event
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
braid_blob.get = async (key, options = {}) => {
|
|
94
|
+
await braid_blob.init()
|
|
95
|
+
|
|
96
|
+
// Read the meta file
|
|
97
|
+
const metaname = `${braid_blob.meta_folder}/${encode_filename(key)}`
|
|
98
|
+
var meta = {}
|
|
99
|
+
try {
|
|
100
|
+
meta = JSON.parse(await fs.promises.readFile(metaname, 'utf8'))
|
|
101
|
+
} catch (e) {}
|
|
102
|
+
if (meta.event == null) return null
|
|
103
|
+
|
|
104
|
+
var result = {
|
|
105
|
+
version: [meta.event],
|
|
106
|
+
content_type: meta.content_type
|
|
107
|
+
}
|
|
108
|
+
if (options.header_cb) await options.header_cb(result)
|
|
109
|
+
if (options.head) return
|
|
110
|
+
|
|
111
|
+
if (options.subscribe) {
|
|
112
|
+
var subscribe_chain = Promise.resolve()
|
|
113
|
+
options.my_subscribe = (x) => subscribe_chain =
|
|
114
|
+
subscribe_chain.then(() => options.subscribe(x))
|
|
115
|
+
|
|
116
|
+
// Start a subscription for future updates
|
|
117
|
+
if (!braid_blob.key_to_subs[key])
|
|
118
|
+
braid_blob.key_to_subs[key] = new Map()
|
|
119
|
+
|
|
120
|
+
var peer = options.peer || Math.random().toString(36).slice(2)
|
|
121
|
+
braid_blob.key_to_subs[key].set(peer, {
|
|
122
|
+
sendUpdate: (update) => {
|
|
123
|
+
options.my_subscribe({
|
|
124
|
+
body: update.body,
|
|
125
|
+
version: update.version,
|
|
126
|
+
content_type: meta.content_type
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// Store unsubscribe function
|
|
132
|
+
result.unsubscribe = () => {
|
|
133
|
+
braid_blob.key_to_subs[key].delete(peer)
|
|
134
|
+
if (!braid_blob.key_to_subs[key].size)
|
|
135
|
+
delete braid_blob.key_to_subs[key]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (options.before_send_cb) await options.before_send_cb(result)
|
|
139
|
+
|
|
140
|
+
// Send an immediate update if needed
|
|
141
|
+
if (!options.parents ||
|
|
142
|
+
!options.parents.length ||
|
|
143
|
+
compare_events(result.version[0], options.parents[0]) > 0) {
|
|
144
|
+
result.sent = true
|
|
145
|
+
options.my_subscribe({
|
|
146
|
+
body: await braid_blob.db.read(key),
|
|
147
|
+
version: result.version,
|
|
148
|
+
content_type: result.content_type
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
// If not subscribe, send the body now
|
|
153
|
+
result.body = await braid_blob.db.read(key)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return result
|
|
27
157
|
}
|
|
28
158
|
|
|
29
159
|
braid_blob.serve = async (req, res, options = {}) => {
|
|
30
160
|
await braid_blob.init()
|
|
31
161
|
|
|
32
|
-
if (!options.key) options.key =
|
|
162
|
+
if (!options.key) options.key = url_file_db.get_key(req.url)
|
|
33
163
|
|
|
34
164
|
braidify(req, res)
|
|
35
165
|
if (res.is_multiplexer) return
|
|
@@ -41,8 +171,7 @@ function create_braid_blob() {
|
|
|
41
171
|
var body = req.method === 'PUT' && await slurp(req)
|
|
42
172
|
|
|
43
173
|
await within_fiber(options.key, async () => {
|
|
44
|
-
const
|
|
45
|
-
const metaname = `${braid_blob.db_folder}/meta/${encode_filename(options.key)}`
|
|
174
|
+
const metaname = `${braid_blob.meta_folder}/${encode_filename(options.key)}`
|
|
46
175
|
|
|
47
176
|
// Read the meta file
|
|
48
177
|
var meta = {}
|
|
@@ -51,102 +180,62 @@ function create_braid_blob() {
|
|
|
51
180
|
} catch (e) {}
|
|
52
181
|
|
|
53
182
|
if (req.method === 'GET') {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
183
|
+
if (!res.hasHeader("editable")) res.setHeader("Editable", "true")
|
|
184
|
+
if (!req.subscribe) res.setHeader("Accept-Subscribe", "true")
|
|
185
|
+
res.setHeader("Merge-Type", "lww")
|
|
186
|
+
|
|
187
|
+
var result = await braid_blob.get(options.key, {
|
|
188
|
+
peer: req.peer,
|
|
189
|
+
head: req.method == "HEAD",
|
|
190
|
+
parents: req.parents || null,
|
|
191
|
+
header_cb: (result) => {
|
|
192
|
+
res.setHeader((req.subscribe ? "Current-" : "") +
|
|
193
|
+
"Version", ascii_ify(result.version.map((x) =>
|
|
194
|
+
JSON.stringify(x)).join(", ")))
|
|
195
|
+
if (result.content_type)
|
|
196
|
+
res.setHeader('Content-Type', result.content_type)
|
|
197
|
+
},
|
|
198
|
+
before_send_cb: (result) =>
|
|
199
|
+
res.startSubscription({ onClose: result.unsubscribe }),
|
|
200
|
+
subscribe: req.subscribe ? (update) => {
|
|
201
|
+
res.sendUpdate({
|
|
202
|
+
version: update.version,
|
|
203
|
+
'Merge-Type': 'lww',
|
|
204
|
+
body: update.body
|
|
205
|
+
})
|
|
206
|
+
} : null
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
if (!result) {
|
|
57
210
|
res.statusCode = 404
|
|
58
|
-
res.setHeader('Content-Type', 'text/plain')
|
|
59
211
|
return res.end('File Not Found')
|
|
60
212
|
}
|
|
61
213
|
|
|
62
|
-
if (
|
|
63
|
-
!isAcceptable(
|
|
214
|
+
if (result.content_type && req.headers.accept &&
|
|
215
|
+
!isAcceptable(result.content_type, req.headers.accept)) {
|
|
64
216
|
res.statusCode = 406
|
|
65
|
-
res.
|
|
66
|
-
return res.end(`Content-Type of ${meta.content_type} not in Accept: ${req.headers.accept}`)
|
|
217
|
+
return res.end(`Content-Type of ${result.content_type} not in Accept: ${req.headers.accept}`)
|
|
67
218
|
}
|
|
68
219
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (meta.content_type)
|
|
77
|
-
res.setHeader('Content-Type', meta.content_type)
|
|
78
|
-
|
|
79
|
-
if (!req.subscribe)
|
|
80
|
-
return res.end(await fs.promises.readFile(filename))
|
|
81
|
-
|
|
82
|
-
if (!res.hasHeader("editable"))
|
|
83
|
-
res.setHeader("Editable", "true")
|
|
84
|
-
|
|
85
|
-
// Start a subscription for future updates.
|
|
86
|
-
if (!braid_blob.key_to_subs[options.key])
|
|
87
|
-
braid_blob.key_to_subs[options.key] = new Map()
|
|
88
|
-
var peer = req.peer || Math.random().toString(36).slice(2)
|
|
89
|
-
braid_blob.key_to_subs[options.key].set(peer, res)
|
|
90
|
-
|
|
91
|
-
res.startSubscription({ onClose: () => {
|
|
92
|
-
braid_blob.key_to_subs[options.key].delete(peer)
|
|
93
|
-
if (!braid_blob.key_to_subs[options.key].size)
|
|
94
|
-
delete braid_blob.key_to_subs[options.key]
|
|
95
|
-
}})
|
|
96
|
-
|
|
97
|
-
// Send an immediate update when:
|
|
98
|
-
if (!req.parents || // 1) They want everything,
|
|
99
|
-
!req.parents.length || // 2) Or everything past the empty set,
|
|
100
|
-
compare_events(meta.event, req.parents[0]) > 0
|
|
101
|
-
// 3) Or what we have is newer
|
|
102
|
-
)
|
|
103
|
-
return res.sendUpdate({
|
|
104
|
-
version: [meta.event],
|
|
105
|
-
'Merge-Type': 'lww',
|
|
106
|
-
body: await fs.promises.readFile(filename)
|
|
107
|
-
})
|
|
108
|
-
else res.write('\n\n') // get the node http code to send headers
|
|
220
|
+
if (req.method == "HEAD") return res.end('')
|
|
221
|
+
else if (!req.subscribe) return res.end(result.body)
|
|
222
|
+
else {
|
|
223
|
+
// If no immediate update was sent,
|
|
224
|
+
// get the node http code to send headers
|
|
225
|
+
if (!result.sent) res.write('\n\n')
|
|
226
|
+
}
|
|
109
227
|
} else if (req.method === 'PUT') {
|
|
110
228
|
// Handle PUT request to update binary files
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
!req.version.length ?
|
|
118
|
-
null :
|
|
119
|
-
req.version[0]
|
|
120
|
-
|
|
121
|
-
if (their_e != null &&
|
|
122
|
-
(meta.event == null ||
|
|
123
|
-
compare_events(their_e, meta.event) > 0)) {
|
|
124
|
-
meta.event = their_e
|
|
125
|
-
|
|
126
|
-
// Write the file
|
|
127
|
-
await fs.promises.writeFile(filename, body)
|
|
128
|
-
|
|
129
|
-
// Write the meta file
|
|
130
|
-
if (req.headers['content-type'])
|
|
131
|
-
meta.content_type = req.headers['content-type']
|
|
132
|
-
await fs.promises.writeFile(metaname, JSON.stringify(meta))
|
|
133
|
-
|
|
134
|
-
// Notify all subscriptions of the update
|
|
135
|
-
// (except the peer which made the PUT request itself)
|
|
136
|
-
if (braid_blob.key_to_subs[options.key])
|
|
137
|
-
for (var [peer, sub] of braid_blob.key_to_subs[options.key].entries())
|
|
138
|
-
if (peer !== req.peer)
|
|
139
|
-
sub.sendUpdate({
|
|
140
|
-
version: [meta.event],
|
|
141
|
-
'Merge-Type': 'lww',
|
|
142
|
-
body
|
|
143
|
-
})
|
|
144
|
-
}
|
|
145
|
-
res.setHeader("Version", meta.event != null ? JSON.stringify(meta.event) : '')
|
|
229
|
+
meta.event = await braid_blob.put(options.key, body, {
|
|
230
|
+
version: req.version,
|
|
231
|
+
content_type: req.headers['content-type'],
|
|
232
|
+
peer: req.peer
|
|
233
|
+
})
|
|
234
|
+
res.setHeader("Version", version_to_header(meta.event != null ? [meta.event] : []))
|
|
146
235
|
res.end('')
|
|
147
236
|
} else if (req.method === 'DELETE') {
|
|
148
237
|
try {
|
|
149
|
-
await
|
|
238
|
+
await braid_blob.db.delete(options.key)
|
|
150
239
|
} catch (e) {}
|
|
151
240
|
try {
|
|
152
241
|
await fs.promises.unlink(metaname)
|
|
@@ -176,6 +265,16 @@ function create_braid_blob() {
|
|
|
176
265
|
return e
|
|
177
266
|
}
|
|
178
267
|
|
|
268
|
+
function ascii_ify(s) {
|
|
269
|
+
return s.replace(/[^\x20-\x7E]/g, c => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0'))
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function version_to_header(version) {
|
|
273
|
+
// Convert version array to header format: JSON without outer brackets
|
|
274
|
+
if (!version || !version.length) return ''
|
|
275
|
+
return ascii_ify(version.map(v => JSON.stringify(v)).join(', '))
|
|
276
|
+
}
|
|
277
|
+
|
|
179
278
|
function encode_filename(filename) {
|
|
180
279
|
// Swap all "!" and "/" characters
|
|
181
280
|
let swapped = filename.replace(/[!/]/g, (match) => (match === "!" ? "/" : "!"))
|
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "braid-blob",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "Library for collaborative blobs over http using braid.",
|
|
5
5
|
"author": "Braid Working Group",
|
|
6
6
|
"repository": "braid-org/braid-blob",
|
|
7
7
|
"homepage": "https://braid.org",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "node test/test.js",
|
|
10
|
+
"test:browser": "node test/test.js --browser"
|
|
11
|
+
},
|
|
8
12
|
"dependencies": {
|
|
9
|
-
"braid-http": "~1.3.82"
|
|
13
|
+
"braid-http": "~1.3.82",
|
|
14
|
+
"url-file-db": "~0.0.7"
|
|
10
15
|
}
|
|
11
16
|
}
|
package/plop.png
ADDED
|
Binary file
|
package/server-demo.js
CHANGED
|
@@ -5,7 +5,8 @@ var braid_blob = require(`${__dirname}/index.js`)
|
|
|
5
5
|
// TODO: set a custom storage base
|
|
6
6
|
// (the default is ./braid-blob-files)
|
|
7
7
|
//
|
|
8
|
-
// braid_blob.
|
|
8
|
+
// braid_blob.db_folder = './custom_files_folder'
|
|
9
|
+
// braid_blob.meta_folder = './custom_meta_folder'
|
|
9
10
|
|
|
10
11
|
var server = require("http").createServer(async (req, res) => {
|
|
11
12
|
console.log(`${req.method} ${req.url}`)
|
|
@@ -18,5 +19,5 @@ server.listen(port, () => {
|
|
|
18
19
|
console.log(`files stored in: ${braid_blob.db_folder}`)
|
|
19
20
|
})
|
|
20
21
|
|
|
21
|
-
// curl -X PUT --data-binary @
|
|
22
|
-
// curl http://localhost:8888/
|
|
22
|
+
// curl -X PUT --data-binary @blob.png http://localhost:8888/blob.png
|
|
23
|
+
// curl http://localhost:8888/blob.png --output new-blob.png
|