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>
69 lines
2.8 KiB
TypeScript
69 lines
2.8 KiB
TypeScript
import { test, expect } from "bun:test";
|
|
import {
|
|
applyIsolation,
|
|
connectionsFromLinks,
|
|
visibleSet,
|
|
isVisible,
|
|
type IsolationAccessors,
|
|
} from "../src/isolation";
|
|
import type { Scope } from "../src/types";
|
|
|
|
// Generic item: owner + scope. ZERO domain — the accessors read these fields.
|
|
interface Item {
|
|
id: string;
|
|
owner: string;
|
|
scope: Scope;
|
|
}
|
|
const acc: IsolationAccessors<Item> = { ownerOf: (i) => i.owner, scopeOf: (i) => i.scope };
|
|
|
|
// alice —— bob (carol is unconnected)
|
|
const links = [{ a: "alice", b: "bob" }];
|
|
const conns = connectionsFromLinks(links);
|
|
|
|
test("connectionsFromLinks / visibleSet: self + direct connections, both directions", () => {
|
|
expect([...conns.neighbors("alice")].sort()).toEqual(["bob"]);
|
|
expect([...conns.neighbors("bob")].sort()).toEqual(["alice"]);
|
|
expect([...conns.neighbors("carol")]).toEqual([]);
|
|
|
|
expect([...visibleSet("alice", conns)].sort()).toEqual(["alice", "bob"]);
|
|
expect([...visibleSet("carol", conns)].sort()).toEqual(["carol"]);
|
|
});
|
|
|
|
test("isVisible matrix: public=all, protected=owner+connections, private=owner", () => {
|
|
const vis = visibleSet("alice", conns); // { alice, bob }
|
|
// public → everyone, regardless of owner
|
|
expect(isVisible("public", "carol", "alice", vis)).toBe(true);
|
|
// protected → owner must be in the visible set
|
|
expect(isVisible("protected", "bob", "alice", vis)).toBe(true);
|
|
expect(isVisible("protected", "carol", "alice", vis)).toBe(false);
|
|
// private → owner must be the current principal
|
|
expect(isVisible("private", "alice", "alice", vis)).toBe(true);
|
|
expect(isVisible("private", "bob", "alice", vis)).toBe(false);
|
|
});
|
|
|
|
test("applyIsolation narrows a mixed collection for the current principal", () => {
|
|
const items: Item[] = [
|
|
{ id: "e", owner: "carol", scope: "public" }, // public → kept for all
|
|
{ id: "p1", owner: "alice", scope: "protected" }, // own protected → kept
|
|
{ id: "p2", owner: "bob", scope: "protected" }, // connection's protected → kept
|
|
{ id: "p3", owner: "carol", scope: "protected" }, // stranger's protected → dropped
|
|
{ id: "s1", owner: "alice", scope: "private" }, // own private → kept
|
|
{ id: "s2", owner: "bob", scope: "private" }, // connection's private → dropped
|
|
];
|
|
|
|
const forAlice = applyIsolation(items, "alice", conns, acc).map((i) => i.id);
|
|
expect(forAlice).toEqual(["e", "p1", "p2", "s1"]);
|
|
|
|
const forCarol = applyIsolation(items, "carol", conns, acc).map((i) => i.id);
|
|
// carol sees: public + her own protected + her own private
|
|
expect(forCarol).toEqual(["e", "p3"]);
|
|
});
|
|
|
|
test("applyIsolation with empty principal passes everything through (hydration guard)", () => {
|
|
const items: Item[] = [
|
|
{ id: "s1", owner: "alice", scope: "private" },
|
|
{ id: "p1", owner: "bob", scope: "protected" },
|
|
];
|
|
expect(applyIsolation(items, "", conns, acc).map((i) => i.id)).toEqual(["s1", "p1"]);
|
|
});
|