refactor: le paquet s'appelle polyfill, « SDK » désigne celui de NextGraph
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. »
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
---
|
||||
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 }`;
|
||||
- be opened through the broker redirect, `https://nextgraph.net/redir/#/?o=<the app's url>` — outside it there is no session;
|
||||
- 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.
|
||||
Reference in New Issue
Block a user