OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-room commits / fa2790e

atproto-room: add non-blocking getNow()/listNow() reads
get()/list() do `await base.update()`, which drives the linearizer fully up-to-date and can HANG on an un-converged plain-writer replica (device-swarm still catching up) — stalling liveness-critical callers (e.g. the hpp device-registry revocation check on login). The view is a HyperDB opened autoUpdate:true, so it already reflects the latest materialized state; getNow/listNow read that current snapshot without the blocking update (at most a few ms stale). Additive; no behavior change to existing get()/list(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fa2790e9a365c9f784a6268bc659a294e1364402
by plankatron <fleeky@gmail.com> · 2026-07-16T23:03:57.000Z

Changed files (2) +22 -2

lib/state-base.js +20 -0
……@@ -484,6 +484,26 @@ export class StateBaseV3 {
484484 return out
485485 }
486486
487 // Non-blocking reads — same as get()/list() but WITHOUT `await base.update()`. base.update() drives the
488 // linearizer fully up-to-date, which can HANG on an un-converged plain-writer replica (device-swarm
489 // still catching up), stalling any caller (e.g. the device-registry revocation check on login). The
490 // view is a HyperDB opened `autoUpdate:true`, so it already reflects the latest MATERIALIZED state —
491 // these read that current snapshot instantly (at most a few ms stale). Use for liveness-critical reads
492 // that must never block; use get()/list() when you need the freshest fully-drained view.
493 async getNow (key) {
494 if (!this._ready) return null
495 const rec = await this.base.view.get(ENTRIES, { key: String(key) })
496 return rec ? this._dec(rec.value) : null
497 }
498
499 async listNow (prefix = '') {
500 if (!this._ready) return {}
501 const out = {}
502 const stream = this.base.view.find(ENTRIES, { gte: { key: prefix }, lt: { key: prefix + '~' } })
503 for await (const rec of stream) out[rec.key] = this._dec(rec.value)
504 return out
505 }
506
487507 // value is an arbitrary JSON-able Variant from the game; stored as a JSON string.
488508 _enc (v) { try { return JSON.stringify(v) } catch { return String(v) } }
489509 _dec (s) { if (typeof s !== 'string') return s; try { return JSON.parse(s) } catch { return s } }
package.json +2 -2
……@@ -1,7 +1,7 @@
11{
22 "name": "atproto-room",
3 "version": "0.2.0",
4 "description": "Attestation-gated, encrypted multi-writer room engine — StateBase (keet-model admission) + Lease + signer + RepoBase + LabelBase/LabelRoom + serve-membrane. Extracted from hyper-proto-peer; consumed by the atproto peer (repo devices) and the co-authored labeler.",
3 "version": "0.2.1",
4 "description": "Attestation-gated, encrypted multi-writer room engine \u2014 StateBase (keet-model admission) + Lease + signer + RepoBase + LabelBase/LabelRoom + serve-membrane. Extracted from hyper-proto-peer; consumed by the atproto peer (repo devices) and the co-authored labeler.",
55 "type": "module",
66 "main": "index.js",
77 "scripts": {