Un polyfill ne doit rien faire de plus que ce qui est prévu. `isNuri` /
`hasReadCap` et les utilitaires SPARQL `escapeLiteral` / `escapeIri` /
`assertNuri` n'ont de pendant à aucun niveau et n'en auront pas : le binding
prend `nuri: String`, le moteur est fortement typé en Rust et n'a besoin
d'aucun prédicat, l'ORM n'expose rien de tel. Le contrat les justifiait parce
qu'ils « restent utiles à n'importe quelle app » — c'est exactement le
raisonnement à refuser : utile n'est pas prévu, et chacun serait un appel à
réécrire le jour du SDK.
Le besoin d'un guard venait de notre propre signature : les entrées publiques
exigeaient `Nuri`, donc un consommateur devait narrower ce qu'il lisait d'une
URL ou du stockage. Elles prennent désormais `NuriLike` — n'importe quelle
chaîne — et valident à l'intérieur (`toNuri`). Ce que la bibliothèque REND
reste typé `Nuri` : l'app en profite gratuitement, et un type plus large ne
cassera rien quand le SDK rendra des chaînes.
Les guards et les utilitaires restent, internes, là où la validation se fait.
Un défaut introduit puis corrigé en chemin, qui valait le test qu'il a produit :
`readUnion` a toujours toléré les trous dans sa liste — un index de scope peut
porter une entrée blanche, et un appelant qui assemble depuis des valeurs
optionnelles n'a pas à compacter. Valider AVANT de filtrer a transformé cette
tolérance en exception. Vide est une absence, pas une référence malformée ; les
deux sont désormais distingués par un test.
170 tests unitaires, e2e 42/42 contre le broker, typecheck vert sur la
bibliothèque, l'exemple et le harnais.
Le harnais e2e parlait à un sac de méthodes posé sur `window.__sdk`. Il prouvait
que les fonctions s'exécutaient, jamais qu'on pouvait écrire une application avec
— et cet écart a livré un vrai défaut : l'inbox d'un document était verte en test
et inutilisable en vrai, parce que le harnais faisait traverser une adresse d'une
identité à l'autre par une variable, ce qu'aucune application ne peut faire.
`examples/notebook` est une application minimale en DOM natif, qui résout
`@ng-eventually/client` comme un consommateur externe (workspace, dépendance
déclarée, aucun import privilégié). Elle ne peut faire que ce qu'une application
peut faire.
Elle s'est déjà payée deux fois pendant son écriture :
- `UnionSubject.subject` et `.graph` étaient typés `string` alors que ce sont
toujours des références de document. Un consommateur devait donc caster ce
qu'il venait de lire avant de le repasser — un cast à cet endroit précis
rouvre la confusion que les types template literal existent pour fermer.
- l'écran d'accès normalisait ce que l'utilisateur SAISIT mais pas ce que l'URL
porte, si bien qu'un lien `?ng-id=@Erin` ouvrait un espace différent de celui
de la même personne tapant `erin`. Une seule normalisation désormais, celle
du registre.
Le domaine est volontairement mince — des notes — mais suffit à exercer le
placement par scope, la possession de caps, le partage dirigé, les inbox par
document et la lecture réactive.
170 tests unitaires, typecheck vert sur la lib, l'exemple et le harnais.
L'entrée publique `/polyfill` portait le magasin d'injection (le `ng` injecté,
les dépendances du registre, QUI est connecté, le singleton `CapRegistry`). Tout
module interne l'importait donc pour atteindre la config — ce qui faisait de
l'entrée une dépendance du code qu'elle publie, avec les cycles
`polyfill` <-> `connect` et `polyfill` <-> `inbox`.
Le magasin rejoint `shared-wallet/` : rien n'est injecté en amont, l'app importe
le SDK et « qui suis-je » est la session — il n'y a pas de relais d'utilisateur
courant parce qu'un wallet a exactement un user. Ce module est la forme de cette
absence, il s'évapore en entier à la migration.
L'entrée ne fait plus que ré-exporter. Plus aucun module interne n'importe
`polyfill`.
Les 25 modules étaient à plat, nommés d'après ce qu'ils font mécaniquement
(`store-registry`, `read-model`, `reach`, `caps`). Rien dans l'arborescence ne
disait lesquels DEVIENDRONT le vrai SDK, lesquels tiennent lieu du travail que
le verifier fera nativement, et lesquels n'existent que parce qu'un wallet est
partagé — trois destins sans rapport.
Quatre dossiers, les deux fichiers d'entrée restant à la racine pour que
l'`exports` du paquet et le code du consommateur ne bougent pas :
- `model/` — le modèle d'adressage de la cible, transcrit : vocabulaire pur,
pas d'I/O. Survit comme connaissance.
- `surface/` — ce que l'app touche, chaque symbole ayant un pendant cible
documenté. Supprimé quand l'alias bascule ; le code de l'app est inchangé.
- `emulated-verifier/` — les doublures de ce que le verifier fait nativement :
possession, dépôt des caps, frontière, non-livraison, traitement des inbox,
registres de branche, ouverture de repo. **C'est le dossier où diverger du
modèle est possible.** Le préfixe `emulated-` porte le sens : tient lieu de,
jamais est — cette bibliothèque ne réside dans aucune couche de la cible,
elle les référence.
- `shared-wallet/` — n'existe que parce qu'un wallet héberge toutes les
identités. Aucun pendant, rien sur quoi s'aligner ; sa seule loi est de
rester invisible depuis `surface/`. S'évapore, remplacé par rien.
`store-registry-api.ts` devient `surface/placement.ts` : il faisait déjà à la
main ce que la frontière de dossier fait structurellement — c'est la meilleure
preuve interne du bien-fondé de ce rangement.
Ce commit ne fait que déplacer et recâbler les imports (src, test, e2e). Les
scissions des modules à cheval suivent.
157 tests unitaires, typecheck src/test/e2e vert.