| … | … | @@ -45,7 +45,7 @@ export class DidPeer { |
| 45 | 45 | // corestore: a Corestore (cores are global by key). did: the DID. verifyKey: its signing pubkey |
| 46 | 46 | // (Uint8Array) — REQUIRED to accept a served head. onHead: (head) => void on every NEW verified |
| 47 | 47 | // head. onError: optional (err) => void. |
| 48 | | constructor (corestore, { did, verifyKey, handle = null, onHead = null, onError = null } = {}) { |
| 48 | constructor (corestore, { did, verifyKey, handle = null, onHead = null, onError = null, noRepo = false } = {}) { |
| 49 | 49 | if (!corestore) throw new Error('DidPeer: corestore required') |
| 50 | 50 | if (!did) throw new Error('DidPeer: did required') |
| 51 | 51 | if (!verifyKey) throw new Error('DidPeer: verifyKey (DID signing pubkey) required — auth is mandatory') |
| … | … | @@ -55,6 +55,11 @@ export class DidPeer { |
| 55 | 55 | this.handle = handle |
| 56 | 56 | this._onHead = onHead |
| 57 | 57 | this._onError = onError |
| 58 | // noRepo: follow the firehose ONLY (verified head + tail ops) and DO NOT open/replicate the repo core. |
| 59 | // Records come from elsewhere (the shared content-addressed OverlayRepoStore, co-located with the |
| 60 | // producer). This is the "one copy" consumer: no duplicate repo-core replica. getRepo/getRecord/ |
| 61 | // listRecords return null/[]/throw (this.repo stays null) — the caller reads records from the store. |
| 62 | this._noRepo = !!noRepo |
| 58 | 63 | this.repo = null |
| 59 | 64 | this.seq = null |
| 60 | 65 | // Optional write/session/blob RPC to the peer (peer-signs-over-RPC). Set by DidPeerRouter from the |
| … | … | @@ -73,12 +78,13 @@ export class DidPeer { |
| 73 | 78 | async provide ({ repoKey, firehoseKey } = {}) { return this._openCores(repoKey, firehoseKey) } |
| 74 | 79 | |
| 75 | 80 | async _openCores (repoKey, firehoseKey) { |
| 76 | | if (this.repo) return |
| 81 | if (this.seq) return // firehose is opened in BOTH modes; the idempotency guard (repo may stay null under noRepo) |
| 77 | 82 | if (this._opening) return this._opening |
| 78 | 83 | this._opening = (async () => { |
| 79 | 84 | this._repoKey = toKey(repoKey) |
| 80 | 85 | this._firehoseKey = toKey(firehoseKey) |
| 81 | | this.repo = await new RepoStore(this.corestore, { key: this._repoKey }).ready() |
| 86 | // noRepo: skip the repo core entirely — never opened, so corestore.replicate never syncs it (no replica). |
| 87 | if (!this._noRepo) this.repo = await new RepoStore(this.corestore, { key: this._repoKey }).ready() |
| 82 | 88 | this.seq = await new FirehoseSequencer(this.corestore.get({ key: this._firehoseKey })).ready() |
| 83 | 89 | // FULL seed so this node can serve the cores to others (download all + follow appends) — this is |
| 84 | 90 | // the "each peer seeds" torrent behaviour; a read-only consumer that does NOT want to seed can |
| … | … | @@ -89,7 +95,8 @@ export class DidPeer { |
| 89 | 95 | // added another append listener, an UNBOUNDED range/listener leak that a re-authoring or ballooning |
| 90 | 96 | // producer turned into runaway replication churn (the ~100MB/s "never settles" amplifier). |
| 91 | 97 | const seed = (core) => { try { core.download({ start: 0, end: -1 }) } catch {} } |
| 92 | | seed(this.repo.core); seed(this.seq.core) |
| 98 | if (this.repo) seed(this.repo.core) |
| 99 | seed(this.seq.core) |
| 93 | 100 | // EVENT-DRIVEN: re-derive the served head on every firehose append (local or replicated), and |
| 94 | 101 | // re-announce the new length to connected peers. No poll loop. |
| 95 | 102 | this.seq.core.on('append', () => { this._reconcile(); this._announceAll() }) |
| … | … | @@ -401,7 +408,7 @@ export class DidPeerRouter { |
| 401 | 408 | // verifyKey resolved lazily at front time). Idempotent. |
| 402 | 409 | add (did, opts = {}) { |
| 403 | 410 | this._fronted.add(did) |
| 404 | | if (opts.verifyKey || opts.onHead || opts.onError || opts.handle) { |
| 411 | if (opts.verifyKey || opts.onHead || opts.onError || opts.handle || opts.noRepo) { |
| 405 | 412 | this._opts.set(did, { ...(this._opts.get(did) || {}), ...opts }) |
| 406 | 413 | } |
| 407 | 414 | this._joinDid(did) |
| … | … | @@ -409,7 +416,7 @@ export class DidPeerRouter { |
| 409 | 416 | if (existing) return existing |
| 410 | 417 | const o = this._opts.get(did) || {} |
| 411 | 418 | if (!o.verifyKey) return this // bridge allow-set add (verifyKey lazy); return value unused/chainable |
| 412 | | const p = new DidPeer(this.corestore, { did, verifyKey: o.verifyKey, handle: o.handle || null, onHead: o.onHead || null, onError: o.onError || null }) |
| 419 | const p = new DidPeer(this.corestore, { did, verifyKey: o.verifyKey, handle: o.handle || null, onHead: o.onHead || null, onError: o.onError || null, noRepo: o.noRepo }) |
| 413 | 420 | this._peers.set(did, p) |
| 414 | 421 | for (const chan of this._channels) p.announceVia(chan._announce) // (re-add) announce if already held |
| 415 | 422 | return p |
| … | … | @@ -480,7 +487,7 @@ export class DidPeerRouter { |
| 480 | 487 | const boundFh = peer._firehoseKey ? b4a.toString(peer._firehoseKey, 'hex') : null |
| 481 | 488 | if (boundFh !== fhKeyHex) peer = null // primary already bound to a different co-owner -> new front |
| 482 | 489 | } |
| 483 | | if (!peer) peer = new DidPeer(this.corestore, { did, verifyKey, handle: entry.coords.handle || o.handle || null, onHead: o.onHead || null, onError: o.onError || null }) |
| 490 | if (!peer) peer = new DidPeer(this.corestore, { did, verifyKey, handle: entry.coords.handle || o.handle || null, onHead: o.onHead || null, onError: o.onError || null, noRepo: o.noRepo }) |
| 484 | 491 | if (entry.coords.handle) peer.handle = entry.coords.handle |
| 485 | 492 | if (this._useRpc && entry.rpc) peer.rpc = entry.rpc |
| 486 | 493 | await peer.provide({ repoKey: entry.coords.repoKey, firehoseKey: entry.coords.firehoseKey }) |