737729c9ce
Le nom @ng-eventually/sdk entrait en collision avec le SDK de NextGraph, dont ce paquet est justement un polyfill. Impossible d'écrire « le SDK » sans lever l'ambiguïté à chaque phrase — et le contrat publié, lu par une application, était le pire endroit pour laisser traîner ça. packages/sdk → packages/polyfill, @ng-eventually/sdk → @ng-eventually/polyfill, contract_sdk-surface → contract_polyfill-surface, e2e/sdk-entry.ts → e2e/polyfill-entry.ts, docs/sdk-reference.md → docs/polyfill-reference.md. Les occurrences de « SDK » qui désignent celui de NextGraph restent intactes, y compris les chemins dans nextgraph-rs (sdk/js/orm, sdk/js/web). Le tri s'est fait occurrence par occurrence, pas par substitution. Le contrat énonce désormais son identité en une phrase : « This package is a polyfill of NextGraph's SDK. »
138 lines
5.8 KiB
TypeScript
138 lines
5.8 KiB
TypeScript
/**
|
|
* physical — the polyfill's OWN machinery, operating on the PHYSICAL user.
|
|
*
|
|
* ── Two levels, two APIs, and only one of them is the app's ───────────────
|
|
* NextGraph sees exactly one user: the physical one, whose wallet everybody opens.
|
|
* On top of it the library fabricates **virtual users** — what the consumer calls
|
|
* an identity. Those are two different levels, and conflating them is how a
|
|
* boundary gets a hole in it:
|
|
*
|
|
* | | Level | Who calls it | Guarded |
|
|
* |---|---|---|---|
|
|
* | `docs.*`, `subscribeDoc` | the **virtual user** | the consumer app, and the library on the user's behalf | YES — confined to the connected user (`reach.ts`) |
|
|
* | this module | the **physical user** | the library's own machinery, and nothing else | no — it *is* the machinery the boundary is built on |
|
|
*
|
|
* **Nothing here is exported from the package.** `index.ts` must never re-export
|
|
* this module: an app holding these functions could read any document of any
|
|
* virtual user, which is precisely the boundary they exist below.
|
|
*
|
|
* ── Why a separate module rather than exemptions ──────────────────────────
|
|
* The store-root pointer and the doc-shim — the index of virtual users — cannot be
|
|
* subject to the boundary: resolving *which* documents a virtual user owns is what
|
|
* makes virtual users exist at all. An earlier version handled that with a list of
|
|
* exempt NURIs consulted by the guard. Separating the FUNCTIONS is stronger: the
|
|
* machinery does not call the guarded primitive and get waved through, it calls a
|
|
* different primitive that was never guarded. There is no exemption list to widen,
|
|
* to get wrong, or to infer.
|
|
*
|
|
* The rule for deciding which side a call belongs to:
|
|
*
|
|
* > Does this operate on the index of virtual users (the shim), or on the content
|
|
* > of one virtual user? The first is machinery; everything else is the user's,
|
|
* > and is confined.
|
|
*
|
|
* A virtual user's own stores, its inbox and its documents are the user's — they go
|
|
* through `docs.*` and are guarded, even though the library is what calls them.
|
|
*
|
|
* At migration this module disappears with the shim: there is no physical/virtual
|
|
* split once each user opens their own wallet.
|
|
*/
|
|
|
|
import { getConfig } from "./bootstrap";
|
|
import { logAccess } from "./access-log";
|
|
import { subscribeDocUnguarded } from "../surface/subscribe";
|
|
import { openRepoUnguarded } from "../emulated-verifier/open-repo";
|
|
import type { DocChange, DocChangeType, Unsubscribe } from "../surface/subscribe";
|
|
import { isNuri } from "../model/nuri";
|
|
import type { Nuri } from "../model/types";
|
|
|
|
/**
|
|
* Create a document as the PHYSICAL user — the shim's own documents (the doc-shim,
|
|
* a virtual user's store documents at provisioning time, an inbox document).
|
|
*
|
|
* Creation is the one operation with no boundary to check: the document does not
|
|
* exist yet, so nobody can hold its cap. What matters is who is credited with it
|
|
* afterwards, which the caller decides by filing the cap among the caps that holder holds.
|
|
*/
|
|
export async function physicalCreate(
|
|
sessionId: string,
|
|
crdt = "Graph",
|
|
cls = "data:graph",
|
|
dest = "store",
|
|
store?: unknown,
|
|
): Promise<Nuri> {
|
|
const { ng } = getConfig();
|
|
const nuri = await ng.doc_create(sessionId, crdt, cls, dest, store);
|
|
if (typeof nuri !== "string" || !isNuri(nuri)) {
|
|
throw new Error(
|
|
`[ng-eventually] physicalCreate: the broker returned something that is not a NextGraph reference: ${JSON.stringify(nuri)}`,
|
|
);
|
|
}
|
|
logAccess("WRITE", nuri, "physicalCreate");
|
|
return nuri;
|
|
}
|
|
|
|
/**
|
|
* Read as the PHYSICAL user — for the shim only (the store-root pointer, the
|
|
* doc-shim's account records).
|
|
*
|
|
* Unguarded by design: this is how the library learns which documents a virtual
|
|
* user owns, so it cannot itself depend on knowing that. Do not reach for it to
|
|
* read a virtual user's content — that is `docs.sparqlQuery`, which is confined.
|
|
*/
|
|
export async function physicalQuery(
|
|
sessionId: string,
|
|
query: string,
|
|
base: string | undefined,
|
|
anchor: Nuri,
|
|
label = "physicalQuery",
|
|
): Promise<unknown> {
|
|
const { ng } = getConfig();
|
|
const result = await ng.sparql_query(sessionId, query, base, anchor);
|
|
logAccess("READ", anchor, label, " (physical)");
|
|
return result;
|
|
}
|
|
|
|
/** Write as the PHYSICAL user — the shim's own records. See {@link physicalQuery}. */
|
|
export async function physicalUpdate(
|
|
sessionId: string,
|
|
query: string,
|
|
anchor: Nuri,
|
|
label = "physicalUpdate",
|
|
): Promise<void> {
|
|
const { ng } = getConfig();
|
|
logAccess("WRITE", anchor, label, " (physical)");
|
|
return ng.sparql_update(sessionId, query, anchor);
|
|
}
|
|
|
|
// --- the rest of the privileged door ---------------------------------------
|
|
//
|
|
// Moved here 2026-08-03 so that ONE module is the machinery's entire unguarded API,
|
|
// which is what this module's own doctrine asked for (see the header: separate
|
|
// functions, never exemptions). Before this they lived beside their guarded twins in
|
|
// `surface/subscribe.ts` and `emulated-verifier/open-repo.ts` — one import away from
|
|
// being reached by mistake.
|
|
|
|
/**
|
|
* Subscribe as the PHYSICAL user — the shim's own documents. The machinery's
|
|
* counterpart to `subscribeDoc`; never exported from the package.
|
|
*/
|
|
export function subscribePhysicalDoc(
|
|
nuri: Nuri,
|
|
onChange: (r: DocChange, type: DocChangeType) => void,
|
|
): Unsubscribe {
|
|
return subscribeDocUnguarded(nuri, onChange);
|
|
}
|
|
|
|
/**
|
|
* Open a repo as the PHYSICAL user — the shim's own documents (store-root, doc-shim).
|
|
* The machinery's counterpart to `ensureRepoOpen`: resolving WHICH documents a virtual
|
|
* user owns cannot itself be confined to that user.
|
|
*
|
|
* Never exported from the package.
|
|
*/
|
|
export async function ensurePhysicalRepoOpen(nuri: Nuri): Promise<void> {
|
|
if (!nuri) return;
|
|
return openRepoUnguarded(nuri);
|
|
}
|