braid-blob 0.0.30 → 0.0.32
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 +4 -4
- package/README.md +1 -1
- package/index.js +6 -22
- package/package.json +1 -1
- package/test/tests.js +0 -95
package/AI-README.md
CHANGED
|
@@ -103,7 +103,7 @@ COMPARISON_RULES:
|
|
|
103
103
|
1. Compare numeric length of timestamp
|
|
104
104
|
2. If equal, lexicographic compare timestamp
|
|
105
105
|
3. If equal, lexicographic compare full version string
|
|
106
|
-
MERGE_TYPE:
|
|
106
|
+
MERGE_TYPE: arbitrary-writer-wins (aww)
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
## METADATA_SCHEMA
|
|
@@ -145,7 +145,7 @@ SIDE_EFFECTS:
|
|
|
145
145
|
VERSION_LOGIC:
|
|
146
146
|
- If options.version provided: use options.version[0]
|
|
147
147
|
- Else: generate "{peer}-{max(Date.now(), last_version_seq+1)}"
|
|
148
|
-
- Only write if new version > existing version (
|
|
148
|
+
- Only write if new version > existing version (aww)
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
### get(key, options)
|
|
@@ -320,7 +320,7 @@ HTTP_HEADERS:
|
|
|
320
320
|
Current-Version: "v1" # for subscribed GET
|
|
321
321
|
Editable: true
|
|
322
322
|
Accept-Subscribe: true
|
|
323
|
-
Merge-Type:
|
|
323
|
+
Merge-Type: aww
|
|
324
324
|
Content-Type: mime/type
|
|
325
325
|
|
|
326
326
|
STATUS_CODES:
|
|
@@ -332,7 +332,7 @@ STATUS_CODES:
|
|
|
332
332
|
|
|
333
333
|
BRAID_UPDATE_FORMAT:
|
|
334
334
|
Version: "v1"\r\n
|
|
335
|
-
Merge-Type:
|
|
335
|
+
Merge-Type: aww\r\n
|
|
336
336
|
Content-Length: N\r\n
|
|
337
337
|
\r\n
|
|
338
338
|
{body}
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
|
|
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 arbitrary-writer-wins (AWW) conflict resolution and persistent storage.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
package/index.js
CHANGED
|
@@ -96,7 +96,7 @@ function create_braid_blob() {
|
|
|
96
96
|
if (peer !== options.peer)
|
|
97
97
|
sub.sendUpdate({
|
|
98
98
|
version: [meta.event],
|
|
99
|
-
'Merge-Type': '
|
|
99
|
+
'Merge-Type': 'aww',
|
|
100
100
|
body
|
|
101
101
|
})
|
|
102
102
|
}
|
|
@@ -247,7 +247,7 @@ function create_braid_blob() {
|
|
|
247
247
|
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
248
248
|
if (!res.hasHeader("editable")) res.setHeader("Editable", "true")
|
|
249
249
|
if (!req.subscribe) res.setHeader("Accept-Subscribe", "true")
|
|
250
|
-
res.setHeader("Merge-Type", "
|
|
250
|
+
res.setHeader("Merge-Type", "aww")
|
|
251
251
|
|
|
252
252
|
try {
|
|
253
253
|
var result = await braid_blob.get(options.key, {
|
|
@@ -267,7 +267,7 @@ function create_braid_blob() {
|
|
|
267
267
|
subscribe: req.subscribe ? (update) => {
|
|
268
268
|
res.sendUpdate({
|
|
269
269
|
version: update.version,
|
|
270
|
-
'Merge-Type': '
|
|
270
|
+
'Merge-Type': 'aww',
|
|
271
271
|
body: update.body
|
|
272
272
|
})
|
|
273
273
|
} : null
|
|
@@ -416,17 +416,6 @@ function create_braid_blob() {
|
|
|
416
416
|
}
|
|
417
417
|
|
|
418
418
|
// Remote -> local: subscribe to remote updates
|
|
419
|
-
// We need both: remote_res (for Editable header) and local file exists
|
|
420
|
-
var got_remote_res, got_local_file
|
|
421
|
-
var remote_res_promise = new Promise(done => got_remote_res = done)
|
|
422
|
-
var local_file_promise = new Promise(done => got_local_file = done)
|
|
423
|
-
|
|
424
|
-
// Apply read-only once we have both remote response and local file
|
|
425
|
-
Promise.all([remote_res_promise, local_file_promise]).then(async () => {
|
|
426
|
-
var read_only = remote_res.headers?.get('editable') === 'false'
|
|
427
|
-
await braid_blob.db.set_read_only(a, read_only)
|
|
428
|
-
})
|
|
429
|
-
|
|
430
419
|
var b_ops = {
|
|
431
420
|
signal: ac.signal,
|
|
432
421
|
dont_retry: true,
|
|
@@ -435,7 +424,6 @@ function create_braid_blob() {
|
|
|
435
424
|
version: update.version,
|
|
436
425
|
content_type: update.headers?.['content-type']
|
|
437
426
|
})
|
|
438
|
-
got_local_file()
|
|
439
427
|
remote_first_put()
|
|
440
428
|
},
|
|
441
429
|
on_error: handle_error
|
|
@@ -446,15 +434,11 @@ function create_braid_blob() {
|
|
|
446
434
|
}
|
|
447
435
|
|
|
448
436
|
// Set up both subscriptions, handling cases where one doesn't exist yet
|
|
449
|
-
braid_blob.get(a, a_ops).then(x =>
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
braid_blob.get(a, a_ops))
|
|
453
|
-
})
|
|
437
|
+
braid_blob.get(a, a_ops).then(x =>
|
|
438
|
+
x || remote_first_put_promise.then(() =>
|
|
439
|
+
braid_blob.get(a, a_ops)))
|
|
454
440
|
|
|
455
|
-
// Get the response to check Editable header
|
|
456
441
|
var remote_res = await braid_blob.get(b, b_ops)
|
|
457
|
-
if (remote_res) got_remote_res()
|
|
458
442
|
|
|
459
443
|
// If remote doesn't exist yet, wait for it to be created then reconnect
|
|
460
444
|
if (!remote_res) {
|
package/package.json
CHANGED
package/test/tests.js
CHANGED
|
@@ -1147,101 +1147,6 @@ runTest(
|
|
|
1147
1147
|
'shared content'
|
|
1148
1148
|
)
|
|
1149
1149
|
|
|
1150
|
-
runTest(
|
|
1151
|
-
"test sync readonly remote to nonexistent local",
|
|
1152
|
-
async () => {
|
|
1153
|
-
var local_key = 'test-sync-readonly-local-' + Math.random().toString(36).slice(2)
|
|
1154
|
-
var remote_key = 'test-sync-readonly-remote-' + Math.random().toString(36).slice(2)
|
|
1155
|
-
|
|
1156
|
-
// Put something on the server first with Editable: false
|
|
1157
|
-
var r1 = await braid_fetch(`/eval`, {
|
|
1158
|
-
method: 'POST',
|
|
1159
|
-
body: `void (async () => {
|
|
1160
|
-
try {
|
|
1161
|
-
// Create a custom handler that sets Editable: false
|
|
1162
|
-
req.method = 'PUT'
|
|
1163
|
-
req.version = ['100']
|
|
1164
|
-
req.body = Buffer.from('readonly content')
|
|
1165
|
-
await braid_blob.put('${remote_key}', req.body, { version: req.version })
|
|
1166
|
-
res.end('created')
|
|
1167
|
-
} catch (e) {
|
|
1168
|
-
res.end('error: ' + e.message + ' ' + e.stack)
|
|
1169
|
-
}
|
|
1170
|
-
})()`
|
|
1171
|
-
})
|
|
1172
|
-
var result = await r1.text()
|
|
1173
|
-
if (result.startsWith('error:')) return result
|
|
1174
|
-
|
|
1175
|
-
// Now sync to a local key that doesn't exist, with a readonly remote
|
|
1176
|
-
var r2 = await braid_fetch(`/eval`, {
|
|
1177
|
-
method: 'POST',
|
|
1178
|
-
body: `void (async () => {
|
|
1179
|
-
try {
|
|
1180
|
-
var fs = require('fs').promises
|
|
1181
|
-
var test_id = 'test-sync-readonly-' + Math.random().toString(36).slice(2)
|
|
1182
|
-
var db_folder = __dirname + '/' + test_id + '-db'
|
|
1183
|
-
var meta_folder = __dirname + '/' + test_id + '-meta'
|
|
1184
|
-
|
|
1185
|
-
var bb = braid_blob.create_braid_blob()
|
|
1186
|
-
bb.db_folder = db_folder
|
|
1187
|
-
bb.meta_folder = meta_folder
|
|
1188
|
-
|
|
1189
|
-
// Create a mock server URL that returns Editable: false
|
|
1190
|
-
var http = require('http')
|
|
1191
|
-
var {http_server: braidify} = require('braid-http')
|
|
1192
|
-
|
|
1193
|
-
var mock_server = http.createServer((req2, res2) => {
|
|
1194
|
-
braidify(req2, res2)
|
|
1195
|
-
res2.setHeader('Editable', 'false')
|
|
1196
|
-
braid_blob.serve(req2, res2, { key: '${remote_key}' })
|
|
1197
|
-
})
|
|
1198
|
-
|
|
1199
|
-
await new Promise(resolve => mock_server.listen(0, resolve))
|
|
1200
|
-
var port = mock_server.address().port
|
|
1201
|
-
|
|
1202
|
-
var remote_url = new URL('http://localhost:' + port + '/${remote_key}')
|
|
1203
|
-
var ac = new AbortController()
|
|
1204
|
-
|
|
1205
|
-
// Start sync - local key doesn't exist yet
|
|
1206
|
-
bb.sync('${local_key}', remote_url, { signal: ac.signal })
|
|
1207
|
-
|
|
1208
|
-
// Wait for sync to happen
|
|
1209
|
-
await new Promise(done => setTimeout(done, 500))
|
|
1210
|
-
|
|
1211
|
-
// Stop sync
|
|
1212
|
-
ac.abort()
|
|
1213
|
-
mock_server.close()
|
|
1214
|
-
|
|
1215
|
-
// Check if local file exists and has the content
|
|
1216
|
-
var result = await bb.get('${local_key}')
|
|
1217
|
-
|
|
1218
|
-
var response
|
|
1219
|
-
if (!result) {
|
|
1220
|
-
response = 'file not created'
|
|
1221
|
-
} else if (result.body.toString() !== 'readonly content') {
|
|
1222
|
-
response = 'wrong content: ' + result.body.toString()
|
|
1223
|
-
} else {
|
|
1224
|
-
// Check if the file is actually read-only
|
|
1225
|
-
await bb.init()
|
|
1226
|
-
var is_readonly = await bb.db.is_read_only('${local_key}')
|
|
1227
|
-
response = is_readonly ? 'synced and readonly' : 'synced but NOT readonly'
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
// Clean up
|
|
1231
|
-
await fs.rm(db_folder, { recursive: true, force: true })
|
|
1232
|
-
await fs.rm(meta_folder, { recursive: true, force: true })
|
|
1233
|
-
|
|
1234
|
-
res.end(response)
|
|
1235
|
-
} catch (e) {
|
|
1236
|
-
res.end('error: ' + e.message + ' ' + e.stack)
|
|
1237
|
-
}
|
|
1238
|
-
})()`
|
|
1239
|
-
})
|
|
1240
|
-
return await r2.text()
|
|
1241
|
-
},
|
|
1242
|
-
'synced and readonly'
|
|
1243
|
-
)
|
|
1244
|
-
|
|
1245
1150
|
runTest(
|
|
1246
1151
|
"test sync does not disconnect unnecessarily",
|
|
1247
1152
|
async () => {
|