braid-blob 0.0.40 → 0.0.41

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
@@ -54,11 +54,6 @@ DEPENDENCY_UPDATES:
54
54
  - Format: "updates to {package}@{version}"
55
55
  - Example: "0.0.18 - ... updates to url-file-db 0.0.8"
56
56
 
57
- RESOLVED_ISSUES:
58
- - url-file-db < 0.0.13: Bug where reading non-existent files returned "index" file contents
59
- - Fixed in url-file-db 0.0.13+ (properly returns null for non-existent files)
60
- - Caused "test sync local to remote" to fail with unexpected "shared content"
61
- - url-file-db 0.0.15+ also relaxed path requirements (no longer requires leading "/")
62
57
  ```
63
58
 
64
59
  ## MODULE_STRUCTURE
@@ -67,7 +62,7 @@ RESOLVED_ISSUES:
67
62
  EXPORT: create_braid_blob() -> braid_blob_instance
68
63
  MODULE_TYPE: CommonJS
69
64
  MAIN_ENTRY: index.js
70
- DEPENDENCIES: [braid-http, url-file-db, fs, path]
65
+ DEPENDENCIES: [braid-http, fs]
71
66
  ```
72
67
 
73
68
  ## DATA_MODEL
@@ -81,9 +76,9 @@ braid_blob_instance = {
81
76
 
82
77
  // Runtime state
83
78
  cache: object // internal cache
79
+ meta_cache: object // metadata cache
84
80
  key_to_subs: Map<key, Map<peer, subscription>> // subscription tracking
85
- db: url_file_db_instance // blob storage backend
86
- meta_db: url_file_db_instance // metadata storage backend
81
+ db: {read, write, delete} // blob storage backend (auto-created or custom)
87
82
 
88
83
  // Methods
89
84
  init: async () -> void
@@ -130,6 +125,7 @@ INPUT:
130
125
  content_type?: string // MIME type
131
126
  peer?: string // peer identifier
132
127
  skip_write?: boolean // skip disk write (for external changes)
128
+ db?: {read, write, delete} // custom db backend (overrides braid_blob.db)
133
129
  signal?: AbortSignal // for URL mode
134
130
  headers?: object // for URL mode
135
131
  }
@@ -137,8 +133,8 @@ INPUT:
137
133
  OUTPUT: version_string
138
134
 
139
135
  SIDE_EFFECTS:
140
- - Writes blob to db_folder via url-file-db
141
- - Writes metadata to meta_folder/db
136
+ - Writes blob to db (options.db or braid_blob.db)
137
+ - Writes metadata to meta_folder
142
138
  - Notifies active subscriptions (except originating peer)
143
139
  - If key instanceof URL: makes remote HTTP PUT via braid_fetch
144
140
 
@@ -163,6 +159,7 @@ INPUT:
163
159
  parents?: [version] // fork-point for subscriptions
164
160
  version?: [version] // request specific version
165
161
  peer?: string // peer identifier
162
+ db?: {read, write, delete} // custom db backend (overrides braid_blob.db)
166
163
  signal?: AbortSignal // for URL mode
167
164
  dont_retry?: boolean // for URL mode subscriptions
168
165
  }
@@ -294,14 +291,11 @@ isAcceptable(contentType, acceptHeader) -> boolean
294
291
 
295
292
  ```
296
293
  db_folder/
297
- {url_file_db structure}
298
- - Blob data stored via url-file-db
299
- - Key mapping: URL-safe encoding of keys
294
+ {encoded_key} # Blob data files
295
+ - Key encoding: encode_filename() escapes special chars
300
296
 
301
297
  meta_folder/
302
- peer.txt # Peer ID (auto-generated if missing)
303
- db/ # url-file-db for metadata
304
- {encoded_key}.txt # JSON: {event: version, content_type: mime}
298
+ {encoded_key} # JSON metadata files: {event: version, content_type: mime}
305
299
  ```
306
300
 
307
301
  ## PROTOCOL_DETAILS
@@ -344,8 +338,8 @@ BRAID_UPDATE_FORMAT:
344
338
  INITIALIZATION:
345
339
  - init() called lazily by put/get/serve
346
340
  - init() runs once (subsequent calls return same promise)
347
- - Creates db and meta_db url-file-db instances
348
- - Loads or generates peer ID
341
+ - Creates db object with read/write/delete methods (or uses provided db_folder object)
342
+ - Generates peer ID if not set
349
343
 
350
344
  SUBSCRIPTION_MANAGEMENT:
351
345
  - key_to_subs: Map<string, Map<string, {sendUpdate}>>
@@ -354,11 +348,6 @@ SUBSCRIPTION_MANAGEMENT:
354
348
  - Prevents echo: put() doesn't notify originating peer
355
349
  - Serialized updates: subscribe_chain ensures sequential callback execution
356
350
 
357
- FILE_WATCHING:
358
- - url-file-db monitors db_folder for external changes
359
- - External changes trigger put() with skip_write: true
360
- - Subscriptions notified of external changes
361
-
362
351
  CONCURRENCY_CONTROL:
363
352
  - within_fiber(key, fn) serializes operations per key
364
353
  - Uses promise chain stored in within_fiber.chains[key]
@@ -413,14 +402,6 @@ braid-http:
413
402
  - http_server (braidify): Adds Braid protocol support to Node.js HTTP
414
403
  - fetch (braid_fetch): Braid-aware fetch implementation
415
404
  - Handles: Subscribe headers, Version headers, streaming updates
416
-
417
- url-file-db (^0.0.15):
418
- - Bidirectional URL ↔ filesystem mapping
419
- - Collision-resistant encoding (case-insensitive filesystem safe)
420
- - File watching for external changes
421
- - Separate instances for blobs (db) and metadata (meta_db)
422
- - API change in 0.0.15: use get_canonical_path() instead of url_path_to_canonical_path()
423
- - Fixed in 0.0.13+: properly returns null for non-existent files (not "index" content)
424
405
  ```
425
406
 
426
407
  ## ERROR_CONDITIONS
@@ -455,7 +436,7 @@ TEST_RUNNER: test/test.js
455
436
  - Browser mode: Opens puppeteer, loads test.html
456
437
 
457
438
  TEST_SUITE: test/tests.js
458
- - 40+ test cases covering:
439
+ - 50+ test cases covering:
459
440
  - Basic put/get operations
460
441
  - Subscriptions and updates
461
442
  - Version conflict resolution
package/index.js CHANGED
@@ -126,7 +126,7 @@ function create_braid_blob() {
126
126
  meta.event = their_e
127
127
 
128
128
  if (!options.skip_write)
129
- await braid_blob.db.write(key, body)
129
+ await (options.db || braid_blob.db).write(key, body)
130
130
  if (options.signal?.aborted) return
131
131
 
132
132
  if (options.content_type)
@@ -254,12 +254,12 @@ function create_braid_blob() {
254
254
  // Send an immediate update if needed
255
255
  if (compare_events(result.version?.[0], options.parents?.[0]) > 0) {
256
256
  result.sent = true
257
- result.body = await braid_blob.db.read(key)
257
+ result.body = await (options.db || braid_blob.db).read(key)
258
258
  options.my_subscribe(result)
259
259
  }
260
260
  } else {
261
261
  // If not subscribe, send the body now
262
- result.body = await braid_blob.db.read(key)
262
+ result.body = await (options.db || braid_blob.db).read(key)
263
263
  }
264
264
 
265
265
  return result
@@ -294,7 +294,7 @@ function create_braid_blob() {
294
294
  var meta = await get_meta(key)
295
295
  if (options.signal?.aborted) return
296
296
 
297
- await braid_blob.db.delete(key)
297
+ await (options.db || braid_blob.db).delete(key)
298
298
  await delete_meta(key)
299
299
 
300
300
  // Notify all subscriptions of the delete
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-blob",
3
- "version": "0.0.40",
3
+ "version": "0.0.41",
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
@@ -1635,6 +1635,170 @@ runTest(
1635
1635
  'stopped'
1636
1636
  )
1637
1637
 
1638
+ runTest(
1639
+ "test options.db in put writes to custom db",
1640
+ async () => {
1641
+ var r1 = await braid_fetch(`/eval`, {
1642
+ method: 'POST',
1643
+ body: `void (async () => {
1644
+ var test_key = '/test-custom-db-put-' + Math.random().toString(36).slice(2)
1645
+
1646
+ // Create a simple in-memory db
1647
+ var custom_storage = {}
1648
+ var custom_db = {
1649
+ read: async (key) => custom_storage[key] || null,
1650
+ write: async (key, data) => { custom_storage[key] = data },
1651
+ delete: async (key) => { delete custom_storage[key] }
1652
+ }
1653
+
1654
+ // Put using the custom db
1655
+ await braid_blob.put(test_key, Buffer.from('custom db content'), {
1656
+ version: ['100'],
1657
+ db: custom_db
1658
+ })
1659
+
1660
+ // Verify content is in custom db
1661
+ var custom_content = await custom_db.read(test_key)
1662
+ var custom_ok = custom_content && custom_content.toString() === 'custom db content'
1663
+
1664
+ // Verify content is NOT in the default db
1665
+ var default_content = await braid_blob.db.read(test_key)
1666
+ var default_empty = default_content === null
1667
+
1668
+ res.end(custom_ok && default_empty ? 'true' :
1669
+ 'custom_ok=' + custom_ok + ', default_empty=' + default_empty)
1670
+ })()`
1671
+ })
1672
+ return await r1.text()
1673
+ },
1674
+ 'true'
1675
+ )
1676
+
1677
+ runTest(
1678
+ "test options.db in get reads from custom db",
1679
+ async () => {
1680
+ var r1 = await braid_fetch(`/eval`, {
1681
+ method: 'POST',
1682
+ body: `void (async () => {
1683
+ var test_key = '/test-custom-db-get-' + Math.random().toString(36).slice(2)
1684
+
1685
+ // Create a simple in-memory db with some content
1686
+ var custom_storage = {}
1687
+ custom_storage[test_key] = Buffer.from('from custom db')
1688
+ var custom_db = {
1689
+ read: async (key) => custom_storage[key] || null,
1690
+ write: async (key, data) => { custom_storage[key] = data },
1691
+ delete: async (key) => { delete custom_storage[key] }
1692
+ }
1693
+
1694
+ // Put with skip_write to just create meta
1695
+ await braid_blob.put(test_key, Buffer.from('ignored'), {
1696
+ version: ['200'],
1697
+ skip_write: true
1698
+ })
1699
+
1700
+ // Get using the custom db - should read from custom db
1701
+ var result = await braid_blob.get(test_key, { db: custom_db })
1702
+
1703
+ res.end(result && result.body.toString() === 'from custom db' ? 'true' :
1704
+ 'got: ' + (result ? result.body.toString() : 'null'))
1705
+ })()`
1706
+ })
1707
+ return await r1.text()
1708
+ },
1709
+ 'true'
1710
+ )
1711
+
1712
+ runTest(
1713
+ "test options.db in delete deletes from custom db",
1714
+ async () => {
1715
+ var r1 = await braid_fetch(`/eval`, {
1716
+ method: 'POST',
1717
+ body: `void (async () => {
1718
+ var test_key = '/test-custom-db-delete-' + Math.random().toString(36).slice(2)
1719
+
1720
+ // Create a simple in-memory db
1721
+ var custom_storage = {}
1722
+ custom_storage[test_key] = Buffer.from('custom content')
1723
+ var custom_db = {
1724
+ read: async (key) => custom_storage[key] || null,
1725
+ write: async (key, data) => { custom_storage[key] = data },
1726
+ delete: async (key) => { delete custom_storage[key] }
1727
+ }
1728
+
1729
+ // Also put to default db
1730
+ await braid_blob.put(test_key, Buffer.from('default content'), {
1731
+ version: ['300']
1732
+ })
1733
+
1734
+ // Delete using custom db - should only delete from custom db
1735
+ await braid_blob.delete(test_key, { db: custom_db })
1736
+
1737
+ // Verify custom db content is gone
1738
+ var custom_content = await custom_db.read(test_key)
1739
+ var custom_deleted = custom_content === null
1740
+
1741
+ // Verify default db content still exists
1742
+ var default_content = await braid_blob.db.read(test_key)
1743
+ var default_exists = default_content && default_content.toString() === 'default content'
1744
+
1745
+ res.end(custom_deleted && default_exists ? 'true' :
1746
+ 'custom_deleted=' + custom_deleted + ', default_exists=' + default_exists)
1747
+ })()`
1748
+ })
1749
+ return await r1.text()
1750
+ },
1751
+ 'true'
1752
+ )
1753
+
1754
+ runTest(
1755
+ "test options.db in get subscribe uses custom db for initial update",
1756
+ async () => {
1757
+ var r1 = await braid_fetch(`/eval`, {
1758
+ method: 'POST',
1759
+ body: `void (async () => {
1760
+ var test_key = '/test-custom-db-sub-' + Math.random().toString(36).slice(2)
1761
+
1762
+ // Create a simple in-memory db with content
1763
+ var custom_storage = {}
1764
+ custom_storage[test_key] = Buffer.from('subscribe custom content')
1765
+ var custom_db = {
1766
+ read: async (key) => custom_storage[key] || null,
1767
+ write: async (key, data) => { custom_storage[key] = data },
1768
+ delete: async (key) => { delete custom_storage[key] }
1769
+ }
1770
+
1771
+ // Create meta with version using skip_write
1772
+ await braid_blob.put(test_key, Buffer.from('ignored'), {
1773
+ version: ['400'],
1774
+ skip_write: true
1775
+ })
1776
+
1777
+ // Subscribe using custom db - initial update should come from custom db
1778
+ var ac = new AbortController()
1779
+ var received_content = null
1780
+
1781
+ await braid_blob.get(test_key, {
1782
+ db: custom_db,
1783
+ signal: ac.signal,
1784
+ subscribe: (update) => {
1785
+ received_content = update.body.toString()
1786
+ }
1787
+ })
1788
+
1789
+ // Wait for update
1790
+ await new Promise(done => setTimeout(done, 50))
1791
+ ac.abort()
1792
+
1793
+ res.end(received_content === 'subscribe custom content' ? 'true' :
1794
+ 'got: ' + received_content)
1795
+ })()`
1796
+ })
1797
+ return await r1.text()
1798
+ },
1799
+ 'true'
1800
+ )
1801
+
1638
1802
  }
1639
1803
 
1640
1804
  // Export for Node.js (CommonJS)