Files

7.8 KiB

type, summary, against
type summary against
usage The nine 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 @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's session curates it by going through that inbox. 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

PlacementstoreRegistry.createEntityDoc("public"), for the index document itself; storeRegistry.listMyEntityDocs("public"), to find again, at each connection, the indexes this identity owns; storeRegistry.openDocumentInbox(doc), at creation to open one and afterwards to resolve its address.

ReadingreadUnion([doc]) and the type UnionSubject. Used both for the index document and for resolving a deposited reference.

Writingdocs.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.

Inboxinbox.postToDocument(doc, { payload }) for depositing, inbox.readForDocument(doc) for the owner going through it, inbox.watch(address, onDeposits) so that a deposit made while the owner is connected is applied as it lands, and the shape of Deposit (from / payload / ts), which our IncomingDeposit mirrors.

TypesNuri, NuriLike, PrincipalId, UnionSubject.

Bootstrap, in the end-to-end application onlyconfigure, 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.resolveScopeGraph/resolveWriteGraph, inbox.share/post/read/readSynced/readSyncedForDocument/processInbox, 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 — and resolved, never opened, afterwards. The engagement disclaims coalescing openDocumentInbox across pages, so the only call that can CREATE one is in createIndex, under its owner, at the moment the document is created. Every later call is on a document whose inbox already exists, where the same entry resolves the address instead; the engagement guarantees that idempotence within a page, which is the level our watching lives at.

One session watches its own indexes, and nothing stops it. inbox.watch's unsubscribe is deliberately dropped: our watching lasts exactly as long as the identity is connected, which is what the engagement does with the inboxes it watches on its own account. It also means the inbox address never leaves the one adapter function that resolves it — everything above names a document.

Our watching relies on subscriptions coexisting on one document. inbox.watch opens a subscribeDoc on the inbox document, and the engagement already watches every inbox this identity may read. Before subscriptions coexisted, one of those two would have silenced the other with nothing raised anywhere. Verified in the resolved copy: the fan-out holds a set of listeners.

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.

Watching a document's inbox needs an address, and the only call that hands one out is the one that creates one. The engagement is explicit that an application never resolves an inbox address, and it publishes inbox.watch(address, …) with no watchForDocument(doc, …) beside readForDocument. So the one thing an owner cannot avoid — being told about deposits on its own document — is also the one place this layer must hold an address, obtained from openDocumentInbox, whose other job is to create. A watchForDocument(doc, onDeposits) would close that gap and keep the "never resolve an address" rule whole.

The version declared and the version the guarantees describe disagree. The resolved copy's package.json says 1.0.0-dev.1, while the engagement dates the continuous inbox observation and the coexisting subscriptions to 1.0.0-dev.2. The code has them, so nothing is broken; but a pin cannot express what we actually depend on, and against: above names the string we resolve rather than the one the guarantees belong to.

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.