136 lines
12 KiB
Markdown
136 lines
12 KiB
Markdown
---
|
|
type: contract
|
|
summary: What @ng-helpers/indexing engages to do — create an index, take a reference anyone deposits, and make it an entry once the index's creator connects; the index itself is an ordinary document anyone queries
|
|
---
|
|
|
|
# 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 creating an index and depositing a reference to an object into one (open to anyone). What becomes of that reference is covered too, but never as a call — see `## Guarantees`. Reading is not covered: the document is ordinary, and `## Surface` has the shape to query it.
|
|
|
|
It does not cover NextGraph itself — documents, identity, sharing, transport, all of which reach it through a port you supply — 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 build the port from it — `polyfillPort({ sessionId })`, where `sessionId` is what `@ng-eventually/polyfill`'s own `init(…)` hands its callback;
|
|
- **await `indexing(port)` once at startup and keep what it produces.** That handle is one identity's — no call takes an identifier, so two users mean two handles — and it is also that identity's connection: awaiting it is what makes entries appear, dropping it is what stops them;
|
|
- reach a broker — nothing here is answered locally;
|
|
- **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;
|
|
- **connect as an index's creator if that index is ever to fill.** Unconditional, whoever holds its reference: an index whose creator never returns stays as it was, however many references it is handed.
|
|
|
|
**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. That reference is the only way anyone reaches it, 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 — and `@ng-eventually/polyfill` arrives the same way.
|
|
|
|
## Surface
|
|
|
|
Full typed shape: the package's `types` entry. The load-bearing signatures:
|
|
|
|
```ts
|
|
// wiring
|
|
export function polyfillPort(options: PolyfillPortOptions): NextGraphPort;
|
|
export interface PolyfillPortOptions { readonly sessionId: string | number }
|
|
/** This identity's handle, and its connection: see `## Guarantees`. */
|
|
export function indexing(port: NextGraphPort): Promise<Indexing>;
|
|
|
|
// addressing (import these from here)
|
|
export type Nuri = `did:ng:${string}`;
|
|
export type NuriLike = Nuri | string;
|
|
export type { UnionSubject, NextGraphPort, IncomingDeposit, ObjectResolution };
|
|
|
|
// the three acts
|
|
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. */
|
|
createIndex(field: string): Promise<Nuri>;
|
|
/** Deposits a bare reference to an object into the index. Open to ANYONE. Throws
|
|
* when the document cannot take one, rather than losing it. */
|
|
refer(index: NuriLike, object: NuriLike): Promise<void>;
|
|
/** Convenience: the entries, ordered by value, without the index's own subject.
|
|
* Refuses a document declaring no field. NOT an engagement — see below. */
|
|
read(index: NuriLike): Promise<IndexEntry[]>;
|
|
}
|
|
|
|
// what an index holds, and what a depositor sends
|
|
export interface IndexEntry { readonly object: Nuri; readonly value: string }
|
|
export interface IndexDescriptor { readonly field: string }
|
|
export type IndexDeposit = Nuri; // the reference IS the whole payload
|
|
export function decodeReference(payload: unknown): Nuri | null; // untrusted input
|
|
|
|
// the IRIs it is written with
|
|
export const INDEX_FIELD: string; // "urn:ng-helpers:index:field"
|
|
export const ENTRY_VALUE: string; // "urn:ng-helpers:index:value"
|
|
```
|
|
|
|
**What an index document holds** — two shapes, and this layer writes nothing else:
|
|
|
|
- on the **index's own subject**, `INDEX_FIELD` carries the predicate an indexed object must carry, as a **literal**, not a URI;
|
|
- 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, which two sessions racing each other produce.
|
|
|
|
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 deposited into an index becomes an entry once the index's creator is connected.** Awaiting `indexing(port)` is that connection: across every index that identity owns, what was deposited while it was away becomes an entry then, and what arrives from that moment on becomes one as it lands. Nothing to call, schedule or configure, and no way to aim it at one index — that session serves all of them.
|
|
|
|
**The field is declared once, inside the document, and cannot be changed.** `createIndex` refuses an empty or blank one at the door; an index created on a useless field is useless for good. A document that ends up declaring several — which nothing on this surface can do, only a direct write to it — stops gaining entries, loudly and permanently.
|
|
|
|
**A new index is ready the moment `createIndex` produces it** — deposit into it straight away; nothing to open or register.
|
|
|
|
**Depositing is open to anyone; writing is the creator's alone.** `refer` is a deposit, so a stranger contributes to an index they could not write. What is deposited is a **bare reference** — no operation, no claim, no copy of the value: what the object itself says, when its entry is made, is what goes in.
|
|
|
|
**An index ONLY EVER GROWS.** No call removes an entry, for anyone including the creator: this package cannot express a removal at all. The answer to "this entry must go" is a fresh index.
|
|
|
|
**The same references produce the same index, whatever order they arrived in.** One reference deposited a hundred times leaves one entry, and an object already indexed is passed over rather than read again. The cost: no deposit is ever retired, so the work behind an index is linear in its history.
|
|
|
|
**None of this can deny you anything.** A session that could not find its indexes, catch one up, or stay posted about one still hands you a working handle, and nothing deposited is lost: the next connection makes its entry. Such failures, and every reference that could not be resolved, are on this package's log stream.
|
|
|
|
**Everything deposited is untrusted.** Anyone may deposit anything; `decodeReference` returns `null` for whatever is not a reference, and such a payload is passed over rather than stopping the rest.
|
|
|
|
## Non-guarantees
|
|
|
|
**Reading is not an engagement.** `read` is sugar over the shape above; its ordering, its tie-breaks and what it makes of a subject carrying several values are free to change. The order an index comes out in is the caller's decision — if it matters, query the document and order the result.
|
|
|
|
**Nothing tells you what became of a reference, or when.** No report, no callback, nothing to wait on, no ordering between a deposit and a read; while an index's creator stays away, a deposit waits as long as that lasts. A reference that could not be resolved is warned about on the log stream; an object carrying nothing for the field, one carrying several, a self-reference and a payload that is no reference are not reported at all. Reading the index is how you find out.
|
|
|
|
**No refresh.** An already-indexed object is never read again, so one whose value changes later keeps its original indefinitely.
|
|
|
|
**No private data.** Only objects the index's creator can open are indexed; one it cannot read is not added.
|
|
|
|
**A handle is one identity for its whole life, and nothing releases what it holds.** An application that changes identity within one page must build a new handle and drop the old one, which goes on listening under a session that holds nothing.
|
|
|
|
**Connecting reads this identity's whole public store** — a store read plus one read per document, every time a handle is built. An index whose read did not answer then is not found, silently; its entries are made at the next connection.
|
|
|
|
**The narrow behaviours are open questions.** An object carrying nothing for the field is not added; one carrying several values is not added. Each may change.
|
|
|
|
**No cross-broker reach.** A NURI resolves for users of one broker.
|
|
|
|
**No depositor authentication and no rate limit.** Anyone may deposit any number of payloads into any index.
|
|
|
|
**A BET, named as one — this layer's, not yours.** That a document can receive deposits at all is aligned with NextGraph; **what a deposit carries, and what receiving one does, are not** — upstream has defined 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** (`3.0.0`); work on a branch carries a **pre-release** of the version it heads for (`3.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 — `v3.0.0`.
|
|
|
|
**`3.0.0` stops engaging on reading, and drops a re-export that never existed.** The guarantees describing what `read` does — ordering, the tie-break on NURI, a subject with several values, a leniently-read declaration — are gone, replaced by the storage shape under `## Surface`. `PrincipalId` leaves it too: listed as re-exported, never exported by `src/index.ts`. **Nothing to migrate and no call behaves differently** — `read` still orders exactly as before. A **major** under the rule above: the surface loses a symbol, and `## Guarantees` loses statements you may no longer rely on.
|
|
|
|
**`2.0.0` removed `Indexing.curate(index)` — and `CurationReport`, `CurationOutcome`, `SkipReason` with it — and made `indexing(port)` a promise.** **Migrating from `1.x`**: delete every call to `curate`, `await` the `indexing(port)` you already make, and where you read a `CurationReport` read the index instead. `2.0.1` was prose and one relaxed deployment requirement; nothing to migrate. `1.0.1` and `1.0.0` keep resolving.
|
|
|
|
Cut on `main`: pin `3.0.0`, and anchor your `usage_` leaf on `against: @ng-helpers/indexing@3.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.
|