11 KiB
type, summary, against
| type | summary | against |
|---|---|---|
| usage | The polyfill entries the indexing layer and its end-to-end application stand on - placement, one anchored write, the deposit that becomes an entry, and the one-shot read | @ng-eventually/polyfill@1.0.0-dev.5 (pulled d44063961682f84d4b804e5b349cd0a791dd9550) |
usage_ng-helpers - @ng-helpers/indexing on polyfill-surface
The consumer is the indexing layer: an index is an ordinary public document that declares a field, anyone may hand it a reference by depositing into it, and its entries appear because the layer below applies that deposit. NextGraph has no indexing concept, so nothing of what a deposit MEANS belongs upstream - the polyfill's inbox stays generic and carries an opaque payload.
Two callers here, and they consume different things. The library (src/) reaches the polyfill from exactly ONE file, src/polyfill-adapter.ts: everything else is written against an internal port, so a change to the engagement breaks that one file and nothing else. The end-to-end application (e2e/indexing-app.ts, e2e/bridge.ts) is written the way a consumer of @ng-helpers/indexing writes one, and consumes the polyfill directly for its bootstrap and for reading - which is the point: if something is awkward there, it is awkward for every application.
Consumed surface
Library - placement, write, inbox (src/polyfill-adapter.ts)
storeRegistry.createEntityDoc(...)- the index document, in its creator's public store. The call site still passes the string"public", the argument form this engagement withdrew; moving it to the store reference (await storeRegistry.publicStore()) is ours to do, and until it is done this one call does not hold against the version pinned above.storeRegistry.openDocumentInbox(doc)- called once, at creation, under the owner. The address it returns is deliberately dropped.docs.sparqlUpdate(sessionId, update, anchor)- the only write this package makes, always an anchoredINSERT DATAof one triple.inbox.postToDocument(doc, { payload })- the deposit, where the payload IS a baredid:ng:reference.- Types
NuriandNuriLike, type-only, so they are literally the published ones rather than a copy that can drift.
End-to-end application - bootstrap and reading (e2e/indexing-app.ts, e2e/bridge.ts)
configure({ ng, useShape, init, sharedWallet }), this package's owninit(callback, true, [])for thesession_idits callback delivers, andensureIdentity().readUnion([doc])and the typeUnionSubject- how an index is read, and how a referenced document is read. See## Needs.docs.sparqlQuery(sessionId, query, undefined, anchor)- an anchored SELECT, as the second of the two ways anyone reads an index.storeRegistry.listMyEntityDocs("public")- withdrawn by this engagement, and still called in three places here. What it was asked is in## Needs.
Not consumed, and safely evolvable as far as this repository is concerned: useShapeQuery, useShape (a stub is handed to configure and never called), subscribeDoc / subscribeDocs, docs.docCreate, the three store accessors, inbox.share / post / read / readForDocument / readSynced / readSyncedForDocument / processInbox / watch, ng, initNg, and the types Scope, ShapeObservable, ShapeQuery, Deposit and PrincipalId.
Constraints
A bare reference deposited into a document's inbox becomes an entry on that document. This is the single guarantee the whole layer rests on: add deposits a did:ng: reference and nothing else, precisely because that shape is this engagement's own and it acts on it. Nothing of indexing's meaning travels in the payload, and there is no second shape to fall back on.
One handle is one identity. The 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 the multi-actor end-to-end journeys honest: a depositor obtains the index reference the way an application does.
sessionId is relayed, never converted. It is carried at the engagement's own string | number and handed 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; the unit suite executes that SPARQL against an engine that refuses anything else, so a removal is unrunnable rather than merely absent. What this asks of the engagement is exactly one write primitive and no more.
The document is named once, as the anchor. The statement carries no GRAPH <...> wrapper, because the anchor already scopes the write to that repo's default graph. Naming it twice would be two chances to disagree.
A document's inbox is opened from ONE place - create, under the owner, at the moment the document is created - and no address is held anywhere above that call. The non-guarantee about openDocumentInbox not coalescing across pages is therefore respected by construction rather than by care.
This package declares the polyfill a PEER dependency, so the application above remains the one place it is named. The single-copy rule is the one packaging mistake the surface cannot protect anyone from, and a library that declared it a direct dependency would be manufacturing exactly that.
Every NURI is validated before it reaches a query or a deposit, because no type guard is published (see ## Frictions).
This layer stays out of urn:ng-eventually: entirely. Its predicates are urn:ng-helpers: and the subjects it writes are always did:ng: references, so nothing it writes falls under the reserved prefix and nothing it writes is dropped on read.
An empty read is never concluded from. Where the end-to-end suite reads an index it polls to a bound and reports what the index held instead, rather than reading once and calling an empty answer an answer.
The identity ensureIdentity() returns is opaque here - checked for non-emptiness, carried, printed in a run report, never parsed or rendered as a name.
Frictions
The acquisition channel carries no version, so the Change policy has nothing to bite on. The engagement asks for a pinned version and says the channel by which the source reaches an application is agreed with that application; nothing has been agreed here. This repository reaches the package as a filesystem link and declares the peer range *, so what it compiles against is whatever branch happens to be checked out at the other end - and the ref: pinned for this contract is a branch, for the same reason. The against: line above is the only pin that actually exists on this side. A tag-addressable, fetchable source would turn the whole semver discipline from a description into a mechanism.
No published NURI type guard. ## Guarantees says so plainly, so this layer carries its own. It is a guess at what the engagement considers a valid Nuri, and a wrong guess either rejects a legitimate reference or lets one through into a query.
No published escaping helpers. The engagement lists no escapeIri / escapeLiteral, and this package composes SPARQL against docs.sparqlUpdate - so it carries its own escaping rather than reach into the provider. That is a security-relevant duplication: two implementations of one rule, and the one running here is not the one the provider tests.
No published way to write triples above the raw SPARQL primitive. docs.sparqlUpdate is the level this layer had to align on, which is where the two frictions above come from. An "add these triples to this document" entry would delete the query composition, the escaping and the NURI validation from this package in a single move.
readUnion has no stated behaviour for a document it cannot read. ## Guarantees says what it returns for a document it can, and it says elsewhere 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. The worst is assumed (swallowed, therefore indistinguishable from empty) and every read path here is written defensively around that guess. One sentence in ## Guarantees would replace it.
Everything now routed through useShapeQuery is out of reach of an application that does not wire the ORM. This repository's application hands configure a useShape stub, because it reads through readUnion. That was a free choice under the previous surface; it is now the thing that puts the replacement for the withdrawn store enumeration out of reach, and it will put the replacement for any future one out of reach too.
Needs
A one-shot read of a named document's subjects that needs no ORM - readUnion as it stands, or something that answers the same question. @ng-helpers/indexing publishes to its OWN consumers that an index is an ordinary document, read with readUnion([index]) or an anchored SELECT and recognised by two IRIs; that sentence is in this package's published entry point, and the application above is written to it. This engagement itself names readUnion the one-shot read, and names it the remedy for the reactive path that a deployed broker does not answer. Withdrawing it removes the read this layer tells every consumer to use, and leaves the anchored SELECT as the only fallback - which puts every reader of an index back on raw SPARQL, which is where the three frictions above come from. If it has to go, what is needed in its place is a one-shot, non-reactive read of the subjects of a document whose reference is already held, with no ORM wired and no shape declared.
A way to observe which documents an identity's public store contains. The withdrawn listMyEntityDocs had exactly one use here and it is an observation, not a business read: after injecting a failure into the inbox step of create, the suite asks whether a document nobody named was left behind in the store. No reference for that document is held - discovering it IS the question - so the stated replacement does not reach it: a shape query is not scoped to a store, it requires the document to carry something a shape matches (an index document carries one urn:ng-helpers: predicate and no type triple), and it needs an ORM this application does not wire. The Change policy's reason for the removal is understood and the shape of an answer is the provider's to choose; what is stated here is the question left unanswerable.
A way to tell "examined and discarded" from "not examined yet". A deposit this engagement applies is consumed whether or not it produced an entry, and a refusal goes to the provider's own log stream - nothing an application can observe, and by the engagement's own words there is no way to ask what was lost. The cost here is concrete: the journey that checks that an object carrying nothing for an index's field is NOT indexed cannot be waited for, because no reading distinguishes "the deposit was examined and skipped" from "the deposit has not been examined yet" - and this repository's suite says so in its own header rather than hiding it, declaring that check weaker than its name. Anything observable from the application - a per-deposit outcome, or merely a count that moves when a queue has been drained - would make it a real one. This layer holds nothing of its own on purpose: an index only ever grows, and the reference is the whole of what is deposited.