/** * The polyfill bootstrap — the ONLY non-SDK surface of the client. * * It injects the REAL SDK and the polyfill settings; afterwards the SDK-shaped * exports (`ng`, `useShape`, `inbox`) behave as drop-ins. This is exposed at the * subpath `@ng-eventually/client/polyfill` so the main entry * (`@ng-eventually/client`) stays a **pure, SDK-identical** surface. Everything * here is removed at migration. */ import type { NgLike, UseShapeLike, PrincipalId } from "./types"; import type { RegistrySession } from "./store-registry"; import { CapRegistry } from "./caps"; import { setAccessLog } from "./access-log"; /** * Consumer-injected dependencies of the storeRegistry (polyfill-era). The * registry itself is generic (it knows only native scopes); the consumer wires * up how to reach the shared-wallet session and how to normalize an identity id * used as the shim key. Removed at migration along with the whole shim. */ export interface StoreRegistryDeps { /** Resolve the current shared-wallet session (id + private-store anchor). */ getSession: () => Promise; /** Normalize an identity id for shim keying. Default: trim (identity-ish). */ normalizeId?: (id: string) => string; /** * POINTER micro-guard budget. The account records now live in a subscribable * doc-shim (`did:ng:o:...`) reached through a well-known write-once POINTER triple * in the store-root graph. The doc-shim read is barrier-AUTHORITATIVE, so accounts * need NO retry (this replaces the deleted account-level `provisionRetry`). The * ONLY residual sync-lag window is the store-root pointer read itself — one * write-once triple. This bounded guard re-reads JUST that pointer a few times if a * fresh cold read misses it; it can never provision or fork an account (worst case: * a couple extra reads before an existing pointer is seen). Enable it where the REAL * broker is used (app + e2e). Left UNSET (the default) → `attempts: 1` = single * read, keeping the synchronous unit fakes fast and unchanged. */ pointerGuard?: { attempts?: number; baseMs?: number; maxStepMs?: number }; } export interface EventuallyConfig { /** The REAL `@ng-org/web` `ng` (injected to avoid a hard import / alias loop). */ ng: NgLike; /** The REAL `@ng-org/orm` `useShape`. */ useShape: UseShapeLike; /** Shared-wallet credentials — polyfill only (one wallet for everyone). */ sharedWallet?: { name: string; secret: string }; /** Initial current user; may also be set later via {@link setCurrentUser}. */ currentUser?: PrincipalId; /** * Turn on the OFF-by-default document access log (see {@link ./access-log}): * every real read/write is printed, prefixed by the active identity, to * diagnose the shared-wallet isolation leak. Also enablable without a code * change via the env var `NG_EVENTUALLY_ACCESS_LOG=1`. Default: false. */ debugAccessLog?: boolean; /** REAL `@ng-org/web` `init` (lifecycle) — forwarded by the lib's `init()`. */ init?: (...args: any[]) => any; /** REAL `@ng-org/orm` `initNg` (ORM signals) — forwarded by the lib's `initNg()`. */ initNg?: (...args: any[]) => any; } let cfg: EventuallyConfig | null = null; let currentUser: PrincipalId | null = null; /** Required fields of StoreRegistryDeps after defaults are applied. `pointerGuard` * defaults to `{ attempts: 1 }` (single read) when the consumer leaves it unset. */ type ResolvedRegistryDeps = Required< Pick >; let registryDeps: ResolvedRegistryDeps | null = null; /** The emulated ReadCap/WriteCap registry. Empty until the app declares caps; * while it has no read policy the read filter passes through (no regression). */ let caps = new CapRegistry(); export function configure(c: EventuallyConfig): void { cfg = c; currentUser = c.currentUser ?? null; setAccessLog(c.debugAccessLog ?? false); } /** @internal — used by the SDK-shaped wrappers to reach the injected real SDK. */ export function getConfig(): EventuallyConfig { if (!cfg) throw new Error("[ng-eventually] configure() must be called before use"); return cfg; } /** Reset the injected config back to un-configured (mainly for tests, so a * suite that calls configure() can restore the not-configured guard state). */ export function resetConfig(): void { cfg = null; currentUser = null; } /** * Wire the storeRegistry's consumer-injected dependencies (session + identity-id * normalization). Must be called before any storeRegistry.* use. Separate from * {@link configure} because it's storeRegistry-specific and, like the shim, * disappears at migration. */ export function configureStoreRegistry(deps: StoreRegistryDeps): void { registryDeps = { getSession: deps.getSession, normalizeId: deps.normalizeId ?? ((id: string) => id.trim()), // Default: single read (no re-read). Only the real-broker consumers (app + e2e) // opt into the bounded pointer micro-guard; unit fakes stay synchronous. pointerGuard: deps.pointerGuard ?? { attempts: 1 }, }; } /** @internal — used by the storeRegistry to reach its injected dependencies. */ export function getStoreRegistryDeps(): ResolvedRegistryDeps { if (!registryDeps) { throw new Error("[ng-eventually] configureStoreRegistry() must be called before use"); } return registryDeps; } /** Reset storeRegistry deps (mainly for tests). */ export function resetStoreRegistry(): void { registryDeps = null; } /** * Set the current identity id — who the SDK is reading/writing as. In the target * this is the wallet user established at wallet-import time; here the consumer * relays that id through this call so the read filter and the inbox `from` know * who is acting. Passing `null` clears it (no identity yet, e.g. during startup). */ export function setCurrentUser(id: PrincipalId | null): void { currentUser = id; } export function getCurrentUser(): PrincipalId | null { return currentUser; } /** The emulated cap registry — the app opens a document's read policy and issues * directed read grants on it (as it will via real cap operations in the target). * The read filter consults it. */ export function getCaps(): CapRegistry { return caps; } /** Reset all emulated caps (mainly for tests / fresh sessions). */ export function resetCaps(): void { caps = new CapRegistry(); } // Cap surface — polyfill-era (caps are emulated now; native at migration). // Re-exported here so the whole polyfill API lives under /polyfill. export { CapRegistry } from "./caps";