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.
Files changed (2) hide show
  1. package/index.js +364 -373
  2. 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.init = async () => {
18
- // We only want to initialize once
19
- var init_p = real_init()
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
- // Set up db - either use provided object or create file-based storage
37
- if (typeof braid_blob.db_folder === 'string') {
38
- await require('fs').promises.mkdir(braid_blob.db_folder, { recursive: true })
39
- braid_blob.db = {
40
- read: async (key) => {
41
- var file_path = `${braid_blob.db_folder}/${encode_filename(key)}`
42
- try {
43
- return await require('fs').promises.readFile(file_path)
44
- } catch (e) {
45
- if (e.code === 'ENOENT') return null
46
- throw e
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
- } else {
63
- // db_folder is already an object with read/write/delete
64
- braid_blob.db = braid_blob.db_folder
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
- // establish a peer id if not already set
68
- if (!braid_blob.peer)
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
- async function get_meta(key) {
74
- if (!braid_blob.meta_cache[key]) {
75
- try {
76
- braid_blob.meta_cache[key] = JSON.parse(
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
- async function save_meta(key) {
89
- await atomic_write(`${braid_blob.meta_folder}/${encode_filename(key)}`,
90
- JSON.stringify(braid_blob.meta_cache[key]), temp_folder)
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
- async function delete_meta(key) {
94
- delete braid_blob.meta_cache[key]
95
- try {
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
- braid_blob.put = async (key, body, options = {}) => {
104
- options = normalize_options(options)
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
- // Handle URL case - make a remote PUT request
107
- if (key instanceof URL) {
108
- var params = {
109
- method: 'PUT',
110
- signal: options.signal,
111
- body
112
- }
113
- if (!options.dont_retry)
114
- params.retry = () => true
115
- for (var x of ['headers', 'version', 'peer'])
116
- if (options[x] != null) params[x] = options[x]
117
- if (options.content_type)
118
- params.headers = { ...params.headers,
119
- 'Content-Type': options.content_type }
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
- return await braid_fetch(key.href, params)
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
- return await within_fiber(key, async () => {
128
- var meta = await get_meta(key)
129
- if (options.signal?.aborted) return
162
+ if (!options.key) {
163
+ var url = new URL(req.url, 'http://localhost')
164
+ options.key = url.pathname
165
+ }
130
166
 
131
- var their_e = options.version ? options.version[0] :
132
- // we'll give them a event id in this case
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
- if (compare_events(their_e, meta.event) > 0) {
137
- meta.event = their_e
170
+ // Handle OPTIONS request
171
+ if (req.method === 'OPTIONS') return res.end()
138
172
 
139
- if (!options.skip_write)
140
- await (options.db || braid_blob.db).write(key, body)
141
- if (options.signal?.aborted) return
173
+ // consume PUT body
174
+ var body = req.method === 'PUT' && await slurp(req)
142
175
 
143
- if (options.content_type)
144
- meta.content_type = options.content_type
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
- await save_meta(key)
147
- if (options.signal?.aborted) return
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
- // Notify all subscriptions of the update
150
- // (except the peer which made the PUT request itself)
151
- var update = {
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
- return meta.event
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.delete = async (key, options = {}) => {
366
+ braid_blob.put = async (key, body, options = {}) => {
281
367
  options = normalize_options(options)
282
368
 
283
- // Handle URL case - make a remote DELETE request
369
+ // Handle URL case - make a remote PUT request
284
370
  if (key instanceof URL) {
285
371
  var params = {
286
- method: 'DELETE',
287
- signal: options.signal
372
+ method: 'PUT',
373
+ signal: options.signal,
374
+ body
288
375
  }
289
376
  if (!options.dont_retry)
290
- params.retry = (res) => res.status !== 309 &&
291
- res.status !== 404 && res.status !== 406
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
- 'Accept': options.content_type }
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
- await (options.db || braid_blob.db).delete(key)
309
- await delete_meta(key)
310
-
311
- // Notify all subscriptions of the delete
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
- if (req.method === 'GET' || req.method === 'HEAD') {
342
- if (!res.hasHeader("editable")) res.setHeader("Editable", "true")
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
- try {
347
- var result = await braid_blob.get(options.key, {
348
- peer: req.peer,
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
- if (!result) {
382
- res.statusCode = 404
383
- return res.end('File Not Found')
384
- }
406
+ if (options.content_type)
407
+ meta.content_type = options.content_type
385
408
 
386
- if (result.content_type && req.headers.accept &&
387
- !isAcceptable(result.content_type, req.headers.accept)) {
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
- if (req.method == "HEAD") return res.end('')
393
- else if (!req.subscribe) return res.end(result.body)
394
- else {
395
- // If no immediate update was sent,
396
- // get the node http code to send headers
397
- if (!result.sent) res.write('\n\n')
398
- }
399
- } else if (req.method === 'PUT') {
400
- // Handle PUT request to update binary files
401
- var event = await braid_blob.put(options.key, body, {
402
- version: req.version,
403
- content_type: req.headers['content-type'],
404
- peer: req.peer
405
- })
406
- res.setHeader("Version", version_to_header(event != null ? [event] : []))
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.sync = (a, b, options = {}) => {
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
- if ((a instanceof URL) === (b instanceof URL)) {
422
- braid_blob.get(a, {
423
- ...options,
424
- subscribe: async update => {
425
- if (update.delete) await braid_blob.delete(b, {
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
- var ac = null
457
- options.signal?.addEventListener('abort', () => ac?.abort())
447
+ return await braid_fetch(key.href, params)
448
+ }
458
449
 
459
- function handle_error(e) {
460
- if (options.signal?.aborted) return
461
- console.log(`disconnected from ${b.href}, retrying in ${braid_blob.reconnect_delay_ms ?? 1000}ms`)
462
- setTimeout(connect, braid_blob.reconnect_delay_ms ?? 1000)
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
- async function connect() {
466
- if (options.signal?.aborted) return
467
- if (options.on_pre_connect) await options.on_pre_connect()
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
- // Abort stuff in the previous connect
470
- ac?.abort()
471
- ac = new AbortController()
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
- try {
474
- // Check if remote has our current version (simple fork-point check)
475
- var server_has_our_version = false
476
- var local_version = (await braid_blob.get(a, {
477
- ...options,
478
- signal: ac.signal,
479
- head: true
480
- }))?.version
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
- // Local -> remote
493
- await braid_blob.get(a, {
494
- ...options,
495
- signal: ac.signal,
496
- parents: server_has_our_version ? local_version : null,
497
- subscribe: async update => {
498
- try {
499
- if (update.delete) {
500
- var x = await braid_blob.delete(b, {
501
- ...options,
502
- signal: ac.signal,
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
- // Remote -> local
527
- var remote_res = await braid_blob.get(b, {
528
- ...options,
529
- signal: ac.signal,
530
- dont_retry: true,
531
- parents: local_version,
532
- subscribe: async update => {
533
- if (update.delete) await braid_blob.delete(a, {
534
- ...options,
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
- connect()
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 = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-blob",
3
- "version": "0.0.49",
3
+ "version": "0.0.50",
4
4
  "description": "Library for collaborative blobs over http using braid.",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braid-blob",