Files
ng-eventually/packages/polyfill/src/index.ts
T
Sylvain Duchesne 0c21adebf2 feat!: retirer readUnion de la surface publiee, et lire un document par requete ancree
readUnion etait N requetes ancrees plus une politique de lot : une lecture
qui echoue avalee en reponse vide, un document dont on ne detient pas la cle
ecarte au lieu d'etre refuse, un regroupement en sacs de proprietes, et le
filtrage du prefixe reserve. Rien en amont ne repond a cette composition, et
son annotation citait sparql_query -- une citation exacte attachee a une
affirmation fausse. Une composition ne s'aligne pas sur une fonction.

Ce qui reste est ce qu'il y avait dessous : docs.sparqlQuery ancre sur un
document, qui va deja chercher la cle servie par le magasin public -- donc
une reference nue suffit toujours pour lire un document public, sans rien
changer a useShapeQuery. Lire plusieurs documents, c'est cet appel une fois
par document, et la tolerance a une lecture qui echoue revient a l'appelant,
la ou l'amont la laisse : une requete ancree sur un depot que le verifier ne
detient pas rejette.

Cote src, l'unique lecture interne restante devient read-document.ts : un
document, une requete ancree, precedee de l'ouverture du depot -- barriere
que index-deposit consulte pour distinguer une absence d'un depot non
synchronise. Le harnais e2e et l'exemple composent la leur, avec leur catch
visible au point d'appel.

Version majeure : 1.0.0-dev.8.
2026-09-21 14:08:05 +02:00

227 lines
15 KiB
TypeScript

/**
* `@ng-eventually/polyfill` — the one door. Everything an application imports, it imports
* from here.
*
* ── What the single entry costs, and how that cost is paid ────────────────
* There were two entries until 2026-08-07 (`.` and `./polyfill`), and the second one
* carried a signal worth naming before removing it: *what you import from that path is
* exactly what you will delete at migration*. One door loses that — nothing at an
* import line now distinguishes `configure`, which goes away, from `docs`, which the
* real SDK replaces in place. Three things carry it instead:
*
* 1. **The `POLYFILL-ERA` block below**, which is the deletion list. It is short by
* construction, and it is meant to keep shrinking.
* 2. **`docs/api-contract.md`**, which rules on every symbol with an epistemic label
* (PASSTHROUGH / LEVEL-1 SHAPE / ASSUMPTION / NO COUNTERPART) and whose export
* inventory is pinned by `test/vocabulary.test.ts` — so it cannot go stale
* quietly, which a hand-kept list would.
* 3. **The names themselves.** Every published name is built from the target's own
* vocabulary or carries a marker saying why it exists only here — pinned by the
* same test. A name that has to disappear says so.
*
* ── What is deliberately NOT published ────────────────────────────────────
* The entry publishes what an application CALLS, and nothing else. Not the machinery
* accessors (`getConfig`, `getStoreRegistryDeps` — internal wiring the surface reaches
* through `shared-wallet/bootstrap`), and not the test resets (`resetConfig`,
* `resetStoreRegistry`, `resetCaps` — the suite reaches them by their internal path,
* which is what they are for). Merging the entries made publishing those a visible
* choice rather than an inherited one; the choice is no.
*
* Earlier removals, each because an application coding against it learns something it
* must unlearn — the one failure this library exists to prevent:
*
* - `getCaps` / `CapRegistry` (2026-08-05) — the emulation's engine room. It has
* neither a successor nor an inert form, so anything built on it must be rewritten.
* - `getCurrentUser` (2026-08-05) — an application knows who it signed in; asking the
* library back is a convenience of the shared wallet, not a brick of the model.
* - `virtualUsers` / `IdentityStore` (2026-08-05) — remembering an identity between
* sessions is the application's job upstream too. The gate persists what IT needs.
* - `hasCap(doc)` (2026-08-06) — it read like "may I read this?", and once a public
* store serves its caps to whoever asks (`emulated-verifier/public-store.ts`) the
* two answers part company: a readable document answers `false` right up until
* something asks. Upstream you open a document and find out.
*/
// ── SDK-SHAPED — a target counterpart for every symbol ──────────────────────
// At migration the build alias is removed and these resolve to the real SDK.
//
// WHAT each symbol is aligned on is recorded BESIDE IT, not in a document that drifts:
//
// // @provenance <name> kind=<kind> level=<1|2|3|none> ref=<path:symbol|none> [announced-by=<who>@<date>] — <note>
//
// `level` numbers the target's stack from the bottom (1 engine, 2 wasm binding /
// `@ng-org/web`, 3 JS ORM); `ref` cites a SYMBOL, never a line number — in `nextgraph-rs`
// for every kind that cites the target, and in THIS working copy for the one kind that
// has no upstream file to cite. `kind` is closed: `passthrough` (upstream's own symbol,
// name and shape), `aligned` (our name or ergonomics, upstream's semantics),
// `declared-not-wired` (upstream DEFINES the shape and constructs it nowhere — an
// absence, which is NOT evidence the thing is ours), `announced-not-written` (upstream's
// developers have STATED this direction and nothing in the tree carries it yet),
// `reserved-name` (upstream has declared a NAME with no shape behind it),
// `invention` (nothing answers, a documented bet), `divergent` (upstream does this, we do
// it differently). Pinned by `test/provenance.test.ts`; the reasoning behind each ruling
// stays in `docs/api-contract.md`, and where the two disagree the annotation wins.
//
// `announced-not-written` is the STRONGEST kind available to a symbol with no counterpart
// in the tree — deviating TOWARD what the target has announced is alignment, and calling
// it `invention` tells a later reader the shape is ours to change when it is not. Being
// the strongest, it is the hardest to assert: it carries `announced-by=<who>@<date>` and
// its `ref` is the `.md` in this working copy where the statement is on record. An
// announcement nobody wrote down is hearsay, so the test refuses it.
// A type is published only when a PUBLISHED SIGNATURE uses it. `export *` published
// eight in one gesture (2026-08-10: it was a blanket re-export), of which two named
// nothing a consumer can reach — `ReadCap` (used only by two private helpers of
// `surface/inbox.ts`) and `InboxScope` (used only by the unpublished
// `account-registry.userInbox`). A published type with no published signature is a
// promise about the target that nothing here keeps: it invites a consumer to hold a
// value it has no call to obtain — and for `ReadCap`, the one value the model says a
// caller must never be handed on request. They stay DEFINED in `model/types.ts`, where
// the library uses them; they stop being surface. `docs/api-contract.md` § 10, § 14.
// Each one, and the signature that earns it its place:
// Nuri every reference the surface RETURNS
// NuriLike every reference the surface ACCEPTS
// PrincipalId `ensureIdentity`, `inbox.Deposit`/`PostOptions`, `EventuallyConfig`
// NgLike `EventuallyConfig.ng`
// UseShapeLike `EventuallyConfig.useShape`
// OrmSubscriptionLike `EventuallyConfig.ormSubscription`
// GetObjectsLike `EventuallyConfig.getObjects`
// InsertObjectLike `EventuallyConfig.insertObject`
// RdfOrmConfig `getObjects`
//
// `OrmSubscriptionHandle` - what that last one RETURNS - is deliberately NOT here. An
// application hands the shape query in and reads the snapshot back; it never holds a
// subscription, and publishing the handle would offer a path around the capability filter
// and the shared-wallet identity boundary. It stays defined in `model/types.ts`.
//
// `Scope` left this list in `1.0.0-dev.5`, and it is the only one that left because the
// TYPE was wrong rather than unreachable. It was `"public" | "protected" | "private"` -
// our own three strings under a name the SDK also uses, for a different thing: upstream
// a `Scope` is `{ graphs, subjects }` and scopes a READ, while those three strings are
// `doc_create`'s `store_type` and place a WRITE. One name over two questions. The read
// takes the SDK's `Scope` now (re-exported below, from the SDK itself), and a store is
// named by its reference. The three strings stay DEFINED in `model/types.ts` as
// `StoreType`, where the library's own modules still tell the three apart.
export type {
Nuri,
NuriLike,
PrincipalId,
NgLike,
UseShapeLike,
OrmSubscriptionLike,
GetObjectsLike,
InsertObjectLike,
} from "./model/types";
export { useShape } from "./surface/use-shape";
export { useShapeQuery } from "./surface/use-shape-query";
export type { ShapeQuery, ShapeObservable } from "./surface/use-shape-query";
// The ORM's one-shot read and its subscription-free write - WRAPPED, at upstream's own
// names and signatures, never re-exported. `surface/orm.ts` says what each wrapper adds,
// and which neighbours of theirs in `@ng-org/orm`'s entry are deliberately not here
// (`RdfOrmSubscription`, `DiscreteOrmSubscription`, `ngSession`, `getRaw`).
export { getObjects, insertObject } from "./surface/orm";
export { init, initNg } from "./surface/lifecycle";
export * as inbox from "./surface/inbox";
export * as docs from "./surface/docs";
export { subscribeDoc, subscribeDocs, docChangeType } from "./surface/subscribe";
export type { DocChange, DocChangeType, Unsubscribe } from "./surface/subscribe";
// `readUnion` and `UnionSubject` were published here until `1.0.0-dev.8`. They are gone,
// and nothing replaces them: a `document read` over several documents was N anchored
// queries plus a batch policy, which no level of the target answers - so its provenance
// cited `sparql_query`, a citation that is exact attached to a claim that is false. A
// composition cannot be aligned on a single function. Reading one document by name is
// `docs.sparqlQuery(sessionId, query, undefined, doc)`, which IS upstream's own primitive
// and is published beside this line; reading several is that call, once per document.
export * as storeRegistry from "./surface/placement";
// SDK type re-exports — so the app imports these from @ng-eventually/polyfill too, not from
// @ng-org. `export type` is ERASED at build, so this adds NO runtime @ng-org import to
// the lib (no risk of a duplicate SDK copy in the bundle).
// @provenance ShapeType kind=passthrough level=3 ref=sdk/js/shex-orm/src/types.ts:ShapeType
// @provenance BaseType kind=passthrough level=3 ref=sdk/js/shex-orm/src/types.ts:BaseType
// @provenance Schema kind=passthrough level=3 ref=sdk/js/shex-orm/src/types.ts:Schema
export type { ShapeType, BaseType, Schema } from "@ng-org/shex-orm";
// @provenance DeepSignalSet kind=passthrough level=3 ref=sdk/js/alien-deepsignals/src/types.ts:DeepSignalSet
export type { DeepSignalSet } from "@ng-org/alien-deepsignals";
// The scope `useShapeQuery` takes, re-exported from the SDK rather than restated here:
// restating it is how a type drifts, and the two axes it filters on (`graphs`,
// `subjects`) are upstream's to change.
// @provenance Scope kind=passthrough level=3 ref=sdk/js/orm/src/types.ts:Scope
export type { Scope } from "@ng-org/orm";
// What `getObjects` takes: a `Scope` plus `where` and `orderBy`. It is re-exported from
// the SDK's `/all` subpath because that is where the SDK puts it - the type the function
// DECLARES is not exported beside the function, which is upstream's packaging and not a
// choice here. Restating it would be how it drifts.
// @provenance RdfOrmConfig kind=passthrough level=3 ref=sdk/js/orm/src/utilTypes.ts:OrmConfig - the published `@ng-org/orm@0.1.2-alpha.21` renames it `RdfOrmConfig` and re-parameterises it on the shape's type rather than its `ShapeType`, which the cited revision has not yet done
export type { RdfOrmConfig } from "@ng-org/orm/all";
// @provenance NG kind=passthrough level=2 ref=sdk/js/web/src/index.ts:NG — 88 members, all forwarded; the proxy adds none and removes none
export type { NG } from "@ng-org/web";
// ── POLYFILL-ERA — THE DELETION LIST ────────────────────────────────────────
// Everything below exists because one shared wallet hosts every user, and nothing
// below has a target counterpart. At migration each call goes, and the imports with
// them. Keep this block short: an addition here is a promise to delete it later.
/**
* Inject the real SDK, and tell the library about the shared wallet. Upstream nothing
* is injected — an application imports the SDK and opens its own wallet — so this call
* is the shape of that absence. `docs/api-contract.md` § 1.
*
* **It is the ONLY call here**, and keeping it that way is the design target: an
* application's bootstrap should be one line to delete, not four.
*/
export { configure } from "./shared-wallet/bootstrap";
export type { EventuallyConfig } from "./shared-wallet/bootstrap";
// `RegistrySession` went with `getSession` (2026-08-12): it was published for exactly one
// reason — an application typed the session thunk it injected with it — and a published
// type whose signature is gone is a promise about the target that nothing keeps. Upstream a
// session is RETURNED, never assembled, so no consumer has a session shape to declare. It
// stays DEFINED in `shared-wallet/account-registry.ts`, where the library uses it.
// --- what this block deliberately does NOT contain --------------------------
//
// Three calls were published here and removed on 2026-08-07, when the count had drifted
// to four against a target of two. Each removal is a thing an application no longer does:
//
// - `configureStoreRegistry` — folded into `configure`. Two bootstrap calls existed
// because the library has two internals, which is not a reason a caller should pay.
// - `setCurrentUser` — the access gate sets the identity (`ensureIdentity`, below).
// An application naming its own identity is the gesture that inverts the model, and
// it must not have a published call to reach for. The e2e harness plays several
// identities on one page and reaches it by its internal path, which is what a
// harness is allowed to do and an application is not.
// - `connectedUser` — `ensureIdentity` awaits it. Upstream, opening the session IS the
// connection; no application awaits a second call, so ours should not either.
//
// And two FIELDS of `EventuallyConfig` on 2026-08-12, for the same reason one call up:
//
// - `getSession` — the session is `init()`'s to deliver, and this package's to keep. An
// application that assembles one out of `session_id` / `private_store_id` / … is
// building a shape the target never asks for, and every consumer built the same one.
// - `normalizeId` — the identities being normalized are this package's own invention, so
// there was never a decision here for a consumer to make.
//
// Both are still substitutable through `shared-wallet/bootstrap`'s `configureStoreRegistry`
// — the suites and the e2e harness need it, and nothing published reaches it.
// ── the access gate — polyfill-era in substance, one line in the app ────────
// One call before the app renders. It shows a technical barrier only while the shared
// wallet needs one; the day the wallet supplies the identity it resolves silently, and
// this line stays as it is (`shared-wallet/access-gate.ts`).
export { ensureIdentity } from "./shared-wallet/access-gate";
export type { SharedWalletConfig } from "./shared-wallet/access-gate";
import type { NG } from "@ng-org/web";
import { wrappedNg } from "./surface/ng-proxy";
/**
* SDK-identical `ng` (wrapped). Drop-in replacement for `@ng-org/web`'s `ng`.
*
* Declared `NG` — upstream's own type for its `ng` (`index.d.ts:NG`). It was published as
* `Record<string, any>` until 2026-08-14, which announced a different surface from the one
* it forwards to: an application got no completion and no check on any of the 88 members.
*/
// @provenance ng kind=passthrough level=2 ref=sdk/js/web/src/index.ts:ng — a Proxy over the injected real `ng`; only `sparql_update` is overridden (the emulated write guard)
export const ng: NG = wrappedNg();