From 5cdc6ce77feacb0d020b8619f3c21d51d521efe6 Mon Sep 17 00:00:00 2001 From: Sylvain Duchesne Date: Mon, 13 Jul 2026 16:40:37 +0200 Subject: [PATCH] =?UTF-8?q?feat(client):=20inbox.readSynced=20=E2=80=94=20?= =?UTF-8?q?lecture=20inbox=20gated=20sur=20la=20barri=C3=A8re=20first-Stat?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ensureRepoOpen(target) (attend le 1er State, barrière de sync déterministe) puis read(target) — même pattern que discovery.readIndex. Permet au propriétaire d'un événement de voir un dépôt DÉJÀ synchronisé au lieu de lire trop tôt un inbox vide. Consommé par Festipod (materializeAttendance). Pas de polling. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/client/src/inbox.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/client/src/inbox.ts b/packages/client/src/inbox.ts index 9f0ba93..7cb4698 100644 --- a/packages/client/src/inbox.ts +++ b/packages/client/src/inbox.ts @@ -26,6 +26,7 @@ import { sparqlUpdate, sparqlQuery } from "./docs"; import { subscribeDoc } from "./subscribe"; +import { ensureRepoOpen } from "./open-repo"; import { getCurrentUser, getStoreRegistryDeps } from "./polyfill"; import { escapeLiteral } from "./sparql"; import type { Nuri, PrincipalId } from "./types"; @@ -195,6 +196,31 @@ export async function read(targetInbox: Nuri): Promise { /** Alias for {@link read} — the name that reads as "process the inbox now". */ export const materialize = read; +/** + * COLD, BARRIER-GATED read of `targetInbox` — the reliable "process the inbox at + * (re)connection" read. Opens/subscribes the inbox repo and AWAITS its first + * `State` (the deterministic sync barrier — after it, presence is guaranteed and + * absence definitive, {@link ensureRepoOpen}) BEFORE the anchored {@link read}. + * + * Why this over a plain {@link read}: on a FRESH session over the persistent + * wallet (a (re)connection / new page), the inbox repo is not yet in the verifier's + * `self.repos`, so a plain anchored `read` resolves an unopened repo and silently + * returns 0 deposits — even for a deposit a remote session already synced to the + * broker. Gating on the sync barrier makes the read see the synced deposits. This + * is the SAME cold-read heal `discovery.readIndex` applies to the index inbox. + * + * NOT for the `watch` path: {@link watch} already holds the repo open via its own + * `subscribeDoc`, so opening a second bootstrap subscription from inside a watch + * re-read would be redundant and could race the watch's own initial-`State` + * delivery. Use this from a COLD reader (materialize-at-connection), like + * `discovery.readIndex` does. Idempotent per session (no polling); a no-op open on + * the unit fake-ng path (no `doc_subscribe`) so `bun test` is unaffected. + */ +export async function readSynced(targetInbox: Nuri): Promise { + await ensureRepoOpen(targetInbox); + return read(targetInbox); +} + /** * Subscription over an inbox — **event-driven, not polled**. Subscribes to the * inbox document via {@link subscribeDoc} (the platform's `doc_subscribe` push):