--- type: usage summary: The seven polyfill entries the indexing layer stands on, the constraints it holds itself to, and what it had to build for want of a published helper against: "@ng-eventually/polyfill@1.0.0-dev.1" --- # usage_ng-helpers — `@ng-helpers/indexing` on `polyfill-surface` The consumer is the indexing layer: an index is an ordinary public document, contributions reach it through its inbox, and its owner curates it. NextGraph has no indexing concept, so nothing of what a deposit *means* belongs upstream — the polyfill's inbox stays generic and carries opaque payloads, and this layer decides what they say. The whole runtime dependency passes through **one file**, the adapter that builds our `NextGraphPort`. Everything else in the package is written against that port, so a change to the engagement breaks exactly one file and nothing else. The types are imported type-only, so they are literally the published ones rather than a copy that can drift. ## Consumed surface **Placement** — `storeRegistry.createEntityDoc("public")`, for the index document itself; `storeRegistry.openDocumentInbox(doc)`, called once at creation. **Reading** — `readUnion([doc])` and the type `UnionSubject`. Used both for the index document and for resolving a deposited reference. **Writing** — `docs.sparqlUpdate(sessionId, update, anchor)`, with the document named ONCE as the anchor so the statement carries no `GRAPH <…>` wrapper. It is the only write this package makes, and it is always an `INSERT DATA` of literal triples. **Inbox** — `inbox.postToDocument(doc, { payload })` for depositing, `inbox.readForDocument(doc)` for the owner draining it, and the shape of `Deposit` (`from` / `payload` / `ts`), which our `IncomingDeposit` mirrors. **Types** — `Nuri`, `NuriLike`, `PrincipalId`, `UnionSubject`. **Bootstrap, in the end-to-end application only** — `configure`, this package's `init` (for the `sessionId` its callback delivers), and `ensureIdentity`. Everything else on the engagement is offered and NOT consumed: `watchShape`, `useShape`, `subscribeDoc`/`subscribeDocs`, `docs.docCreate`, `docs.sparqlQuery`, `storeRegistry.listMyEntityDocs`/`resolveScopeGraph`/`resolveWriteGraph`, `inbox.share`/`post`/`read`/`readSynced`/`readSyncedForDocument`/`processInbox`/`watch`, `ng`, `initNg`. It is safely evolvable as far as this layer is concerned. ## Constraints **One port is one identity.** The polyfill's session is one user's and no call takes an identifier, so an `Indexing` handle is one person's. Two users mean two handles — which is also what keeps our multi-actor tests honest: a depositor obtains the index NURI the way an application does, never through a shared variable. **`sessionId` is relayed, never converted.** We carry it at the engagement's own `string | number` and hand it back untouched. **The write is add-only, and structurally so.** There is no delete builder anywhere in this package, and the only statement it can compose is an anchored `INSERT DATA`. Our own tests execute that SPARQL against an engine that refuses anything else, so a removal is unrunnable rather than merely undetected. This constrains what we ask of the engagement: we need exactly one write primitive and no more. **The inbox is opened at creation, from one place.** The engagement disclaims coalescing `openDocumentInbox` across pages, so we never open an index's inbox anywhere but in `createIndex`, under its owner, at the moment the document is created. **Every payload out of an inbox is untrusted input.** Anyone may deposit anything, so nothing read from a deposit reaches a query before being checked; a non-reference is reported, never thrown on. **An empty read is never treated as "empty".** Nothing in this layer reads `[]` from `readUnion` as "a valid index that happens to hold nothing" — the descriptor check refuses a document declaring no field, and that refusal aborts curation before a single write. ## Frictions **The engagement does not say what `readUnion` does with a document it cannot read.** It says what it returns for a document it can, and it says that a rejection means "unknown, never absent" — but not whether an unreadable document inside the list comes back as a rejection or is swallowed into the result. We assume the worst (swallowed, therefore indistinguishable from empty) and code defensively around it. A sentence in `## Guarantees` settling this would replace a guess we are carrying in every read path. **No published NURI type guard.** `## Guarantees` states plainly that no type guard is published, so this layer carries its own — and it needs one, because a NURI arrives here from an untrusted inbox deposit and must be checked before it can be written between angle brackets. The check we wrote is a guess at what the engagement considers a valid `Nuri`, and a wrong guess is either a rejected legitimate reference or an injected one. **No published escaping helpers.** The engagement lists no `escapeIri`/`escapeLiteral`, and we compose SPARQL against `docs.sparqlUpdate` — so this package carries its own escaping rather than reach into the provider's internals. That is a security-relevant duplication of something the provider certainly already has: two implementations of the same rule, one of which is not the one the provider tests. **No published way to write triples above the raw SPARQL primitive.** `docs.sparqlUpdate` is the level we had to align on, which is why the two frictions above exist at all. A published "add these triples to this document" would remove the query composition, the escaping and the NURI validation from this layer in one move.