feat!: useShapeQuery lit par la requete de forme du SDK

This commit is contained in:
Sylvain Duchesne
2026-09-21 11:41:31 +02:00
parent a5e553e65b
commit 77e78ec81d
29 changed files with 1190 additions and 971 deletions
@@ -404,8 +404,8 @@ function recordFromRows(
// The ONE place the `""`-for-corrupt case is absorbed. `VirtualUserRecord` promises
// real NURIs; a shim missing a scope document yields `""` here, exactly as it
// always has, and the cast records that this is a KNOWN gap rather than a proven
// invariant. Callers already test for the empty value (e.g. `watchShape` skips a
// falsy container). Rejecting such a record outright would be the right fix and is
// invariant. Callers already test for the empty value (`listMyEntityDocs` reads
// nothing out of a falsy container). Rejecting such a record outright would be the right fix and is
// a behaviour change — its own lot, not this one.
return {
id: id || fallbackId,
@@ -703,8 +703,8 @@ async function writeRecord(doc: Nuri, record: VirtualUserRecord): Promise<void>
*
* ── Why this is load-bearing (concurrent-provision de-dup) ──────────────────
* On a fresh page over the persistent wallet, SEVERAL independent callers hit
* `ensureAccount(A)` near-simultaneously (watchShape public/protected, the container
* subscriptions, the app's owned-events effect). With the barrier-authoritative
* `ensureAccount(A)` near-simultaneously (a listing per store, an inbox enumeration, the
* app's own effects). With the barrier-authoritative
* resolveAccount a fresh page NO LONGER mistakes sync-lag for absence — but if the
* account is GENUINELY new, N concurrent callers would still each see 0 and each
* provision a set of scope docs (an in-session fork). De-duping concurrent provisions
@@ -1263,20 +1263,13 @@ export async function readUserStore(indexDoc: Nuri): Promise<Nuri[]> {
return out;
}
/**
* The scope-INDEX document NURI of ONE account (`id`) for `scope` — the
* store-container document that LISTS the account's per-entity document NURIs
* (what {@link listMyEntityDocs} reads). Exposed so a reactive reader
* ({@link watchShape}) can SUBSCRIBE to this index document and re-resolve the
* entity-doc set when the index changes (a new entity created appends a NURI
* here). Idempotent via `ensureAccount`'s cache. At migration this becomes the
* user's real per-scope store NURI (the container the store itself provides).
*/
// @provenance userStoreDoc kind=aligned level=2 ref=sdk/js/web/src/index.ts:Session — the per-scope index document stands in for the store the session names directly
export async function userStoreDoc(id: string, scope: StoreType): Promise<Nuri> {
const record = await ensureAccount(id);
return storeOf(record, scope);
}
// `userStoreDoc(id, scope)` was REMOVED in `1.0.0-dev.6`. It handed out the scope-INDEX
// document so that a reactive reader could SUBSCRIBE to it and re-resolve its document set
// when the index changed - and the reactive reader was the only thing that ever wanted it.
// `useShapeQuery` now reads through the SDK's own shape query, which follows the data
// rather than a container this package maintains, so the last caller went with the
// pipeline. `storeOf(await ensureAccount(id), scope)` is the two-line body, for whoever
// needs the value again.
/**
* The entity-document NURIs of `scope` belonging to ONE account (`id`) —
@@ -15,7 +15,15 @@
* The entry now only re-exports; the internals import this module instead.
*/
import type { NgLike, UseShapeLike, Nuri, NuriLike, PrincipalId, ReadCap } from "../model/types";
import type {
NgLike,
UseShapeLike,
OrmSubscriptionLike,
Nuri,
NuriLike,
PrincipalId,
ReadCap,
} from "../model/types";
import type { SharedWalletConfig } from "./access-gate";
import { toNuri } from "../model/nuri";
import type { RegistrySession } from "./account-registry";
@@ -113,6 +121,19 @@ export interface EventuallyConfig {
ng: NgLike;
/** The REAL `@ng-org/orm` `useShape`. */
useShape: UseShapeLike;
/**
* The REAL `@ng-org/orm` `RdfOrmSubscription.getOrCreate` - the SDK's shape query,
* which is what {@link ../surface/use-shape-query}.useShapeQuery reads through since
* `1.0.0-dev.6`. Pass it as it stands: `ormSubscription: RdfOrmSubscription.getOrCreate`.
*
* OPTIONAL, and that is a scope decision rather than a soft one: an application that
* never calls `useShapeQuery` has nothing to inject here, and every suite of this
* package that exercises another surface would otherwise carry a stub for a call it
* does not make. What is NOT optional is the consequence - `useShapeQuery` without it
* publishes `isError` naming this field, never an empty set, because "nobody answered"
* reported as "there is nothing" is the one failure that surface exists to prevent.
*/
ormSubscription?: OrmSubscriptionLike;
/**
* POINTER micro-guard budget — see {@link StoreRegistryDeps.pointerGuard}. Left unset
* → a single read, which keeps the synchronous unit fakes fast.
@@ -414,7 +435,7 @@ export function hasCap(nuri: NuriLike): boolean {
*/
export function resetCaps(): void {
// Clear IN PLACE rather than rebuilding: whoever subscribed to the registry's
// change signal (`watchShape`) stays subscribed to the live instance instead of
// change signal (`useShapeQuery`) stays subscribed to the live instance instead of
// silently holding a listener on an orphaned one.
caps.clear();
// …and forget which documents were already asked about, or the emulated public-store