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 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: last-write-wins (lww)
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 (lww)
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: lww
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: lww\r\n
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 last-write-wins (LWW) conflict resolution and persistent storage.
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': 'lww',
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", "lww")
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': 'lww',
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
- if (x) got_local_file()
451
- else remote_first_put_promise.then(() =>
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-blob",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "description": "Library for collaborative blobs over http using braid.",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braid-blob",
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 () => {