refactor: renommer client → sdk, et fusionner les deux portes en une
Deux mouvements de surface, aucun changement de comportement. **`packages/client` → `packages/sdk`, `@ng-eventually/client` → `@ng-eventually/sdk`.** « client » ne disait rien : ce paquet EST le SDK que l'application appelle, et c'est tout ce qu'elle appelle. L'ancien nom reste comme mot-clé de recherche dans `docs/source-layout-by-fate.md` et le tableau des paquets du README. **Une seule entrée.** L'entrée `./polyfill` disparaît ; ses symboles applicatifs — `configure`, `configureStoreRegistry`, `setCurrentUser`, `connectedUser` et leurs types — vivent dans un bloc `POLYFILL-ERA` de `src/index.ts`. Ce que la seconde porte portait mérite d'être nommé avant d'être retiré : *ce qu'on importe de ce chemin est exactement ce qu'on supprimera à la migration*. Une seule porte perd ce signal — rien à la ligne d'import ne distingue `configure`, qui part, de `docs`, que le vrai SDK remplace sur place. Trois choses le portent désormais : le bloc lui-même, l'inventaire d'exports de `docs/api-contract.md` (épinglé par `test/vocabulary.test.ts`, donc il ne peut pas rancir en silence), et le contrôle de vocabulaire sur les noms publiés. **Six symboles quittent la surface au passage**, et la fusion est ce qui a rendu le choix visible plutôt qu'hérité : - `getConfig` / `getStoreRegistryDeps` — câblage interne, atteint par `shared-wallet/bootstrap` ; - `resetConfig` / `resetStoreRegistry` / `resetCaps` — remises à zéro de test, atteintes par leur chemin interne, ce qui est leur raison d'être ; - le `share` direct — `inbox.share` a toujours été la même fonction, et la publier deux fois brouillait la frontière qu'elle servait à marquer. Corrections d'affirmations fausses trouvées en chemin : le contrat annonçait `isNuri` / `hasReadCap` sur la porte SDK alors qu'ils ne sont plus exportés depuis le passage au permissif en entrée (`NuriLike` validé à la porte) ; le README du paquet documentait `capFor`, `shareCap`, `getCaps` et `publishRepoLink`, dont aucun n'existe ; et le README de l'app d'exemple affirmait que la suite e2e la pilote, ce qui reste à faire. 179 tests unitaires, typecheck bibliothèque / exemple / harnais, e2e 42/42 contre le broker en ligne — mesuré une fois après le renommage, une fois après la fusion.
This commit is contained in:
@@ -1,114 +0,0 @@
|
||||
# @ng-eventually/client
|
||||
|
||||
Two entry points — the data-plane is SDK-identical, the polyfill bootstrap is
|
||||
separate:
|
||||
|
||||
| Import | Surface |
|
||||
|---|---|
|
||||
| `@ng-eventually/client` | The same signature as the SDK — `ng`, `useShape`, `inbox` (+ types). A drop-in for `@ng-org/web` / `@ng-org/orm`; as NextGraph matures it resolves to the real SDK (build alias removed) with no code change. |
|
||||
| `@ng-eventually/client/polyfill` | The only non-SDK surface — `configure`, `setCurrentUser`, and the capability surface (`capFor`, `shareCap`, `getCaps`). It falls away as NextGraph matures. |
|
||||
|
||||
> **Reading is key possession, and the isolation here is still fake.** The cap
|
||||
> surface has the shape of the real model — you hold a document's `ReadCap` or you
|
||||
> do not read it, and there is no authorization list anywhere — but nothing is
|
||||
> encrypted yet and several read paths bypass the guard entirely. Nothing this
|
||||
> library does may be described as "anonymous" or "private" until per-document
|
||||
> encryption lands (P1b).
|
||||
|
||||
```ts
|
||||
// bootstrap (the only non-SDK call) — inject the real SDK
|
||||
import { configure } from "@ng-eventually/client/polyfill";
|
||||
configure({ ng: realNg, useShape: realUseShape, sharedWallet, currentUser });
|
||||
|
||||
// from here on, a pure SDK surface:
|
||||
import { ng, useShape, inbox } from "@ng-eventually/client";
|
||||
await ng.doc_create(/* … */);
|
||||
const set = useShape(MyShape, scope); // filtered to what the identity may read
|
||||
await inbox.post(targetInbox, ref); // deposit (anticipated SDK API)
|
||||
```
|
||||
|
||||
## Principle — the polyfill compensates, it never extends
|
||||
|
||||
**The polyfill's ONLY reason to exist is to bridge a NextGraph implementation gap
|
||||
or a bug.** Every non-SDK surface must map to a capability NextGraph will provide
|
||||
natively, and must fall away at that point. The polyfill MUST NOT add functionality
|
||||
of its own — no bespoke features, no observability/tooling, no convenience API that
|
||||
isn't strictly "NextGraph will do this natively later." The test for any proposed
|
||||
addition: *does it compensate a real, exhibited NextGraph gap or bug?* If not, it
|
||||
does not belong here — build it in the consumer application, not in the polyfill.
|
||||
Corollary: a compensation whose gap is not actually exhibited on the target broker
|
||||
is dead weight, not defensive code — it should be removed, not kept "just in case."
|
||||
|
||||
What the polyfill adds on top of the real SDK (each emulated for now, native as
|
||||
NextGraph matures):
|
||||
- Shared-wallet identity (one wallet for everyone; the current identity id is
|
||||
relayed to the SDK).
|
||||
- Capability emulation — per-identity **cap possession** (`capFor`) and a read filter
|
||||
over it: you read the documents whose cap you hold. Creating a document files its
|
||||
cap; receiving one is an inbox deposit. There is no authorization list.
|
||||
- Anticipated methods (inbox `post`, `shareCap`) with their future-SDK shapes,
|
||||
emulated for now.
|
||||
|
||||
Generic: no application domain. The consumer application injects its shapes and
|
||||
performs the acts of sharing. The relationship concept ("who is connected to whom")
|
||||
is the consumer application's own — the client exposes only "share this one
|
||||
document's cap to that inbox".
|
||||
|
||||
### The cap surface in three calls
|
||||
|
||||
```ts
|
||||
import { capFor, shareCap, getCaps } from "@ng-eventually/client/polyfill";
|
||||
import { storeRegistry } from "@ng-eventually/client";
|
||||
|
||||
// Creating a document records its cap and you hold it — nothing to declare.
|
||||
const doc = await storeRegistry.createEntityDoc(myId, "protected");
|
||||
capFor(doc); // → `${doc}:r:…` — you hold it
|
||||
|
||||
// Share it with one recipient, addressed by their inbox. They need no "receive"
|
||||
// operation: their existing inbox.watch absorbs it.
|
||||
await shareCap(capFor(doc)!, theirInbox);
|
||||
|
||||
// Publishing is TWO acts: place the data in your public store, and circulate its
|
||||
// LINK. There is no discovery — you cannot be found, you can only be reached — so
|
||||
// the link has to travel: into an inbox, or into a document the reader already
|
||||
// holds. The bare NURI would name the document without opening it.
|
||||
const link = getCaps().publishRepoLink(publicDoc);
|
||||
await shareCap(link, theirInbox);
|
||||
```
|
||||
|
||||
The one invariant to keep in mind: **you never derive a cap from a bare reference.**
|
||||
You look it up in what you hold, or you were given it. A `did:ng:o:…` without `:r:`
|
||||
names a document and grants nothing.
|
||||
|
||||
### The types carry that invariant
|
||||
|
||||
`Nuri` and `ReadCap` are **template literal types**, not `string` aliases:
|
||||
|
||||
```ts
|
||||
type Nuri = `did:ng:${string}`
|
||||
type ReadCap = `did:ng:${string}:r:${string}`
|
||||
```
|
||||
|
||||
They are still strings — assignable to `string`, JSON-serializable, no wrapper — but
|
||||
the distinction is checked. A `ReadCap` goes wherever a `Nuri` is expected (a cap
|
||||
*is* a NURI with the key inside); the reverse does not compile:
|
||||
|
||||
```ts
|
||||
await shareCap(doc, theirInbox); // ✗ Argument of type '`did:ng:${string}`' is not
|
||||
// assignable to '`did:ng:${string}:r:${string}`'
|
||||
```
|
||||
|
||||
A string that comes from outside your code — storage, a URL, JSON, a form — is a
|
||||
plain `string`. **Narrow it, do not cast it**: a cast re-opens exactly the confusion
|
||||
the types close.
|
||||
|
||||
```ts
|
||||
import { isNuri, hasReadCap } from "@ng-eventually/client";
|
||||
|
||||
const saved = localStorage.getItem("cap");
|
||||
if (saved && hasReadCap(saved)) await shareCap(saved, theirInbox); // ✓ narrowed
|
||||
```
|
||||
|
||||
The runtime guards remain regardless — a JavaScript caller never meets the compiler,
|
||||
and a cast bypasses it — so passing a bare reference where a cap belongs throws with
|
||||
a message that says so.
|
||||
@@ -1,58 +0,0 @@
|
||||
/**
|
||||
* @ng-eventually/client — the surface a consumer application codes against.
|
||||
*
|
||||
* Everything here has a target-SDK counterpart, verified or assumed, listed in
|
||||
* `docs/api-contract.md`. Import `ng` / `useShape` from here rather than from the
|
||||
* SDK during the polyfill period; at migration the build alias is removed and
|
||||
* these resolve to the real SDK.
|
||||
*
|
||||
* **This entry carries no machinery.** The earlier header claimed it exposed "ONLY
|
||||
* what `@ng-org/web` / `@ng-org/orm` expose", which was false as written: it also
|
||||
* shipped the whole `store-registry` module (account resolution, cap registers,
|
||||
* cache resets) and `accounts` (browser identity persistence, polyfill-era with no
|
||||
* SDK counterpart). Both leaked machinery onto the entry whose promise is that it
|
||||
* survives migration. `storeRegistry` is now the app-facing slice only
|
||||
* (`store-registry-api.ts`); `accounts` moved to `/polyfill`.
|
||||
*
|
||||
* The polyfill bootstrap — `configure`, the capability helpers, the current user,
|
||||
* identity persistence — lives at `@ng-eventually/client/polyfill`: everything an
|
||||
* application needs TODAY that will not exist tomorrow, kept apart so what goes
|
||||
* away is visible at the import line.
|
||||
*/
|
||||
|
||||
export * from "./model/types";
|
||||
export { useShape } from "./surface/use-shape";
|
||||
export { watchShape } from "./surface/watch-shape";
|
||||
export type { ShapeQuery, ShapeObservable } from "./surface/watch-shape";
|
||||
export { init, initNg } from "./surface/lifecycle";
|
||||
// The access gate: 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";
|
||||
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` is exposed as a function, not under a `readModel` namespace: "model" is
|
||||
// neither the target's vocabulary nor neutral glue, and the namespace bought nothing —
|
||||
// it held one published function. Renamed 2026-08-03 by the vocabulary check.
|
||||
export { readUnion } from "./surface/read-model";
|
||||
export type { UnionSubject } from "./surface/read-model";
|
||||
export * as storeRegistry from "./surface/placement";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// SDK type re-exports — so the app imports these from @ng-eventually/client 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).
|
||||
export type { ShapeType, BaseType, Schema } from "@ng-org/shex-orm";
|
||||
export type { DeepSignalSet } from "@ng-org/alien-deepsignals";
|
||||
export type { NG } from "@ng-org/web";
|
||||
|
||||
import { makeNg } from "./surface/ng-proxy";
|
||||
|
||||
/** SDK-identical `ng` (wrapped). Drop-in replacement for `@ng-org/web`'s `ng`. */
|
||||
export const ng: Record<string, any> = makeNg();
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* `@ng-eventually/client/polyfill` — the polyfill-era door.
|
||||
*
|
||||
* Everything importable here disappears at migration, and that is the point of the
|
||||
* separate entry: what an application imports from this path is exactly what it will
|
||||
* have to delete. The SDK-identical entry (`@ng-eventually/client`) carries the other
|
||||
* promise — a target counterpart for every symbol.
|
||||
*
|
||||
* This file only RE-EXPORTS. The implementation lives with the fate it belongs to:
|
||||
* the injection store and the current-user relay in `shared-wallet/bootstrap.ts`
|
||||
* (no counterpart, evaporates), the cap registry in `emulated-verifier/caps.ts`
|
||||
* (stands in for the verifier's own bookkeeping). Keeping the store here made the
|
||||
* published entry a dependency of the code it publishes.
|
||||
*/
|
||||
|
||||
export type { StoreRegistryDeps, EventuallyConfig } from "./shared-wallet/bootstrap";
|
||||
export {
|
||||
configure,
|
||||
getConfig,
|
||||
resetConfig,
|
||||
configureStoreRegistry,
|
||||
getStoreRegistryDeps,
|
||||
resetStoreRegistry,
|
||||
setCurrentUser,
|
||||
resetCaps,
|
||||
} from "./shared-wallet/bootstrap";
|
||||
|
||||
// Cap surface — polyfill-era (caps are emulated now; native at migration).
|
||||
// Re-exported here so the whole polyfill API lives under /polyfill. `share`
|
||||
// lives in `inbox.ts` because sharing IS an inbox deposit (upstream: a sealed
|
||||
// message carrying the cap), but it is surfaced here so the cap vocabulary stays
|
||||
// on the polyfill side of the boundary rather than in the SDK-identical entry.
|
||||
export { share } from "./surface/inbox";
|
||||
export { connectedUser } from "./emulated-verifier/connect";
|
||||
|
||||
// --- what is deliberately NOT published --------------------------------------
|
||||
//
|
||||
// Removed 2026-08-05, each because an application coding against it learns something it
|
||||
// must unlearn — the one failure this library exists to prevent:
|
||||
//
|
||||
// - `getCaps` / `CapRegistry` — the emulation's engine room. The consumer question is
|
||||
// `capFor(doc)`: do I hold this? The registry object has neither a successor nor an
|
||||
// inert form, so anything built on it must be rewritten rather than left alone.
|
||||
// - `getCurrentUser` — 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` — remembering an identity between sessions is the
|
||||
// application's job upstream too. The gate persists what IT needs
|
||||
// (`shared-wallet/access-gate.ts`); nothing else has to be exposed.
|
||||
//
|
||||
// And one more, removed 2026-08-06 with the public-store emulation:
|
||||
//
|
||||
// - `hasCap(doc)` — "do I hold this document's cap?". 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 for it. Nor is the question
|
||||
// one the target answers — upstream you open a document and find out. It had no
|
||||
// caller outside the tests, which now use the internal registry directly.
|
||||
//
|
||||
// What remains here is the whole polyfill-era surface: `configure`, `setCurrentUser`,
|
||||
// `share`, `connectedUser` and the test resets. Two of them are what an application calls.
|
||||
|
||||
// --- identity persistence (polyfill-era, no SDK counterpart) ----------------
|
||||
//
|
||||
// Moved here from the SDK-identical entry on 2026-08-03. `accounts` persists WHICH
|
||||
// virtual user is connected, in browser storage — a notion that exists only because
|
||||
// one shared wallet hosts several identities. The real SDK has no counterpart: there
|
||||
// each user opens their own wallet, and "who am I" is the session. Shipping it from
|
||||
// the SDK entry advertised as durable something that disappears at migration.
|
||||
// Config-shaped types the bootstrap needs; both describe the shim, not the SDK.
|
||||
export type { VirtualUserRecord, RegistrySession } from "./shared-wallet/account-registry";
|
||||
@@ -0,0 +1,123 @@
|
||||
# @ng-eventually/sdk
|
||||
|
||||
One entry point. Most of what it publishes has the same signature as the future SDK —
|
||||
`ng`, `useShape`, `watchShape`, `docs`, `inbox`, `storeRegistry`, `readUnion` (+ types) —
|
||||
and is a drop-in for `@ng-org/web` / `@ng-org/orm`: as NextGraph matures it resolves to
|
||||
the real SDK (build alias removed) with no code change.
|
||||
|
||||
**Four calls do not, and they are the whole of what you will delete:** `configure`,
|
||||
`configureStoreRegistry`, `setCurrentUser`, `connectedUser`. They exist because one
|
||||
shared wallet hosts every user; upstream, an application imports the SDK and each user
|
||||
opens their own wallet. `src/index.ts` groups them under a heading that says so.
|
||||
|
||||
*(There were two entry points until 2026-08-07, `.` and `./polyfill`, and the second one
|
||||
WAS that list. One door is easier to import from and says less — hence the grouping, and
|
||||
hence `docs/api-contract.md`, whose export inventory a test keeps honest.)*
|
||||
|
||||
Per-symbol, with the target signature and an epistemic label on every claim:
|
||||
[`docs/api-contract.md`](../../docs/api-contract.md).
|
||||
|
||||
> **Reading is key possession, and the isolation here is still fake.** The cap surface
|
||||
> has the shape of the real model — you hold a document's `ReadCap` or you do not read
|
||||
> it, and there is no authorization list anywhere — but nothing is encrypted yet and
|
||||
> the stand-in key is a constant. Nothing this library does may be described as
|
||||
> "anonymous" or "private" until per-document encryption lands (P1b).
|
||||
|
||||
```ts
|
||||
import {
|
||||
// SDK-shaped — the real SDK replaces these in place.
|
||||
ensureIdentity, storeRegistry, inbox, readUnion, docs,
|
||||
// Polyfill-era — these go away, and they are the whole of what goes away.
|
||||
configure, configureStoreRegistry, setCurrentUser,
|
||||
} from "@ng-eventually/sdk";
|
||||
|
||||
configure({ ng: realNg, useShape: realUseShape, sharedWallet });
|
||||
configureStoreRegistry({ getSession });
|
||||
await ensureIdentity(); // who am I (shared wallet)
|
||||
const doc = await storeRegistry.createEntityDoc(me, "protected");
|
||||
await docs.sparqlUpdate(sid, `INSERT DATA { … }`, doc);
|
||||
const subjects = await readUnion(await storeRegistry.listMyEntityDocs(me, "protected"));
|
||||
```
|
||||
|
||||
## Principle — the polyfill compensates, it never extends
|
||||
|
||||
**Its only reason to exist is to bridge a NextGraph implementation gap.** Every
|
||||
non-SDK surface must map to something NextGraph will provide natively, and must fall
|
||||
away at that point — no bespoke features, no observability, no convenience API that
|
||||
isn't strictly *"NextGraph will do this later"*. The test for any proposed addition:
|
||||
*does it compensate a real, exhibited gap?* If not, it belongs in the consumer
|
||||
application. And a compensation whose gap is not actually exhibited on the target
|
||||
broker is dead weight, not defensive code.
|
||||
|
||||
Both halves are binding — **the surface AND the implementation** stay as close as
|
||||
possible to what NextGraph plans. The question to ask at every choice: *would this
|
||||
make a caller learn something it has to UNLEARN at migration?* If yes, it is a
|
||||
deviation, whatever it buys.
|
||||
|
||||
What the polyfill adds, each emulated now and native later:
|
||||
|
||||
- **Shared-wallet identity** — one wallet hosts every user, so the library fabricates
|
||||
*virtual users* and confines every access to the connected one
|
||||
(`emulated-verifier/reach.ts`). Upstream, each user opens their own wallet.
|
||||
- **Capability emulation** — per-identity cap possession plus a read filter over it:
|
||||
you read the documents whose cap you hold. There is no authorization list, because
|
||||
the real model has none.
|
||||
- **Inbox** — `post`, `postToDocument`, `share`, and the recipient's processing.
|
||||
The model is verified (an inbox is a keypair on one repo); no JS surface exists yet.
|
||||
|
||||
Generic by construction: no application domain here. See
|
||||
[`examples/notebook`](../../examples/notebook) for an application written against it,
|
||||
which the e2e suite drives.
|
||||
|
||||
## How a document is reached — the three acts, and no others
|
||||
|
||||
```ts
|
||||
import { storeRegistry, inbox, readUnion } from "@ng-eventually/sdk";
|
||||
|
||||
// 1. CREATE — you hold its cap, with nothing to declare.
|
||||
const doc = await storeRegistry.createEntityDoc(me, "protected");
|
||||
|
||||
// 2. GIVE TO READ — name the document and the person. The key is looked up and
|
||||
// sealed into a deposit; the recipient applies it by connecting, with nothing
|
||||
// to call. Irreversible: there is no revoking a key already handed out.
|
||||
await inbox.share(doc, "bob");
|
||||
|
||||
// 3. CIRCULATE THE REFERENCE — no call at all. Every reference this surface returns
|
||||
// is BARE: it names the document and grants nothing. If the document sits in a
|
||||
// PUBLIC store, the store serves its read cap to whoever asks, so the bare
|
||||
// reference is enough to read it — and if it does not, the reference still names
|
||||
// it and opens nothing.
|
||||
const publicDoc = await storeRegistry.createEntityDoc(me, "public");
|
||||
// …put `publicDoc` in a QR code, a message, another document. Nothing else to do.
|
||||
await readUnion([publicDoc]); // a stranger holding only this reads it
|
||||
```
|
||||
|
||||
**The invariant behind all three: you never derive a cap from a bare reference.** You
|
||||
look it up in what you hold, you were given it, or a public store served it. A
|
||||
`did:ng:o:…` without `:r:` names a document and opens nothing — which is what makes
|
||||
confidentiality composable: a widely circulated document may point at a restricted
|
||||
one, and following the reference gets you a name, not a key. See
|
||||
[`docs/readcap-and-nuri-model.md`](../../docs/readcap-and-nuri-model.md) § 0.
|
||||
|
||||
## The types carry that invariant
|
||||
|
||||
`Nuri` and `ReadCap` are **template literal types**, not `string` aliases:
|
||||
|
||||
```ts
|
||||
type Nuri = `did:ng:${string}`
|
||||
type ReadCap = `did:ng:${string}:r:${string}`
|
||||
```
|
||||
|
||||
They are still strings — assignable to `string`, JSON-serializable, no wrapper — but
|
||||
the distinction is checked. A `ReadCap` goes wherever a `Nuri` is expected (a cap *is*
|
||||
a NURI with the key inside); the reverse does not compile.
|
||||
|
||||
**Permissive in, precise out.** Public entries take `NuriLike` (`Nuri | string`) and
|
||||
validate at the door, so a value coming from storage, a URL or a form needs no
|
||||
narrowing and no cast on your side; what they *return* is a precise `Nuri`. The
|
||||
runtime checks stay regardless — a JavaScript caller never meets the compiler.
|
||||
|
||||
```ts
|
||||
const saved = localStorage.getItem("doc"); // string | null
|
||||
if (saved) await readUnion([saved]); // ✓ validated at the door
|
||||
```
|
||||
@@ -1,10 +1,10 @@
|
||||
# SDK reference — reading data with `@ng-eventually/client`
|
||||
# SDK reference — reading data with `@ng-eventually/sdk`
|
||||
|
||||
**Audience:** anyone using `@ng-eventually/client` (the app that consumes it, and
|
||||
**Audience:** anyone using `@ng-eventually/sdk` (the app that consumes it, and
|
||||
the lib itself when honoring the contract). This is the reference on the SDK's
|
||||
**read/reactivity surface** — how you read data and how a read stays live.
|
||||
|
||||
`@ng-eventually/client` is written and consumed as if NextGraph were a **finished,
|
||||
`@ng-eventually/sdk` is written and consumed as if NextGraph were a **finished,
|
||||
mature SDK**: documents per entity placed by scope, capabilities, inboxes, and a
|
||||
**reactive ORM**. This file documents that finished-SDK contract. Where today's
|
||||
emulation does not yet deliver it, that is called out in one clearly-separated
|
||||
@@ -27,7 +27,7 @@ cited by `file:symbol` throughout so a future agent can re-verify cheaply.
|
||||
> reads are the exception, not the rule.**
|
||||
|
||||
```ts
|
||||
import { useShape } from "@ng-eventually/client";
|
||||
import { useShape } from "@ng-eventually/sdk";
|
||||
import { EventShapeType } from "…/shapes/orm/…";
|
||||
|
||||
function EventList() {
|
||||
@@ -108,9 +108,9 @@ and every subsequent patch to a `DeepSignalSet`
|
||||
`useDeepSignal` (`@ng-org/alien-deepsignals/react`). Vue and Svelte adapters exist
|
||||
alongside the React one (`sdk/js/orm/src/frontendAdapters/{vue,svelte}/`).
|
||||
|
||||
`@ng-eventually/client` re-exports `useShape` from
|
||||
`@ng-eventually/sdk` re-exports `useShape` from
|
||||
[`../src/surface/use-shape.ts`](../src/surface/use-shape.ts); import it from the SDK
|
||||
(`@ng-eventually/client`), never from `@ng-org/orm` directly.
|
||||
(`@ng-eventually/sdk`), never from `@ng-org/orm` directly.
|
||||
|
||||
### What you get, in order
|
||||
|
||||
@@ -161,7 +161,7 @@ computes a result and returns once (`sparql_query`,
|
||||
`sdk/js/lib-wasm/src/lib.rs:352`/`553`; no "subscribe to a query" exists —
|
||||
`sparql_query` is not reactive).
|
||||
|
||||
In `@ng-eventually/client` the one-shot read is exposed as:
|
||||
In `@ng-eventually/sdk` the one-shot read is exposed as:
|
||||
|
||||
- **`docs.sparqlQuery(sid, query, base?, anchor?)`** — a raw anchored SPARQL query
|
||||
([`../src/surface/docs.ts`](../src/surface/docs.ts)). `anchor` = the document NURI to read; the
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Real-broker plumbing for the SDK e2e harness — a DEDICATED test wallet for
|
||||
* `@ng-eventually/client`, fully separate from any consumer app's profile.
|
||||
* `@ng-eventually/sdk`, fully separate from any consumer app's profile.
|
||||
*
|
||||
* Adapted from the Festipod app's `src/shared/support/hooks.ts` (the reference
|
||||
* real-broker Playwright flow): headless wallet CREATION on nextgraph.eu, broker
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
*
|
||||
* Standalone (NOT `bun test`). Run:
|
||||
* bun run e2e/reactivity-doc-subscribe.ts
|
||||
* (or `bun run test:e2e:reactivity` from packages/client)
|
||||
* (or `bun run test:e2e:reactivity` from packages/sdk)
|
||||
*
|
||||
* It reuses the exact real-broker plumbing of run.ts / broker.ts: the dedicated lib
|
||||
* wallet, the broker iframe, `window.__sdk`. The CROSS case opens a SECOND page on
|
||||
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Real-broker e2e runner for `@ng-eventually/client` — the polyfill's OWN suite,
|
||||
* Real-broker e2e runner for `@ng-eventually/sdk` — the polyfill's OWN suite,
|
||||
* in the SDK domain (no application concepts), with a DEDICATED wallet.
|
||||
*
|
||||
* Standalone (NOT `bun test`), so it never mixes into the fake-ng unit suite.
|
||||
* Run: `bun run e2e/run.ts` (or `bun run test:e2e` from packages/client).
|
||||
* Run: `bun run e2e/run.ts` (or `bun run test:e2e` from packages/sdk).
|
||||
*
|
||||
* It: builds the SDK page bundle, creates/reuses the dedicated lib wallet, opens
|
||||
* the broker iframe on the real broker with that wallet, waits for `window.__sdk`
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* SDK e2e harness entry — the MINIMAL page loaded inside the broker iframe.
|
||||
*
|
||||
* It imports the REAL `@ng-org/web` `ng` + this package (`@ng-eventually/client`),
|
||||
* It imports the REAL `@ng-org/web` `ng` + this package (`@ng-eventually/sdk`),
|
||||
* configures the polyfill session injection exactly the way a consumer does
|
||||
* (`configure` + `configureStoreRegistry`), waits for the real broker to hand back
|
||||
* a session, then exposes `window.__sdk`: a flat bag of async methods the
|
||||
@@ -19,10 +19,7 @@ import {
|
||||
configure,
|
||||
configureStoreRegistry,
|
||||
setCurrentUser,
|
||||
resetCaps,
|
||||
connectedUser,
|
||||
} from "@ng-eventually/client/polyfill";
|
||||
import {
|
||||
docs,
|
||||
subscribeDoc,
|
||||
subscribeDocs,
|
||||
@@ -31,19 +28,19 @@ import {
|
||||
storeRegistry,
|
||||
useShape as libUseShape,
|
||||
watchShape,
|
||||
} from "@ng-eventually/client";
|
||||
} from "@ng-eventually/sdk";
|
||||
// The harness tests the LIBRARY, so it legitimately reaches machinery a consumer
|
||||
// application must not — but through the internal path, never the published entry.
|
||||
// `storeRegistry` above is the app-facing slice; these are the shim internals.
|
||||
import * as registryInternals from "../src/shared-wallet/account-registry";
|
||||
import { getCaps, getCurrentUser } from "../src/shared-wallet/bootstrap";
|
||||
import { getCaps, getCurrentUser, resetCaps } from "../src/shared-wallet/bootstrap";
|
||||
import { documentInboxAddress } from "../src/emulated-verifier/branch-registers";
|
||||
import * as virtualUsers from "../src/shared-wallet/virtual-users";
|
||||
import { ensureIdentity } from "@ng-eventually/client";
|
||||
import { ensureIdentity } from "@ng-eventually/sdk";
|
||||
// The harness narrows for its OWN assertions; a consumer never has to (the entries take
|
||||
// plain strings and validate inside). Internal path, like the rest of its machinery.
|
||||
import { isNuri } from "../src/model/nuri";
|
||||
import type { Nuri, ShapeObservable, ShapeQuery } from "@ng-eventually/client";
|
||||
import type { Nuri, ShapeObservable, ShapeQuery } from "@ng-eventually/sdk";
|
||||
|
||||
const { IdentityStore } = virtualUsers;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
{
|
||||
"name": "@ng-eventually/client",
|
||||
"name": "@ng-eventually/sdk",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"description": "SDK-identical client wrapper over @ng-org/web + @ng-org/orm with emulated capabilities and inbox. Drop-in; remove at migration.",
|
||||
"description": "SDK-identical wrapper over @ng-org/web + @ng-org/orm with emulated capabilities and inbox. Drop-in; remove at migration.",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./polyfill": "./src/polyfill.ts"
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ng-org/web": "*",
|
||||
@@ -16,10 +15,18 @@
|
||||
"@ng-org/alien-deepsignals": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@ng-org/web": { "optional": true },
|
||||
"@ng-org/orm": { "optional": true },
|
||||
"@ng-org/shex-orm": { "optional": true },
|
||||
"@ng-org/alien-deepsignals": { "optional": true }
|
||||
"@ng-org/web": {
|
||||
"optional": true
|
||||
},
|
||||
"@ng-org/orm": {
|
||||
"optional": true
|
||||
},
|
||||
"@ng-org/shex-orm": {
|
||||
"optional": true
|
||||
},
|
||||
"@ng-org/alien-deepsignals": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ng-org/web": "0.1.2-alpha.13",
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* `@ng-eventually/sdk` — 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. The
|
||||
// per-symbol ruling, with its epistemic label, is in `docs/api-contract.md`.
|
||||
|
||||
export * from "./model/types";
|
||||
export { useShape } from "./surface/use-shape";
|
||||
export { watchShape } from "./surface/watch-shape";
|
||||
export type { ShapeQuery, ShapeObservable } from "./surface/watch-shape";
|
||||
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` is exposed as a function, not under a `readModel` namespace: "model" is
|
||||
// neither the target's vocabulary nor neutral glue, and the namespace bought nothing —
|
||||
// it held one published function. Renamed 2026-08-03 by the vocabulary check.
|
||||
export { readUnion } from "./surface/read-model";
|
||||
export type { UnionSubject } from "./surface/read-model";
|
||||
export * as storeRegistry from "./surface/placement";
|
||||
|
||||
// SDK type re-exports — so the app imports these from @ng-eventually/sdk 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).
|
||||
export type { ShapeType, BaseType, Schema } from "@ng-org/shex-orm";
|
||||
export type { DeepSignalSet } from "@ng-org/alien-deepsignals";
|
||||
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.
|
||||
*/
|
||||
export { configure, configureStoreRegistry, setCurrentUser } from "./shared-wallet/bootstrap";
|
||||
export type { EventuallyConfig, StoreRegistryDeps } from "./shared-wallet/bootstrap";
|
||||
export type { RegistrySession } from "./shared-wallet/account-registry";
|
||||
/**
|
||||
* Await the connection work `setCurrentUser` fires: restore what was shared with this
|
||||
* user, and drain its inboxes. An application need not call it — the work runs anyway —
|
||||
* but it may want to know it has finished. Upstream this is the session opening.
|
||||
*/
|
||||
export { connectedUser } from "./emulated-verifier/connect";
|
||||
|
||||
// ── 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 { makeNg } from "./surface/ng-proxy";
|
||||
|
||||
/** SDK-identical `ng` (wrapped). Drop-in replacement for `@ng-org/web`'s `ng`. */
|
||||
export const ng: Record<string, any> = makeNg();
|
||||
@@ -25,11 +25,11 @@ export type Nuri = `did:ng:${string}`;
|
||||
* That confusion, left to runtime, silently turns "naming is not reading" into
|
||||
* "naming is reading" — the exact inversion this model exists to remove.
|
||||
*
|
||||
* It constrains the consumer's code the same way, which is the point: an app that
|
||||
* reads a cap back from storage, a URL or JSON gets a `string` and must pass it
|
||||
* through {@link isNuri} / {@link hasReadCap} (exported from the SDK entry) to use
|
||||
* it — a validation it should be doing anyway. The runtime guards stay regardless:
|
||||
* a JavaScript consumer bypasses the compiler entirely.
|
||||
* A consumer holding a plain `string` (from storage, a URL, JSON, a form) does NOT
|
||||
* have to narrow it: every public entry takes {@link NuriLike} and validates at the
|
||||
* door (`toNuri`), which is why no type guard is exported. Permissive in, precise
|
||||
* out. The runtime checks stay regardless — a JavaScript consumer never meets the
|
||||
* compiler, and a cast bypasses it.
|
||||
*/
|
||||
export type ReadCap = `did:ng:${string}:r:${string}`;
|
||||
|
||||
+1
-1
@@ -920,7 +920,7 @@ export async function createEntityDoc(id: string, scope: Scope): Promise<Nuri> {
|
||||
// `indexDoc` anchor scopes it) — the CANONICAL, always-safe shape the
|
||||
// anchored default-graph read queries (readUserStore below, same as
|
||||
// read-model.ts). Not a round-trip necessity on the current broker: the e2e
|
||||
// harness (`packages/client/e2e/`) verified an anchored `GRAPH <plainNuri>`
|
||||
// harness (`packages/sdk/e2e/`) verified an anchored `GRAPH <plainNuri>`
|
||||
// write ALSO round-trips here (same repo graph, no phantom graph); no-GRAPH
|
||||
// is kept as a simplicity/safety convention. entityNuri is a NURI stored as
|
||||
// a literal → escapeLiteral.
|
||||
+1
-1
@@ -31,7 +31,7 @@
|
||||
* (BARE-encoded Rust structs, base64url'd); decoding them to report concrete
|
||||
* write TARGETS (topics/docs) would mean duplicating the WASM verifier's wire
|
||||
* format in this polyfill, which is explicitly out of scope (SDK internals live
|
||||
* in the `@ng-eventually/client`-independent core repo, per this repo's
|
||||
* in the `@ng-eventually/sdk`-independent core repo, per this repo's
|
||||
* doctrine) — so only the pending COUNT is reported, never fabricated targets.
|
||||
* `sessionStorage` access itself can throw (sandboxed iframe, disabled storage —
|
||||
* see the exact error string handled in the core repo's `main.ts`
|
||||
@@ -168,7 +168,7 @@ export async function post(targetInboxLike: NuriLike, opts: PostOptions): Promis
|
||||
// default graph (same shape as read-model.ts readDoc/readUnion). This is the
|
||||
// CANONICAL, always-safe shape and the one the anchored default-graph read
|
||||
// queries. (Not a round-trip necessity on the current broker: the e2e harness
|
||||
// `packages/client/e2e/` verified that an anchored `GRAPH <plainNuri>` write
|
||||
// `packages/sdk/e2e/` verified that an anchored `GRAPH <plainNuri>` write
|
||||
// ALSO round-trips here — it resolves to the same repo graph, no phantom graph.
|
||||
// The no-GRAPH form is kept as a simplicity/safety convention; re-verify with
|
||||
// that harness if the broker version changes.)
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Lifecycle re-exports — SDK-shaped forwarders so the app imports `init` /
|
||||
* `initNg` from `@ng-eventually/client` rather than from `@ng-org/*`. They
|
||||
* `initNg` from `@ng-eventually/sdk` rather than from `@ng-org/*`. They
|
||||
* delegate to the REAL functions injected at `configure()`. Passthrough today;
|
||||
* a hook point later (e.g. opening the shared wallet on `init`).
|
||||
*/
|
||||
@@ -7,7 +7,8 @@
|
||||
*/
|
||||
import { getCurrentUser } from "../src/shared-wallet/bootstrap";
|
||||
import { test, expect, afterEach } from "bun:test";
|
||||
import {configure,configureStoreRegistry,resetConfig,resetStoreRegistry,setCurrentUser} from "../src/polyfill";
|
||||
import { configure, configureStoreRegistry, setCurrentUser } from "../src/index";
|
||||
import { resetConfig, resetStoreRegistry } from "../src/shared-wallet/bootstrap";
|
||||
import { ensureIdentity } from "../src/shared-wallet/access-gate";
|
||||
|
||||
const KEY = "ng-eventually:identity";
|
||||
@@ -21,7 +21,8 @@
|
||||
import { describe, it, expect, mock, beforeEach, afterEach, afterAll } from "bun:test";
|
||||
import { setAccessLog, enabled, shortNuri } from "../src/shared-wallet/access-log";
|
||||
import { docCreate, sparqlUpdate, sparqlQuery } from "../src/surface/docs";
|
||||
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,setCurrentUser,resetCaps,connectedUser} from "../src/polyfill";
|
||||
import { configure, configureStoreRegistry, connectedUser, setCurrentUser } from "../src/index";
|
||||
import { resetCaps, resetConfig, resetStoreRegistry } from "../src/shared-wallet/bootstrap";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -26,7 +26,8 @@ import {
|
||||
resetRegistryCache,
|
||||
} from "../src/shared-wallet/account-registry";
|
||||
import type { RegistrySession } from "../src/shared-wallet/account-registry";
|
||||
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig} from "../src/polyfill";
|
||||
import { configure, configureStoreRegistry } from "../src/index";
|
||||
import { resetConfig, resetStoreRegistry } from "../src/shared-wallet/bootstrap";
|
||||
import { resetOpenedRepos } from "../src/emulated-verifier/open-repo";
|
||||
|
||||
afterAll(() => {
|
||||
+2
-1
@@ -23,7 +23,8 @@
|
||||
import { describe, it, expect, mock, afterAll, beforeEach } from "bun:test";
|
||||
import { ensureAccount, resolveWriteGraph, resetRegistryCache } from "../src/shared-wallet/account-registry";
|
||||
import { resetOpenedRepos } from "../src/emulated-verifier/open-repo";
|
||||
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,resetCaps,setCurrentUser} from "../src/polyfill";
|
||||
import { configure, configureStoreRegistry, setCurrentUser } from "../src/index";
|
||||
import { resetCaps, resetConfig, resetStoreRegistry } from "../src/shared-wallet/bootstrap";
|
||||
import { resetInfrastructure } from "../src/emulated-verifier/reach";
|
||||
|
||||
const SESSION = { sessionId: "sid-cold", privateStoreId: "PRIV-COLD" };
|
||||
+3
-1
@@ -26,7 +26,9 @@ import {
|
||||
} from "../src/shared-wallet/account-registry";
|
||||
import { documentInboxAddress, openDocumentInbox } from "../src/emulated-verifier/branch-registers";
|
||||
import type { RegistrySession } from "../src/shared-wallet/account-registry";
|
||||
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,resetCaps,setCurrentUser,share,connectedUser} from "../src/polyfill";
|
||||
import { configure, configureStoreRegistry, connectedUser, setCurrentUser } from "../src/index";
|
||||
import { resetCaps, resetConfig, resetStoreRegistry } from "../src/shared-wallet/bootstrap";
|
||||
import { share } from "../src/surface/inbox";
|
||||
import { post, postToDocument, read as readInbox } from "../src/surface/inbox";
|
||||
import { readUnion } from "../src/surface/read-model";
|
||||
import { sparqlUpdate } from "../src/surface/docs";
|
||||
@@ -25,7 +25,8 @@ test("throws a clear error when configure() was not called", async () => {
|
||||
});
|
||||
|
||||
// From here on, a fake real `ng` is injected via configure().
|
||||
import {configure,resetCaps,setCurrentUser} from "../src/polyfill";
|
||||
import { configure, setCurrentUser } from "../src/index";
|
||||
import { resetCaps } from "../src/shared-wallet/bootstrap";
|
||||
|
||||
function fakeNg() {
|
||||
return {
|
||||
@@ -2,7 +2,8 @@ import { test, expect, mock, beforeEach, afterAll } from "bun:test";
|
||||
import { post, read, materialize, watch } from "../src/surface/inbox";
|
||||
import { userInbox, resetRegistryCache } from "../src/shared-wallet/account-registry";
|
||||
import type { Deposit } from "../src/surface/inbox";
|
||||
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,setCurrentUser} from "../src/polyfill";
|
||||
import { configure, configureStoreRegistry, setCurrentUser } from "../src/index";
|
||||
import { resetConfig, resetStoreRegistry } from "../src/shared-wallet/bootstrap";
|
||||
import type { RegistrySession } from "../src/shared-wallet/account-registry";
|
||||
|
||||
// This suite injects a fake `ng` via configure() and reuses the storeRegistry's
|
||||
+3
-1
@@ -21,7 +21,9 @@ import { test, expect, mock, afterAll } from "bun:test";
|
||||
import { createEntityDoc, resetRegistryCache, userInbox, listMyEntityDocs } from "../src/shared-wallet/account-registry";
|
||||
import type { RegistrySession } from "../src/shared-wallet/account-registry";
|
||||
import type { Nuri, ReadCap } from "../src/model/types";
|
||||
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,resetCaps,setCurrentUser,share} from "../src/polyfill";
|
||||
import { configure, configureStoreRegistry, setCurrentUser } from "../src/index";
|
||||
import { resetCaps, resetConfig, resetStoreRegistry } from "../src/shared-wallet/bootstrap";
|
||||
import { share } from "../src/surface/inbox";
|
||||
import { read as readInbox } from "../src/surface/inbox";
|
||||
import { filterReadable } from "../src/emulated-verifier/read-filter";
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { getCaps } from "../src/shared-wallet/bootstrap";
|
||||
import { test, expect, mock, afterEach } from "bun:test";
|
||||
import { makeNg } from "../src/surface/ng-proxy";
|
||||
import {configure,resetConfig,resetCaps,setCurrentUser} from "../src/polyfill";
|
||||
import { configure, setCurrentUser } from "../src/index";
|
||||
import { resetCaps, resetConfig } from "../src/shared-wallet/bootstrap";
|
||||
|
||||
// This suite injects a fake `ng` via configure() and declares WRITE caps —
|
||||
// which stay an authorization list on purpose: only READING is key possession
|
||||
@@ -20,7 +20,8 @@
|
||||
import { describe, it, expect, mock, beforeEach, afterAll } from "bun:test";
|
||||
import { ensureRepoOpen, ensureReposOpen, resetOpenedRepos } from "../src/emulated-verifier/open-repo";
|
||||
import { readUnion } from "../src/surface/read-model";
|
||||
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,resetCaps,setCurrentUser} from "../src/polyfill";
|
||||
import { configure, configureStoreRegistry, setCurrentUser } from "../src/index";
|
||||
import { resetCaps, resetConfig, resetStoreRegistry } from "../src/shared-wallet/bootstrap";
|
||||
import { resetInfrastructure } from "../src/emulated-verifier/reach";
|
||||
import { resetRegistryCache } from "../src/shared-wallet/account-registry";
|
||||
|
||||
@@ -9,14 +9,8 @@ import { test, expect, mock, afterEach } from "bun:test";
|
||||
import { exposeReadCap, fetchReadCap, resetPublicStoreFetches } from "../src/emulated-verifier/public-store";
|
||||
import { mintCap } from "../src/emulated-verifier/caps";
|
||||
import { getCaps } from "../src/shared-wallet/bootstrap";
|
||||
import {
|
||||
configure,
|
||||
configureStoreRegistry,
|
||||
resetConfig,
|
||||
resetStoreRegistry,
|
||||
resetCaps,
|
||||
setCurrentUser,
|
||||
} from "../src/polyfill";
|
||||
import { configure, configureStoreRegistry, setCurrentUser } from "../src/index";
|
||||
import { resetCaps, resetConfig, resetStoreRegistry } from "../src/shared-wallet/bootstrap";
|
||||
import type { Nuri } from "../src/model/types";
|
||||
|
||||
const SHIM = "urn:ng-eventually:shim";
|
||||
@@ -14,7 +14,8 @@ import { test, expect, mock, afterAll } from "bun:test";
|
||||
import { sparqlQuery, sparqlUpdate, depositInto } from "../src/surface/docs";
|
||||
import { createEntityDoc, resetRegistryCache, userInbox } from "../src/shared-wallet/account-registry";
|
||||
import type { RegistrySession } from "../src/shared-wallet/account-registry";
|
||||
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,resetCaps,setCurrentUser} from "../src/polyfill";
|
||||
import { configure, configureStoreRegistry, setCurrentUser } from "../src/index";
|
||||
import { resetCaps, resetConfig, resetStoreRegistry } from "../src/shared-wallet/bootstrap";
|
||||
import { mayReach, mustNotAttempt } from "../src/emulated-verifier/reach";
|
||||
import { hasReadCap } from "../src/model/nuri";
|
||||
|
||||
@@ -192,17 +193,18 @@ test("a BARE reference is reachable when the cap is possessed elsewhere", async
|
||||
// machinery reaches every virtual user's documents.
|
||||
test("the machinery is NOT part of the package's public surface", async () => {
|
||||
const entry: Record<string, unknown> = await import("../src/index");
|
||||
const polyfill: Record<string, unknown> = await import("../src/polyfill");
|
||||
|
||||
for (const surface of [entry, polyfill]) {
|
||||
for (const name of Object.keys(surface)) {
|
||||
expect(name).not.toMatch(/^physical/);
|
||||
}
|
||||
for (const name of Object.keys(entry)) {
|
||||
expect(name).not.toMatch(/^physical/);
|
||||
}
|
||||
// Named explicitly, so adding one and forgetting the rule fails here.
|
||||
for (const forbidden of ["physicalQuery", "physicalUpdate", "physicalCreate", "subscribePhysicalDoc"]) {
|
||||
expect(entry[forbidden]).toBeUndefined();
|
||||
expect(polyfill[forbidden]).toBeUndefined();
|
||||
}
|
||||
// …and the machinery accessors the merged entry deliberately stopped publishing
|
||||
// (2026-08-07): internal wiring and test resets are reached by their internal path.
|
||||
for (const unpublished of ["getConfig", "getStoreRegistryDeps", "resetConfig", "resetStoreRegistry", "resetCaps", "getCaps", "getCurrentUser"]) {
|
||||
expect(entry[unpublished]).toBeUndefined();
|
||||
}
|
||||
// The cross-account fan-out is gone from the registry entirely.
|
||||
const registry = entry.storeRegistry as Record<string, unknown>;
|
||||
@@ -2,7 +2,8 @@ import { getCaps } from "../src/shared-wallet/bootstrap";
|
||||
import { test, expect, mock, afterAll } from "bun:test";
|
||||
import { readUnion } from "../src/surface/read-model";
|
||||
import type { Nuri } from "../src/model/types";
|
||||
import {configure,configureStoreRegistry,resetCaps,setCurrentUser} from "../src/polyfill";
|
||||
import { configure, configureStoreRegistry, setCurrentUser } from "../src/index";
|
||||
import { resetCaps } from "../src/shared-wallet/bootstrap";
|
||||
|
||||
// The cap registry is process-wide, so each inject() starts from an empty one:
|
||||
// once ANY cap exists the possession gate is in force for every reader, and a
|
||||
+2
-1
@@ -10,7 +10,8 @@ import {
|
||||
resetRegistryCache,
|
||||
} from "../src/shared-wallet/account-registry";
|
||||
import type { RegistrySession } from "../src/shared-wallet/account-registry";
|
||||
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig} from "../src/polyfill";
|
||||
import { configure, configureStoreRegistry } from "../src/index";
|
||||
import { resetConfig, resetStoreRegistry } from "../src/shared-wallet/bootstrap";
|
||||
|
||||
// This suite injects a fake `ng` via configure(); bun runs test files in a
|
||||
// shared process with a single module singleton, and may run this file BEFORE
|
||||
@@ -1,6 +1,7 @@
|
||||
import { test, expect, mock, afterAll } from "bun:test";
|
||||
import { subscribeDoc, subscribeDocs } from "../src/surface/subscribe";
|
||||
import {configure,configureStoreRegistry,resetConfig,resetStoreRegistry} from "../src/polyfill";
|
||||
import { configure, configureStoreRegistry } from "../src/index";
|
||||
import { resetConfig, resetStoreRegistry } from "../src/shared-wallet/bootstrap";
|
||||
import type { RegistrySession } from "../src/shared-wallet/account-registry";
|
||||
|
||||
// subscribeDoc/subscribeDocs wrap the REAL injected `ng.doc_subscribe`. This
|
||||
@@ -91,10 +91,10 @@ function words(name: string): string[] {
|
||||
|
||||
const SRC = path.join(import.meta.dir, "..", "src");
|
||||
|
||||
/** Every identifier the two entry points publish, read from the `export` statements. */
|
||||
/** Every identifier the entry point publishes, read from its `export` statements. */
|
||||
function publishedNames(): string[] {
|
||||
const out = new Set<string>();
|
||||
for (const entry of ["index.ts", "polyfill.ts"]) {
|
||||
for (const entry of ["index.ts"]) {
|
||||
const text = fs.readFileSync(path.join(SRC, entry), "utf8");
|
||||
// `export * as ns from "…"`
|
||||
for (const m of text.matchAll(/export \* as (\w+) from/g)) out.add(m[1]!);
|
||||
@@ -156,7 +156,7 @@ test("a reserved-namespace key cannot be produced by a consumer's normalizeId",
|
||||
// injected by the consumer — so a careless one must be refused, not trusted. A
|
||||
// collision would key a user onto an infrastructure account: reads and writes on
|
||||
// documents that are not theirs.
|
||||
const { configureStoreRegistry, resetStoreRegistry } = await import("../src/polyfill");
|
||||
const { configureStoreRegistry, resetStoreRegistry } = await import("../src/shared-wallet/bootstrap");
|
||||
const { ensureAccount, resetRegistryCache } = await import(
|
||||
"../src/shared-wallet/account-registry"
|
||||
);
|
||||
@@ -195,7 +195,7 @@ function contractInventory(): Record<string, string[]> {
|
||||
return out;
|
||||
}
|
||||
|
||||
test("the api-contract appendix lists exactly what the entries export", () => {
|
||||
test("the api-contract appendix lists exactly what the entry exports", () => {
|
||||
// The appendix is the instrument a reader diffs against when the surface moves. It
|
||||
// went stale once — still naming `storeRegistry`'s shim internals after the entry had
|
||||
// been narrowed to seven functions — and a stale inventory is worse than none: it
|
||||
@@ -217,7 +217,7 @@ test("the api-contract appendix lists exactly what the entries export", () => {
|
||||
/** Names that live inside a re-exported namespace rather than on the entry itself. */
|
||||
function isNamespaceMember(name: string): boolean {
|
||||
const src = path.join(import.meta.dir, "..", "src");
|
||||
for (const entry of ["index.ts", "polyfill.ts"]) {
|
||||
for (const entry of ["index.ts"]) {
|
||||
const text = fs.readFileSync(path.join(src, entry), "utf8");
|
||||
for (const m of text.matchAll(/export \* as \w+ from "\.\/([^"]+)"/g)) {
|
||||
const file = path.join(src, m[1]! + ".ts");
|
||||
@@ -25,7 +25,8 @@
|
||||
|
||||
import { describe, it, expect, mock, beforeEach, afterEach, afterAll } from "bun:test";
|
||||
import { watchShape } from "../src/surface/watch-shape";
|
||||
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,resetCaps,setCurrentUser} from "../src/polyfill";
|
||||
import { configure, configureStoreRegistry, setCurrentUser } from "../src/index";
|
||||
import { resetCaps, resetConfig, resetStoreRegistry } from "../src/shared-wallet/bootstrap";
|
||||
import { resetRegistryCache, createEntityDoc } from "../src/shared-wallet/account-registry";
|
||||
import { resetOpenedRepos, setOpenTimeoutForTests, getSyncState } from "../src/emulated-verifier/open-repo";
|
||||
|
||||
Reference in New Issue
Block a user