OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-store commits / 7c5d028

atproto-store: extract as standalone repo (peer/bridge split, step 2)
Holepunch-native atproto storage substrate (RepoStore/RepoManager write+sign path, firehose sequencer, read-view, blob-store, account-registry) extracted from atproto-bridge/store per DECISION-2026-07-03-peer-bridge-split.md. This is the substrate hyper-peer builds on (§12 atproto-store). Depends on atproto-repo (file:../atproto-repo). Owns its golden fixture (test/vectors). Tests: 19/19. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7c5d0289b32166769b04b0b4aecaf537d5253fa8
by plankatron <fleeky@gmail.com> · 2026-07-03T20:47:31.000Z

Changed files (16) +2576 -0

.gitignore +1 -0
……@@ -0,0 +1 @@
1node_modules/
lib/account-registry.js +40 -0
……@@ -0,0 +1,40 @@
1// AccountRegistry — the multi-tenant PDS's durable roster of hosted repos.
2//
3// One classic Hyperbee (same substrate as RepoStore) on a reserved core of the SHARED
4// corestore: key = did, value = the account record (handle, storeName, signing key,
5// genesis op, active flag). This is what lets a restarted PDS re-open every account it
6// hosts instead of forgetting them. The signing key lives here in the PDS custody model
7// (the bsky PDS holds account keys); hardening the at-rest storage is a tracked loose
8// end (see the hyperident plaintext-seed note in the handoff).
9
10import Hyperbee from 'hyperbee'
11
12export class AccountRegistry {
13 constructor (corestore, opts = {}) {
14 this._store = corestore
15 this._name = opts.name || 'pds-accounts'
16 this.bee = null
17 }
18
19 async ready () {
20 const core = this._store.get({ name: this._name })
21 await core.ready()
22 this.bee = new Hyperbee(core, { keyEncoding: 'utf-8', valueEncoding: 'json' })
23 await this.bee.ready()
24 return this
25 }
26
27 async put (did, record) { await this.bee.put(did, record) }
28
29 async get (did) {
30 const node = await this.bee.get(did)
31 return node ? node.value : null
32 }
33
34 // Every persisted account record, in did (key) order.
35 async * list () {
36 for await (const node of this.bee.createReadStream()) yield node.value
37 }
38
39 async close () { if (this.bee) await this.bee.close() }
40}
lib/blob-store.js +58 -0
……@@ -0,0 +1,58 @@
1// BlobStore — content-addressed blob storage for the PDS (images, avatars).
2//
3// atproto blobs are opaque bytes addressed by a CIDv1/raw/sha256 CID; a record references
4// one via a blob ref ({$type:'blob', ref:{$link:<cid>}, mimeType, size}), and
5// com.atproto.sync.getBlob serves it back by CID. Content-addressed, so it's shared across
6// accounts on this PDS (the same bytes upload once). Backed by ONE classic Hyperbee on the
7// shared corestore — same substrate as RepoStore — value framed as [4-byte headerLen] +
8// JSON{mimeType,size} + raw bytes, so a single binary bee holds bytes + their mime type.
9//
10// (hyperblobs would be the heavier "right" tool for very large blobs; a Hyperbee is ample
11// for avatars/images and adds no dependency. Revisit if blob sizes grow.)
12
13import Corestore from 'corestore'
14import Hyperbee from 'hyperbee'
15import b4a from 'b4a'
16import { cidForBlobSync } from 'atproto-repo/lib/cid.js'
17
18export class BlobStore {
19 constructor (storage, opts = {}) {
20 this._ownStore = !(storage instanceof Corestore)
21 this._store = this._ownStore ? new Corestore(storage) : storage
22 this._name = opts.name || 'pds-blobs'
23 this.bee = null
24 }
25
26 async ready () {
27 const core = this._store.get({ name: this._name })
28 await core.ready()
29 this.bee = new Hyperbee(core, { keyEncoding: 'utf-8', valueEncoding: 'binary' })
30 await this.bee.ready()
31 return this
32 }
33
34 // Store bytes; returns { cid (CID), mimeType, size }. Idempotent (content-addressed).
35 async put (bytes, mimeType = 'application/octet-stream') {
36 const buf = b4a.isBuffer(bytes) ? bytes : b4a.from(bytes)
37 const cid = cidForBlobSync(buf)
38 const header = b4a.from(JSON.stringify({ mimeType, size: buf.length }), 'utf-8')
39 const len = b4a.allocUnsafe(4)
40 len.writeUInt32BE(header.length, 0)
41 await this.bee.put(cid.toString(), b4a.concat([len, header, buf]))
42 return { cid, mimeType, size: buf.length }
43 }
44
45 // Fetch by CID string; returns { mimeType, size, bytes } or null.
46 async get (cidStr) {
47 const node = await this.bee.get(cidStr)
48 if (!node) return null
49 const frame = node.value
50 const headerLen = frame.readUInt32BE(0)
51 const header = JSON.parse(b4a.toString(frame.subarray(4, 4 + headerLen), 'utf-8'))
52 return { mimeType: header.mimeType, size: header.size, bytes: frame.subarray(4 + headerLen) }
53 }
54
55 async has (cidStr) { return !!(await this.bee.get(cidStr)) }
56
57 async close () { if (this.bee) await this.bee.close() }
58}
lib/corestore.js +14 -0
……@@ -0,0 +1,14 @@
1// The store package owns the Holepunch substrate — so the corestore dependency resolves
2// here (store/node_modules), and higher layers (pds) compose store abstractions without
3// reaching for corestore directly.
4
5import Corestore from 'corestore'
6
7// A dir path (or an existing Corestore) -> a Corestore. Idempotent on an instance.
8export function openCorestore (storage) {
9 return storage instanceof Corestore ? storage : new Corestore(storage)
10}
11
12export function isCorestore (x) { return x instanceof Corestore }
13
14export { Corestore }
lib/firehose.js +95 -0
……@@ -0,0 +1,95 @@
1// Firehose #commit frame — the atproto com.atproto.sync.subscribeRepos wire format.
2//
3// A frame = dag-cbor({t:'#commit', op:1}) ++ dag-cbor(body). Built to match millipds
4// (repo_ops.py apply_writes) field-for-field. `seq` and `time` are firehose-server
5// state supplied by the caller. `blocks` is a CAR (root = the new commit) of the
6// changed blocks.
7
8import * as dagCbor from '@ipld/dag-cbor'
9import { writeCar } from 'atproto-repo/lib/repo.js'
10
11// subscribeRepos frame = dag-cbor(header {t, op:1}) ++ dag-cbor(body). Each header is
12// a constant byte prefix (dag-cbor canonicalises map order), so we emit/detect by it.
13export const COMMIT_HEADER = dagCbor.encode({ t: '#commit', op: 1 })
14export const IDENTITY_HEADER = dagCbor.encode({ t: '#identity', op: 1 })
15export const ACCOUNT_HEADER = dagCbor.encode({ t: '#account', op: 1 })
16
17function frame (header, body) {
18 const bodyBytes = dagCbor.encode(body)
19 const out = new Uint8Array(header.length + bodyBytes.length)
20 out.set(header, 0)
21 out.set(bodyBytes, header.length)
22 return out
23}
24
25// Assemble a #commit frame from a RepoManager.applyWrite() result.
26export async function commitFrame (write, { seq, time }) {
27 const carBytes = await writeCar(write.commitCid, [write.commitBlock, ...write.newBlocks])
28 return frame(COMMIT_HEADER, {
29 ops: write.ops,
30 seq,
31 rev: write.rev,
32 since: write.since,
33 prev: null,
34 repo: write.signedCommit.did,
35 time,
36 blobs: [],
37 blocks: carBytes,
38 commit: write.commitCid,
39 rebase: false,
40 tooBig: false,
41 prevData: write.prevData
42 })
43}
44
45// #identity — tells a relay a repo's handle/DID binding (needed for discovery).
46export function identityFrame ({ seq, did, handle, time }) {
47 const body = { seq, did, time }
48 if (handle) body.handle = handle
49 return frame(IDENTITY_HEADER, body)
50}
51
52// #account — repo hosting status (active true = "this PDS hosts this repo").
53export function accountFrame ({ seq, did, active = true, status, time }) {
54 const body = { seq, did, time, active }
55 if (status) body.status = status
56 return frame(ACCOUNT_HEADER, body)
57}
58
59const HEADERS = [['#commit', COMMIT_HEADER], ['#identity', IDENTITY_HEADER], ['#account', ACCOUNT_HEADER]]
60
61// Re-stamp a frame's `seq` (relay egress): a relay aggregates many PDS firehoses and MUST
62// present its own monotonic seq in each frame (the cursor contract). Decodes, replaces
63// body.seq, re-encodes with the same header. Unknown frames pass through unchanged.
64export function restampFrame (frameBytes, seq) {
65 const d = decodeFrame(frameBytes)
66 if (!d) return frameBytes
67 const header = d.t === '#commit' ? COMMIT_HEADER : d.t === '#identity' ? IDENTITY_HEADER : ACCOUNT_HEADER
68 d.body.seq = seq
69 const bodyBytes = dagCbor.encode(d.body)
70 const out = new Uint8Array(header.length + bodyBytes.length)
71 out.set(header, 0)
72 out.set(bodyBytes, header.length)
73 return out
74}
75
76// Split + decode ANY known subscribeRepos frame → { t, body } (null if unknown).
77export function decodeFrame (frameBytes) {
78 for (const [t, header] of HEADERS) {
79 if (frameBytes.length >= header.length &&
80 Buffer.from(frameBytes.subarray(0, header.length)).equals(Buffer.from(header))) {
81 return { t, body: dagCbor.decode(frameBytes.subarray(header.length)) }
82 }
83 }
84 return null
85}
86
87// Split + decode a #commit frame. Returns { header, body } or null if not a #commit.
88export function decodeCommitFrame (frameBytes) {
89 const prefix = frameBytes.subarray(0, COMMIT_HEADER.length)
90 if (!Buffer.from(prefix).equals(Buffer.from(COMMIT_HEADER))) return null
91 return {
92 header: { t: '#commit', op: 1 },
93 body: dagCbor.decode(frameBytes.subarray(COMMIT_HEADER.length))
94 }
95}
lib/read-view.js +69 -0
……@@ -0,0 +1,69 @@
1// Bridge READ-view — pure functions turning RepoStore (Hyperbee) state into atproto
2// wire responses (com.atproto.sync.* / repo.*). No HTTP here (that's the thin face,
3// later); no storage-specific atproto state in the store. rev/prev are INPUTS — commit
4// head-state (monotonic rev, prev-linking) advances on writes, which is the next slice.
5//
6// Curve note: the commit block carries our secp256k1 sig (millipds uses P-256), so the
7// commit CID differs from millipds — but the MST + record blocks are curve-independent
8// and byte-identical to millipds output (verified against the golden CAR).
9
10import * as dagCbor from '@ipld/dag-cbor'
11import { buildMst, reachableBlocks } from 'atproto-repo/lib/mst.js'
12import { unsignedCommit, signCommit, commitCid } from 'atproto-repo/lib/commit.js'
13import { cidForBytesSync } from 'atproto-repo/lib/cid.js'
14import { writeCar } from 'atproto-repo/lib/repo.js'
15
16// Build every block of the repo from the store's exact bytes (no re-encode).
17async function assembleRepo (store, { did, rev, prev = null, privateKey }) {
18 const recs = await store.records() // [{ key, bytes, record }]
19 const recordBlocks = []
20 const mstRecords = []
21 for (const r of recs) {
22 const cid = cidForBytesSync(r.bytes)
23 recordBlocks.push({ cid, bytes: r.bytes })
24 mstRecords.push({ key: r.key, value: cid })
25 }
26 const { root: dataCid, ns } = buildMst(mstRecords)
27 const mstBlocks = [...reachableBlocks(ns, dataCid).values()]
28 const signed = signCommit(unsignedCommit({ did, rev, data: dataCid, prev }), privateKey)
29 const cCid = commitCid(signed)
30 const commitBlock = { cid: cCid, bytes: dagCbor.encode(signed) }
31 return { commitCid: cCid, dataCid, signedCommit: signed, commitBlock, mstBlocks, recordBlocks }
32}
33
34// com.atproto.sync.getRepo -> full CAR (commit + MST + records).
35export async function getRepo (store, meta) {
36 const r = await assembleRepo(store, meta)
37 const carBytes = await writeCar(r.commitCid, [r.commitBlock, ...r.mstBlocks, ...r.recordBlocks])
38 return { carBytes, commitCid: r.commitCid, dataCid: r.dataCid, signedCommit: r.signedCommit }
39}
40
41// com.atproto.sync.getLatestCommit -> { cid, rev }.
42export async function getLatestCommit (store, meta) {
43 const r = await assembleRepo(store, meta)
44 return { cid: r.commitCid.toString(), rev: r.signedCommit.rev }
45}
46
47// com.atproto.repo.getRecord -> { uri, cid, value }.
48export async function getRecord (store, collection, rkey, { did }) {
49 const raw = await store.getRaw(collection, rkey)
50 if (!raw) return null
51 return {
52 uri: `at://${did}/${collection}/${rkey}`,
53 cid: cidForBytesSync(raw.bytes).toString(),
54 value: raw.record
55 }
56}
57
58// com.atproto.repo.listRecords -> [{ uri, cid, value }] for a collection.
59export async function listRecords (store, collection, { did }) {
60 const out = []
61 for await (const { rkey, record } of store.list(collection)) {
62 out.push({
63 uri: `at://${did}/${collection}/${rkey}`,
64 cid: cidForBytesSync(dagCbor.encode(record)).toString(),
65 value: record
66 })
67 }
68 return out
69}
lib/repo-manager.js +116 -0
……@@ -0,0 +1,116 @@
1// RepoManager — the WRITE path + commit head-state over a RepoStore.
2//
3// Holds the current commit head { rev, commitCid, dataCid }. Each applyWrite mutates
4// the store, rebuilds the MST, mints a new monotonic rev, signs a new commit, and
5// returns the diff needed to emit a firehose #commit (see firehose.js).
6//
7// `newBlocks` is the MINIMAL Sync-v1.1 covering proof (slice 3b): the MST-inversion
8// proof set (`created | deletion_proof | inductive_proof_nodes`) plus the new record —
9// NOT the whole tree (which would trip a relay's `tooBig`). See firehoseProofBlocks
10// in repo/lib/mst.js. Prior record CIDs travel in each op's `prev` so a cold relay can
11// invert with no old-tree state.
12
13import * as dagCbor from '@ipld/dag-cbor'
14import { buildMst, firehoseProofBlocks } from 'atproto-repo/lib/mst.js'
15import { unsignedCommit, signCommit, commitCid } from 'atproto-repo/lib/commit.js'
16import { cidForBytesSync } from 'atproto-repo/lib/cid.js'
17import { tidNow } from './tid.js'
18
19export class RepoManager {
20 constructor (store, { did, privateKey, clock } = {}) {
21 this.store = store
22 this.did = did
23 this.privateKey = privateKey
24 this._clock = clock // optional injectable micros clock for tests
25 this.rev = null
26 this.commitCid = null
27 this.dataCid = null
28 }
29
30 _nextRev () { return this._clock ? tidNow(0, this._clock) : tidNow() }
31
32 // Build the current head. On restart, RESTORE the persisted rev (recomputing the commit
33 // from records is deterministic, so the head is byte-stable); only mint a fresh rev for a
34 // genuinely new repo (no saved head). Re-minting on every open forged a "phantom commit"
35 // with a new rev/CID on each restart — see docs/ARCHITECTURE-AUDIT.md.
36 async init () {
37 const saved = await this.store.loadHead()
38 const rev = saved ? saved.rev : this._nextRev()
39 const { commitCid: cc, dataCid } = await this._commitCurrent(rev)
40 this.rev = rev; this.commitCid = cc; this.dataCid = dataCid
41 if (!saved) await this.store.saveHead({ rev, commitCid: cc.toString(), dataCid: dataCid.toString() })
42 return this
43 }
44
45 async _commitCurrent (rev) {
46 const recs = await this.store.records()
47 const mstRecords = recs.map(r => ({ key: r.key, value: cidForBytesSync(r.bytes) }))
48 const { root: dataCid, ns } = buildMst(mstRecords)
49 const signed = signCommit(unsignedCommit({ did: this.did, rev, data: dataCid, prev: null }), this.privateKey)
50 return { signed, commitCid: commitCid(signed), dataCid, ns }
51 }
52
53 // Apply one write and advance the head. Returns everything firehose.js needs.
54 // op = { action: 'create'|'update'|'delete', collection, rkey, record? }
55 async applyWrite ({ action, collection, rkey, record }) {
56 const prevRev = this.rev
57 const prevData = this.dataCid
58 const path = `${collection}/${rkey}`
59
60 // Capture the OLD record set + the prior value at this path — both are inputs to
61 // the MST-inversion proof (the prior CID lets a cold relay invert the commit).
62 const oldRecs = await this.store.records()
63 const existing = await this.store.getRaw(collection, rkey)
64 const priorCid = existing ? cidForBytesSync(existing.bytes) : null
65
66 let opCid = null
67 if (action === 'create' || action === 'update') {
68 await this.store.put(collection, rkey, record)
69 const raw = await this.store.getRaw(collection, rkey)
70 opCid = cidForBytesSync(raw.bytes)
71 } else if (action === 'delete') {
72 await this.store.del(collection, rkey)
73 } else {
74 throw new Error(`invalid action: ${action}`)
75 }
76
77 const rev = this._nextRev()
78 const { signed, commitCid: cc, dataCid } = await this._commitCurrent(rev)
79
80 // Minimal Sync-v1.1 covering proof (created | deletion_proof | inductive_proof).
81 const newRecs = await this.store.records()
82 const toMst = (recs) => recs.map(r => ({ key: r.key, value: cidForBytesSync(r.bytes) }))
83 const proofOp = { action, path, cid: opCid, prev: priorCid }
84 const proof = firehoseProofBlocks({ oldRecords: toMst(oldRecs), newRecords: toMst(newRecs), ops: [proofOp] })
85
86 // Invariants: the independently-rebuilt proof trees must match the signed commit.
87 if (!proof.dataCid.equals(dataCid)) throw new Error('proof dataCid != signed commit data')
88 if (!proof.oldRoot.equals(prevData)) throw new Error('proof oldRoot != prevData')
89
90 const blocks = [...proof.blocks.values()]
91 if (opCid) {
92 const raw = await this.store.getRaw(collection, rkey)
93 blocks.push({ cid: opCid, bytes: raw.bytes })
94 }
95
96 // Wire op: {action, path, cid} per the repoOp lexicon; `prev` carried for
97 // update/delete so a relay can invert without holding the old tree.
98 const op = { action, path, cid: opCid }
99 if (action === 'update' || action === 'delete') op.prev = priorCid
100
101 this.rev = rev; this.commitCid = cc; this.dataCid = dataCid
102 await this.store.saveHead({ rev, commitCid: cc.toString(), dataCid: dataCid.toString() })
103
104 return {
105 commitCid: cc,
106 commitBlock: { cid: cc, bytes: dagCbor.encode(signed) },
107 signedCommit: signed,
108 dataCid,
109 prevData,
110 rev,
111 since: prevRev,
112 ops: [op],
113 newBlocks: blocks
114 }
115 }
116}
lib/repo-store.js +100 -0
……@@ -0,0 +1,100 @@
1// RepoStore — the Holepunch-native home for an atproto repo.
2//
3// Records live in a CLASSIC Hyperbee (matches our production stack — NOT hyperbee2/
4// autobee, which are experimental; see the ecosystem audit): key = `collection/rkey`,
5// value = the record's canonical DAG-CBOR bytes. Single-writer (one author key);
6// multi-device is handled by sharing the mnemonic-derived key. The atproto-repo
7// module is the *bridge* that reads this store and emits MST/CAR/commits on demand —
8// this layer stores nothing atproto-specific beyond the records themselves.
9
10import Corestore from 'corestore'
11import Hyperbee from 'hyperbee'
12import * as dagCbor from '@ipld/dag-cbor'
13import b4a from 'b4a'
14
15// Range upper bound for a `collection/` prefix scan. '/' (0x2f) + 1 = '0' (0x30),
16// and all atproto rkey chars sort >= '2' > '/', so `collection/…` < `collection0`.
17function prefixUpper (prefix) {
18 return prefix.slice(0, -1) + String.fromCharCode(prefix.charCodeAt(prefix.length - 1) + 1)
19}
20
21export class RepoStore {
22 constructor (storage, opts = {}) {
23 this._ownStore = !(storage instanceof Corestore)
24 this._store = this._ownStore ? new Corestore(storage) : storage
25 this._name = opts.name || 'atproto-repo'
26 this._key = opts.key || null // open a specific core by KEY (a replica of another instance's repo)
27 this.bee = null
28 this.core = null
29 }
30
31 static key (collection, rkey) { return `${collection}/${rkey}` }
32
33 // The underlying Corestore — so the PDS can open sibling cores (e.g. the firehose
34 // sequence log) on the SAME store rather than a second Corestore over the same dir.
35 get corestore () { return this._store }
36
37 async ready () {
38 // By KEY = replicate another instance's repo (read-only over the swarm); by NAME = our own.
39 this.core = this._key ? this._store.get({ key: this._key }) : this._store.get({ name: this._name })
40 await this.core.ready()
41 this.bee = new Hyperbee(this.core, { keyEncoding: 'utf-8', valueEncoding: 'binary' })
42 await this.bee.ready()
43 return this
44 }
45
46 async put (collection, rkey, record) {
47 await this.bee.put(RepoStore.key(collection, rkey), b4a.from(dagCbor.encode(record)))
48 }
49
50 async get (collection, rkey) {
51 const node = await this.bee.get(RepoStore.key(collection, rkey))
52 return node ? dagCbor.decode(node.value) : null
53 }
54
55 // Raw stored bytes + decoded record (the bridge CIDs the exact bytes).
56 async getRaw (collection, rkey) {
57 const node = await this.bee.get(RepoStore.key(collection, rkey))
58 return node ? { bytes: node.value, record: dagCbor.decode(node.value) } : null
59 }
60
61 async del (collection, rkey) {
62 await this.bee.del(RepoStore.key(collection, rkey))
63 }
64
65 // Yield { rkey, record } for one collection, in key order.
66 async * list (collection) {
67 const gte = `${collection}/`
68 const lt = prefixUpper(gte)
69 for await (const node of this.bee.createReadStream({ gte, lt })) {
70 yield { rkey: node.key.slice(gte.length), record: dagCbor.decode(node.value) }
71 }
72 }
73
74 // All records as { key, bytes, record } in key order. `bytes` is the canonical
75 // DAG-CBOR — the bridge CIDs these directly to build the MST (byte-exact).
76 async records () {
77 const out = []
78 for await (const node of this.bee.createReadStream()) {
79 out.push({ key: node.key, bytes: node.value, record: dagCbor.decode(node.value) })
80 }
81 return out
82 }
83
84 // Persist the commit head ({ rev, commitCid, dataCid }) as core USER DATA — NOT a record,
85 // so it never appears in records()/the MST. This is what makes the head stable across a
86 // restart: the rev is restored, not re-minted (which would forge a phantom commit).
87 async saveHead (head) {
88 await this.core.setUserData('atproto-head', b4a.from(JSON.stringify(head)))
89 }
90
91 async loadHead () {
92 const v = await this.core.getUserData('atproto-head')
93 return v ? JSON.parse(b4a.toString(v)) : null
94 }
95
96 async close () {
97 if (this.bee) await this.bee.close()
98 if (this._ownStore) await this._store.close()
99 }
100}
lib/tid.js +26 -0
……@@ -0,0 +1,26 @@
1// atproto TID — the 13-char, lexicographically-sortable timestamp id used for `rev`
2// (and default rkeys). value = (unixMicros << 10) | clockId(10 bits), top bit 0,
3// encoded in the sortable base32 alphabet. Monotonic within a process.
4
5const S32 = '234567abcdefghijklmnopqrstuvwxyz' // atproto sortable base32
6
7function s32encode (n) {
8 let s = ''
9 for (let i = 0; i < 13; i++) { s = S32[Number(n % 32n)] + s; n /= 32n }
10 return s
11}
12
13let _last = 0n
14
15// clock() returns micros; injectable for deterministic tests.
16export function tidNow (clockId = 0, clock = () => BigInt(Date.now()) * 1000n) {
17 let micros = clock()
18 const val = (micros << 10n) | (BigInt(clockId) & 0x3ffn)
19 const t = val > _last ? val : _last + 1n // strictly monotonic
20 _last = t
21 return s32encode(t)
22}
23
24export function isValidTid (s) {
25 return typeof s === 'string' && s.length === 13 && [...s].every(c => S32.includes(c))
26}
package-lock.json +1561 -0
……@@ -0,0 +1,1561 @@
1{
2 "name": "atproto-store",
3 "version": "0.0.1",
4 "lockfileVersion": 3,
5 "requires": true,
6 "packages": {
7 "": {
8 "name": "atproto-store",
9 "version": "0.0.1",
10 "dependencies": {
11 "@ipld/dag-cbor": "^9.2.1",
12 "atproto-repo": "file:../atproto-repo",
13 "b4a": "^1.6.0",
14 "corestore": "^7.4.0",
15 "hyperbee": "^2.20.0",
16 "random-access-memory": "^6.2.0"
17 },
18 "devDependencies": {
19 "brittle": "^4.0.2"
20 }
21 },
22 "../atproto-repo": {
23 "version": "0.0.1",
24 "hasInstallScript": true,
25 "dependencies": {
26 "@ipld/car": "^5.4.0",
27 "@ipld/dag-cbor": "^9.2.1",
28 "@noble/curves": "^1.9.7",
29 "@noble/hashes": "^1.5.0",
30 "multiformats": "^13.3.0"
31 },
32 "devDependencies": {
33 "atproto-identity": "file:../atproto-identity",
34 "brittle": "^4.0.2"
35 }
36 },
37 "node_modules/@hyperswarm/secret-stream": {
38 "version": "6.9.1",
39 "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.9.1.tgz",
40 "integrity": "sha512-xb0S5y3YJwBakD77JOGBHlBxdp63mHClZoXBYoLv+9wH8e054ESKlmQptWqjJK5dv5VMUIVYOJB4MaOpB0JdGw==",
41 "license": "Apache-2.0",
42 "dependencies": {
43 "b4a": "^1.1.0",
44 "hypercore-crypto": "^3.3.1",
45 "noise-curve-ed": "^2.0.1",
46 "noise-handshake": "^4.0.0",
47 "sodium-secretstream": "^1.1.0",
48 "sodium-universal": "^5.0.0",
49 "streamx": "^2.14.0",
50 "timeout-refresh": "^2.0.0",
51 "unslab": "^1.3.0"
52 }
53 },
54 "node_modules/@ipld/dag-cbor": {
55 "version": "9.2.7",
56 "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.7.tgz",
57 "integrity": "sha512-ZmfXmElRWATr+hoUTSAOr6HUcjVhOcNHDqgczc76qte2DHHFEK0ZhNzUcdTDQhF/VSIvf2ioaRTRLWwLc83sNw==",
58 "license": "Apache-2.0 OR MIT",
59 "dependencies": {
60 "cborg": "^5.0.1",
61 "multiformats": "^13.1.0"
62 }
63 },
64 "node_modules/@jridgewell/resolve-uri": {
65 "version": "3.1.2",
66 "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
67 "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
68 "dev": true,
69 "license": "MIT",
70 "engines": {
71 "node": ">=6.0.0"
72 }
73 },
74 "node_modules/@jridgewell/sourcemap-codec": {
75 "version": "1.5.5",
76 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
77 "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
78 "dev": true,
79 "license": "MIT"
80 },
81 "node_modules/@jridgewell/trace-mapping": {
82 "version": "0.3.31",
83 "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
84 "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
85 "dev": true,
86 "license": "MIT",
87 "dependencies": {
88 "@jridgewell/resolve-uri": "^3.1.0",
89 "@jridgewell/sourcemap-codec": "^1.4.14"
90 }
91 },
92 "node_modules/@types/istanbul-lib-coverage": {
93 "version": "2.0.6",
94 "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
95 "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
96 "dev": true,
97 "license": "MIT"
98 },
99 "node_modules/atproto-repo": {
100 "resolved": "../atproto-repo",
101 "link": true
102 },
103 "node_modules/b4a": {
104 "version": "1.8.1",
105 "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz",
106 "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==",
107 "license": "Apache-2.0",
108 "peerDependencies": {
109 "react-native-b4a": "*"
110 },
111 "peerDependenciesMeta": {
112 "react-native-b4a": {
113 "optional": true
114 }
115 }
116 },
117 "node_modules/bare-abort": {
118 "version": "2.0.13",
119 "resolved": "https://registry.npmjs.org/bare-abort/-/bare-abort-2.0.13.tgz",
120 "integrity": "sha512-zdc8l88eB11Jsz5rDd6sCAgv2kUFXgdrZWoMlgU6JMkfAi1/uuGFC3IEHswKbIRQTk5H3T5CMuechsXYxiaHlQ==",
121 "dev": true,
122 "license": "Apache-2.0"
123 },
124 "node_modules/bare-addon-resolve": {
125 "version": "1.10.0",
126 "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz",
127 "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==",
128 "license": "Apache-2.0",
129 "dependencies": {
130 "bare-module-resolve": "^1.10.0",
131 "bare-semver": "^1.0.0"
132 },
133 "peerDependencies": {
134 "bare-url": "*"
135 },
136 "peerDependenciesMeta": {
137 "bare-url": {
138 "optional": true
139 }
140 }
141 },
142 "node_modules/bare-ansi-escapes": {
143 "version": "2.2.3",
144 "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz",
145 "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==",
146 "license": "Apache-2.0",
147 "dependencies": {
148 "bare-stream": "^2.6.5"
149 },
150 "peerDependencies": {
151 "bare-buffer": "*"
152 },
153 "peerDependenciesMeta": {
154 "bare-buffer": {
155 "optional": true
156 }
157 }
158 },
159 "node_modules/bare-assert": {
160 "version": "1.2.0",
161 "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz",
162 "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==",
163 "license": "Apache-2.0",
164 "dependencies": {
165 "bare-inspect": "^3.1.2"
166 }
167 },
168 "node_modules/bare-buffer": {
169 "version": "3.6.1",
170 "resolved": "https://registry.npmjs.org/bare-buffer/-/bare-buffer-3.6.1.tgz",
171 "integrity": "sha512-8mzp60t6jdSD3cEGmyxAV1YrAN2+mnxiUvBPQHJJ4WNk0hSVLJGSIEolRj09ZP8yt/+oSj2N2f5kDVW8297n4A==",
172 "devOptional": true,
173 "license": "Apache-2.0",
174 "engines": {
175 "bare": ">=1.20.0"
176 }
177 },
178 "node_modules/bare-bundle": {
179 "version": "1.10.0",
180 "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz",
181 "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==",
182 "dev": true,
183 "license": "Apache-2.0",
184 "peerDependencies": {
185 "bare-buffer": "*",
186 "bare-url": "*"
187 },
188 "peerDependenciesMeta": {
189 "bare-buffer": {
190 "optional": true
191 },
192 "bare-url": {
193 "optional": true
194 }
195 }
196 },
197 "node_modules/bare-channel": {
198 "version": "5.2.4",
199 "resolved": "https://registry.npmjs.org/bare-channel/-/bare-channel-5.2.4.tgz",
200 "integrity": "sha512-enkaOtvXUiZsLBSbataxV/QBjehCOK+SUTd2JQWUYW2iIOufpPu9jyZ9bqJqkEquktrK/rpEMVEmKsPzoci/sA==",
201 "dev": true,
202 "license": "Apache-2.0",
203 "dependencies": {
204 "bare-events": "^2.0.0",
205 "bare-stream": "^2.7.0",
206 "bare-structured-clone": "^1.4.0"
207 },
208 "engines": {
209 "bare": ">=1.7.0"
210 }
211 },
212 "node_modules/bare-cov": {
213 "version": "1.2.2",
214 "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.2.tgz",
215 "integrity": "sha512-d/b4HdL5ohZDwBvDegJeSkmo1X2MGlm22jXzuY2D7wl2W1+7nk/b7+HMogwgUa+zi8rVVRJQKHCHPHYBJpvlvw==",
216 "dev": true,
217 "license": "Apache-2.0",
218 "dependencies": {
219 "bare-fs": "^4.1.2",
220 "bare-inspector": "^6.0.1",
221 "bare-path": "^3.0.0",
222 "bare-process": "^4.2.1",
223 "bare-url": "^2.1.5",
224 "bare-v8-to-istanbul": "^1.0.2",
225 "picomatch": "^4.0.2",
226 "which-runtime": "^1.2.1"
227 }
228 },
229 "node_modules/bare-crypto": {
230 "version": "1.15.3",
231 "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.15.3.tgz",
232 "integrity": "sha512-macV9lbyJTsLPRXJkBtz8ivTGEo3LCyJInLT9IB/PWJ7pRXwvHs/FP4bx/fWw+HZkiepIYCAV2cuU5CR92XWCw==",
233 "dev": true,
234 "license": "Apache-2.0",
235 "dependencies": {
236 "bare-assert": "^1.2.0",
237 "bare-stream": "^2.6.3"
238 },
239 "peerDependencies": {
240 "bare-buffer": "*"
241 },
242 "peerDependenciesMeta": {
243 "bare-buffer": {
244 "optional": true
245 }
246 }
247 },
248 "node_modules/bare-debug-log": {
249 "version": "2.0.0",
250 "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz",
251 "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==",
252 "dev": true,
253 "license": "Apache-2.0",
254 "dependencies": {
255 "bare-os": "^3.0.1"
256 }
257 },
258 "node_modules/bare-dns": {
259 "version": "2.1.4",
260 "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz",
261 "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==",
262 "dev": true,
263 "license": "Apache-2.0",
264 "engines": {
265 "bare": ">=1.7.0"
266 }
267 },
268 "node_modules/bare-encoding": {
269 "version": "1.0.3",
270 "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz",
271 "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==",
272 "dev": true,
273 "license": "Apache-2.0",
274 "peerDependencies": {
275 "bare-buffer": "*"
276 },
277 "peerDependenciesMeta": {
278 "bare-buffer": {
279 "optional": true
280 }
281 }
282 },
283 "node_modules/bare-env": {
284 "version": "3.0.0",
285 "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz",
286 "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==",
287 "dev": true,
288 "license": "Apache-2.0",
289 "dependencies": {
290 "bare-os": "^3.0.1"
291 }
292 },
293 "node_modules/bare-events": {
294 "version": "2.9.1",
295 "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz",
296 "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==",
297 "license": "Apache-2.0",
298 "peerDependencies": {
299 "bare-abort-controller": "*"
300 },
301 "peerDependenciesMeta": {
302 "bare-abort-controller": {
303 "optional": true
304 }
305 }
306 },
307 "node_modules/bare-format": {
308 "version": "1.0.2",
309 "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.2.tgz",
310 "integrity": "sha512-GswdhnOnP9QtwRbrf4wLApw5widkaLMsLe2XOs35fQD2YfEN1ApoGka+cZ7PfvzxMgfYXmMhj/2OGlVn5/Dxgw==",
311 "dev": true,
312 "license": "Apache-2.0",
313 "dependencies": {
314 "bare-inspect": "^3.0.0"
315 }
316 },
317 "node_modules/bare-fs": {
318 "version": "4.7.3",
319 "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.3.tgz",
320 "integrity": "sha512-xRgplks8SvcKkdlv2M6Z2LZmRsmqd+x0nXXGXeMEjwdibj1HSDrlnqBRLeYdMvsgCox7Bq0e+DHwfczOfsn6IA==",
321 "license": "Apache-2.0",
322 "dependencies": {
323 "bare-events": "^2.5.4",
324 "bare-path": "^3.0.0",
325 "bare-stream": "^2.6.4",
326 "bare-url": "^2.2.2",
327 "fast-fifo": "^1.3.2"
328 },
329 "engines": {
330 "bare": ">=1.16.0"
331 },
332 "peerDependencies": {
333 "bare-buffer": "*"
334 },
335 "peerDependenciesMeta": {
336 "bare-buffer": {
337 "optional": true
338 }
339 }
340 },
341 "node_modules/bare-hrtime": {
342 "version": "2.1.1",
343 "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz",
344 "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==",
345 "dev": true,
346 "license": "Apache-2.0"
347 },
348 "node_modules/bare-http-parser": {
349 "version": "1.1.5",
350 "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.1.5.tgz",
351 "integrity": "sha512-sPaDetLbWRmth04JmuTCvw/hoNdWJvYUJN8n1tYdGW3HM0mMnCvK2f0oIxE6HK7iOq+WlsRzEMg1LT/b0wGbLQ==",
352 "dev": true,
353 "license": "Apache-2.0"
354 },
355 "node_modules/bare-http1": {
356 "version": "4.5.7",
357 "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.7.tgz",
358 "integrity": "sha512-PRuzs9ywt4vUvrC3mnHhQyaQfLYzdFy8XKOH0oKeKmYfyYYUqXBkdbJE2csESLBxJEbKDBjxPGLU+Qa0l1ds4A==",
359 "dev": true,
360 "license": "Apache-2.0",
361 "dependencies": {
362 "bare-events": "^2.6.0",
363 "bare-http-parser": "^1.1.1",
364 "bare-stream": "^2.10.0",
365 "bare-tcp": "^2.2.0"
366 },
367 "peerDependencies": {
368 "bare-buffer": "*",
369 "bare-url": "*"
370 },
371 "peerDependenciesMeta": {
372 "bare-buffer": {
373 "optional": true
374 },
375 "bare-url": {
376 "optional": true
377 }
378 }
379 },
380 "node_modules/bare-https": {
381 "version": "3.0.0",
382 "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-3.0.0.tgz",
383 "integrity": "sha512-W1GRSCzn+xXKf5bMcPs/hg6Ga1bxPqb7owGfS+tvlBQfPe5Q2STcanRuKZrgU60v5uKrhXH5cgWwM+DLqvXZgQ==",
384 "dev": true,
385 "license": "Apache-2.0",
386 "dependencies": {
387 "bare-http1": "^4.4.0",
388 "bare-tcp": "^2.2.0",
389 "bare-tls": "^3.0.0"
390 }
391 },
392 "node_modules/bare-inspect": {
393 "version": "3.1.4",
394 "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz",
395 "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==",
396 "license": "Apache-2.0",
397 "dependencies": {
398 "bare-ansi-escapes": "^2.1.0",
399 "bare-type": "^1.0.0"
400 },
401 "engines": {
402 "bare": ">=1.18.0"
403 }
404 },
405 "node_modules/bare-inspector": {
406 "version": "6.1.0",
407 "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-6.1.0.tgz",
408 "integrity": "sha512-PRxmZ4gF+K3TLzGubgRFvzdECybTCSKackgNsAdd4e7SdvICxMkGj+p5iX7bSKYSmgDnxgCR+2Z6UXmWykKhvw==",
409 "dev": true,
410 "license": "Apache-2.0",
411 "dependencies": {
412 "bare-events": "^2.1.0",
413 "bare-http1": "^4.0.0",
414 "bare-stream": "^2.0.0",
415 "bare-url": "^2.0.0",
416 "bare-ws": "^3.0.0"
417 },
418 "engines": {
419 "bare": ">=1.29.0"
420 },
421 "peerDependencies": {
422 "bare-tcp": "*"
423 },
424 "peerDependenciesMeta": {
425 "bare-tcp": {
426 "optional": true
427 }
428 }
429 },
430 "node_modules/bare-module": {
431 "version": "6.4.0",
432 "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.4.0.tgz",
433 "integrity": "sha512-Yn4V5g5EqGQL4LYUOmt7fjKzj2JPWyJOqE3lPoeZwfUH5rk4CKUfZj6JhDwbzhBYCqqmUgjgQ5aY8cihAPILLA==",
434 "dev": true,
435 "license": "Apache-2.0",
436 "dependencies": {
437 "bare-bundle": "^1.3.0",
438 "bare-module-lexer": "^1.0.0",
439 "bare-module-resolve": "^1.8.0",
440 "bare-path": "^3.0.0",
441 "bare-type-stripper": "^0.1.2",
442 "bare-url": "^2.0.1"
443 },
444 "engines": {
445 "bare": ">=1.29.4"
446 },
447 "peerDependencies": {
448 "bare-buffer": "*"
449 },
450 "peerDependenciesMeta": {
451 "bare-buffer": {
452 "optional": true
453 }
454 }
455 },
456 "node_modules/bare-module-lexer": {
457 "version": "1.6.0",
458 "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.6.0.tgz",
459 "integrity": "sha512-uVDdbh6EnypLg5K4Gz5GkFloXiJu0QN5S1JNiKdKbiLoyTjUqIXKnHR/8fb2QejDT/+5BNlnqlbkAAg1iEZSFA==",
460 "dev": true,
461 "license": "Apache-2.0",
462 "dependencies": {
463 "require-addon": "^1.0.2"
464 },
465 "peerDependencies": {
466 "bare-buffer": "*"
467 },
468 "peerDependenciesMeta": {
469 "bare-buffer": {
470 "optional": true
471 }
472 }
473 },
474 "node_modules/bare-module-resolve": {
475 "version": "1.12.2",
476 "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.2.tgz",
477 "integrity": "sha512-j+hiD5k99qec4KjJvYsI67q5AOBifmy9JG3oeMVxTmvrhn2sIdp8StrUvZu4YNgwTpO+NhniQG16N1ETDe1k5w==",
478 "license": "Apache-2.0",
479 "dependencies": {
480 "bare-semver": "^1.0.0"
481 },
482 "peerDependencies": {
483 "bare-url": "*"
484 },
485 "peerDependenciesMeta": {
486 "bare-url": {
487 "optional": true
488 }
489 }
490 },
491 "node_modules/bare-net": {
492 "version": "2.3.2",
493 "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.3.2.tgz",
494 "integrity": "sha512-I+yz+pqbYsBkxDsnu5vkKvy7RSNY9CcAvu2jZT6PsmdXJQG1i3dmD5V7xc3334OVp2absgtUEYLmmuNFlphBzg==",
495 "dev": true,
496 "license": "Apache-2.0",
497 "dependencies": {
498 "bare-events": "^2.2.2",
499 "bare-pipe": "^4.0.0",
500 "bare-stream": "^2.0.0",
501 "bare-tcp": "^2.0.0"
502 }
503 },
504 "node_modules/bare-os": {
505 "version": "3.9.3",
506 "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.3.tgz",
507 "integrity": "sha512-fF4Q7QsyKVF5Rj0qvI8BgUNjqzC2JvQlpTaPLjVJVxYVUX5Zr9un+y3w1HmA4nNKdFmRBT8z/WmrjvXzXVerKQ==",
508 "license": "Apache-2.0",
509 "engines": {
510 "bare": ">=1.14.0"
511 }
512 },
513 "node_modules/bare-path": {
514 "version": "3.0.1",
515 "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.1.tgz",
516 "integrity": "sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==",
517 "license": "Apache-2.0",
518 "dependencies": {
519 "bare-os": "^3.0.1"
520 }
521 },
522 "node_modules/bare-pipe": {
523 "version": "4.2.2",
524 "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.2.2.tgz",
525 "integrity": "sha512-Wkf6//CmKS8inHrkgi3X/xNMeaGfs2I0ayLM0B8cbPB4DxYHcoes6Ng+muCBfXNIJ9qmEjwDDJjAUz7nRxxjgQ==",
526 "dev": true,
527 "license": "Apache-2.0",
528 "peer": true,
529 "dependencies": {
530 "bare-events": "^2.0.0",
531 "bare-stream": "^2.0.0"
532 },
533 "engines": {
534 "bare": ">=1.16.0"
535 }
536 },
537 "node_modules/bare-posix": {
538 "version": "1.0.1",
539 "resolved": "https://registry.npmjs.org/bare-posix/-/bare-posix-1.0.1.tgz",
540 "integrity": "sha512-b4quA8dyRVvX0aFIAIiev5zFATHwIUGr42Gmt+CqiBNg/bdsNkrj4bbkeotMOMiljJS4DCME5kywJO/TFwKo1A==",
541 "dev": true,
542 "license": "Apache-2.0"
543 },
544 "node_modules/bare-process": {
545 "version": "4.5.0",
546 "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.5.0.tgz",
547 "integrity": "sha512-NTtYDiwleJjWWNg4+yzS6B/RovL/LLPHVvoOM+18W+dYAXCmky0zFfN66hqqBbrlGAlb52/QgKrdzPR3x1QyHA==",
548 "dev": true,
549 "license": "Apache-2.0",
550 "dependencies": {
551 "bare-abort": "^2.0.13",
552 "bare-env": "^3.0.0",
553 "bare-events": "^2.3.1",
554 "bare-hrtime": "^2.0.0",
555 "bare-os": "^3.7.1",
556 "bare-posix": "^1.0.1",
557 "bare-signals": "^4.0.0",
558 "bare-stdio": "^1.0.1"
559 }
560 },
561 "node_modules/bare-semver": {
562 "version": "1.1.0",
563 "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.1.0.tgz",
564 "integrity": "sha512-1Hw5qJ7hXdVt3uPUqjeFTuxyvBUJauvz5A1I2jk8gzjZMHp04n//6nV9MDbG9CMw78JHY2lGV0w6s//LrASm2w==",
565 "license": "Apache-2.0"
566 },
567 "node_modules/bare-signals": {
568 "version": "4.2.0",
569 "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz",
570 "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==",
571 "dev": true,
572 "license": "Apache-2.0",
573 "dependencies": {
574 "bare-events": "^2.5.3",
575 "bare-os": "^3.3.1"
576 },
577 "engines": {
578 "bare": ">=1.7.0"
579 }
580 },
581 "node_modules/bare-stdio": {
582 "version": "1.0.2",
583 "resolved": "https://registry.npmjs.org/bare-stdio/-/bare-stdio-1.0.2.tgz",
584 "integrity": "sha512-3WJDqtvVGP4f+j68kyEC05umOYNwKJ1xG+YAXL8yZ605WgNqiRhVaFq+mVIhBt2eKNp7pa5vCQdhOt1pNh79SA==",
585 "dev": true,
586 "license": "Apache-2.0",
587 "dependencies": {
588 "bare-fs": "^4.5.2",
589 "bare-pipe": "^4.1.5",
590 "bare-tty": "^5.0.3"
591 }
592 },
593 "node_modules/bare-stream": {
594 "version": "2.13.3",
595 "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz",
596 "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==",
597 "license": "Apache-2.0",
598 "dependencies": {
599 "b4a": "^1.8.1",
600 "streamx": "^2.25.0",
601 "teex": "^1.0.1"
602 },
603 "peerDependencies": {
604 "bare-abort-controller": "*",
605 "bare-buffer": "*",
606 "bare-events": "*"
607 },
608 "peerDependenciesMeta": {
609 "bare-abort-controller": {
610 "optional": true
611 },
612 "bare-buffer": {
613 "optional": true
614 },
615 "bare-events": {
616 "optional": true
617 }
618 }
619 },
620 "node_modules/bare-structured-clone": {
621 "version": "1.6.0",
622 "resolved": "https://registry.npmjs.org/bare-structured-clone/-/bare-structured-clone-1.6.0.tgz",
623 "integrity": "sha512-AZjEERyqF7kAuudlmgrweT2KwwWM0LsGG4Gx/bWyFwJrKU7E3wNG8c09z2DIrdw93p3vDtfOX8fyHOcXfy5m+g==",
624 "dev": true,
625 "license": "Apache-2.0",
626 "dependencies": {
627 "bare-buffer": "^3.6.0",
628 "bare-type": "^1.1.0",
629 "bare-url": "^2.4.0",
630 "compact-encoding": "^3.0.1"
631 },
632 "engines": {
633 "bare": ">=1.2.0"
634 }
635 },
636 "node_modules/bare-stylize": {
637 "version": "0.0.1",
638 "resolved": "https://registry.npmjs.org/bare-stylize/-/bare-stylize-0.0.1.tgz",
639 "integrity": "sha512-l3MjmIl476bWijYWf3RbE+osl4iuXSOMudzp0vAqzIK7gPgn/+G3oAxp8Oin9CFF911KBP0LO9kts8Ci8mGZaQ==",
640 "dev": true,
641 "license": "Apache-2.0",
642 "dependencies": {
643 "bare-ansi-escapes": "^2.2.3",
644 "bare-process": "^4.2.1"
645 }
646 },
647 "node_modules/bare-subprocess": {
648 "version": "5.2.3",
649 "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.3.tgz",
650 "integrity": "sha512-07wwswlV7M3sC9IykbZRZ/jHAkrXFWVLqdBWGv1y0ojCimtRD9hGwxdHmR5FUFmDUZLNsBmTYJNQqgio5+A85Q==",
651 "dev": true,
652 "license": "Apache-2.0",
653 "dependencies": {
654 "bare-env": "^3.0.0",
655 "bare-events": "^2.5.4",
656 "bare-os": "^3.0.1",
657 "bare-pipe": "^4.0.0",
658 "bare-url": "^2.2.2"
659 },
660 "engines": {
661 "bare": ">=1.7.0"
662 },
663 "peerDependencies": {
664 "bare-buffer": "*"
665 },
666 "peerDependenciesMeta": {
667 "bare-buffer": {
668 "optional": true
669 }
670 }
671 },
672 "node_modules/bare-tcp": {
673 "version": "2.5.1",
674 "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.5.1.tgz",
675 "integrity": "sha512-N32OosAegmGzksr/huxIYLU/uaUARte6utO+vAu/EPQztMM7iAC55VeQSfzr2/XBJoz2OIETMvmU7/sdfiOGjw==",
676 "dev": true,
677 "license": "Apache-2.0",
678 "dependencies": {
679 "bare-dns": "^2.0.4",
680 "bare-events": "^2.5.4",
681 "bare-stream": "^2.6.4"
682 },
683 "engines": {
684 "bare": ">=1.16.0"
685 },
686 "peerDependencies": {
687 "bare-pipe": "*"
688 },
689 "peerDependenciesMeta": {
690 "bare-pipe": {
691 "optional": true
692 }
693 }
694 },
695 "node_modules/bare-tls": {
696 "version": "3.1.7",
697 "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-3.1.7.tgz",
698 "integrity": "sha512-AMw8tJlb3LhzAmhgXRcjDrTlNxR3gXXyj6G8eU9iwvCFtiUBD8MxAW7bwunA1gXDukgo40A970jX0APc2jMU7A==",
699 "dev": true,
700 "license": "Apache-2.0",
701 "dependencies": {
702 "bare-net": "^2.0.1",
703 "bare-stream": "^2.6.4"
704 },
705 "engines": {
706 "bare": ">=1.7.0"
707 }
708 },
709 "node_modules/bare-tty": {
710 "version": "5.1.1",
711 "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.1.1.tgz",
712 "integrity": "sha512-cXCMAlcLCFTCWwCtXBIhDuXa4hutTEbrHHIK5pvSSb4BLaSGJ9tvxVN3mvGOvXXqONS7l04mTiP+eIUlZKI92g==",
713 "dev": true,
714 "license": "Apache-2.0",
715 "dependencies": {
716 "bare-events": "^2.2.0",
717 "bare-signals": "^4.0.0",
718 "bare-stream": "^2.0.0"
719 },
720 "engines": {
721 "bare": ">=1.16.0"
722 }
723 },
724 "node_modules/bare-type": {
725 "version": "1.1.0",
726 "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz",
727 "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==",
728 "license": "Apache-2.0",
729 "engines": {
730 "bare": ">=1.2.0"
731 }
732 },
733 "node_modules/bare-type-stripper": {
734 "version": "0.1.4",
735 "resolved": "https://registry.npmjs.org/bare-type-stripper/-/bare-type-stripper-0.1.4.tgz",
736 "integrity": "sha512-FdZhp9XEnQpj8AWFmIft/sVUyKS9XSmB6PhcxBHhuEDxxZM5Kkt8+kFS7eEpLXR7TkaRkNpSENoGH/8lpAmtkA==",
737 "dev": true,
738 "license": "Apache-2.0",
739 "dependencies": {
740 "require-addon": "^1.0.2"
741 },
742 "peerDependencies": {
743 "bare-buffer": "*"
744 },
745 "peerDependenciesMeta": {
746 "bare-buffer": {
747 "optional": true
748 }
749 }
750 },
751 "node_modules/bare-url": {
752 "version": "2.4.5",
753 "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz",
754 "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==",
755 "license": "Apache-2.0",
756 "dependencies": {
757 "bare-path": "^3.0.0"
758 }
759 },
760 "node_modules/bare-utils": {
761 "version": "1.6.0",
762 "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.6.0.tgz",
763 "integrity": "sha512-WhQEIkkAxkSnW7u1QgrI0AfNm5JpMruETXeYsb5qnkBJ0TTfNKygZmsh6rkoHBANaV+C/7Jed7bJP9OmEHG7rQ==",
764 "dev": true,
765 "license": "Apache-2.0",
766 "dependencies": {
767 "bare-debug-log": "^2.0.0",
768 "bare-encoding": "^1.0.0",
769 "bare-format": "^1.0.0",
770 "bare-inspect": "^3.0.0",
771 "bare-stylize": "^0.0.1",
772 "bare-type": "^1.0.6"
773 }
774 },
775 "node_modules/bare-v8-to-istanbul": {
776 "version": "1.0.2",
777 "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz",
778 "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==",
779 "dev": true,
780 "license": "Apache-2.0",
781 "dependencies": {
782 "bare-assert": "^1.0.2",
783 "bare-fs": "^4.1.2",
784 "bare-module": "^6.1.2",
785 "bare-path": "^3.0.0",
786 "bare-process": "^4.2.0",
787 "bare-url": "^2.1.5",
788 "bare-utils": "^1.2.0",
789 "v8-to-istanbul": "^9.3.0",
790 "which-runtime": "^1.2.1"
791 }
792 },
793 "node_modules/bare-ws": {
794 "version": "3.0.0",
795 "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-3.0.0.tgz",
796 "integrity": "sha512-q2v0UIQ0cFQBXQMp+0FRaoSk1EoYgOhzO7yio0TqBV6Rkfot97mbBYxb1ssw5faVCisVaFxQYY8113SMLYQBow==",
797 "dev": true,
798 "license": "Apache-2.0",
799 "dependencies": {
800 "bare-crypto": "^1.2.0",
801 "bare-events": "^2.3.1",
802 "bare-http1": "^4.0.0",
803 "bare-https": "^3.0.0",
804 "bare-stream": "^2.1.2"
805 },
806 "peerDependencies": {
807 "bare-buffer": "*",
808 "bare-url": "*"
809 },
810 "peerDependenciesMeta": {
811 "bare-buffer": {
812 "optional": true
813 },
814 "bare-url": {
815 "optional": true
816 }
817 }
818 },
819 "node_modules/big-sparse-array": {
820 "version": "1.0.3",
821 "resolved": "https://registry.npmjs.org/big-sparse-array/-/big-sparse-array-1.0.3.tgz",
822 "integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==",
823 "license": "MIT"
824 },
825 "node_modules/brittle": {
826 "version": "4.0.2",
827 "resolved": "https://registry.npmjs.org/brittle/-/brittle-4.0.2.tgz",
828 "integrity": "sha512-frnh7gB76uiSmFAf4+uIsaKjygMEvzUbN/GhxS+30WKz0pd/COBVuj+O9TmW9t0kIzJTafv8xquV6DriW316Vw==",
829 "dev": true,
830 "license": "Apache-2.0",
831 "dependencies": {
832 "b4a": "^1.8.0",
833 "bare-assert": "^1.2.0",
834 "bare-buffer": "^3.6.0",
835 "bare-channel": "^5.2.3",
836 "bare-cov": "^1.2.1",
837 "bare-fs": "^4.5.5",
838 "bare-os": "^3.7.1",
839 "bare-path": "^3.0.0",
840 "bare-process": "^4.3.0",
841 "bare-subprocess": "^5.2.2",
842 "bare-url": "^2.3.2",
843 "error-stack-parser": "^2.1.4",
844 "globbie": "^1.0.3",
845 "paparam": "^1.10.0",
846 "same-object": "^1.0.2",
847 "streamx": "^2.25.0",
848 "test-tmp": "^1.4.0",
849 "tmatch": "^5.0.0",
850 "which-runtime": "^1.3.2"
851 },
852 "bin": {
853 "brittle": "advise.js",
854 "brittle-bare": "brittle-bare.js",
855 "brittle-make-test": "make-test.js",
856 "brittle-node": "brittle-node.js"
857 }
858 },
859 "node_modules/cborg": {
860 "version": "5.1.6",
861 "resolved": "https://registry.npmjs.org/cborg/-/cborg-5.1.6.tgz",
862 "integrity": "sha512-apu/mOSODJ98JXBz3cbiv/gAFX4oaCZQQ53+8b4IPjc1wylYxd8eJis87QP7ic4AOE4O02j02jjfUgVZUJIIvA==",
863 "license": "Apache-2.0",
864 "bin": {
865 "cborg": "lib/bin.js"
866 }
867 },
868 "node_modules/codecs": {
869 "version": "3.1.0",
870 "resolved": "https://registry.npmjs.org/codecs/-/codecs-3.1.0.tgz",
871 "integrity": "sha512-Dqx8NwvBvnMeuPQdVKy/XEF71igjR5apxBvCGeV0pP1tXadOiaLvDTXt7xh+/5wI1ASB195mXQGJbw3Ml4YDWQ==",
872 "license": "MIT",
873 "dependencies": {
874 "b4a": "^1.6.3"
875 }
876 },
877 "node_modules/compact-encoding": {
878 "version": "3.3.0",
879 "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-3.3.0.tgz",
880 "integrity": "sha512-e64XyzlBvTRJ3iScuU/U2w25Dglkm7fhrRbrGaHIwuTlNnzjRKMaQBCVl7mJRvZg7wI5a9wX1IVTXCuaAANNkw==",
881 "license": "Apache-2.0",
882 "dependencies": {
883 "b4a": "^1.3.0"
884 }
885 },
886 "node_modules/convert-source-map": {
887 "version": "2.0.0",
888 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
889 "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
890 "dev": true,
891 "license": "MIT"
892 },
893 "node_modules/corestore": {
894 "version": "7.11.0",
895 "resolved": "https://registry.npmjs.org/corestore/-/corestore-7.11.0.tgz",
896 "integrity": "sha512-zGlF6dBZ9bvyRliz6g+NW0tGRwGYmmc84o1TARbXOJ+fPRXeKTkVzVV95eWIJEU3+6WBUbcvIBQKmKiNmiTAdg==",
897 "license": "MIT",
898 "dependencies": {
899 "b4a": "^1.6.7",
900 "bare-events": "^2.8.3",
901 "hypercore": "^11.32.0",
902 "hypercore-crypto": "^3.4.2",
903 "hypercore-errors": "^1.4.0",
904 "hypercore-id-encoding": "^1.3.0",
905 "ready-resource": "^1.1.1",
906 "sodium-universal": "^5.0.1",
907 "streamx": "^2.26.0",
908 "which-runtime": "^1.2.1"
909 }
910 },
911 "node_modules/debounceify": {
912 "version": "1.1.0",
913 "resolved": "https://registry.npmjs.org/debounceify/-/debounceify-1.1.0.tgz",
914 "integrity": "sha512-eKuHDVfJVg+u/0nPy8P+fhnLgbyuTgVxuCRrS/R7EpDSMMkBDgSes41MJtSAY1F1hcqfHz3Zy/qpqHHIp/EhdA==",
915 "license": "MIT"
916 },
917 "node_modules/device-file": {
918 "version": "2.3.1",
919 "resolved": "https://registry.npmjs.org/device-file/-/device-file-2.3.1.tgz",
920 "integrity": "sha512-bmON44lwxJPle9N2OcH4tqM44pMGZKT8G6OkzXkz0urvqQ9LKkoQdTS6w0ztYfmLdUE27R4UUmx0+y2gEz4Jug==",
921 "license": "Apache-2.0",
922 "dependencies": {
923 "b4a": "^1.6.7",
924 "bare-fs": "^4.0.1",
925 "bare-path": "^3.0.0",
926 "fd-lock": "^2.1.0",
927 "fs-native-extensions": "^1.4.0",
928 "ready-resource": "^1.2.0"
929 }
930 },
931 "node_modules/error-stack-parser": {
932 "version": "2.1.4",
933 "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
934 "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
935 "dev": true,
936 "license": "MIT",
937 "dependencies": {
938 "stackframe": "^1.3.4"
939 }
940 },
941 "node_modules/events-universal": {
942 "version": "1.0.1",
943 "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
944 "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
945 "license": "Apache-2.0",
946 "dependencies": {
947 "bare-events": "^2.7.0"
948 }
949 },
950 "node_modules/fast-fifo": {
951 "version": "1.3.2",
952 "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
953 "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
954 "license": "MIT"
955 },
956 "node_modules/fd-lock": {
957 "version": "2.2.0",
958 "resolved": "https://registry.npmjs.org/fd-lock/-/fd-lock-2.2.0.tgz",
959 "integrity": "sha512-Il4jWBhjjgvUg+z8d0bC7ncXqB42caqKTUpnZ9jpcqiPmw8bkIixt7Kic1czbZPMxAQD/9kEqfZ5Dq77nSll0w==",
960 "license": "Apache-2.0",
961 "dependencies": {
962 "bare-fs": "^4.5.0",
963 "fs-native-extensions": "^1.4.4",
964 "ready-resource": "^1.2.0",
965 "resource-on-exit": "^1.0.0"
966 }
967 },
968 "node_modules/flat-tree": {
969 "version": "1.13.0",
970 "resolved": "https://registry.npmjs.org/flat-tree/-/flat-tree-1.13.0.tgz",
971 "integrity": "sha512-fT3HIuCPwHhFgJ20QYzDHgUG0zMmFg5cHvFiFo5h+QMSJ28TihsEVY0f8HGliuO+pOzmvjMx1odToeaEWkTnyQ==",
972 "license": "MIT"
973 },
974 "node_modules/fs-native-extensions": {
975 "version": "1.5.0",
976 "resolved": "https://registry.npmjs.org/fs-native-extensions/-/fs-native-extensions-1.5.0.tgz",
977 "integrity": "sha512-nuZLFm9mGCxvyi7Llww/J4OyifKCS21nEUTAmnlTZp3FObPOvA32aCedCmt4Z+8yk+caqfClNaSCfn/P7T7FLQ==",
978 "license": "Apache-2.0",
979 "dependencies": {
980 "require-addon": "^1.1.0",
981 "which-runtime": "^1.2.0"
982 }
983 },
984 "node_modules/generate-object-property": {
985 "version": "2.0.0",
986 "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-2.0.0.tgz",
987 "integrity": "sha512-KwuURPyqn2Mz8DdV29pJwQu0Y7tcsbkULr82eeOcY/ZllFK6I9Wm8dsRByIu7CKWlFi9BdW1b3mcXMp/kQBQsw==",
988 "license": "MIT",
989 "dependencies": {
990 "is-property": "^1.0.0"
991 }
992 },
993 "node_modules/generate-string": {
994 "version": "1.0.1",
995 "resolved": "https://registry.npmjs.org/generate-string/-/generate-string-1.0.1.tgz",
996 "integrity": "sha512-IfTY0dKZM43ACyGvXkbG7De7WY7MxTS5VO6Juhe8oJKpCmrYYXoqp/cJMskkpi0k9H8wuXq0H+eI898/BCqvXg==",
997 "license": "MIT"
998 },
999 "node_modules/globbie": {
1000 "version": "1.0.3",
1001 "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz",
1002 "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==",
1003 "dev": true,
1004 "license": "Apache-2.0",
1005 "dependencies": {
1006 "bare-fs": "^4.1.2",
1007 "bare-path": "^3.0.0",
1008 "bare-process": "^4.2.1",
1009 "picomatch": "^4.0.2"
1010 }
1011 },
1012 "node_modules/hyperbee": {
1013 "version": "2.27.3",
1014 "resolved": "https://registry.npmjs.org/hyperbee/-/hyperbee-2.27.3.tgz",
1015 "integrity": "sha512-PXURH2U4juUZyJRKHTrY5z1zX851pmI1Q0jfv5F/hCIErDt/ND8jOZuxc3hfOLM9f0W3qJEDTMlV5AJBkVPy8w==",
1016 "license": "MIT",
1017 "dependencies": {
1018 "b4a": "^1.6.0",
1019 "codecs": "^3.0.0",
1020 "debounceify": "^1.0.0",
1021 "hypercore-errors": "^1.0.0",
1022 "mutexify": "^1.4.0",
1023 "protocol-buffers-encodings": "^1.2.0",
1024 "rache": "^1.0.0",
1025 "ready-resource": "^1.0.0",
1026 "resolve-reject-promise": "^1.1.0",
1027 "safety-catch": "^1.0.2",
1028 "streamx": "^2.12.4",
1029 "unslab": "^1.2.0"
1030 }
1031 },
1032 "node_modules/hypercore": {
1033 "version": "11.33.5",
1034 "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-11.33.5.tgz",
1035 "integrity": "sha512-K88HURg5MvLjPxW8y6AzHDaf5rcaC81Ujaz4z8ugsl5zwucpmWRDf4YKXrIKGLSWyVSysSKywYDUYoAfMKJkWg==",
1036 "license": "MIT",
1037 "dependencies": {
1038 "@hyperswarm/secret-stream": "^6.0.0",
1039 "b4a": "^1.1.0",
1040 "bare-events": "^2.2.0",
1041 "big-sparse-array": "^1.0.3",
1042 "compact-encoding": "^3.0.0",
1043 "fast-fifo": "^1.3.0",
1044 "flat-tree": "^1.9.0",
1045 "hypercore-crypto": "^3.2.1",
1046 "hypercore-errors": "^1.5.0",
1047 "hypercore-id-encoding": "^1.2.0",
1048 "hypercore-storage": "^3.0.0",
1049 "is-options": "^1.0.1",
1050 "nanoassert": "^2.0.0",
1051 "protomux": "^3.5.0",
1052 "quickbit-universal": "^2.2.0",
1053 "random-array-iterator": "^1.0.0",
1054 "safety-catch": "^1.0.1",
1055 "sodium-universal": "^5.0.1",
1056 "streamx": "^2.12.4",
1057 "unslab": "^1.3.0",
1058 "z32": "^1.0.0"
1059 }
1060 },
1061 "node_modules/hypercore-crypto": {
1062 "version": "3.7.0",
1063 "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.7.0.tgz",
1064 "integrity": "sha512-SWxobptQf2V/+ZLCCDRTXqFzGfTPIkNIuDyLKXpEMfcpJ1/ec94N9aWgylHcWOHmRt14ng/KR7z0BugebWHK9Q==",
1065 "license": "MIT",
1066 "dependencies": {
1067 "b4a": "^1.6.6",
1068 "compact-encoding": "^3.0.0",
1069 "sodium-universal": "^5.0.0"
1070 }
1071 },
1072 "node_modules/hypercore-errors": {
1073 "version": "1.5.0",
1074 "resolved": "https://registry.npmjs.org/hypercore-errors/-/hypercore-errors-1.5.0.tgz",
1075 "integrity": "sha512-5KQ/SuDxsvet+7qWA35Ay6zdD9WyAHQoyWHGcPUTbmJBd300gvNIJoi3oma7kp4TTCSzii6qYumNZe/s0j/saQ==",
1076 "license": "Apache-2.0",
1077 "dependencies": {
1078 "hypercore-id-encoding": "^1.3.0"
1079 }
1080 },
1081 "node_modules/hypercore-id-encoding": {
1082 "version": "1.3.0",
1083 "resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz",
1084 "integrity": "sha512-W6sHdGo5h7LXEsoWfKf/KfuROZmZRQDlGqJF2EPHW+noCK66Vvr0+zE6cL0vqQi18s0kQPeN7Sq3QyR0Ytc2VQ==",
1085 "license": "Apache-2.0",
1086 "dependencies": {
1087 "b4a": "^1.5.3",
1088 "z32": "^1.0.0"
1089 }
1090 },
1091 "node_modules/hypercore-storage": {
1092 "version": "3.1.2",
1093 "resolved": "https://registry.npmjs.org/hypercore-storage/-/hypercore-storage-3.1.2.tgz",
1094 "integrity": "sha512-Iw6C0Nx9QIIyFLfALiDNBUpkWTB19gqrxiK3igfTpp0STqqbL4AO0ywrY9+Mv9ynt0PNs/hTnlTW6WRe76iOUw==",
1095 "license": "Apache-2.0",
1096 "dependencies": {
1097 "b4a": "^1.6.7",
1098 "bare-path": "^3.0.0",
1099 "compact-encoding": "^3.1.0",
1100 "device-file": "^2.1.2",
1101 "flat-tree": "^1.12.1",
1102 "hypercore-crypto": "^3.4.2",
1103 "hyperschema": "^1.21.0",
1104 "index-encoder": "^3.3.2",
1105 "resolve-reject-promise": "^1.0.0",
1106 "rocksdb-native": "^3.11.0",
1107 "scope-lock": "^1.2.4",
1108 "streamx": "^2.21.1"
1109 }
1110 },
1111 "node_modules/hyperschema": {
1112 "version": "1.21.0",
1113 "resolved": "https://registry.npmjs.org/hyperschema/-/hyperschema-1.21.0.tgz",
1114 "integrity": "sha512-lEnIbLTUQf7w6FU6X+R3yUJhBwCzF4ewRnAaYOrPdcVWe1rbOf94PzMiXb5pBKxroCXzlrPLxVujxAsTrrHc8g==",
1115 "license": "Apache-2.0",
1116 "dependencies": {
1117 "bare-fs": "^4.0.1",
1118 "compact-encoding": "^3.0.0",
1119 "generate-object-property": "^2.0.0",
1120 "generate-string": "^1.0.1"
1121 }
1122 },
1123 "node_modules/index-encoder": {
1124 "version": "3.5.0",
1125 "resolved": "https://registry.npmjs.org/index-encoder/-/index-encoder-3.5.0.tgz",
1126 "integrity": "sha512-idZ1cxtZz2dRV6rUiaP9Xo99UjXbSzjcMacoQmxUMu/A7fEQcNPngvwDJYeWelQUS5XFlY71/or70lKn6XnwbQ==",
1127 "license": "Apache-2.0",
1128 "dependencies": {
1129 "b4a": "^1.6.4"
1130 }
1131 },
1132 "node_modules/is-options": {
1133 "version": "1.0.2",
1134 "resolved": "https://registry.npmjs.org/is-options/-/is-options-1.0.2.tgz",
1135 "integrity": "sha512-u+Ai74c8Q74aS8BuHwPdI1jptGOT1FQXgCq8/zv0xRuE+wRgSMEJLj8lVO8Zp9BeGb29BXY6AsNPinfqjkr7Fg==",
1136 "license": "MIT",
1137 "dependencies": {
1138 "b4a": "^1.1.1"
1139 }
1140 },
1141 "node_modules/is-property": {
1142 "version": "1.0.2",
1143 "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
1144 "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
1145 "license": "MIT"
1146 },
1147 "node_modules/multiformats": {
1148 "version": "13.4.2",
1149 "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz",
1150 "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==",
1151 "license": "Apache-2.0 OR MIT"
1152 },
1153 "node_modules/mutexify": {
1154 "version": "1.4.0",
1155 "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz",
1156 "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==",
1157 "license": "MIT",
1158 "dependencies": {
1159 "queue-tick": "^1.0.0"
1160 }
1161 },
1162 "node_modules/nanoassert": {
1163 "version": "2.0.0",
1164 "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz",
1165 "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==",
1166 "license": "ISC"
1167 },
1168 "node_modules/noise-curve-ed": {
1169 "version": "2.1.0",
1170 "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.1.0.tgz",
1171 "integrity": "sha512-zAzJx+VwZM3w6EA1hTmDhJfvAnCeBQn/1FAeZ0LtGxCcCtlAK/uJXQVF/eDVUOaAZ286lHlx77WJ+qj9SmsRRg==",
1172 "license": "ISC",
1173 "dependencies": {
1174 "b4a": "^1.1.0",
1175 "nanoassert": "^2.0.0",
1176 "sodium-universal": "^5.0.0"
1177 }
1178 },
1179 "node_modules/noise-handshake": {
1180 "version": "4.2.0",
1181 "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-4.2.0.tgz",
1182 "integrity": "sha512-9O/VTNX/E2/AToyMTTDU0J/4WhaXMTdqc2DHs9vf+snoZ0cenSBq0dNYTVV1snYYEkmo6QeRrYMxtqtoYnY+LA==",
1183 "license": "Apache-2.0",
1184 "dependencies": {
1185 "b4a": "^1.1.0",
1186 "nanoassert": "^2.0.0",
1187 "sodium-universal": "^5.0.0"
1188 }
1189 },
1190 "node_modules/paparam": {
1191 "version": "1.10.1",
1192 "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.1.tgz",
1193 "integrity": "sha512-viyQI64VIja0Za3njIzhoEP8ZVkgPowhZPuG0E96NwBfYJ6ZIyrrlhWGtFPkdN7eYLl2L8CTWL+wla0evm1KKQ==",
1194 "dev": true,
1195 "license": "Apache-2.0"
1196 },
1197 "node_modules/picomatch": {
1198 "version": "4.0.5",
1199 "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
1200 "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
1201 "dev": true,
1202 "license": "MIT",
1203 "engines": {
1204 "node": ">=12"
1205 },
1206 "funding": {
1207 "url": "https://github.com/sponsors/jonschlinkert"
1208 }
1209 },
1210 "node_modules/protocol-buffers-encodings": {
1211 "version": "1.2.0",
1212 "resolved": "https://registry.npmjs.org/protocol-buffers-encodings/-/protocol-buffers-encodings-1.2.0.tgz",
1213 "integrity": "sha512-daeNPuKh1NlLD1uDfbLpD+xyUTc07nEtfHwmBZmt/vH0B7VOM+JOCOpDcx9ZRpqHjAiIkGqyTDi+wfGSl17R9w==",
1214 "license": "MIT",
1215 "dependencies": {
1216 "b4a": "^1.6.0",
1217 "signed-varint": "^2.0.1",
1218 "varint": "5.0.0"
1219 }
1220 },
1221 "node_modules/protomux": {
1222 "version": "3.11.0",
1223 "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.11.0.tgz",
1224 "integrity": "sha512-Ze43XaNHbL6zQ/zJwVYvue7Aj8pDB1HNnISsrFhAur3291Y+Iu2fMhOpySD73J/32BoBIUYrhl07lJsBR/fUVA==",
1225 "license": "MIT",
1226 "dependencies": {
1227 "b4a": "^1.3.1",
1228 "compact-encoding": "^3.0.0",
1229 "queue-tick": "^1.0.0",
1230 "safety-catch": "^1.0.1",
1231 "unslab": "^1.3.0"
1232 }
1233 },
1234 "node_modules/queue-tick": {
1235 "version": "1.0.1",
1236 "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
1237 "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
1238 "license": "MIT"
1239 },
1240 "node_modules/quickbit-native": {
1241 "version": "2.4.8",
1242 "resolved": "https://registry.npmjs.org/quickbit-native/-/quickbit-native-2.4.8.tgz",
1243 "integrity": "sha512-FcCcqI+nIAWGknqhtrYT5TSD7t/N+Xd8ctM+2PrIIBuwOi5hx0SxAvuPtzLIEMfT/2h9+fhBakUe2uALOHX6yw==",
1244 "license": "Apache-2.0",
1245 "optional": true,
1246 "dependencies": {
1247 "require-addon": "^1.1.0"
1248 }
1249 },
1250 "node_modules/quickbit-universal": {
1251 "version": "2.2.0",
1252 "resolved": "https://registry.npmjs.org/quickbit-universal/-/quickbit-universal-2.2.0.tgz",
1253 "integrity": "sha512-w02i1R8n7+6pEKTud8DfF8zbFY9o7RtPlUc3jWbtCkDKvhbx/AvV7oNnz4/TcmsPGpSJS+fq5Ud6RH6+YPvSGg==",
1254 "license": "ISC",
1255 "dependencies": {
1256 "b4a": "^1.6.0",
1257 "simdle-universal": "^1.1.0"
1258 },
1259 "optionalDependencies": {
1260 "quickbit-native": "^2.2.0"
1261 }
1262 },
1263 "node_modules/rache": {
1264 "version": "1.0.0",
1265 "resolved": "https://registry.npmjs.org/rache/-/rache-1.0.0.tgz",
1266 "integrity": "sha512-e0k0g0w/8jOCB+7YqCIlOa+OJ38k0wrYS4x18pMSmqOvLKoyhmMhmQyCcvfY6VaP8D75cqkEnlakXs+RYYLqNg==",
1267 "license": "Apache-2.0"
1268 },
1269 "node_modules/random-access-memory": {
1270 "version": "6.2.1",
1271 "resolved": "https://registry.npmjs.org/random-access-memory/-/random-access-memory-6.2.1.tgz",
1272 "integrity": "sha512-hUeu1PbGLmWeyze9LwwSNaqloivNYjFsARIYxRdgUgn0wrdvMG+RszrfTG8814zfcXOgy4pFO2TpX/Cl3hRO4w==",
1273 "license": "MIT",
1274 "dependencies": {
1275 "b4a": "^1.6.0",
1276 "is-options": "^1.0.2",
1277 "random-access-storage": "^3.0.0"
1278 }
1279 },
1280 "node_modules/random-access-storage": {
1281 "version": "3.0.2",
1282 "resolved": "https://registry.npmjs.org/random-access-storage/-/random-access-storage-3.0.2.tgz",
1283 "integrity": "sha512-Es9maUyWdJXWKckKy9s1+vT+DEgAt+PBb9lxPaake/0EDUsHehloKGv9v1zimS2V3gpFAcQXubvc1Rgci2sDPQ==",
1284 "license": "MIT",
1285 "dependencies": {
1286 "bare-events": "^2.2.0",
1287 "queue-tick": "^1.0.0"
1288 }
1289 },
1290 "node_modules/random-array-iterator": {
1291 "version": "1.0.0",
1292 "resolved": "https://registry.npmjs.org/random-array-iterator/-/random-array-iterator-1.0.0.tgz",
1293 "integrity": "sha512-u7xCM93XqKEvPTP6xZp2ehttcAemKnh73oKNf1FvzuVCfpt6dILDt1Kxl1LeBjm2iNIeR49VGFhy4Iz3yOun+Q==",
1294 "license": "MIT"
1295 },
1296 "node_modules/ready-resource": {
1297 "version": "1.2.0",
1298 "resolved": "https://registry.npmjs.org/ready-resource/-/ready-resource-1.2.0.tgz",
1299 "integrity": "sha512-nfcco/8iAFV0M+2PYnmIc+/xY0iRb35d42HFHQ7AfjulbGEAFa+XWpByfwSyeVeiBoMLLFVMv1HixxNCqzSQ1g==",
1300 "license": "MIT",
1301 "dependencies": {
1302 "bare-events": "^2.2.0"
1303 }
1304 },
1305 "node_modules/refcounter": {
1306 "version": "1.0.0",
1307 "resolved": "https://registry.npmjs.org/refcounter/-/refcounter-1.0.0.tgz",
1308 "integrity": "sha512-1WosVzUy0kPUaPMEtlNDwm99UsteALIhXXR8rerELoa63WkYIXAl0hxgwPFrIYBRWZPGUyekQ04FRtPJ7dHk9w==",
1309 "license": "Apache-2.0"
1310 },
1311 "node_modules/require-addon": {
1312 "version": "1.2.0",
1313 "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz",
1314 "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==",
1315 "license": "Apache-2.0",
1316 "dependencies": {
1317 "bare-addon-resolve": "^1.3.0"
1318 },
1319 "engines": {
1320 "bare": ">=1.10.0"
1321 }
1322 },
1323 "node_modules/resolve-reject-promise": {
1324 "version": "1.1.0",
1325 "resolved": "https://registry.npmjs.org/resolve-reject-promise/-/resolve-reject-promise-1.1.0.tgz",
1326 "integrity": "sha512-LWsTOA91AqzBTjSGgX79Tc130pwcBK6xjpJEO+qRT5IKZ6bGnHKcc8QL3upUBcWuU8OTIDzKK2VNSwmmlqvAVg==",
1327 "license": "MIT"
1328 },
1329 "node_modules/resource-on-exit": {
1330 "version": "1.0.0",
1331 "resolved": "https://registry.npmjs.org/resource-on-exit/-/resource-on-exit-1.0.0.tgz",
1332 "integrity": "sha512-ViJwJAknCkLRJRPR+9SISQQ7R5eRgtdIHLJsM2hHx1MweAJbJxJ5XnMjjq0Lc7ZGv44ufzAqds1nKxiVkdy4ag==",
1333 "license": "Apache-2.0"
1334 },
1335 "node_modules/rocksdb-native": {
1336 "version": "3.17.1",
1337 "resolved": "https://registry.npmjs.org/rocksdb-native/-/rocksdb-native-3.17.1.tgz",
1338 "integrity": "sha512-33hbNSx57vbdM/kyP6dozy1eRLeiN69kTNcbPZ6AMkOjFtSY3G6O94TxKNgUIq53IQzFP6kClriKHeu4mu1q6w==",
1339 "license": "Apache-2.0",
1340 "dependencies": {
1341 "compact-encoding": "^3.0.0",
1342 "ready-resource": "^1.0.0",
1343 "refcounter": "^1.0.0",
1344 "require-addon": "^1.0.2",
1345 "resolve-reject-promise": "^1.1.0",
1346 "signal-promise": "^1.0.3",
1347 "streamx": "^2.16.1"
1348 },
1349 "engines": {
1350 "bare": ">=1.16.0"
1351 }
1352 },
1353 "node_modules/safety-catch": {
1354 "version": "1.0.3",
1355 "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.3.tgz",
1356 "integrity": "sha512-Zq+J1TefpoEq/HTUabo0YXX5MNvttjWYODGohgPBO2jfko8Wqx3JYMgE823szDFVamdH5PlpByvfiWScTdSYDA==",
1357 "license": "MIT"
1358 },
1359 "node_modules/same-object": {
1360 "version": "1.0.2",
1361 "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz",
1362 "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==",
1363 "dev": true,
1364 "license": "MIT"
1365 },
1366 "node_modules/scope-lock": {
1367 "version": "1.2.4",
1368 "resolved": "https://registry.npmjs.org/scope-lock/-/scope-lock-1.2.4.tgz",
1369 "integrity": "sha512-BpSd8VCuCxW9ZitcdIC/vjs3gMaP9bRBL5nkHcyfX2VrS52n13/rHuBA2xJ/S/4DPuRdAO/Bk8pWd8eD/gHCIA==",
1370 "license": "Apache-2.0"
1371 },
1372 "node_modules/signal-promise": {
1373 "version": "1.0.3",
1374 "resolved": "https://registry.npmjs.org/signal-promise/-/signal-promise-1.0.3.tgz",
1375 "integrity": "sha512-WBgv0UnIq2C+Aeh0/n+IRpP6967eIx9WpynTUoiW3isPpfe1zu2LJzyfXdo9Tgef8yR/sGjcMvoUXD7EYdiz+g==",
1376 "license": "MIT"
1377 },
1378 "node_modules/signed-varint": {
1379 "version": "2.0.1",
1380 "resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz",
1381 "integrity": "sha512-abgDPg1106vuZZOvw7cFwdCABddfJRz5akcCcchzTbhyhYnsG31y4AlZEgp315T7W3nQq5P4xeOm186ZiPVFzw==",
1382 "license": "MIT",
1383 "dependencies": {
1384 "varint": "~5.0.0"
1385 }
1386 },
1387 "node_modules/simdle-native": {
1388 "version": "1.3.9",
1389 "resolved": "https://registry.npmjs.org/simdle-native/-/simdle-native-1.3.9.tgz",
1390 "integrity": "sha512-Isc8sP4OiiIU0mpslD4GHEnR0VQWvR/54WN7YtwEDkNdTJVWtpmvsSvsgRlw5BNGxdYXlVRegdnrSu10H/PhvA==",
1391 "license": "Apache-2.0",
1392 "optional": true,
1393 "dependencies": {
1394 "b4a": "^1.6.0",
1395 "require-addon": "^1.1.0"
1396 }
1397 },
1398 "node_modules/simdle-universal": {
1399 "version": "1.1.2",
1400 "resolved": "https://registry.npmjs.org/simdle-universal/-/simdle-universal-1.1.2.tgz",
1401 "integrity": "sha512-3n3w1bs+uwgHKQjt6arez83EywNlhZzYvNOhvAASTl/8KqNIcqr6aHyGt3JRlfuUC7iB0tomJRPlJ2cRGIpBzA==",
1402 "license": "ISC",
1403 "dependencies": {
1404 "b4a": "^1.6.0"
1405 },
1406 "optionalDependencies": {
1407 "simdle-native": "^1.1.1"
1408 }
1409 },
1410 "node_modules/sodium-native": {
1411 "version": "5.1.0",
1412 "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.1.0.tgz",
1413 "integrity": "sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==",
1414 "license": "MIT",
1415 "dependencies": {
1416 "bare-assert": "^1.2.0",
1417 "require-addon": "^1.1.0",
1418 "which-runtime": "^1.2.1"
1419 },
1420 "engines": {
1421 "bare": ">=1.16.0"
1422 }
1423 },
1424 "node_modules/sodium-secretstream": {
1425 "version": "1.2.0",
1426 "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz",
1427 "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==",
1428 "license": "MIT",
1429 "dependencies": {
1430 "b4a": "^1.1.1",
1431 "sodium-universal": "^5.0.0"
1432 }
1433 },
1434 "node_modules/sodium-universal": {
1435 "version": "5.0.1",
1436 "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz",
1437 "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==",
1438 "license": "MIT",
1439 "dependencies": {
1440 "sodium-native": "^5.0.1"
1441 },
1442 "peerDependencies": {
1443 "sodium-javascript": "~0.8.0"
1444 },
1445 "peerDependenciesMeta": {
1446 "sodium-javascript": {
1447 "optional": true
1448 }
1449 }
1450 },
1451 "node_modules/stackframe": {
1452 "version": "1.3.4",
1453 "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
1454 "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
1455 "dev": true,
1456 "license": "MIT"
1457 },
1458 "node_modules/streamx": {
1459 "version": "2.28.0",
1460 "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz",
1461 "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==",
1462 "license": "MIT",
1463 "dependencies": {
1464 "events-universal": "^1.0.0",
1465 "fast-fifo": "^1.3.2",
1466 "text-decoder": "^1.1.0"
1467 }
1468 },
1469 "node_modules/teex": {
1470 "version": "1.0.1",
1471 "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
1472 "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
1473 "license": "MIT",
1474 "dependencies": {
1475 "streamx": "^2.12.5"
1476 }
1477 },
1478 "node_modules/test-tmp": {
1479 "version": "1.4.0",
1480 "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz",
1481 "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==",
1482 "dev": true,
1483 "license": "MIT",
1484 "dependencies": {
1485 "bare-fs": "^4.0.1",
1486 "bare-os": "^3.3.0",
1487 "bare-path": "^3.0.0"
1488 }
1489 },
1490 "node_modules/text-decoder": {
1491 "version": "1.2.7",
1492 "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
1493 "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
1494 "license": "Apache-2.0",
1495 "dependencies": {
1496 "b4a": "^1.6.4"
1497 }
1498 },
1499 "node_modules/timeout-refresh": {
1500 "version": "2.0.1",
1501 "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-2.0.1.tgz",
1502 "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==",
1503 "license": "MIT"
1504 },
1505 "node_modules/tmatch": {
1506 "version": "5.0.0",
1507 "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz",
1508 "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==",
1509 "dev": true,
1510 "license": "ISC",
1511 "engines": {
1512 "node": ">=8"
1513 }
1514 },
1515 "node_modules/unslab": {
1516 "version": "1.3.0",
1517 "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz",
1518 "integrity": "sha512-YATkfKAFj47kTzmiQrWXMyRvaVrHsW6MEALa4bm+FhiA2YG4oira+Z3DXN6LrYOYn2Y8eO94Lwl9DOHjs1FpoQ==",
1519 "license": "Apache-2.0",
1520 "dependencies": {
1521 "b4a": "^1.6.6"
1522 }
1523 },
1524 "node_modules/v8-to-istanbul": {
1525 "version": "9.3.0",
1526 "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
1527 "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
1528 "dev": true,
1529 "license": "ISC",
1530 "dependencies": {
1531 "@jridgewell/trace-mapping": "^0.3.12",
1532 "@types/istanbul-lib-coverage": "^2.0.1",
1533 "convert-source-map": "^2.0.0"
1534 },
1535 "engines": {
1536 "node": ">=10.12.0"
1537 }
1538 },
1539 "node_modules/varint": {
1540 "version": "5.0.0",
1541 "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz",
1542 "integrity": "sha512-gC13b/bWrqQoKY2EmROCZ+AR0jitc6DnDGaQ6Ls9QpKmuSgJB1eQ7H3KETtQm7qSdMWMKCmsshyCmUwMLh3OAA==",
1543 "license": "MIT"
1544 },
1545 "node_modules/which-runtime": {
1546 "version": "1.4.0",
1547 "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.4.0.tgz",
1548 "integrity": "sha512-0ugbP4CJW4e2D20jvEcC4973dCgIaHI4Rw1PT+26U9zEve7FyYdWAIwUnoeOYvoCfn+wXHoHTKb1KhkYlb60Pw==",
1549 "license": "Apache-2.0"
1550 },
1551 "node_modules/z32": {
1552 "version": "1.1.0",
1553 "resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz",
1554 "integrity": "sha512-1WUHy+VS6d0HPNspDxvLssBbeQjXMjSnpv0vH82vRAUfg847NmX3OXozp/hRP5jPhxBbrVzrgvAt+UsGNzRFQQ==",
1555 "license": "MIT",
1556 "dependencies": {
1557 "b4a": "^1.5.3"
1558 }
1559 }
1560 }
1561}
package.json +21 -0
……@@ -0,0 +1,21 @@
1{
2 "name": "atproto-store",
3 "version": "0.0.1",
4 "description": "Holepunch-native atproto repo store — records in a classic Hyperbee (collection/rkey -> DAG-CBOR). The P2P home; the atproto-repo module is the bridge serializer over it.",
5 "type": "module",
6 "main": "lib/repo-store.js",
7 "scripts": {
8 "test": "brittle-node test/*.test.js"
9 },
10 "dependencies": {
11 "atproto-repo": "file:../atproto-repo",
12 "@ipld/dag-cbor": "^9.2.1",
13 "b4a": "^1.6.0",
14 "corestore": "^7.4.0",
15 "hyperbee": "^2.20.0",
16 "random-access-memory": "^6.2.0"
17 },
18 "devDependencies": {
19 "brittle": "^4.0.2"
20 }
21}
test/firehose-proof.test.js +168 -0
……@@ -0,0 +1,168 @@
1// Slice 3b: the MST-inversion proof. A #commit's `blocks` CAR must be a MINIMAL,
2// self-contained covering proof of the old→new transition (not the whole tree).
3//
4// We prove relay-validity the way a cold relay would, using ONLY the emitted CAR:
5// - inversion: replay ops backward from the new data root → must reach prevData
6// (a missing block throws — the CAR is then insufficient);
7// - inclusion: a created/updated record resolves at its path in the new tree;
8// - exclusion: a deleted record is provably absent in the new tree;
9// - minimality: far fewer MST nodes than the full tree (else a real repo is tooBig).
10// The prior record CIDs travel in each op's `prev`, so no old-tree state is needed.
11
12import test from 'brittle'
13import { mkdtempSync, rmSync } from 'node:fs'
14import { join } from 'node:path'
15import { tmpdir } from 'node:os'
16import { RepoStore } from '../lib/repo-store.js'
17import { RepoManager } from '../lib/repo-manager.js'
18import { commitFrame, decodeCommitFrame } from '../lib/firehose.js'
19import { readCar, loadCommit } from 'atproto-repo/lib/repo.js'
20import {
21 buildMst, reachableBlocks, nodeStoreFromBlocks, OverlayNodeStore,
22 NodeWrangler, findRpathProof
23} from 'atproto-repo/lib/mst.js'
24import { cidForBytesSync } from 'atproto-repo/lib/cid.js'
25
26const DID = 'did:plc:yu4kgcktm7qtrwo5ofyiaf4c'
27const PRIV = new Uint8Array(32).fill(7)
28const iso = '2026-07-02T00:00:00.000Z'
29
30async function newMgr (t) {
31 const dir = mkdtempSync(join(tmpdir(), 'fhproof-'))
32 const store = await new RepoStore(dir).ready()
33 t.teardown(async () => { try { await store.close() } catch {} rmSync(dir, { recursive: true, force: true }) })
34 let micros = 1_700_000_000_000_000n
35 const mgr = new RepoManager(store, { did: DID, privateKey: PRIV, clock: () => (micros += 1_000_000n) })
36 await mgr.init()
37 return { store, mgr }
38}
39
40// Seed a repo with n posts so the MST has real height (subtrees), like a live repo.
41async function seed (mgr, n) {
42 for (let i = 0; i < n; i++) {
43 await mgr.applyWrite({
44 action: 'create', collection: 'app.bsky.feed.post', rkey: `3seed${i.toString().padStart(3, '0')}`,
45 record: { $type: 'app.bsky.feed.post', text: `seed ${i}`, createdAt: iso }
46 })
47 }
48}
49
50// ---- the cold-relay verifier: everything below uses ONLY the emitted CAR ----
51
52function nsFromCar (car) { return nodeStoreFromBlocks(car.blocks) }
53
54// Replay ops backward from the new data root using only CAR blocks. Returns the
55// reconstructed root CID; throws (missing node) if the CAR is insufficient.
56function invertFromCar (car, body) {
57 const overlay = new OverlayNodeStore(nsFromCar(car))
58 const w = new NodeWrangler(overlay)
59 let root = loadCommit(car).value.data
60 for (let i = body.ops.length - 1; i >= 0; i--) {
61 const op = body.ops[i]
62 if (op.action === 'create') root = w.delRecord(root, op.path)
63 else root = w.putRecord(root, op.path, op.prev) // prev decoded as a CID
64 }
65 return root
66}
67
68// how many of the CAR's blocks are MST nodes (exclude commit + record blocks)
69function mstNodeCount (car, body) {
70 let recordCids = 0
71 for (const op of body.ops) if (op.cid) recordCids++
72 return car.blocks.size - 1 /* commit */ - recordCids
73}
74
75function fullTreeNodeCount (records) {
76 const mstRecs = records.map(r => ({ key: r.key, value: cidForBytesSync(r.bytes) }))
77 const { root, ns } = buildMst(mstRecs)
78 return reachableBlocks(ns, root).size
79}
80
81// 1. CREATE into a real (multi-node) tree: inversion + inclusion + minimality
82test('create: CAR inverts to prevData, includes the record, ships << full tree', async (t) => {
83 const result = await (async () => {
84 const { store, mgr } = await newMgr(t)
85 await seed(mgr, 40)
86 const w = await mgr.applyWrite({ action: 'create', collection: 'app.bsky.feed.post', rkey: '3newpost01', record: { $type: 'app.bsky.feed.post', text: 'hi', createdAt: iso } })
87 const { body } = decodeCommitFrame(await commitFrame(w, { seq: 100, time: iso }))
88 const car = await readCar(body.blocks)
89
90 const inverted = invertFromCar(car, body) // uses CAR only
91 if (!inverted.equals(body.prevData)) return false // inversion valid
92
93 const { value } = findRpathProof(nsFromCar(car), body.commitDataRoot ?? loadCommit(car).value.data, 'app.bsky.feed.post/3newpost01')
94 if (value === null || !value.equals(w.ops[0].cid)) return false // inclusion proof
95
96 const full = fullTreeNodeCount(await store.records())
97 const shipped = mstNodeCount(car, body)
98 t.comment(`create: shipped ${shipped} MST nodes vs full tree ${full}`)
99 return shipped < full // minimal, not whole-tree
100 })()
101 t.ok(result, 'create: CAR inverts to prevData, includes the record, ships << full tree')
102})
103
104// 2. DELETE from a real tree: inversion + exclusion proof + minimality
105test('delete: CAR inverts to prevData, proves record absent, ships << full tree', async (t) => {
106 const result = await (async () => {
107 const { store, mgr } = await newMgr(t)
108 await seed(mgr, 40)
109 await mgr.applyWrite({ action: 'create', collection: 'app.bsky.feed.post', rkey: '3victim01', record: { $type: 'app.bsky.feed.post', text: 'bye', createdAt: iso } })
110 const w = await mgr.applyWrite({ action: 'delete', collection: 'app.bsky.feed.post', rkey: '3victim01' })
111 const { body } = decodeCommitFrame(await commitFrame(w, { seq: 101, time: iso }))
112 const car = await readCar(body.blocks)
113
114 const inverted = invertFromCar(car, body)
115 if (!inverted.equals(body.prevData)) return false // inversion valid
116
117 const { value } = findRpathProof(nsFromCar(car), loadCommit(car).value.data, 'app.bsky.feed.post/3victim01')
118 if (value !== null) return false // exclusion proof
119
120 const prevOk = body.ops[0].action === 'delete' && body.ops[0].cid === null && body.ops[0].prev != null
121 const full = fullTreeNodeCount(await store.records())
122 const shipped = mstNodeCount(car, body)
123 t.comment(`delete: shipped ${shipped} MST nodes vs full tree ${full}`)
124 return prevOk && shipped < full
125 })()
126 t.ok(result, 'delete: CAR inverts to prevData, proves record absent, ships << full tree')
127})
128
129// 3. UPDATE in a real tree: inversion restores the prior value; inclusion of the new one
130test('update: CAR inverts to prevData (prior value restored) + includes new value', async (t) => {
131 const result = await (async () => {
132 const { mgr } = await newMgr(t)
133 await seed(mgr, 40)
134 await mgr.applyWrite({ action: 'create', collection: 'app.bsky.feed.post', rkey: '3edit01', record: { $type: 'app.bsky.feed.post', text: 'v1', createdAt: iso } })
135 const w = await mgr.applyWrite({ action: 'update', collection: 'app.bsky.feed.post', rkey: '3edit01', record: { $type: 'app.bsky.feed.post', text: 'v2', createdAt: iso } })
136 const { body } = decodeCommitFrame(await commitFrame(w, { seq: 102, time: iso }))
137 const car = await readCar(body.blocks)
138
139 const inverted = invertFromCar(car, body)
140 if (!inverted.equals(body.prevData)) return false
141 const prevOk = body.ops[0].action === 'update' && body.ops[0].prev != null && !body.ops[0].prev.equals(body.ops[0].cid)
142
143 const { value } = findRpathProof(nsFromCar(car), loadCommit(car).value.data, 'app.bsky.feed.post/3edit01')
144 return prevOk && value !== null && value.equals(w.ops[0].cid)
145 })()
146 t.ok(result, 'update: CAR inverts to prevData (prior value restored) + includes new value')
147})
148
149// 4. Regression: the emitted set must be SUFFICIENT — invertFromCar throws on a
150// missing node. Corrupt the CAR (drop one MST node) and confirm inversion fails.
151test('negative control: dropping a proof node breaks inversion (proof is load-bearing)', async (t) => {
152 const result = await (async () => {
153 const { mgr } = await newMgr(t)
154 await seed(mgr, 40)
155 const w = await mgr.applyWrite({ action: 'create', collection: 'app.bsky.feed.post', rkey: '3ctrl01', record: { $type: 'app.bsky.feed.post', text: 'x', createdAt: iso } })
156 const { body } = decodeCommitFrame(await commitFrame(w, { seq: 103, time: iso }))
157 const car = await readCar(body.blocks)
158 // drop one MST node (not the commit, not the record)
159 const commitCid = loadCommit(car).cid.toString()
160 const recordCid = w.ops[0].cid.toString()
161 let dropped = null
162 for (const k of car.blocks.keys()) { if (k !== commitCid && k !== recordCid) { dropped = k; break } }
163 car.blocks.delete(dropped)
164 try { invertFromCar(car, body); return false } // should have thrown
165 catch { return true }
166 })()
167 t.ok(result, 'negative control: dropping a proof node breaks inversion (proof is load-bearing)')
168})
test/firehose.test.js +122 -0
……@@ -0,0 +1,122 @@
1// Slice 3: write path (RepoManager) + #commit firehose frames. Verified structurally
2// (header byte-exact, body field-set, blocks is a valid CAR, ops correct, head-state
3// advances) and against a LIVE millipds #commit frame (field-set parity).
4
5import test from 'brittle'
6import { mkdtempSync, rmSync } from 'node:fs'
7import { join } from 'node:path'
8import { tmpdir } from 'node:os'
9import { RepoStore } from '../lib/repo-store.js'
10import { RepoManager } from '../lib/repo-manager.js'
11import { commitFrame, decodeCommitFrame, COMMIT_HEADER } from '../lib/firehose.js'
12import { isValidTid } from '../lib/tid.js'
13import { readCar, loadCommit } from 'atproto-repo/lib/repo.js'
14import { getRecord } from '../lib/read-view.js'
15
16const DID = 'did:plc:yu4kgcktm7qtrwo5ofyiaf4c'
17const PRIV = new Uint8Array(32).fill(7)
18const EXPECTED_BODY_KEYS = ['blobs', 'blocks', 'commit', 'ops', 'prev', 'prevData', 'rebase', 'repo', 'rev', 'seq', 'since', 'time', 'tooBig'].sort()
19
20async function newMgr (t) {
21 const dir = mkdtempSync(join(tmpdir(), 'firehose-'))
22 const store = await new RepoStore(dir).ready()
23 t.teardown(async () => { try { await store.close() } catch {} rmSync(dir, { recursive: true, force: true }) })
24 let micros = 1_700_000_000_000_000n // deterministic monotonic clock
25 const mgr = new RepoManager(store, { did: DID, privateKey: PRIV, clock: () => (micros += 1_000_000n) })
26 await mgr.init()
27 return { store, mgr }
28}
29const iso = '2026-07-01T00:00:00.000Z'
30
31// 1. create -> frame header byte-exact + full body field set
32test('create emits a #commit frame: header byte-exact, body fields complete', async (t) => {
33 const { mgr } = await newMgr(t)
34 const w = await mgr.applyWrite({ action: 'create', collection: 'app.bsky.feed.post', rkey: '3aaa', record: { $type: 'app.bsky.feed.post', text: 'hi', createdAt: iso } })
35 const frame = await commitFrame(w, { seq: 1, time: iso })
36 const headerOk = Buffer.from(frame.subarray(0, COMMIT_HEADER.length)).equals(Buffer.from(COMMIT_HEADER))
37 const dec = decodeCommitFrame(frame)
38 const keysOk = JSON.stringify(Object.keys(dec.body).sort()) === JSON.stringify(EXPECTED_BODY_KEYS)
39 t.ok(headerOk && dec !== null && keysOk, 'create emits a #commit frame: header byte-exact, body fields complete')
40})
41
42// 2. body values + ops
43test('body: ops/rev/since/commit/repo correct; rev is a valid TID', async (t) => {
44 const { mgr } = await newMgr(t)
45 const before = mgr.rev
46 const w = await mgr.applyWrite({ action: 'create', collection: 'app.bsky.feed.post', rkey: '3bbb', record: { $type: 'app.bsky.feed.post', text: 'yo', createdAt: iso } })
47 const { body } = decodeCommitFrame(await commitFrame(w, { seq: 5, time: iso }))
48 t.ok(body.repo === DID &&
49 body.seq === 5 &&
50 body.rebase === false && body.tooBig === false && body.prev === null &&
51 body.since === before && isValidTid(body.rev) && body.rev > before &&
52 body.commit.equals(w.commitCid) &&
53 body.ops.length === 1 && body.ops[0].action === 'create' &&
54 body.ops[0].path === 'app.bsky.feed.post/3bbb' && body.ops[0].cid !== null,
55 'body: ops/rev/since/commit/repo correct; rev is a valid TID')
56})
57
58// 3. blocks is a valid CAR rooted at the commit, containing commit + record
59test('body.blocks is a valid CAR rooted at the new commit', async (t) => {
60 const { mgr } = await newMgr(t)
61 const w = await mgr.applyWrite({ action: 'create', collection: 'app.bsky.feed.post', rkey: '3ccc', record: { $type: 'app.bsky.feed.post', text: 'z', createdAt: iso } })
62 const { body } = decodeCommitFrame(await commitFrame(w, { seq: 2, time: iso }))
63 const car = await readCar(body.blocks)
64 const commit = loadCommit(car)
65 t.ok(car.roots[0].equals(body.commit) && commit.value.did === DID && commit.value.data.equals(w.dataCid),
66 'body.blocks is a valid CAR rooted at the new commit')
67})
68
69// 4. delete op + monotonic rev + head-state reflected in reads
70test('delete: op cid null, rev advances, read-view reflects removal', async (t) => {
71 const { store, mgr } = await newMgr(t)
72 await mgr.applyWrite({ action: 'create', collection: 'app.bsky.feed.post', rkey: '3ddd', record: { $type: 'app.bsky.feed.post', text: 'tmp', createdAt: iso } })
73 const revAfterCreate = mgr.rev
74 const w = await mgr.applyWrite({ action: 'delete', collection: 'app.bsky.feed.post', rkey: '3ddd' })
75 const { body } = decodeCommitFrame(await commitFrame(w, { seq: 3, time: iso }))
76 const gone = await getRecord(store, 'app.bsky.feed.post', '3ddd', { did: DID })
77 t.ok(body.ops[0].action === 'delete' && body.ops[0].cid === null &&
78 body.rev > revAfterCreate && body.since === revAfterCreate && gone === null,
79 'delete: op cid null, rev advances, read-view reflects removal')
80})
81
82// The live subscribeRepos stream leaves a refed TLSSocket that ws.close() doesn't
83// release; brittle ends the run on `beforeExit` (loop drain), so unref it or the
84// runner hangs after all asserts pass. (This is why the pre-brittle version force-exited.)
85function unrefLingeringTlsSockets () {
86 if (typeof process._getActiveHandles !== 'function') return
87 for (const h of process._getActiveHandles()) {
88 if (h && h.constructor && h.constructor.name === 'TLSSocket') { try { h.unref() } catch {} }
89 }
90}
91
92// 5. LIVE millipds #commit frame: our body field-set matches (non-fatal skip)
93test('LIVE millipds #commit body field-set matches ours (or skip)', async (t) => {
94 let liveKeys, unreachable = null
95 try {
96 liveKeys = await new Promise((resolve, reject) => {
97 const ws = new WebSocket('wss://pds.7rnx.net/xrpc/com.atproto.sync.subscribeRepos?cursor=0')
98 ws.binaryType = 'arraybuffer'
99 const to = setTimeout(() => { try { ws.close() } catch {} ; reject(new Error('timeout')) }, 9000)
100 ws.onmessage = (ev) => {
101 const dec = decodeCommitFrame(new Uint8Array(ev.data))
102 if (dec) { clearTimeout(to); try { ws.close() } catch {} ; resolve(Object.keys(dec.body).sort()) }
103 }
104 ws.onerror = (e) => { clearTimeout(to); reject(new Error('ws error')) }
105 })
106 } catch (e) {
107 unreachable = e.message
108 } finally {
109 unrefLingeringTlsSockets()
110 }
111 if (unreachable !== null) {
112 t.comment(`millipds firehose unreachable: ${unreachable} — skipped`)
113 t.pass('millipds firehose unreachable — skipped')
114 return
115 }
116 t.comment('live #commit keys: ' + liveKeys.join(','))
117 // our keys must be a superset-or-equal of millipds's core set (millipds may add fields)
118 const ours = new Set(EXPECTED_BODY_KEYS)
119 const missing = liveKeys.filter(k => !ours.has(k))
120 if (missing.length) t.comment('fields millipds has that we lack: ' + missing.join(','))
121 t.is(missing.length, 0, 'LIVE millipds #commit body field-set matches ours')
122})
test/read-view.test.js +99 -0
……@@ -0,0 +1,99 @@
1// Slice 2: the bridge read-view over RepoStore. Oracle = the golden CAR (millipds
2// output). The commit block differs (our secp256k1 sig vs millipds P-256), so we diff
3// the CURVE-INDEPENDENT parts: MST + record blocks byte-for-byte, and commit.data.
4
5import test from 'brittle'
6import { readFileSync, mkdtempSync, rmSync } from 'node:fs'
7import { fileURLToPath } from 'node:url'
8import { dirname, join } from 'node:path'
9import { tmpdir } from 'node:os'
10import { RepoStore } from '../lib/repo-store.js'
11import { getRepo, getLatestCommit, getRecord, listRecords } from '../lib/read-view.js'
12import { readCar, loadCommit } from 'atproto-repo/lib/repo.js'
13
14const here = dirname(fileURLToPath(import.meta.url))
15const goldenBytes = readFileSync(join(here, 'vectors', 'bridgetest-repo.car'))
16const DID = 'did:plc:bh2hiuoeb34g4lif62zj2vx7'
17const REV = '3mpmksfzaqm7k'
18const POST_RKEY = '3mpmksfzaqm7k'
19const PRIV = new Uint8Array(32).fill(7) // any valid secp256k1 scalar; commit sig isn't the oracle
20const splitKey = (k) => { const i = k.indexOf('/'); return [k.slice(0, i), k.slice(i + 1)] }
21
22async function storeWithGolden (t) {
23 const dir = mkdtempSync(join(tmpdir(), 'readview-'))
24 const s = await new RepoStore(dir).ready()
25 t.teardown(async () => { try { await s.close() } catch {} rmSync(dir, { recursive: true, force: true }) })
26 const car = await readCar(goldenBytes)
27 const { listRecords: list } = await import('atproto-repo/lib/repo.js')
28 for (const r of list(car)) { const [c, rk] = splitKey(r.key); await s.put(c, rk, r.value) }
29 return { s, car }
30}
31
32const golden = await readCar(goldenBytes)
33const goldenCommit = loadCommit(golden)
34const goldenRoot = golden.roots[0].toString()
35
36// 1. getRepo: MST + record blocks byte-identical to millipds; commit.data matches.
37test('getRepo: non-commit blocks byte-match golden + data==commit.data', async (t) => {
38 const { s } = await storeWithGolden(t)
39 const { carBytes, dataCid, commitCid } = await getRepo(s, { did: DID, rev: REV, privateKey: PRIV })
40 const ours = await readCar(carBytes)
41 const ok = (() => {
42 if (!dataCid.equals(goldenCommit.value.data)) return false
43 // golden non-commit blocks
44 const goldenBlocks = new Map()
45 for (const [k, v] of golden.blocks) if (k !== goldenRoot) goldenBlocks.set(k, v.bytes)
46 // our non-commit blocks (skip our commit cid)
47 let ourNonCommit = 0
48 for (const [k, v] of ours.blocks) {
49 if (k === commitCid.toString()) continue
50 ourNonCommit++
51 const g = goldenBlocks.get(k)
52 if (!g || !Buffer.from(g).equals(Buffer.from(v.bytes))) return false
53 }
54 return ourNonCommit === goldenBlocks.size && goldenBlocks.size === 2 // mst node + record
55 })()
56 t.ok(ok, 'getRepo: non-commit blocks byte-match golden + data==commit.data')
57})
58
59// 2. getLatestCommit: rev preserved, commit cid present.
60test('getLatestCommit returns { cid, rev }', async (t) => {
61 const { s } = await storeWithGolden(t)
62 const c = await getLatestCommit(s, { did: DID, rev: REV, privateKey: PRIV })
63 t.ok(c.rev === REV && /^bafyrei[a-z2-7]+$/.test(c.cid), 'getLatestCommit returns { cid, rev }')
64})
65
66// 3. getRecord: CID matches the golden record CID (curve-independent), value intact.
67test('getRecord CID == golden record CID + value intact', async (t) => {
68 const { s } = await storeWithGolden(t)
69 const rec = await getRecord(s, 'app.bsky.feed.post', POST_RKEY, { did: DID })
70 // golden record cid: the MST entry's value; grab from listRecords
71 const { listRecords: list } = await import('atproto-repo/lib/repo.js')
72 const goldenRec = list(golden).find(r => r.key === `app.bsky.feed.post/${POST_RKEY}`)
73 t.ok(rec.cid === goldenRec.cid.toString() &&
74 rec.uri === `at://${DID}/app.bsky.feed.post/${POST_RKEY}` &&
75 rec.value.text.includes('pds.7rnx.net'),
76 'getRecord CID == golden record CID + value intact')
77})
78
79// 4. listRecords: one post, correct uri/cid.
80test('listRecords scopes + shapes correctly', async (t) => {
81 const { s } = await storeWithGolden(t)
82 const recs = await listRecords(s, 'app.bsky.feed.post', { did: DID })
83 t.ok(recs.length === 1 && recs[0].uri.endsWith(POST_RKEY) && recs[0].cid.startsWith('bafyrei'),
84 'listRecords scopes + shapes correctly')
85})
86
87// 5. LIVE oracle (bonus, non-fatal): millipds getRecord CID must equal ours.
88test('LIVE millipds getRecord CID matches (or skip if PDS down)', async (t) => {
89 let live
90 try {
91 const res = await fetch(`https://pds.7rnx.net/xrpc/com.atproto.repo.getRecord?repo=${DID}&collection=app.bsky.feed.post&rkey=${POST_RKEY}`, { signal: AbortSignal.timeout(8000) })
92 if (!res.ok) { t.comment('millipds not serving — skipped'); t.pass('LIVE millipds getRecord — skipped'); return }
93 live = await res.json()
94 } catch { t.comment('millipds unreachable — skipped'); t.pass('LIVE millipds getRecord — skipped'); return }
95 const { s } = await storeWithGolden(t)
96 const rec = await getRecord(s, 'app.bsky.feed.post', POST_RKEY, { did: DID })
97 t.comment(`live=${live.cid} ours=${rec.cid}`)
98 t.is(live.cid, rec.cid, 'LIVE millipds getRecord CID matches')
99})
test/repo-store.test.js +86 -0
……@@ -0,0 +1,86 @@
1// Slice 1 (Holepunch-first): the classic-hyperbee RepoStore, verified against the
2// MST oracle. The point: records stored + read back through real Hyperbee produce
3// the SAME MST root as building directly from the records — so the native store is a
4// faithful, byte-transparent home, and the bridge (atproto-repo) can serialize it.
5
6import test from 'brittle'
7import { readFileSync, mkdtempSync, rmSync } from 'node:fs'
8import { fileURLToPath } from 'node:url'
9import { dirname, join } from 'node:path'
10import { tmpdir } from 'node:os'
11import * as dagCbor from '@ipld/dag-cbor'
12import { RepoStore } from '../lib/repo-store.js'
13// bridge oracle (cross-module; pure-JS path):
14import { buildMst } from 'atproto-repo/lib/mst.js'
15import { cidForBytesSync } from 'atproto-repo/lib/cid.js'
16import { readCar, loadCommit, listRecords } from 'atproto-repo/lib/repo.js'
17
18const here = dirname(fileURLToPath(import.meta.url))
19const goldenCar = readFileSync(join(here, 'vectors', 'bridgetest-repo.car'))
20
21// records [{key, bytes}] -> MST root CID (this is what the bridge will do)
22const mstRootOf = (recs) => buildMst(recs.map(r => ({ key: r.key, value: cidForBytesSync(r.bytes) }))).root
23const splitKey = (k) => { const i = k.indexOf('/'); return [k.slice(0, i), k.slice(i + 1)] }
24
25const newStore = async (t) => {
26 const dir = mkdtempSync(join(tmpdir(), 'repostore-'))
27 const s = await new RepoStore(dir).ready()
28 t.teardown(async () => { try { await s.close() } catch {} rmSync(dir, { recursive: true, force: true }) })
29 return s
30}
31
32// 1. round-trip
33test('put -> get round-trips a record', async (t) => {
34 const s = await newStore(t)
35 const rec = { $type: 'app.bsky.feed.post', text: 'hello', createdAt: '2026-07-01T00:00:00.000Z' }
36 await s.put('app.bsky.feed.post', '3aaa', rec)
37 const got = await s.get('app.bsky.feed.post', '3aaa')
38 // compare canonical DAG-CBOR bytes (JSON.stringify is key-order-sensitive; dag-cbor canonicalises)
39 t.ok(Buffer.from(dagCbor.encode(got)).equals(Buffer.from(dagCbor.encode(rec))),
40 'put -> get round-trips a record')
41})
42
43// 2. delete
44test('del removes a record', async (t) => {
45 const s = await newStore(t)
46 await s.put('app.bsky.feed.post', '3aaa', { $type: 'x', text: 'a' })
47 await s.del('app.bsky.feed.post', '3aaa')
48 const got = await s.get('app.bsky.feed.post', '3aaa')
49 t.is(got, null, 'del removes a record')
50})
51
52// 3. list by collection + records() ordering
53test('list scopes to a collection; records() is key-ordered', async (t) => {
54 const s = await newStore(t)
55 await s.put('app.bsky.feed.post', '3bbb', { text: 'b' })
56 await s.put('app.bsky.feed.post', '3aaa', { text: 'a' })
57 await s.put('app.bsky.actor.profile', 'self', { displayName: 'me' })
58 const posts = []
59 for await (const r of s.list('app.bsky.feed.post')) posts.push(r.rkey)
60 const allKeys = (await s.records()).map(r => r.key)
61 const listOk = JSON.stringify(posts) === JSON.stringify(['3aaa', '3bbb'])
62 const orderedOk = JSON.stringify(allKeys) === JSON.stringify([...allKeys].sort())
63 const scopedOk = !posts.includes('self')
64 t.ok(listOk && orderedOk && scopedOk, 'list scopes to a collection; records() is key-ordered')
65})
66
67// 4. ORACLE A — real golden repo: records through the store -> golden MST root
68test('golden repo records via store -> MST root == commit.data', async (t) => {
69 const car = await readCar(goldenCar)
70 const commitData = loadCommit(car).value.data
71 const s = await newStore(t)
72 for (const r of listRecords(car)) { const [c, rk] = splitKey(r.key); await s.put(c, rk, r.value) }
73 const root = mstRootOf(await s.records())
74 t.ok(root.equals(commitData), 'golden repo records via store -> MST root == commit.data')
75})
76
77// 5. ORACLE B — store is byte-transparent to MST building (multi-record)
78test('store->MST root == direct->MST root (byte-transparent)', async (t) => {
79 const recs = []
80 for (let i = 0; i < 40; i++) recs.push({ key: `app.test.record/${String(i).padStart(4, '0')}`, record: { $type: 'app.test.record', n: i, note: `row ${i}` } })
81 const s = await newStore(t)
82 for (const r of recs) { const [c, rk] = splitKey(r.key); await s.put(c, rk, r.record) }
83 const storeRoot = mstRootOf(await s.records())
84 const directRoot = mstRootOf(recs.map(r => ({ key: r.key, bytes: dagCbor.encode(r.record) })))
85 t.ok(storeRoot.equals(directRoot), 'store->MST root == direct->MST root (byte-transparent)')
86})
test/vectors/bridgetest-repo.car +0 -0
(no content changes — binary or rename)