Files
ng-helpers/.project/concepts/indexing/indexing-layer/contract_indexing-layer.md
T

12 KiB

type, summary
type summary
contract What @ng-helpers/indexing engages to do — create an index, and hand one a reference anyone may deposit; the index itself is an ordinary document anyone queries, and what it receives becomes entries through the layer below

contract_indexing-layer — @ng-helpers/indexing

Scope

This package builds an index on top of NextGraph: an ordinary public document that holds one entry per indexed object, keyed by that object's NURI and carrying its value for a single declared field.

It covers two acts: creating an index, and handing one a reference to an object (open to anyone). It is a thin helper, and its whole job is to spare you the address an index receives on — you name documents, never an inbox.

It does not cover what becomes of a reference you hand over: making entries of what an index receives belongs to the layer below, and there is no call here that does it, aims it or asks about it. Nor reading — the document is ordinary, and ## Surface has the shape to query it. Nor NextGraph itself: documents, identity, sharing, transport, all reached through the session you hand it. Nor search, filtering, pagination, or querying by anything but the index's field.

Deployment requirements

An application using this package must:

  • have a NextGraph session open under the identity it wants to act as, and hand this package its session id — what @ng-eventually/polyfill's own init(…) hands its callback, relayed and never converted;
  • supply @ng-eventually/polyfill itself, as a peer: the application names it among its own dependencies, and that copy must be the one its own code calls — that package requires exactly one instance of itself. It is also what turns what an index receives into entries, so there is nothing extra to install or configure for that;
  • reach a broker — nothing here is answered locally.

A handle is one identity's and holds nothing: build one at startup and keep it, or build one where you need it.

Holding an index's reference. An index is reached by its NURI, held however the application holds any other reference — per user, per context, or read out of a document it opens anyway. It is the only way anyone reaches the index, and losing it loses the index. Hardcoding it is what a single GLOBAL index needs, and only that case; anything narrower is discovered.

Obtaining it. Not published to npm and not built output: the entry point is TypeScript source, so whatever builds the application compiles it — as does @ng-eventually/polyfill.

Surface

Full typed shape: the package's types entry. All of it, and it is deliberately this small:

/** This identity's handle. It reaches nothing on its own — only the two acts do.
 *  `sessionId` is what
 *  `@ng-eventually/polyfill`'s own `init(…)` hands its callback, relayed, never converted. */
export function indexing(sessionId: string | number): Indexing;

export interface Indexing {
  /** A new index in THIS identity's public store, and its NURI. Any user may. `field`
   *  is the predicate an indexed object must carry; an empty or blank one throws. */
  create(field: string): Promise<Nuri>;
  /** Hands the index a bare reference to an object. Open to ANYONE. Throws when the
   *  document cannot take one, rather than losing it. Added now, visible later. */
  add(index: NuriLike, object: NuriLike): Promise<void>;
}

// addressing, as the two acts above speak it
export type Nuri = `did:ng:${string}`;
export type NuriLike = Nuri | string;

// the IRIs an index is written with — how you recognise what you read
export const INDEX_FIELD: string;   // "urn:ng-helpers:index:field"
export const ENTRY_VALUE: string;   // "urn:ng-helpers:index:value"

There is no call here that reads an index, and that absence is the engagement. An index is an ordinary document: its contents come back the way any other document's do, and the two IRIs above are the whole of what you need to make sense of them. A helper of ours would only teach a shape you would have to unlearn.

What an index document holds — two shapes:

  • on the index's own subject, INDEX_FIELD carries the predicate an indexed object must carry, as a literal, not a URI. create writes it, and it is the only thing this package ever writes;
  • on each entry, whose subject is the indexed object's own did:ng: NURI, ENTRY_VALUE carries that object's value for the field, as a literal. One subject may carry more than one.

That is everything reading takes: an anchored SELECT ?object ?value WHERE { ?object <urn:ng-helpers:index:value> ?value } returns the entries, for a stranger owning neither the index nor the objects exactly as for its creator; readUnion([index]) returns the same subjects plus the index's own.

Guarantees

An index is an ordinary public document, and nothing marks it as one. It lives in its creator's public store, so any reader opens it from the reference alone; its creator owns it, and any user may create one.

A reference you hand an index becomes one of its entries. Not by anything on this surface: the layer below processes what an index receives, and that is what an entry is made by. Nothing to call, schedule or configure — and nothing to wait on, which is why add returns as soon as the reference is lodged rather than when it can be read back.

The field is declared once, at creation, and this package never changes it. create refuses an empty or blank one at the door; an index created on a useless field is useless for good.

A new index is ready the moment create produces it — hand it a reference straight away; nothing to open or register.

Handing a reference over is open to anyone; writing an index is its creator's alone. add is not a write, so a stranger contributes to an index they could not write a triple into. What travels is a bare reference — no operation, no claim, no copy of the value, and no index either, since the one it is handed to identifies it. What the object itself says, when its entry is made, is what goes in. And it is a refusal rather than a silent loss: add throws when the document cannot take a reference, and refuses anything that is not a NURI before it goes anywhere.

Handing the same reference over twice is safe, and so is doing it a hundred times. A reference is an invitation to look, never an instruction. Do it when the object is created, and again whenever anyone notices the index may not have it yet — including a third party who only read the index.

Nothing on this surface removes anything. No call takes an entry out of an index, for anyone including its creator: this package cannot express a removal at all. The answer to "this entry must go" is a fresh index.

Non-guarantees

Nothing tells you what became of a reference, or when. No report, no callback, nothing to wait on, no ordering between handing one over and reading the index. Whether an entry is made at all, what value it ends up holding, and how long that takes are not engaged here — reading the index is how you find out. add checks that a reference is a NURI and nothing more: not that the object exists, that it is readable, or that it carries the index's field.

No order, and nobody to ask for one. Nothing here hands you an index's contents, so the order they come out in is whatever your own query says — and a subject carrying several values is yours to make something of.

A handle holds nothing, and there is nothing to release. It is one identity's for its whole life — an application changing identity within one page builds another, which is free.

No cross-broker reach. A NURI resolves for users of one broker.

No depositor authentication and no rate limit. Anyone may hand any index any number of references, and what an index receives is untrusted: telling a reference from the rest is the business of whoever processes it.

A BET, named as one — this layer's, not yours. That a document can receive at all is aligned with NextGraph; what is handed over, and what receiving it does, are not — upstream defines no such shape and offers no hook to extend the one it has. When it does, this layer moves with it, under a major.

Change policy

Semver, and majors are the normal case. This layer sits on a polyfill still converging on a NextGraph that does not ship yet; several behaviours above are open questions and one part is a bet, and settling any narrows this surface.

  • major — an exported symbol is removed or renamed, or an existing call narrows: it throws where it returned, or reports a state you did not have to handle before. Settling an open question counts, and so does anything the bet forces. A signature change you must react to counts; one that only accepts more does not.
  • minor — a symbol is added and nothing existing moves: a new read helper, a new optional option.
  • patch — a fix that changes neither the exported surface nor anything under ## Guarantees, throw text included.

A tag says where it comes from. A release cut on main carries a full version (4.0.0); work on a branch carries a pre-release of the version it heads for (4.1.0-dev.3), which sorts below it, and between two pre-releases nothing is promised. Nothing you pinned is ever withdrawn. The tag is bare — v4.0.0.

4.0.0 cuts this package to two acts. Everything that made entries of what an index received has left it: that work is done by the layer below, on the inbox an index already has, so an index goes on filling without any of the calls listed here.

  • indexing(port)indexing(sessionId), and it no longer returns a promise. Pass the session id you were passing to polyfillPort, and delete the polyfillPort({ sessionId }) call: this package does that wiring itself now, so polyfillPort and PolyfillPortOptions go with it. await indexing(…) still compiles — there is simply nothing left for it to wait for.
  • createIndex(field)create(field), and refer(index, object)add(index, object). Same behaviour, same throws; add is the verb every index carries, and it already says added-now-visible-later.
  • read(index) is gone — read the document. readUnion([index]) gives you its subjects: skip the one whose subject is the index itself, take ENTRY_VALUE off the rest, or run the anchored SELECT under ## Surface. Ordering is yours now; the call you are replacing ordered by value, broke ties on the object's NURI, and refused a document declaring no field.
  • decodeReference and IndexDeposit are gone, with the types that served only the calls above: NextGraphPort, IndexEntry, IndexDescriptor, ObjectResolution, IncomingDeposit, UnionSubject. Nuri, NuriLike and Indexing stay.
  • Nothing replaces the guarantees about processing, and nothing needs to. 3.0.0 engaged that awaiting a handle applied what an identity's indexes had received, kept applying it, caught up a backlog, resolved each reference, passed over an object carrying nothing or too much, and refused an index declaring two fields. None of that is engaged here any more. If your application relied on a handle being what made an index fill, it no longer is — and no longer has to be: the index fills whether that handle exists or not.

3.0.0 stopped engaging on what read returned — its ordering, its tie-breaks, what it made of a subject carrying several values — replacing those guarantees with the storage shape under ## Surface, and dropped PrincipalId, listed as re-exported and never exported. 2.0.0 removed Indexing.curate(index), with CurationReport, CurationOutcome and SkipReason. 2.0.1, 1.0.1 and 1.0.0 keep resolving.

Cut on main: pin 4.0.0, and anchor your usage_ leaf on against: @ng-helpers/indexing@4.0.0.

No changelog file and no deprecation window: the sections above are the release note. Diff ## Guarantees, ## Non-guarantees and ## Surface between two pulls.