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 +13 -32
- package/index.js +4 -4
- package/package.json +1 -1
- package/test/tests.js +164 -0
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,
|
|
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:
|
|
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
|
|
141
|
-
- Writes metadata to meta_folder
|
|
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
|
-
{
|
|
298
|
-
-
|
|
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
|
-
|
|
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
|
|
348
|
-
-
|
|
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
|
-
-
|
|
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
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)
|