654cb90d99
Port the shared-wallet shim mechanics from the Festipod app into the
generic @ng-eventually/client library — zero app-specific knowledge, the
consumer injects the domain (entity->scope mapping, connections, storage).
New namespaces exposed from src/index.ts:
- docs docCreate/sparqlUpdate/sparqlQuery via the REAL injected `ng`
(getConfig().ng), never the public makeNg proxy — the JS-over-
iframe double proxy breaks doc_create postMessage marshaling
(DataCloneError). Validated hard constraint.
- storeRegistry generic (account,scope)->NURI resolver, createEntityDoc/
listEntityDocs + per-scope index, sharedWalletShim in the
private_store, cache. Consumer wiring injected via
configureStoreRegistry({ getSession, normalizeUser }).
- isolation pure applyIsolation (public=all / protected=owner+connections
/ private=owner); accessors + connection graph injected.
- accounts AccountStore (localStorage-backed faux login, storage injected)
+ normalizeUsername. React wrapper intentionally NOT ported.
polyfill.ts gains configureStoreRegistry/getStoreRegistryDeps + resetConfig.
36/36 bun test, tsc --noEmit rc=0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
117 lines
4.4 KiB
TypeScript
117 lines
4.4 KiB
TypeScript
/**
|
|
* 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";
|
|
|
|
/**
|
|
* 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 a username
|
|
* 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<RegistrySession>;
|
|
/** Normalize a username for shim keying. Default: trim (identity-ish). */
|
|
normalizeUser?: (username: string) => string;
|
|
}
|
|
|
|
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;
|
|
/** 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;
|
|
let registryDeps: Required<StoreRegistryDeps> | 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;
|
|
}
|
|
|
|
/** @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 + username
|
|
* 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,
|
|
normalizeUser: deps.normalizeUser ?? ((u: string) => u.trim()),
|
|
};
|
|
}
|
|
|
|
/** @internal — used by the storeRegistry to reach its injected dependencies. */
|
|
export function getStoreRegistryDeps(): Required<StoreRegistryDeps> {
|
|
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 app-level user (the polyfill's notion of "who am I"). */
|
|
export function setCurrentUser(id: PrincipalId | null): void {
|
|
currentUser = id;
|
|
}
|
|
|
|
export function getCurrentUser(): PrincipalId | null {
|
|
return currentUser;
|
|
}
|
|
|
|
/** The emulated cap registry — the app grants/opens caps 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";
|