| … | … | @@ -484,6 +484,26 @@ export class StateBaseV3 { |
| 484 | 484 | return out |
| 485 | 485 | } |
| 486 | 486 | |
| 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 | |
| 487 | 507 | // value is an arbitrary JSON-able Variant from the game; stored as a JSON string. |
| 488 | 508 | _enc (v) { try { return JSON.stringify(v) } catch { return String(v) } } |
| 489 | 509 | _dec (s) { if (typeof s !== 'string') return s; try { return JSON.parse(s) } catch { return s } } |