Files
ng-eventually/.project/concepts/app-contract/contract_polyfill-surface.md
T
Sylvain Duchesne fc3c129bd3 fix: l'identifiant est dans l'URL avant qu'init() ne l'emporte
@ng-org/web fait déjà la redirection vers le broker — même hôte, même forme,
même test de cadre (dist/ngweb.js). Le polyfill n'a donc rien à réimplémenter
là : ce qui lui revient, c'est la seule chose qu'init() ne peut pas faire, à
savoir mettre ?ng-id= dans window.location.href avant qu'il ne le lise.

rememberIdentity() s'exécute désormais sur les trois chemins — identité posée
par l'appelant, venue du stockage, ou saisie à la barrière. Le cas du stockage
était le silencieux : le paramètre restait absent, l'iframe lisait une identité
vide et provisionnait un second espace virtuel, sans erreur.

L'écriture dans le stockage et celle dans la barre d'adresse sont deux try
indépendants : un stockage qui refuse d'écrire ne doit pas emporter avec lui le
paramètre, qui est le seul à franchir la frontière de partition.

Le contrat retire l'obligation « être ouverte via la redirection du broker » :
elle n'a jamais été celle de l'application. Ni broker, ni iframe, ni redirection
n'y sont plus nommés.
2026-08-11 11:59:57 +02:00

137 lines
8.1 KiB
Markdown

---
type: contract
summary: The API @ng-eventually/polyfill exposes to an application — signatures, guaranteed behaviour, and what it does not offer
---
# contract_polyfill-surface — `@ng-eventually/polyfill`
## Scope
This package is a polyfill of NextGraph's SDK.
This package covers placement (creating and listing an application's documents by scope), reading (a document's subjects, one-shot or reactive), sharing a document with a named user, and depositing into inboxes. It does not cover user management, display names, transport, or the operation of a deployment.
### Deployment requirements
An application using this package must:
- serve a wallet file (`.ngw`) from its own bundle, and pass its URL and password to `configure` as `sharedWallet: { fileUrl, password }`;
- call `ensureIdentity()` in a browser context before rendering its interface; it mounts a barrier in the document.
## Surface
Full typed shape: the package's `types` entry, `@ng-eventually/polyfill`. A type is published only when a published signature uses it. The load-bearing signatures:
```ts
// ── bootstrap ────────────────────────────────────────────────────────────
export function configure(c: EventuallyConfig): void;
export interface EventuallyConfig {
ng: NgLike; // the `ng` object from @ng-org/web
useShape: UseShapeLike; // `useShape` from @ng-org/orm
getSession?: () => Promise<RegistrySession>; // resolve the session (a thunk)
normalizeId?: (id: string) => string;
sharedWallet?: SharedWalletConfig; // { fileUrl, password, importUrl? }
currentUser?: PrincipalId;
debugAccessLog?: boolean;
init?: (...args: any[]) => any;
initNg?: (...args: any[]) => any;
}
// ── identity — one await before the application renders ──────────────────
export async function ensureIdentity(): Promise<PrincipalId>; // returns who you are
// ── addressing ───────────────────────────────────────────────────────────
export type Nuri = `did:ng:${string}`;
export type NuriLike = Nuri | string;
export type Scope = "public" | "protected" | "private";
// ── placement: where an application's documents live ─────────────────────
export const storeRegistry: { // no identity parameter — a session is one user's
createEntityDoc(scope: Scope): Promise<Nuri>;
listMyEntityDocs(scope: Scope): Promise<Nuri[]>;
resolveScopeGraph(scope: Scope): Promise<Nuri>;
resolveWriteGraph(scope: Scope): Promise<Nuri>;
openDocumentInbox(doc: NuriLike): Promise<Nuri>;
};
// ── reading ──────────────────────────────────────────────────────────────
export async function readUnion(docs: NuriLike[]): Promise<UnionSubject[]>;
export interface UnionSubject { subject: string; graph: Nuri; props: Record<string, string[]> }
export function useShape(shapeType: unknown, scope: unknown): unknown; // read-filtered view
export function watchShape(query: ShapeQuery): ShapeObservable;
export function subscribeDoc(nuri: NuriLike, onChange: (r: DocChange, t: DocChangeType) => void): Unsubscribe;
export function subscribeDocs(nuris: NuriLike[], onChange: (r: DocChange, t: DocChangeType) => void): Unsubscribe;
// ── low-level document / SPARQL primitives ───────────────────────────────
export const docs: {
docCreate(sessionId: string, crdt?: string, cls?: string, dest?: string, store?: unknown): Promise<Nuri>;
sparqlQuery(sessionId: string, query: string, base?: string, anchor?: NuriLike, label?: string): Promise<unknown>;
sparqlUpdate(sessionId: string, query: string, anchor?: NuriLike, label?: string): Promise<void>;
};
// ── inbox: giving to read, and depositing ────────────────────────────────
export const inbox: {
share(doc: NuriLike, toUser: string): Promise<void>; // give a reader the key
post(targetInbox: NuriLike, opts: PostOptions): Promise<void>;
postToDocument(doc: NuriLike, opts: PostOptions): Promise<void>;
read(targetInbox: NuriLike): Promise<Deposit[]>; // only your own
readForDocument(doc: NuriLike): Promise<Deposit[]>;
readSynced(targetInbox: NuriLike): Promise<Deposit[]>;
processInbox(targetInbox: NuriLike): Promise<Deposit[]>;
watch(targetInbox: NuriLike, onDeposits: (d: Deposit[]) => void): () => void;
materialize: typeof read;
};
export interface Deposit { from: PrincipalId | null; payload: unknown; ts: number }
// ── the wrapped SDK objects ──────────────────────────────────────────────
export const ng: Record<string, any>; // call this instead of the `ng` passed to `configure`
export function init(...args: any[]): any;
export function initNg(...args: any[]): any;
```
## Guarantees
Every entry accepts `NuriLike` and validates at the door; what it returns is a precise `Nuri`. No type guard is published.
A returned reference carries no key — not `createEntityDoc`, not `listMyEntityDocs`, not `UnionSubject.subject` / `.graph`. A reference found inside a document yields a name, not a key.
You read a document whose key you hold: you created it, it was shared with you, or it sits in a public store, which serves its read key to whoever asks. No call answers "may I read this?".
What was shared with you becomes readable after `ensureIdentity()`.
`readUnion` returns one entry per distinct subject present in a document. `subject` is that subject's IRI exactly as written, and is a `string`, because a subject may be any IRI; `graph` is the document reference you passed in, and is the `Nuri` to hand back to this surface. Properties of different subjects are never merged, and the same subject IRI found in two documents stays two entries, told apart by `graph`. Several objects in one document are allowed. Recommended placement is one document per business entity: access is granted per document.
`urn:ng-eventually:` is reserved. Triples whose **subject** falls under that prefix are dropped on read and never returned by `readUnion`; every other IRI is returned.
Only a document's owner writes to it. Holding its read key never grants a write.
`inbox.share(doc, toUser)` names the document and the person; the recipient calls nothing. It refuses a recipient nobody has signed in as, rather than creating them.
`inbox.post` refuses a target that is not an inbox; to reach a document's owner, use `inbox.postToDocument(doc, …)`. Anyone may deposit into an inbox; only its owner reads it.
`ensureIdentity()` settles the identity, completes the connection work it starts, and returns the identity. It takes no identifier, and no other call takes one.
`createEntityDoc` throws if the document cannot be recorded in its store.
## Non-guarantees
**No display name.** `ensureIdentity()` returns an opaque identifier: do not parse it, split it, or render it as a readable name.
**No revocation.** `inbox.share` cannot be undone.
**Nothing per reader on a document in a public store.** No grant, no revocation, no audience list.
**No delegated writing.** A received key never grants a write, and no call adds a writer to a document.
**No mailbox model.** Do not build on the raw deposit list.
**No cross-broker reference.** A returned reference resolves for users of the same broker.
**No unfiltered read through `useShape`.** Members that yield items are filtered and mutations pass through; anything else throws. A document reached through that view alone, read nowhere else first, does not appear.
## Change policy
This surface changes, and shrinks. The package does not offer semantic-version stability.
Re-pull this contract at every upgrade.