braid-blob 0.0.49 → 0.0.50
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 +364 -373
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -14,153 +14,239 @@ function create_braid_blob() {
|
|
|
14
14
|
|
|
15
15
|
var temp_folder = null // will be set in init
|
|
16
16
|
|
|
17
|
-
braid_blob.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
braid_blob.init = () => init_p
|
|
21
|
-
await braid_blob.init()
|
|
22
|
-
|
|
23
|
-
async function real_init() {
|
|
24
|
-
// Ensure our meta folder exists
|
|
25
|
-
await require('fs').promises.mkdir(braid_blob.meta_folder, { recursive: true })
|
|
26
|
-
|
|
27
|
-
// Create a temp folder inside the meta folder for writing temp files,
|
|
28
|
-
// for atomic writing.
|
|
29
|
-
// The temp folder is called "temp",
|
|
30
|
-
// And this is guaranteed not to conflict with any other files,
|
|
31
|
-
// because other files are the result of encode_filename,
|
|
32
|
-
// which always ends with a ".XX" (for handling insensitive filesystems)
|
|
33
|
-
temp_folder = `${braid_blob.meta_folder}/temp`
|
|
34
|
-
await require('fs').promises.mkdir(temp_folder, { recursive: true })
|
|
17
|
+
braid_blob.sync = (a, b, options = {}) => {
|
|
18
|
+
options = normalize_options(options)
|
|
19
|
+
if (!options.peer) options.peer = Math.random().toString(36).slice(2)
|
|
35
20
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
write: async (key, data) => {
|
|
50
|
-
var file_path = `${braid_blob.db_folder}/${encode_filename(key)}`
|
|
51
|
-
await atomic_write(file_path, data, temp_folder)
|
|
52
|
-
},
|
|
53
|
-
delete: async (key) => {
|
|
54
|
-
var file_path = `${braid_blob.db_folder}/${encode_filename(key)}`
|
|
55
|
-
try {
|
|
56
|
-
await require('fs').promises.unlink(file_path)
|
|
57
|
-
} catch (e) {
|
|
58
|
-
if (e.code !== 'ENOENT') throw e
|
|
59
|
-
}
|
|
60
|
-
}
|
|
21
|
+
if ((a instanceof URL) === (b instanceof URL)) {
|
|
22
|
+
braid_blob.get(a, {
|
|
23
|
+
...options,
|
|
24
|
+
subscribe: async update => {
|
|
25
|
+
if (update.delete) await braid_blob.delete(b, {
|
|
26
|
+
...options,
|
|
27
|
+
content_type: update.content_type,
|
|
28
|
+
})
|
|
29
|
+
else await braid_blob.put(b, update.body, {
|
|
30
|
+
...options,
|
|
31
|
+
version: update.version,
|
|
32
|
+
content_type: update.content_type,
|
|
33
|
+
})
|
|
61
34
|
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
35
|
+
})
|
|
36
|
+
braid_blob.get(b, {
|
|
37
|
+
...options,
|
|
38
|
+
subscribe: async update => {
|
|
39
|
+
if (update.delete) await braid_blob.delete(a, {
|
|
40
|
+
...options,
|
|
41
|
+
content_type: update.content_type,
|
|
42
|
+
})
|
|
43
|
+
else await braid_blob.put(a, update.body, {
|
|
44
|
+
...options,
|
|
45
|
+
version: update.version,
|
|
46
|
+
content_type: update.content_type,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
} else {
|
|
51
|
+
// One is local, one is remote - make a=local and b=remote (swap if not)
|
|
52
|
+
if (a instanceof URL) {
|
|
53
|
+
let swap = a; a = b; b = swap
|
|
65
54
|
}
|
|
66
55
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
braid_blob.peer = Math.random().toString(36).slice(2)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
56
|
+
var ac = null
|
|
57
|
+
options.signal?.addEventListener('abort', () => ac?.abort())
|
|
72
58
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
braid_blob.
|
|
77
|
-
await require('fs').promises.readFile(
|
|
78
|
-
`${braid_blob.meta_folder}/${encode_filename(key)}`, 'utf8'))
|
|
79
|
-
} catch (e) {
|
|
80
|
-
if (e.code === 'ENOENT')
|
|
81
|
-
braid_blob.meta_cache[key] = {}
|
|
82
|
-
else throw e
|
|
59
|
+
function handle_error(e) {
|
|
60
|
+
if (options.signal?.aborted) return
|
|
61
|
+
console.log(`disconnected from ${b.href}, retrying in ${braid_blob.reconnect_delay_ms ?? 1000}ms`)
|
|
62
|
+
setTimeout(connect, braid_blob.reconnect_delay_ms ?? 1000)
|
|
83
63
|
}
|
|
84
|
-
}
|
|
85
|
-
return braid_blob.meta_cache[key]
|
|
86
|
-
}
|
|
87
64
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
65
|
+
async function connect() {
|
|
66
|
+
if (options.signal?.aborted) return
|
|
67
|
+
if (options.on_pre_connect) await options.on_pre_connect()
|
|
92
68
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
await require('fs').promises.unlink(
|
|
97
|
-
`${braid_blob.meta_folder}/${encode_filename(key)}`)
|
|
98
|
-
} catch (e) {
|
|
99
|
-
if (e.code !== 'ENOENT') throw e
|
|
100
|
-
}
|
|
101
|
-
}
|
|
69
|
+
// Abort stuff in the previous connect
|
|
70
|
+
ac?.abort()
|
|
71
|
+
ac = new AbortController()
|
|
102
72
|
|
|
103
|
-
|
|
104
|
-
|
|
73
|
+
try {
|
|
74
|
+
// Check if remote has our current version (simple fork-point check)
|
|
75
|
+
var server_has_our_version = false
|
|
76
|
+
var local_version = (await braid_blob.get(a, {
|
|
77
|
+
...options,
|
|
78
|
+
signal: ac.signal,
|
|
79
|
+
head: true
|
|
80
|
+
}))?.version
|
|
81
|
+
if (local_version) {
|
|
82
|
+
var r = await braid_blob.get(b, {
|
|
83
|
+
...options,
|
|
84
|
+
signal: ac.signal,
|
|
85
|
+
head: true,
|
|
86
|
+
dont_retry: true,
|
|
87
|
+
version: local_version,
|
|
88
|
+
})
|
|
89
|
+
server_has_our_version = !!r
|
|
90
|
+
}
|
|
105
91
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
92
|
+
// Local -> remote
|
|
93
|
+
await braid_blob.get(a, {
|
|
94
|
+
...options,
|
|
95
|
+
signal: ac.signal,
|
|
96
|
+
parents: server_has_our_version ? local_version : null,
|
|
97
|
+
subscribe: async update => {
|
|
98
|
+
try {
|
|
99
|
+
if (update.delete) {
|
|
100
|
+
var x = await braid_blob.delete(b, {
|
|
101
|
+
...options,
|
|
102
|
+
signal: ac.signal,
|
|
103
|
+
dont_retry: true,
|
|
104
|
+
content_type: update.content_type,
|
|
105
|
+
})
|
|
106
|
+
if (!x.ok) handle_error(new Error('failed to delete'))
|
|
107
|
+
} else {
|
|
108
|
+
var x = await braid_blob.put(b, update.body, {
|
|
109
|
+
...options,
|
|
110
|
+
signal: ac.signal,
|
|
111
|
+
dont_retry: true,
|
|
112
|
+
version: update.version,
|
|
113
|
+
content_type: update.content_type,
|
|
114
|
+
})
|
|
115
|
+
if ((x.status === 401 || x.status === 403) && options.on_unauthorized) {
|
|
116
|
+
await options.on_unauthorized?.()
|
|
117
|
+
} else if (!x.ok) handle_error(new Error('failed to PUT: ' + x.status))
|
|
118
|
+
}
|
|
119
|
+
} catch (e) {
|
|
120
|
+
if (e.name !== 'AbortError')
|
|
121
|
+
handle_error(e)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
})
|
|
120
125
|
|
|
121
|
-
|
|
126
|
+
// Remote -> local
|
|
127
|
+
var remote_res = await braid_blob.get(b, {
|
|
128
|
+
...options,
|
|
129
|
+
signal: ac.signal,
|
|
130
|
+
dont_retry: true,
|
|
131
|
+
parents: local_version,
|
|
132
|
+
subscribe: async update => {
|
|
133
|
+
if (update.delete) await braid_blob.delete(a, {
|
|
134
|
+
...options,
|
|
135
|
+
signal: ac.signal,
|
|
136
|
+
content_type: update.content_type,
|
|
137
|
+
})
|
|
138
|
+
else await braid_blob.put(a, update.body, {
|
|
139
|
+
...options,
|
|
140
|
+
signal: ac.signal,
|
|
141
|
+
version: update.version,
|
|
142
|
+
content_type: update.content_type,
|
|
143
|
+
})
|
|
144
|
+
},
|
|
145
|
+
on_error: e => {
|
|
146
|
+
options.on_disconnect?.()
|
|
147
|
+
handle_error(e)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
options.on_res?.(remote_res)
|
|
151
|
+
} catch (e) {
|
|
152
|
+
handle_error(e)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
connect()
|
|
122
156
|
}
|
|
157
|
+
}
|
|
123
158
|
|
|
159
|
+
braid_blob.serve = async (req, res, options = {}) => {
|
|
124
160
|
await braid_blob.init()
|
|
125
|
-
if (options.signal?.aborted) return
|
|
126
161
|
|
|
127
|
-
|
|
128
|
-
var
|
|
129
|
-
|
|
162
|
+
if (!options.key) {
|
|
163
|
+
var url = new URL(req.url, 'http://localhost')
|
|
164
|
+
options.key = url.pathname
|
|
165
|
+
}
|
|
130
166
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
`${braid_blob.peer}-${max_seq('' + Date.now(),
|
|
134
|
-
meta.event ? increment_seq(get_event_seq(meta.event)) : '')}`
|
|
167
|
+
braidify(req, res)
|
|
168
|
+
if (res.is_multiplexer) return
|
|
135
169
|
|
|
136
|
-
|
|
137
|
-
|
|
170
|
+
// Handle OPTIONS request
|
|
171
|
+
if (req.method === 'OPTIONS') return res.end()
|
|
138
172
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (options.signal?.aborted) return
|
|
173
|
+
// consume PUT body
|
|
174
|
+
var body = req.method === 'PUT' && await slurp(req)
|
|
142
175
|
|
|
143
|
-
|
|
144
|
-
|
|
176
|
+
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
177
|
+
if (!res.hasHeader("editable")) res.setHeader("Editable", "true")
|
|
178
|
+
if (!req.subscribe) res.setHeader("Accept-Subscribe", "true")
|
|
179
|
+
res.setHeader("Merge-Type", "aww")
|
|
145
180
|
|
|
146
|
-
|
|
147
|
-
|
|
181
|
+
try {
|
|
182
|
+
var result = await braid_blob.get(options.key, {
|
|
183
|
+
peer: req.peer,
|
|
184
|
+
head: req.method === "HEAD",
|
|
185
|
+
version: req.version,
|
|
186
|
+
parents: req.parents,
|
|
187
|
+
header_cb: (result) => {
|
|
188
|
+
res.setHeader((req.subscribe ? "Current-" : "") +
|
|
189
|
+
"Version", version_to_header(result.version))
|
|
190
|
+
if (result.content_type)
|
|
191
|
+
res.setHeader('Content-Type', result.content_type)
|
|
192
|
+
},
|
|
193
|
+
before_send_cb: () => res.startSubscription(),
|
|
194
|
+
subscribe: req.subscribe ? (update) => {
|
|
195
|
+
if (update.delete) {
|
|
196
|
+
update.status = 404
|
|
197
|
+
delete update.delete
|
|
198
|
+
}
|
|
199
|
+
if (update.content_type) {
|
|
200
|
+
update['Content-Type'] = update.content_type
|
|
201
|
+
delete update.content_type
|
|
202
|
+
}
|
|
203
|
+
update['Merge-Type'] = 'aww'
|
|
204
|
+
res.sendUpdate(update)
|
|
205
|
+
} : null
|
|
206
|
+
})
|
|
207
|
+
} catch (e) {
|
|
208
|
+
if (e.message && e.message.startsWith('unknown version')) {
|
|
209
|
+
// Server doesn't have this version
|
|
210
|
+
res.statusCode = 309
|
|
211
|
+
res.statusMessage = 'Version Unknown Here'
|
|
212
|
+
return res.end('')
|
|
213
|
+
} else throw e
|
|
214
|
+
}
|
|
148
215
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
version: [meta.event],
|
|
153
|
-
content_type: meta.content_type,
|
|
154
|
-
body
|
|
155
|
-
}
|
|
156
|
-
if (braid_blob.key_to_subs[key])
|
|
157
|
-
for (var [peer, sub] of braid_blob.key_to_subs[key].entries())
|
|
158
|
-
if (!options.peer || options.peer !== peer)
|
|
159
|
-
await sub.sendUpdate(update)
|
|
216
|
+
if (!result) {
|
|
217
|
+
res.statusCode = 404
|
|
218
|
+
return res.end('File Not Found')
|
|
160
219
|
}
|
|
161
220
|
|
|
162
|
-
|
|
163
|
-
|
|
221
|
+
if (result.content_type && req.headers.accept &&
|
|
222
|
+
!isAcceptable(result.content_type, req.headers.accept)) {
|
|
223
|
+
res.statusCode = 406
|
|
224
|
+
return res.end(`Content-Type of ${result.content_type} not in Accept: ${req.headers.accept}`)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (req.method == "HEAD") return res.end('')
|
|
228
|
+
else if (!req.subscribe) return res.end(result.body)
|
|
229
|
+
else {
|
|
230
|
+
// If no immediate update was sent,
|
|
231
|
+
// get the node http code to send headers
|
|
232
|
+
if (!result.sent) res.write('\n\n')
|
|
233
|
+
}
|
|
234
|
+
} else if (req.method === 'PUT') {
|
|
235
|
+
// Handle PUT request to update binary files
|
|
236
|
+
var event = await braid_blob.put(options.key, body, {
|
|
237
|
+
version: req.version,
|
|
238
|
+
content_type: req.headers['content-type'],
|
|
239
|
+
peer: req.peer
|
|
240
|
+
})
|
|
241
|
+
res.setHeader("Version", version_to_header(event != null ? [event] : []))
|
|
242
|
+
res.end('')
|
|
243
|
+
} else if (req.method === 'DELETE') {
|
|
244
|
+
await braid_blob.delete(options.key, {
|
|
245
|
+
content_type: req.headers['content-type'],
|
|
246
|
+
peer: req.peer
|
|
247
|
+
})
|
|
248
|
+
res.end('')
|
|
249
|
+
}
|
|
164
250
|
}
|
|
165
251
|
|
|
166
252
|
braid_blob.get = async (key, options = {}) => {
|
|
@@ -277,23 +363,23 @@ function create_braid_blob() {
|
|
|
277
363
|
})
|
|
278
364
|
}
|
|
279
365
|
|
|
280
|
-
braid_blob.
|
|
366
|
+
braid_blob.put = async (key, body, options = {}) => {
|
|
281
367
|
options = normalize_options(options)
|
|
282
368
|
|
|
283
|
-
// Handle URL case - make a remote
|
|
369
|
+
// Handle URL case - make a remote PUT request
|
|
284
370
|
if (key instanceof URL) {
|
|
285
371
|
var params = {
|
|
286
|
-
method: '
|
|
287
|
-
signal: options.signal
|
|
372
|
+
method: 'PUT',
|
|
373
|
+
signal: options.signal,
|
|
374
|
+
body
|
|
288
375
|
}
|
|
289
376
|
if (!options.dont_retry)
|
|
290
|
-
params.retry = (
|
|
291
|
-
|
|
292
|
-
for (var x of ['headers', 'peer'])
|
|
377
|
+
params.retry = () => true
|
|
378
|
+
for (var x of ['headers', 'version', 'peer'])
|
|
293
379
|
if (options[x] != null) params[x] = options[x]
|
|
294
380
|
if (options.content_type)
|
|
295
381
|
params.headers = { ...params.headers,
|
|
296
|
-
'
|
|
382
|
+
'Content-Type': options.content_type }
|
|
297
383
|
|
|
298
384
|
return await braid_fetch(key.href, params)
|
|
299
385
|
}
|
|
@@ -305,257 +391,175 @@ function create_braid_blob() {
|
|
|
305
391
|
var meta = await get_meta(key)
|
|
306
392
|
if (options.signal?.aborted) return
|
|
307
393
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
// (except the peer which made the DELETE request itself)
|
|
313
|
-
var update = {
|
|
314
|
-
delete: true,
|
|
315
|
-
content_type: meta.content_type
|
|
316
|
-
}
|
|
317
|
-
if (braid_blob.key_to_subs[key])
|
|
318
|
-
for (var [peer, sub] of braid_blob.key_to_subs[key].entries())
|
|
319
|
-
if (!options.peer || options.peer !== peer)
|
|
320
|
-
sub.sendUpdate(update)
|
|
321
|
-
})
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
braid_blob.serve = async (req, res, options = {}) => {
|
|
325
|
-
await braid_blob.init()
|
|
326
|
-
|
|
327
|
-
if (!options.key) {
|
|
328
|
-
var url = new URL(req.url, 'http://localhost')
|
|
329
|
-
options.key = url.pathname
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
braidify(req, res)
|
|
333
|
-
if (res.is_multiplexer) return
|
|
334
|
-
|
|
335
|
-
// Handle OPTIONS request
|
|
336
|
-
if (req.method === 'OPTIONS') return res.end()
|
|
337
|
-
|
|
338
|
-
// consume PUT body
|
|
339
|
-
var body = req.method === 'PUT' && await slurp(req)
|
|
394
|
+
var their_e = options.version ? options.version[0] :
|
|
395
|
+
// we'll give them a event id in this case
|
|
396
|
+
`${braid_blob.peer}-${max_seq('' + Date.now(),
|
|
397
|
+
meta.event ? increment_seq(get_event_seq(meta.event)) : '')}`
|
|
340
398
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (!req.subscribe) res.setHeader("Accept-Subscribe", "true")
|
|
344
|
-
res.setHeader("Merge-Type", "aww")
|
|
399
|
+
if (compare_events(their_e, meta.event) > 0) {
|
|
400
|
+
meta.event = their_e
|
|
345
401
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
head: req.method === "HEAD",
|
|
350
|
-
version: req.version,
|
|
351
|
-
parents: req.parents,
|
|
352
|
-
header_cb: (result) => {
|
|
353
|
-
res.setHeader((req.subscribe ? "Current-" : "") +
|
|
354
|
-
"Version", version_to_header(result.version))
|
|
355
|
-
if (result.content_type)
|
|
356
|
-
res.setHeader('Content-Type', result.content_type)
|
|
357
|
-
},
|
|
358
|
-
before_send_cb: () => res.startSubscription(),
|
|
359
|
-
subscribe: req.subscribe ? (update) => {
|
|
360
|
-
if (update.delete) {
|
|
361
|
-
update.status = 404
|
|
362
|
-
delete update.delete
|
|
363
|
-
}
|
|
364
|
-
if (update.content_type) {
|
|
365
|
-
update['Content-Type'] = update.content_type
|
|
366
|
-
delete update.content_type
|
|
367
|
-
}
|
|
368
|
-
update['Merge-Type'] = 'aww'
|
|
369
|
-
res.sendUpdate(update)
|
|
370
|
-
} : null
|
|
371
|
-
})
|
|
372
|
-
} catch (e) {
|
|
373
|
-
if (e.message && e.message.startsWith('unknown version')) {
|
|
374
|
-
// Server doesn't have this version
|
|
375
|
-
res.statusCode = 309
|
|
376
|
-
res.statusMessage = 'Version Unknown Here'
|
|
377
|
-
return res.end('')
|
|
378
|
-
} else throw e
|
|
379
|
-
}
|
|
402
|
+
if (!options.skip_write)
|
|
403
|
+
await (options.db || braid_blob.db).write(key, body)
|
|
404
|
+
if (options.signal?.aborted) return
|
|
380
405
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
return res.end('File Not Found')
|
|
384
|
-
}
|
|
406
|
+
if (options.content_type)
|
|
407
|
+
meta.content_type = options.content_type
|
|
385
408
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
res.statusCode = 406
|
|
389
|
-
return res.end(`Content-Type of ${result.content_type} not in Accept: ${req.headers.accept}`)
|
|
390
|
-
}
|
|
409
|
+
await save_meta(key)
|
|
410
|
+
if (options.signal?.aborted) return
|
|
391
411
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
res.end('')
|
|
408
|
-
} else if (req.method === 'DELETE') {
|
|
409
|
-
await braid_blob.delete(options.key, {
|
|
410
|
-
content_type: req.headers['content-type'],
|
|
411
|
-
peer: req.peer
|
|
412
|
-
})
|
|
413
|
-
res.end('')
|
|
414
|
-
}
|
|
412
|
+
// Notify all subscriptions of the update
|
|
413
|
+
// (except the peer which made the PUT request itself)
|
|
414
|
+
var update = {
|
|
415
|
+
version: [meta.event],
|
|
416
|
+
content_type: meta.content_type,
|
|
417
|
+
body
|
|
418
|
+
}
|
|
419
|
+
if (braid_blob.key_to_subs[key])
|
|
420
|
+
for (var [peer, sub] of braid_blob.key_to_subs[key].entries())
|
|
421
|
+
if (!options.peer || options.peer !== peer)
|
|
422
|
+
await sub.sendUpdate(update)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return meta.event
|
|
426
|
+
})
|
|
415
427
|
}
|
|
416
428
|
|
|
417
|
-
braid_blob.
|
|
429
|
+
braid_blob.delete = async (key, options = {}) => {
|
|
418
430
|
options = normalize_options(options)
|
|
419
|
-
if (!options.peer) options.peer = Math.random().toString(36).slice(2)
|
|
420
431
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
...options,
|
|
427
|
-
content_type: update.content_type,
|
|
428
|
-
})
|
|
429
|
-
else await braid_blob.put(b, update.body, {
|
|
430
|
-
...options,
|
|
431
|
-
version: update.version,
|
|
432
|
-
content_type: update.content_type,
|
|
433
|
-
})
|
|
434
|
-
}
|
|
435
|
-
})
|
|
436
|
-
braid_blob.get(b, {
|
|
437
|
-
...options,
|
|
438
|
-
subscribe: async update => {
|
|
439
|
-
if (update.delete) await braid_blob.delete(a, {
|
|
440
|
-
...options,
|
|
441
|
-
content_type: update.content_type,
|
|
442
|
-
})
|
|
443
|
-
else await braid_blob.put(a, update.body, {
|
|
444
|
-
...options,
|
|
445
|
-
version: update.version,
|
|
446
|
-
content_type: update.content_type,
|
|
447
|
-
})
|
|
448
|
-
}
|
|
449
|
-
})
|
|
450
|
-
} else {
|
|
451
|
-
// One is local, one is remote - make a=local and b=remote (swap if not)
|
|
452
|
-
if (a instanceof URL) {
|
|
453
|
-
let swap = a; a = b; b = swap
|
|
432
|
+
// Handle URL case - make a remote DELETE request
|
|
433
|
+
if (key instanceof URL) {
|
|
434
|
+
var params = {
|
|
435
|
+
method: 'DELETE',
|
|
436
|
+
signal: options.signal
|
|
454
437
|
}
|
|
438
|
+
if (!options.dont_retry)
|
|
439
|
+
params.retry = (res) => res.status !== 309 &&
|
|
440
|
+
res.status !== 404 && res.status !== 406
|
|
441
|
+
for (var x of ['headers', 'peer'])
|
|
442
|
+
if (options[x] != null) params[x] = options[x]
|
|
443
|
+
if (options.content_type)
|
|
444
|
+
params.headers = { ...params.headers,
|
|
445
|
+
'Accept': options.content_type }
|
|
455
446
|
|
|
456
|
-
|
|
457
|
-
|
|
447
|
+
return await braid_fetch(key.href, params)
|
|
448
|
+
}
|
|
458
449
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
450
|
+
await braid_blob.init()
|
|
451
|
+
if (options.signal?.aborted) return
|
|
452
|
+
|
|
453
|
+
return await within_fiber(key, async () => {
|
|
454
|
+
var meta = await get_meta(key)
|
|
455
|
+
if (options.signal?.aborted) return
|
|
456
|
+
|
|
457
|
+
await (options.db || braid_blob.db).delete(key)
|
|
458
|
+
await delete_meta(key)
|
|
459
|
+
|
|
460
|
+
// Notify all subscriptions of the delete
|
|
461
|
+
// (except the peer which made the DELETE request itself)
|
|
462
|
+
var update = {
|
|
463
|
+
delete: true,
|
|
464
|
+
content_type: meta.content_type
|
|
463
465
|
}
|
|
466
|
+
if (braid_blob.key_to_subs[key])
|
|
467
|
+
for (var [peer, sub] of braid_blob.key_to_subs[key].entries())
|
|
468
|
+
if (!options.peer || options.peer !== peer)
|
|
469
|
+
sub.sendUpdate(update)
|
|
470
|
+
})
|
|
471
|
+
}
|
|
464
472
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
473
|
+
braid_blob.init = async () => {
|
|
474
|
+
// We only want to initialize once
|
|
475
|
+
var init_p = real_init()
|
|
476
|
+
braid_blob.init = () => init_p
|
|
477
|
+
await braid_blob.init()
|
|
468
478
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
479
|
+
async function real_init() {
|
|
480
|
+
// Ensure our meta folder exists
|
|
481
|
+
await require('fs').promises.mkdir(braid_blob.meta_folder, { recursive: true })
|
|
472
482
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (local_version) {
|
|
482
|
-
var r = await braid_blob.get(b, {
|
|
483
|
-
...options,
|
|
484
|
-
signal: ac.signal,
|
|
485
|
-
head: true,
|
|
486
|
-
dont_retry: true,
|
|
487
|
-
version: local_version,
|
|
488
|
-
})
|
|
489
|
-
server_has_our_version = !!r
|
|
490
|
-
}
|
|
483
|
+
// Create a temp folder inside the meta folder for writing temp files,
|
|
484
|
+
// for atomic writing.
|
|
485
|
+
// The temp folder is called "temp",
|
|
486
|
+
// And this is guaranteed not to conflict with any other files,
|
|
487
|
+
// because other files are the result of encode_filename,
|
|
488
|
+
// which always ends with a ".XX" (for handling insensitive filesystems)
|
|
489
|
+
temp_folder = `${braid_blob.meta_folder}/temp`
|
|
490
|
+
await require('fs').promises.mkdir(temp_folder, { recursive: true })
|
|
491
491
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
dont_retry: true,
|
|
504
|
-
content_type: update.content_type,
|
|
505
|
-
})
|
|
506
|
-
if (!x.ok) handle_error(new Error('failed to delete'))
|
|
507
|
-
} else {
|
|
508
|
-
var x = await braid_blob.put(b, update.body, {
|
|
509
|
-
...options,
|
|
510
|
-
signal: ac.signal,
|
|
511
|
-
dont_retry: true,
|
|
512
|
-
version: update.version,
|
|
513
|
-
content_type: update.content_type,
|
|
514
|
-
})
|
|
515
|
-
if ((x.status === 401 || x.status === 403) && options.on_unauthorized) {
|
|
516
|
-
await options.on_unauthorized?.()
|
|
517
|
-
} else if (!x.ok) handle_error(new Error('failed to PUT: ' + x.status))
|
|
518
|
-
}
|
|
519
|
-
} catch (e) {
|
|
520
|
-
if (e.name !== 'AbortError')
|
|
521
|
-
handle_error(e)
|
|
522
|
-
}
|
|
492
|
+
// Set up db - either use provided object or create file-based storage
|
|
493
|
+
if (typeof braid_blob.db_folder === 'string') {
|
|
494
|
+
await require('fs').promises.mkdir(braid_blob.db_folder, { recursive: true })
|
|
495
|
+
braid_blob.db = {
|
|
496
|
+
read: async (key) => {
|
|
497
|
+
var file_path = `${braid_blob.db_folder}/${encode_filename(key)}`
|
|
498
|
+
try {
|
|
499
|
+
return await require('fs').promises.readFile(file_path)
|
|
500
|
+
} catch (e) {
|
|
501
|
+
if (e.code === 'ENOENT') return null
|
|
502
|
+
throw e
|
|
523
503
|
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
signal: ac.signal,
|
|
536
|
-
content_type: update.content_type,
|
|
537
|
-
})
|
|
538
|
-
else await braid_blob.put(a, update.body, {
|
|
539
|
-
...options,
|
|
540
|
-
signal: ac.signal,
|
|
541
|
-
version: update.version,
|
|
542
|
-
content_type: update.content_type,
|
|
543
|
-
})
|
|
544
|
-
},
|
|
545
|
-
on_error: e => {
|
|
546
|
-
options.on_disconnect?.()
|
|
547
|
-
handle_error(e)
|
|
504
|
+
},
|
|
505
|
+
write: async (key, data) => {
|
|
506
|
+
var file_path = `${braid_blob.db_folder}/${encode_filename(key)}`
|
|
507
|
+
await atomic_write(file_path, data, temp_folder)
|
|
508
|
+
},
|
|
509
|
+
delete: async (key) => {
|
|
510
|
+
var file_path = `${braid_blob.db_folder}/${encode_filename(key)}`
|
|
511
|
+
try {
|
|
512
|
+
await require('fs').promises.unlink(file_path)
|
|
513
|
+
} catch (e) {
|
|
514
|
+
if (e.code !== 'ENOENT') throw e
|
|
548
515
|
}
|
|
549
|
-
}
|
|
550
|
-
options.on_res?.(remote_res)
|
|
551
|
-
} catch (e) {
|
|
552
|
-
handle_error(e)
|
|
516
|
+
}
|
|
553
517
|
}
|
|
518
|
+
} else {
|
|
519
|
+
// db_folder is already an object with read/write/delete
|
|
520
|
+
braid_blob.db = braid_blob.db_folder
|
|
554
521
|
}
|
|
555
|
-
|
|
522
|
+
|
|
523
|
+
// establish a peer id if not already set
|
|
524
|
+
if (!braid_blob.peer)
|
|
525
|
+
braid_blob.peer = Math.random().toString(36).slice(2)
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
async function get_meta(key) {
|
|
530
|
+
if (!braid_blob.meta_cache[key]) {
|
|
531
|
+
try {
|
|
532
|
+
braid_blob.meta_cache[key] = JSON.parse(
|
|
533
|
+
await require('fs').promises.readFile(
|
|
534
|
+
`${braid_blob.meta_folder}/${encode_filename(key)}`, 'utf8'))
|
|
535
|
+
} catch (e) {
|
|
536
|
+
if (e.code === 'ENOENT')
|
|
537
|
+
braid_blob.meta_cache[key] = {}
|
|
538
|
+
else throw e
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return braid_blob.meta_cache[key]
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async function save_meta(key) {
|
|
545
|
+
await atomic_write(`${braid_blob.meta_folder}/${encode_filename(key)}`,
|
|
546
|
+
JSON.stringify(braid_blob.meta_cache[key]), temp_folder)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
async function delete_meta(key) {
|
|
550
|
+
delete braid_blob.meta_cache[key]
|
|
551
|
+
try {
|
|
552
|
+
await require('fs').promises.unlink(
|
|
553
|
+
`${braid_blob.meta_folder}/${encode_filename(key)}`)
|
|
554
|
+
} catch (e) {
|
|
555
|
+
if (e.code !== 'ENOENT') throw e
|
|
556
556
|
}
|
|
557
557
|
}
|
|
558
558
|
|
|
559
|
+
//////////////////////////////////////////////////////////////////
|
|
560
|
+
//////////////////////////////////////////////////////////////////
|
|
561
|
+
//////////////////////////////////////////////////////////////////
|
|
562
|
+
|
|
559
563
|
function compare_events(a, b) {
|
|
560
564
|
if (!a) a = ''
|
|
561
565
|
if (!b) b = ''
|
|
@@ -629,7 +633,7 @@ function create_braid_blob() {
|
|
|
629
633
|
})
|
|
630
634
|
return within_fiber.chains[id] = curr
|
|
631
635
|
}
|
|
632
|
-
|
|
636
|
+
|
|
633
637
|
async function slurp(req) {
|
|
634
638
|
return await new Promise(done => {
|
|
635
639
|
var chunks = []
|
|
@@ -695,19 +699,6 @@ function create_braid_blob() {
|
|
|
695
699
|
}
|
|
696
700
|
}
|
|
697
701
|
|
|
698
|
-
function get_header(headers, key) {
|
|
699
|
-
if (!headers) return
|
|
700
|
-
|
|
701
|
-
// optimization..
|
|
702
|
-
if (headers.hasOwnProperty(key))
|
|
703
|
-
return headers[key]
|
|
704
|
-
|
|
705
|
-
var lowerKey = key.toLowerCase()
|
|
706
|
-
for (var headerKey of Object.keys(headers))
|
|
707
|
-
if (headerKey.toLowerCase() === lowerKey)
|
|
708
|
-
return headers[headerKey]
|
|
709
|
-
}
|
|
710
|
-
|
|
711
702
|
function normalize_options(options = {}) {
|
|
712
703
|
if (!normalize_options.special) {
|
|
713
704
|
normalize_options.special = {
|