139d21843a
Recherche menee sur les 94 citations vers engine/ et sdk/, en comparant l'arbre cite aux artefacts publies. Onze citations de niveau 2 (doc_subscribe, sparql_update, sparql_query, doc_create) sont identiques de part et d'autre, ainsi que ShapeType, Schema et DeepSignalSet. Trois ne l'etaient pas. - Scope : la revision citee declare `graphs?` OPTIONNEL, @ng-org/orm@0.1.2-alpha.21 le rend requis - et c'est le paquet installe qui est reexporte ici. - BaseType : la revision citee garde "@graph" en commentaire derriere un TODO, @ng-org/shex-orm@0.1.2-alpha.9 le declare. - NG : le compte de membres disait 88 en neuf endroits, dont un marque VERIFIED et un autre annoncant une mesure contre alpha.14. Le paquet installe en declare 90 - 84 fonctions et 6 classes RDF - et deux des quatre-vingt-dix, graph_orm_next_page et graph_orm_previous_page, sont absents de l'arbre cite. Le compte vit desormais a un seul endroit, avec la version a laquelle il appartient ; ailleurs il disparait, parce que la phrase disait "tous les membres" et que le chiffre n'y ajoutait qu'une occasion de perimer. Les deux premieres reprennent la forme que RdfOrmConfig et OrmSubscriptionHandle portaient deja : une clause finale nommant la version publiee la ou elle diverge de la revision citee.
124 lines
7.7 KiB
TypeScript
124 lines
7.7 KiB
TypeScript
/**
|
|
* Lifecycle re-exports — SDK-shaped forwarders so the app imports `init` /
|
|
* `initNg` from `@ng-eventually/polyfill` rather than from `@ng-org/*`. They
|
|
* delegate to the REAL functions injected at `configure()`.
|
|
*
|
|
* ── `init` is not a bare passthrough, and that is the point ────────────────
|
|
* The real `init()` hands the page to the broker as its FIRST statement — a top-level
|
|
* document is navigated to NextGraph's login, carrying `window.location.href` AS IT FINDS
|
|
* IT. It knows nothing of `?ng-id=`, and it does not come back: everything the application
|
|
* would have run after that line runs in a document that no longer exists. So an
|
|
* application that signed in on the next line never signed in at all — the barrier never
|
|
* showed, the identifier never reached the URL that crossed, and a first-time user landed
|
|
* on a login with no wallet and no error anywhere.
|
|
*
|
|
* The obvious remedy — "call the gate first" — is an ordering rule written in prose, which
|
|
* every consumer gets to get wrong once; and it deadlocks besides, because signing in used
|
|
* to include waiting for the session `init()` is what opens. So the invariant is carried
|
|
* HERE, by composition: this forwarder settles the identity, then delegates. A caller
|
|
* cannot get the order wrong because a caller no longer takes part in it
|
|
* (`shared-wallet/access-gate.ts`, {@link settleIdentity}).
|
|
*/
|
|
|
|
import { getConfig, rememberHandOver } from "../shared-wallet/bootstrap";
|
|
import { settleIdentity } from "../shared-wallet/access-gate";
|
|
import { captureSession, expectSession } from "../shared-wallet/session";
|
|
import { wrappedNg } from "./ng-proxy";
|
|
|
|
/**
|
|
* Forwards to the real `@ng-org/web` `init`, once the identifier is in the address bar.
|
|
*
|
|
* Awaits {@link settleIdentity} — the session-free half of signing in — and NOT
|
|
* `ensureIdentity()`, which also awaits the connection work, which awaits `getSession()`,
|
|
* which resolves only from the session `init()` has not opened yet. That wait is the
|
|
* deadlock, and avoiding it is what the split in `access-gate.ts` is for.
|
|
*
|
|
* A settling failure REJECTS rather than delegating. No shared wallet configured, or no DOM
|
|
* to ask on, means there is no identifier to hand over — and handing the page to the broker
|
|
* anyway IS the defect, a navigation the user cannot come back from. It fails at the call
|
|
* the application made, where the cause is.
|
|
*
|
|
* The "not injected" error stays SYNCHRONOUS: it is a wiring mistake rather than a runtime
|
|
* one, and it threw synchronously before this forwarder had anything to await.
|
|
*
|
|
* ── It also LISTENS on the way through, and that is the one argument it touches ──
|
|
* The real `init` delivers the session by calling its callback with
|
|
* `{ status: "loggedin", session }` — once, and it is the only channel that ever produces
|
|
* one (`@ng-org/web` `dist/ngweb.js:113-137`, VERIFIED). Until 2026-08-12 every application
|
|
* had to catch that event itself and hand the library a thunk unwrapping it, which is a
|
|
* shape the target never asks anyone to build and which two consumers in a row got wrong.
|
|
*
|
|
* So the callback in position 0 is WRAPPED: the wrapper reads the event, keeps the session
|
|
* (`shared-wallet/session.ts`), and then calls the caller's callback with that same event,
|
|
* unchanged and un-narrowed. Nothing else about the call moves — the remaining arguments and
|
|
* the return value pass straight through, and the caller's callback still sees exactly what
|
|
* the real `init` sent it. It is the one place a wrapper can be, because it is the one place
|
|
* that knows both what the caller asked and what the SDK will answer.
|
|
*
|
|
* A caller that passes NO callback is the same act with nobody listening — upstream accepts
|
|
* it (`callback: Function | null`, and the call site is guarded). The wrapper still goes in,
|
|
* so the package gets its session either way, and calls nothing afterwards.
|
|
*/
|
|
// @provenance init kind=passthrough level=2 ref=sdk/js/web/src/index.ts:init — arguments and return pass straight through; the callback in position 0 is wrapped to keep the session it carries
|
|
export function init(...args: any[]): any {
|
|
const f = getConfig().init;
|
|
if (!f) throw new Error("[ng-eventually] init() not injected — pass it to configure()");
|
|
const [callback, ...rest] = args;
|
|
const listen = (event: unknown): unknown => {
|
|
captureSession(event);
|
|
return typeof callback === "function" ? callback(event) : undefined;
|
|
};
|
|
const delegate = (): unknown => f(listen, ...rest);
|
|
// Two notes taken on the way in, both about what only this call can know.
|
|
//
|
|
// The hand-over, first: this delegation is the whole of it, and a person who reaches the
|
|
// broker without a wallet comes back to THIS document with it already spent. The barrier
|
|
// revives there and needs something to hand the page over with — this, exactly as it
|
|
// would have run the first time (`shared-wallet/access-gate.ts`).
|
|
rememberHandOver((): void => {
|
|
// Un-awaited, and it has to be: the promise this call returned was answered on the way
|
|
// out, so a rejection here has no caller to reach. Reported rather than dropped as an
|
|
// unhandled rejection — the page is on its way to the broker either way.
|
|
void Promise.resolve(delegate()).catch((failure: unknown) => {
|
|
console.error("[ng-eventually] the hand-over to the broker failed", failure);
|
|
});
|
|
});
|
|
// And that it RAN. The session arrives through this call and through no other, so
|
|
// "`init()` has not been called" is the same statement as "no session can ever arrive" —
|
|
// which is what lets `ensureIdentity()` refuse an impossible wait instead of hanging on it
|
|
// (`shared-wallet/session.ts`, `shared-wallet/access-gate.ts`).
|
|
expectSession();
|
|
return settleIdentity().then(delegate);
|
|
}
|
|
|
|
/**
|
|
* Forwards to the real `@ng-org/orm` `initNg` (ORM signals) - with the `ng` in position 0
|
|
* replaced by this package's own wrapped one.
|
|
*
|
|
* -- Why the substitution, and why it is invisible to the caller ------------
|
|
* `initNg(ngImpl, session)` is how the ORM is told which `ng` to reach the engine
|
|
* through: it resolves a module-level promise every subscription, every `insertObject`
|
|
* and every batched patch awaits afterwards. So whatever is handed over HERE is the `ng`
|
|
* every ORM write leaves by - and an ORM write leaves by `ng.graph_orm_update`, which is
|
|
* where this package's write guard sits (`ng-proxy.ts`). Handing over the caller's raw
|
|
* SDK object leaves that guard on a door nothing knocks on.
|
|
*
|
|
* It is the same argument `init`'s callback wrapping above rests on, and it lands the same
|
|
* way: the invariant is carried by COMPOSITION rather than by an instruction a caller gets
|
|
* to get wrong once. "Pass the package's `ng` to `initNg`" would be an ordering rule in
|
|
* prose, unenforceable and silent when missed - and it WAS missed, by both consumers in
|
|
* this repository, which is how the hole was found.
|
|
*
|
|
* Nothing is taken from the caller: `wrappedNg()` forwards every one of its members to
|
|
* the very object `configure()` received, and it is the same object `ng` publishes, so the
|
|
* ORM now reaches the engine through exactly what the application does. The remaining
|
|
* arguments and the return value pass straight through.
|
|
*/
|
|
// @provenance initNg kind=passthrough level=3 ref=sdk/js/orm/src/core.ts:initNg - upstream's own exported name for `initNgSignals`; the arguments and the return pass through, and the `ng` in position 0 is replaced by this package's wrapped one so the ORM's writes meet the emulated write guard
|
|
export function initNg(...args: any[]): any {
|
|
const f = getConfig().initNg;
|
|
if (!f) throw new Error("[ng-eventually] initNg() not injected — pass it to configure()");
|
|
const [, ...rest] = args;
|
|
return f(wrappedNg(), ...rest);
|
|
}
|