Testclient fase 1: er is geen Evolu-client voor Node, dus die stellen we samen
Het plan is gepromoveerd naar Actief/003-Testclient en tier A, en fase 1 is af. De uitkomst die het plan als eerste wilde weten: @evolu/nodejs 3.1.0 bevat geen client. Het pakket levert de relay plus losse bouwstenen, maar er is geen createEvoluDeps voor Node zoals @evolu/web die voor de browser heeft. De afhankelijkheden voor createEvolu worden nu samengesteld in tools/relay-client/src/evolu-node.js, uit de in-memory workers van @evolu/common; src/client.js is de publieke kant met eigenaars en blobs. Vier dingen zaten in de weg en alle vier faalden ze stil, met een instantie die het lijkt te doen tot de eerste schrijfactie: de ontbrekende installPolyfills (Node 24 mist Map.getOrInsertComputed), createEvoluDeps overslaan, de AsyncDisposableStack van initSharedWorker laten vallen, en workers zonder eigen reportDefect. Dat laatste is waarom de andere drie te vinden waren. Uitgeschreven in PLAN.md 4e. tests/test_client_lokaal.mjs schrijft daarom een blob weg en leest hem terug in plaats van alleen een instantie te maken, en heeft een wachthond: bij een defect in een worker blijft de suite anders hangen in plaats van rood te worden, en dat gebeurde bij de mutatietoets letterlijk. Beide mutaties lieten de juiste toets omvallen. Nog niet geprobeerd: praten met de relay. Daarvoor is het adres van de Umbrel nodig en dat komt van de gebruiker; het gaat niet in deze publieke repo. Suite: 405 goed, 0 fout over alle acht de toetsen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+12
@@ -58,6 +58,18 @@ build/
|
|||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.zip
|
*.zip
|
||||||
|
|
||||||
|
# ── Node ──────────────────────────────────────────────────────────────────────
|
||||||
|
# Zowel tools/evolu-relay/ als tools/relay-client/ hebben een package.json. Wat
|
||||||
|
# daar geïnstalleerd wordt hoort niet in de repo: de relay bouwt in zijn image en
|
||||||
|
# de testcliënt installeert lokaal.
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# De gegevens van de testcliënt: per eigenaar een mnemonic en een database. Dit
|
||||||
|
# is sleutelmateriaal, en ook al heeft het buiten de proefopstelling geen waarde,
|
||||||
|
# een publieke repo is er geen plek voor.
|
||||||
|
tools/relay-client/data/
|
||||||
|
tools/relay-client/spike-data/
|
||||||
|
|
||||||
# ── Python ────────────────────────────────────────────────────────────────────
|
# ── Python ────────────────────────────────────────────────────────────────────
|
||||||
# De test importeert tools/electrum-gate/agent.py als module, en Python legt daar
|
# De test importeert tools/electrum-gate/agent.py als module, en Python legt daar
|
||||||
# dan bytecode naast. De test zet dat zelf uit, maar dit is de vangnetregel: zo'n
|
# dan bytecode naast. De test zet dat zelf uit, maar dit is de vangnetregel: zo'n
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
| Plan | App | Volgende stap | Status |
|
| Plan | App | Volgende stap | Status |
|
||||||
|-|-|-|-|
|
|-|-|-|-|
|
||||||
|
| [Testclient](Plannen/Actief/003-Testclient/TAKEN.md) | Relay | **De cliënt met de relay laten praten. Daarvoor is het adres van de Umbrel nodig, en dat komt van de gebruiker.** Fase 1 is af op 09-09-2026: `@evolu/nodejs` blijkt géén cliënt te bevatten, dus de afhankelijkheden voor `createEvolu` worden zelf samengesteld in `tools/relay-client/src/evolu-node.js`. Bewezen zonder relay: een eigenaar aanmaken, een blob wegschrijven en teruglezen, en schoon afsluiten. Vier valstrikken kostten dat, alle vier stil falend; ze staan in `PLAN.md` §4e | 🔶 |
|
||||||
|
|
||||||
## B - Los oppakbaar (geen blokkade, geen vaste volgorde)
|
## B - Los oppakbaar (geen blokkade, geen vaste volgorde)
|
||||||
|
|
||||||
@@ -70,7 +71,6 @@ daaronder, dus deze tabel en de mapinhoud kunnen niet uit elkaar lopen.
|
|||||||
| Plan | App | Afhankelijk van | Waarover het gaat |
|
| Plan | App | Afhankelijk van | Waarover het gaat |
|
||||||
|-|-|-|-|
|
|-|-|-|-|
|
||||||
| [Bereikbaarheid.PLAN.md](Plannen/Masterplannen/Bereikbaarheid.PLAN.md) | Relay | Umbrelapp: er valt niets bereikbaar te maken zolang er niets draait | Synchroniseren buiten het thuisnetwerk. **Suite eist geen TLS**, dus Tailscale blijft open en een certificaat is geen voorwaarde. Het risico van een publiek eindpunt is niet vertrouwelijkheid (dat regelt de versleuteling) maar misbruik als gratis opslag: een kale relay kent geen accounts. Wil je tóch open, dan is een eigenaars-allowlist nodig; drie manieren in §4b |
|
| [Bereikbaarheid.PLAN.md](Plannen/Masterplannen/Bereikbaarheid.PLAN.md) | Relay | Umbrelapp: er valt niets bereikbaar te maken zolang er niets draait | Synchroniseren buiten het thuisnetwerk. **Suite eist geen TLS**, dus Tailscale blijft open en een certificaat is geen voorwaarde. Het risico van een publiek eindpunt is niet vertrouwelijkheid (dat regelt de versleuteling) maar misbruik als gratis opslag: een kale relay kent geen accounts. Wil je tóch open, dan is een eigenaars-allowlist nodig; drie manieren in §4b |
|
||||||
| [Testclient.PLAN.md](Plannen/Masterplannen/Testclient.PLAN.md) | Relay | – | Een eigen cliënt om meerdere eigenaars aan te maken en gegevens door de relay te duwen, zonder Trezor Suite en zonder hardware. Vult het gat dat vier beweringen over deze app op redenering laat rusten in plaats van op een proef, waaronder "blokkeren werkt pas bij de volgende verbinding". Alles vanuit één scherm, en aan de server-app wordt niets gedaan: die draait en synchroniseert met Trezor Suite (bevestigd 09-09-2026) |
|
|
||||||
| [Publicatie-Relay.PLAN.md](Plannen/Masterplannen/Publicatie-Relay.PLAN.md) | Relay | Umbrelapp | Inleveren bij de officiële appstore. De image bestaat en is gepind sinds **Eigenimage** (08-09-2026); wat ontbreekt is `linux/arm64`, en `build.sh` heeft er een ongeteste schakelaar voor. Het andere risico: de eis dat de umbrelOS-inlog aan blijft terwijl de relay een cliënt zonder sessie moet bedienen |
|
| [Publicatie-Relay.PLAN.md](Plannen/Masterplannen/Publicatie-Relay.PLAN.md) | Relay | Umbrelapp | Inleveren bij de officiële appstore. De image bestaat en is gepind sinds **Eigenimage** (08-09-2026); wat ontbreekt is `linux/arm64`, en `build.sh` heeft er een ongeteste schakelaar voor. Het andere risico: de eis dat de umbrelOS-inlog aan blijft terwijl de relay een cliënt zonder sessie moet bedienen |
|
||||||
| [Publicatie-Gate.PLAN.md](Plannen/Masterplannen/Publicatie-Gate.PLAN.md) | Gate | Appstore: de herstart-controle | **Doel van de gebruiker sinds 20-08-2026:** de app inleveren als standaard-app voor Umbrel. Dat verandert de maatstaf van "hij werkt hier" naar "iemand anders keurt het pakket goed". Het meeste is al goed; de image is eigen en gepind sinds **Eigenimage** (08-09-2026). Wat er nog moet: `linux/arm64` erbij bouwen, het app-id kaal maken en de manifestvelden op orde. Het risico zit niet in die lijst maar in de leesmount op de certificaten van Zoraxy |
|
| [Publicatie-Gate.PLAN.md](Plannen/Masterplannen/Publicatie-Gate.PLAN.md) | Gate | Appstore: de herstart-controle | **Doel van de gebruiker sinds 20-08-2026:** de app inleveren als standaard-app voor Umbrel. Dat verandert de maatstaf van "hij werkt hier" naar "iemand anders keurt het pakket goed". Het meeste is al goed; de image is eigen en gepind sinds **Eigenimage** (08-09-2026). Wat er nog moet: `linux/arm64` erbij bouwen, het app-id kaal maken en de manifestvelden op orde. Het risico zit niet in die lijst maar in de leesmount op de certificaten van Zoraxy |
|
||||||
| [Configuratie.PLAN.md](Plannen/Masterplannen/Configuratie.PLAN.md) | Gate | – | **Grotendeels ingehaald op 19-08-2026** en moet opgeschoond worden voordat promotie nog zin heeft: de agent doet de certificaatbronnen en de keuze al, en het hardgecodeerde domein is uit de compose en uit `nginx.conf.template` verdwenen. Wat er nog in zit is een configuratiebestand voor de poort- en padoverstemmingen, plus de README |
|
| [Configuratie.PLAN.md](Plannen/Masterplannen/Configuratie.PLAN.md) | Gate | – | **Grotendeels ingehaald op 19-08-2026** en moet opgeschoond worden voordat promotie nog zin heeft: de agent doet de certificaatbronnen en de keuze al, en het hardgecodeerde domein is uit de compose en uit `nginx.conf.template` verdwenen. Wat er nog in zit is een configuratiebestand voor de poort- en padoverstemmingen, plus de README |
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Open punten - Testclient
|
||||||
|
|
||||||
|
> Vragen die het plan niet beantwoordt en die tijdens het werk beslist moeten worden. Wat af is, verhuist
|
||||||
|
> naar [PROGRESS.md](PROGRESS.md); wat een taak wordt, naar [TAKEN.md](TAKEN.md).
|
||||||
|
|
||||||
|
1. **Hoe zichtbaar is een 401 in de cliënt?** Evolu probeert opnieuw en is ontworpen om een relay te
|
||||||
|
overleven die er niet is. Als een weigering en een relay die plat ligt in de cliënt hetzelfde signaal
|
||||||
|
geven, is de eerste proef uit `PLAN.md` §4c niet af te lezen. De uitweg blijft binnen dit gereedschap:
|
||||||
|
een eigen WebSocket-poging naast Evolu, met dezelfde `OwnerId` in de URL, die de statuscode wel te zien
|
||||||
|
krijgt. Dat is een handeling van een paar regels, want de `OwnerId` staat in de URL van de verbinding
|
||||||
|
(§10 van [Upstream-evolu-relay.md](../../../Referenties/Upstream-evolu-relay.md)).
|
||||||
|
|
||||||
|
2. **Wat is een zinnige blob om mee te experimenteren?** Willekeurige bytes zijn genoeg voor de bytegrens,
|
||||||
|
maar niet om te zien of gegevens werkelijk heen en weer gaan. Iets met leesbare inhoud en een teller is
|
||||||
|
waarschijnlijk beter.
|
||||||
|
|
||||||
|
3. **Wordt de gegevensmap van de proef opgeruimd?** Twintig eigenaars die blijven staan is rommel, en het
|
||||||
|
is sleutelmateriaal. Waarschijnlijk een opdracht die er één weggooit en één die alles weggooit.
|
||||||
|
|
||||||
|
4. **Data per eigenaar wissen op de relay staat al open bij Umbrelapp.** Dit gereedschap maakt dat
|
||||||
|
makkelijk te beproeven maar lost het niet op; het blijft schrijven in andermans schema (§8 van hetzelfde
|
||||||
|
naslagdocument).
|
||||||
|
|
||||||
|
5. **Bevriest deze cliënt de versie van Evolu, of loopt hij mee?** `tools/relay-client/` en
|
||||||
|
`tools/evolu-relay/` hebben nu allebei `@evolu/common` 8.7.0 en `@evolu/nodejs` 3.1.0, en dat is geen
|
||||||
|
toeval maar ook geen afspraak. Meelopen is het eerlijkst, want dan beproef je wat er draait; het kost
|
||||||
|
wel dat de samenstelling uit `PLAN.md` §4e bij elke verhoging opnieuw langs moet. Beslissen bij de
|
||||||
|
eerste verhoging, niet nu.
|
||||||
|
|
||||||
|
6. **Waar staat de relay voor deze cliënt?** Het adres van de Umbrel staat nergens in deze repo en hoort
|
||||||
|
daar ook niet in: dit is een publieke repo (zie `.gitignore`, "Geheimen"). Dus een instelling die de
|
||||||
|
gebruiker zelf zet, met een voorbeeldbestand ernaast. Voor de hand ligt `.env.sample`, want dat patroon
|
||||||
|
staat al in de `.gitignore` van de repo-root.
|
||||||
|
|
||||||
|
7. **Het afsluiten is opgelost maar niet doorgrond.** De volgorde in `dispose` (afhankelijkheden, dan een
|
||||||
|
tik doorlaten, dan de resources van de gedeelde worker, dan de runs) is gevonden door te proberen, niet
|
||||||
|
door de bron van Evolu te lezen. Hij werkt en er is een reden bij opgeschreven, maar het is geen bewijs.
|
||||||
|
Valt het bij een volgende versie opnieuw om, lees dan eerst `Task.js` in plaats van opnieuw te schuiven.
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
# Testclient - plan
|
||||||
|
|
||||||
|
> **App: Evolu Relay.** Promotie op 09-09-2026, dezelfde dag als het masterplan geschreven werd, op
|
||||||
|
> verzoek van de gebruiker. Taken staan in [TAKEN.md](TAKEN.md), voortgang in [PROGRESS.md](PROGRESS.md),
|
||||||
|
> open punten in [OPEN.md](OPEN.md).
|
||||||
|
>
|
||||||
|
> Afhankelijk van: niets. De relay draait sinds 28-08-2026 en de haken die dit plan beproeft bestaan al.
|
||||||
|
|
||||||
|
## 1. Doel
|
||||||
|
|
||||||
|
Een eigen cliënt waarmee je meerdere eigenaars kunt aanmaken, ze op de relay kunt laten verbinden en er
|
||||||
|
gegevens doorheen kunt duwen, om het toegangsbeleid en de opslag te beproeven zonder Trezor Suite en zonder
|
||||||
|
hardware.
|
||||||
|
|
||||||
|
Aanleiding: Suite is vandaag de enige cliënt, en die vraagt een apparaat dat aan het toestel kan (zie
|
||||||
|
[Upstream-evolu-relay.md](../../Referenties/Upstream-evolu-relay.md) §9). Daardoor rust een deel van wat we
|
||||||
|
over deze app opschrijven op redenering in plaats van op een proef. Het scherpste voorbeeld staat in §10 van
|
||||||
|
datzelfde document: blokkeren werkt pas bij de volgende verbinding. Dat is uit de broncode afgeleid en nooit
|
||||||
|
gezien.
|
||||||
|
|
||||||
|
## 2. Afbakening
|
||||||
|
|
||||||
|
Een programma dat buiten de Umbrel draait, op de laptop, en dat via de gepubliceerde relay-poort verbindt.
|
||||||
|
Meerdere eigenaars naast elkaar, elk met een eigen sleutelmateriaal en een eigen lokale database. Eén
|
||||||
|
bedieningsvlak waarin je die eigenaars ziet en aanstuurt.
|
||||||
|
|
||||||
|
## 3. Niet-doelen
|
||||||
|
|
||||||
|
- **Geen tweede relay en geen wijziging aan de app.** Als dit plan een aanpassing in `tools/evolu-relay/`
|
||||||
|
nodig lijkt te hebben, is dat een aanwijzing dat er iets misgaat in de opzet, niet een taak. De relay
|
||||||
|
wordt beproefd zoals hij draait.
|
||||||
|
- **Geen ontcijfering van gegevens van Trezor Suite.** De cliënt maakt eigen sleutels en leest zijn eigen
|
||||||
|
gegevens. Wat Suite op de relay zet blijft onleesbaar, en dat is de belofte in het manifest. Zie
|
||||||
|
`CLAUDE.md`, "Zet geen ontcijfering in deze app", en §11 van het naslagdocument.
|
||||||
|
- **Geen onderdeel van de app store.** Dit is gereedschap in `tools/` en het gaat nooit in een image of in
|
||||||
|
een app-map mee.
|
||||||
|
- **Geen nabouw van het protocol.** Net als bij de relay geldt: `@evolu/common` doet het werk, wij roepen
|
||||||
|
het aan.
|
||||||
|
|
||||||
|
## 4. Ontwerp
|
||||||
|
|
||||||
|
### 4a. Een eigenaar is een mnemonic plus een eigen map
|
||||||
|
|
||||||
|
Evolu leidt een `AppOwner` af uit een mnemonic. Een tweede eigenaar is dus niets anders dan een tweede
|
||||||
|
mnemonic met een eigen SQLite-bestand ernaast; er is geen accountbegrip dat gedeeld moet worden. De cliënt
|
||||||
|
bewaart per eigenaar een map met de mnemonic en de database, onder een pad dat in `.gitignore` staat.
|
||||||
|
|
||||||
|
Dat sleutelmateriaal is van deze proefopstelling en heeft geen waarde buiten de relay, maar het blijft
|
||||||
|
sleutelmateriaal: het staat niet in de repo en het wordt niet in een logregel afgedrukt. Het `OwnerId` wel,
|
||||||
|
want dat is publiek en je hebt het nodig om de eigenaar op de statuspagina terug te vinden (§1b van het
|
||||||
|
naslagdocument).
|
||||||
|
|
||||||
|
### 4b. Eén bedieningsvlak voor de cliënt
|
||||||
|
|
||||||
|
**Alles wat de cliënt doet, gebeurt vanuit één scherm.** Geen losse opdrachten per eigenaar en geen tweede
|
||||||
|
terminal ernaast: je ziet de eigenaars in een lijst en bedient ze daar. Wat dat scherm kan:
|
||||||
|
|
||||||
|
| Onderdeel | Wat je ziet of doet |
|
||||||
|
|-|-|
|
||||||
|
| De lijst | per eigenaar zijn naam, zijn `OwnerId`, of hij verbonden is, wanneer er voor het laatst iets over de lijn ging, en hoeveel er geschreven is |
|
||||||
|
| Per eigenaar | aanmaken, verbinden, verbreken, een blob schrijven met een instelbare omvang, teruglezen, en weggooien |
|
||||||
|
| Het log | wat er per verbinding gebeurt, met tijdstip. Dit is waar de proeven uit §4c af te lezen zijn |
|
||||||
|
| De relay | het adres waarmee verbonden wordt, en of hij een verbinding aanneemt |
|
||||||
|
|
||||||
|
Vorm: een kleine HTTP-server in hetzelfde proces met één pagina ervoor, in de stijl van de statuspagina's
|
||||||
|
van beide apps. Geen bouwstap en geen framework; die pagina's laten zien dat dat hier genoeg is. De
|
||||||
|
opdrachtregel blijft eronder bestaan, want stap 2 van §5 heeft hem toch nodig en het is prettig om een proef
|
||||||
|
te kunnen scripten.
|
||||||
|
|
||||||
|
**Aan de server-app wordt niets gedaan.** Bevestigd door de gebruiker op 09-09-2026: die draait en
|
||||||
|
synchroniseert met Trezor Suite op de Mac. Dit plan raakt `tools/evolu-relay/` dus niet, en een bevinding
|
||||||
|
uit §4c is materiaal voor **Umbrelapp**, geen taak hier.
|
||||||
|
|
||||||
|
**Eén grens om te kennen voordat iemand er knoppen bij wil zetten:** de leerstand openzetten, blokkeren en
|
||||||
|
labelen kán deze UI niet, en dat is de poortindeling van de app en geen tekortkoming van het gereedschap.
|
||||||
|
De relay publiceert hostpoort 3852 en daar praat de cliënt mee, maar de agent-API (`GET /api/status`,
|
||||||
|
`POST /api/command`, `POST /api/label`) hangt achter `app_proxy` met de inlog van umbrelOS, en poort 8000
|
||||||
|
staat niet op de host. Dat blijft dus op de statuspagina van de app, in een tweede tabblad. Het omzeilen
|
||||||
|
daarvan zou een onbeschermde poort vragen, en dat haalt precies de bescherming weg waarvoor de poorten in
|
||||||
|
0.0.2 omgedraaid zijn (`CLAUDE.md`, "De poorten staan omgekeerd ten opzichte van wat je verwacht").
|
||||||
|
|
||||||
|
### 4c. Wat er dan te beproeven valt
|
||||||
|
|
||||||
|
Dit is de eigenlijke opbrengst, en elk punt is vandaag niet te proberen:
|
||||||
|
|
||||||
|
| Proef | Wat er hoort te gebeuren | Waar het vandaan komt |
|
||||||
|
|-|-|-|
|
||||||
|
| Onbekende eigenaar, venster dicht | HTTP 401, socket dicht, niets in de database van de relay, wel een regel in de weigerlijst | §10 van het naslagdocument, afgeleid uit de broncode |
|
||||||
|
| Diezelfde eigenaar toelaten en opnieuw verbinden | de hele geschiedenis komt alsnog binnen, want de cliënt was niets kwijt | §10, punt 3 |
|
||||||
|
| Blokkeren tijdens een lopende verbinding | de eigenaar blijft schrijven tot hij opnieuw verbindt | §10, punt 4. Open punt 10 van **Umbrelapp** |
|
||||||
|
| Venster van twee minuten, met de pagina dicht | de teller loopt in het relay-proces en sluit vanzelf | `CLAUDE.md`, "Het tijdvenster loopt in het relay-proces" |
|
||||||
|
| Eén blob boven `RELAY_MAX_WRITE_BYTES` (1 MB) | geweigerd; meerdere kleinere samen niet, want de grens is per schrijfactie | §8 van het naslagdocument |
|
||||||
|
| Twee eigenaars die tegelijk verbinden | onafhankelijk, en elk ziet alleen zijn eigen gegevens | §1b |
|
||||||
|
|
||||||
|
Wat er als bijvangst uit kan komen, en dat is de reden om de proeven echt te dóen: `owners.json` groeit hier
|
||||||
|
voor het eerst voorbij één of twee regels, en de statuspagina is nog nooit met een handvol eigenaars en een
|
||||||
|
volle weigerlijst gezien.
|
||||||
|
|
||||||
|
### 4d. Waar het staat, en wat het niet raakt
|
||||||
|
|
||||||
|
`tools/relay-client/`, met een eigen `package.json`. Niet in een app-map, want alles daar wordt bij
|
||||||
|
installatie naar het apparaat gekopieerd (`CLAUDE.md`, "Zet geen CLAUDE.md of andere werkbestanden in een
|
||||||
|
app-map"). Geen `VERSION`, geen image, geen manifest: er is niets uit te rollen, dus de versieregels van de
|
||||||
|
twee apps gelden hier niet.
|
||||||
|
|
||||||
|
### 4e. Er is geen Evolu-cliënt voor Node, dus die wordt hier samengesteld
|
||||||
|
|
||||||
|
**Uitgezocht en werkend gekregen op 09-09-2026, en dit is de belangrijkste uitkomst van de eerste sessie.**
|
||||||
|
`@evolu/nodejs` 3.1.0 bevat de relay plus losse bouwstenen (de better-sqlite3-driver, een BroadcastChannel,
|
||||||
|
een klok, `runMain`), maar géén cliënt: er is geen `createEvoluDeps` voor Node zoals `@evolu/web` die voor
|
||||||
|
de browser heeft. `createEvolu` vraagt `EvoluPlatformDeps`, en die zet `tools/relay-client/src/evolu-node.js`
|
||||||
|
zelf in elkaar uit de in-memory bouwstenen van `@evolu/common`.
|
||||||
|
|
||||||
|
Alles draait in één proces, dus de database-worker en de gedeelde worker zijn de in-memory varianten. Dat is
|
||||||
|
geen omweg: de documentatie van Evolu noemt ze de terugvaloptie voor platforms zonder echte workers, en er
|
||||||
|
valt hier niets te winnen met een tweede draad.
|
||||||
|
|
||||||
|
Vier dingen die dat kostte, alle vier stil falend en geen van alle uit de typen af te lezen:
|
||||||
|
|
||||||
|
1. **`installPolyfills()` uit `@evolu/common/polyfills` moet aangeroepen worden.** Evolu gebruikt
|
||||||
|
`Map.prototype.getOrInsertComputed`, en dat heeft Node 24 niet. Zonder de aanroep valt de eerste
|
||||||
|
schrijfactie om met "getOrInsertComputed is not a function". Hun eigen documentatie noemt Node 24
|
||||||
|
letterlijk als reden dat die polyfill bestaat;
|
||||||
|
2. **`createEvoluDeps` is geen optionele verpakking.** Dáár wordt de gedeelde worker aan de database-worker
|
||||||
|
geknoopt en de "tab leader" aangekondigd. Wie zijn `EvoluPlatformDeps` rechtstreeks aan de run geeft,
|
||||||
|
krijgt een instantie die het lijkt te doen tot de eerste schrijfactie afbreekt met "Expected tab leader
|
||||||
|
port.";
|
||||||
|
3. **de AsyncDisposableStack die `initSharedWorker` teruggeeft moet iemand bezitten.** Laat je hem vallen,
|
||||||
|
dan wordt hij pas opgeruimd als de run eronder al weg is, en elk afsluiten eindigt met een
|
||||||
|
`SuppressedError` waarvan de oorzaak ("Cannot use a disposed object") nergens te zien is;
|
||||||
|
4. **een defect in een worker komt standaard uit een microtaak naar buiten** en verdwijnt dus. Beide runs
|
||||||
|
krijgen daarom een eigen `reportDefect`. Voor een cliënt die bestaat om fouten zichtbaar te maken is dat
|
||||||
|
geen luxe.
|
||||||
|
|
||||||
|
Punt 4 is bovendien de reden dat de andere drie te vinden waren. Zonder eigen `reportDefect` zegt dit
|
||||||
|
pakket bij elke fout hetzelfde: "An error was suppressed during disposal", zonder oorzaak.
|
||||||
|
|
||||||
|
**Gevolg voor dit plan:** de samenstelling is een eigen bestand met eigen commentaar, en het is het stuk dat
|
||||||
|
bij een versieverhoging van Evolu als eerste omvalt. De toets die dat moet vangen staat in §7.
|
||||||
|
|
||||||
|
## 5. Het werk in grote lijnen
|
||||||
|
|
||||||
|
1. **De cliënt-API van `@evolu/common` 8.7.0 verifiëren.** Een eigenaar uit een mnemonic, een schema met een
|
||||||
|
kolom waar een blob in past, en verbinden met een `syncUrl`. Dit staat expres eerst: van de relaykant is
|
||||||
|
alles nagetrokken, van de cliëntkant niets. Valt die API tegen, dan verandert de rest van dit plan.
|
||||||
|
2. Een kale opdrachtregel die één eigenaar maakt, verbindt en schrijft. Dat is het moment waarop bekend is
|
||||||
|
of het werkt.
|
||||||
|
3. Meerdere eigenaars naast elkaar, elk in een eigen map, met een register erbij.
|
||||||
|
4. De UI eromheen: de lijst, de knoppen per eigenaar, en de meldingen uit de verbinding.
|
||||||
|
5. De proeven uit §4c lopen, en de uitkomsten terugschrijven naar de plek waar de bewering nu staat: §10 van
|
||||||
|
het naslagdocument en open punt 10 van **Umbrelapp**.
|
||||||
|
|
||||||
|
## 6. Open punten
|
||||||
|
|
||||||
|
1. **Hoe zichtbaar is een 401 in de cliënt?** Evolu probeert opnieuw en is ontworpen om een relay te
|
||||||
|
overleven die er niet is. Als een weigering en een relay die plat ligt in de cliënt hetzelfde signaal
|
||||||
|
geven, is de eerste proef uit §4c niet af te lezen. De uitweg blijft binnen dit gereedschap: een eigen
|
||||||
|
WebSocket-poging naast Evolu, met dezelfde `OwnerId` in de URL, die de statuscode wel te zien krijgt.
|
||||||
|
Dat is een handeling van een paar regels, want de `OwnerId` staat in de URL van de verbinding (§10 van
|
||||||
|
het naslagdocument).
|
||||||
|
2. **Wat is een zinnige blob om mee te experimenteren?** Willekeurige bytes zijn genoeg voor de bytegrens,
|
||||||
|
maar niet om te zien of gegevens werkelijk heen en weer gaan. Iets met leesbare inhoud en een teller is
|
||||||
|
waarschijnlijk beter.
|
||||||
|
3. **Wordt de gegevensmap van de proef opgeruimd?** Twintig eigenaars die blijven staan is rommel, en het
|
||||||
|
is sleutelmateriaal. Waarschijnlijk een opdracht die er één weggooit en één die alles weggooit.
|
||||||
|
4. **Data per eigenaar wissen op de relay staat al open bij Umbrelapp.** Dit gereedschap maakt dat makkelijk
|
||||||
|
te beproeven maar lost het niet op; het blijft schrijven in andermans schema (§8 van het naslagdocument).
|
||||||
|
|
||||||
|
## 7. Verificatie
|
||||||
|
|
||||||
|
**Wat automatisch kan is klein en dat is eerlijk om vooraf te zeggen.** Het meeste van dit gereedschap is
|
||||||
|
netwerk en UI. Wat wél in de suite hoort:
|
||||||
|
|
||||||
|
- het register van eigenaars: aanmaken, teruglezen, een naam die al bestaat, een map die half is;
|
||||||
|
- het afleiden van een `OwnerId` uit een mnemonic, met een vaste mnemonic en een vaste verwachte uitkomst.
|
||||||
|
Dat is de toets die omvalt als de bibliotheek onder ons iets anders gaat doen;
|
||||||
|
- het lezen van de opdrachtregel.
|
||||||
|
|
||||||
|
Alles wat een relay nodig heeft is handwerk, en dat is precies het doel van dit plan: de proeven uit §4c
|
||||||
|
worden met de hand gedaan en de uitkomst wordt opgeschreven. Een groene suite zegt hier dus weinig, en de
|
||||||
|
uitslag van dit plan staat in de documenten die het corrigeert.
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Voortgang - Testclient
|
||||||
|
|
||||||
|
## 09-09-2026 - plan geschreven, gepromoveerd, en de samenstelling werkt
|
||||||
|
|
||||||
|
Het masterplan is geschreven en dezelfde dag gepromoveerd naar `Actief/003-Testclient/`, op verzoek van de
|
||||||
|
gebruiker. Bij het schrijven ging §4b eerst over de verkeerde vraag: "alles vanuit één scherm" werd gelezen
|
||||||
|
als een scherm over cliënt én relay. De server-app blijft ongemoeid en synchroniseert met Trezor Suite op
|
||||||
|
de Mac, dus dat is herschreven naar wat het scherm zelf doet.
|
||||||
|
|
||||||
|
Fase 1 is af, en dat was de stap waarvan het plan zei dat hij eerst moest. **`@evolu/nodejs` bevat geen
|
||||||
|
cliënt**, alleen de relay plus losse bouwstenen; `createEvolu` krijgt zijn afhankelijkheden nu uit
|
||||||
|
`tools/relay-client/src/evolu-node.js`, samengesteld uit de in-memory workers van `@evolu/common`. Vier
|
||||||
|
dingen zaten in de weg, alle vier stil falend: de ontbrekende `installPolyfills()` (Node 24 mist
|
||||||
|
`Map.getOrInsertComputed`), `createEvoluDeps` overslaan ("Expected tab leader port."), de
|
||||||
|
AsyncDisposableStack van `initSharedWorker` laten vallen, en workers zonder eigen `reportDefect`. Dat
|
||||||
|
laatste is waarom de andere drie te vinden waren; zonder dat zegt elke fout hetzelfde en noemt geen oorzaak.
|
||||||
|
|
||||||
|
Bewezen met een wegwerpproef: eigenaar aanmaken, `transports: []`, een rij met een `Uint8Array`-kolom
|
||||||
|
wegschrijven en teruglezen. De bytes komen er ongeschonden uit en het proces sluit schoon af.
|
||||||
|
|
||||||
|
**Nog niet geprobeerd: praten met de relay.** Daar is het adres van de Umbrel voor nodig en dat komt van de
|
||||||
|
gebruiker; het gaat niet in deze publieke repo.
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Taken - Testclient
|
||||||
|
|
||||||
|
> Prioriteit: **A** | Afhankelijk van: –
|
||||||
|
>
|
||||||
|
> **Nieuw en meteen actief op 09-09-2026.** Het masterplan werd dezelfde dag geschreven en op verzoek van
|
||||||
|
> de gebruiker direct gepromoveerd. Nummer 003, onder de bestaande 005: tier A was leeg en dit is nu de
|
||||||
|
> lopende focus, dus de bovenkant van de reeks klopt weer.
|
||||||
|
>
|
||||||
|
> **Waarom dit plan er is:** vier beweringen over Evolu Relay rusten op redenering uit de broncode in
|
||||||
|
> plaats van op een proef, waaronder dat blokkeren pas bij de volgende verbinding werkt. Zonder een cliënt
|
||||||
|
> die geen hardware nodig heeft, blijft dat zo.
|
||||||
|
|
||||||
|
## Volgende stap
|
||||||
|
|
||||||
|
- [ ] **De cliënt met de relay laten praten.** De samenstelling werkt lokaal; wat nog niet geprobeerd is,
|
||||||
|
is `transports` met `createOwnerWebSocketTransport` erin. **Hiervoor is het adres van de relay
|
||||||
|
nodig**, en dat komt van de gebruiker en gaat niet in de repo; zie [OPEN.md](OPEN.md) punt 6
|
||||||
|
|
||||||
|
## Fase 1 - de samenstelling (af)
|
||||||
|
|
||||||
|
- [x] **`@evolu/common` 8.7.0 en `@evolu/nodejs` 3.1.0 geïnstalleerd in `tools/relay-client/`
|
||||||
|
(09-09-2026)**
|
||||||
|
- [x] **Vastgesteld dat `@evolu/nodejs` géén cliënt bevat**, alleen de relay plus losse bouwstenen. De
|
||||||
|
afhankelijkheden voor `createEvolu` worden daarom zelf samengesteld in `src/evolu-node.js`; zie
|
||||||
|
[PLAN.md](PLAN.md) §4e voor de vier valstrikken die dat kostte
|
||||||
|
- [x] **Bewezen dat het werkt zonder relay (09-09-2026):** een eigenaar aanmaken, een Evolu-instantie met
|
||||||
|
`transports: []`, een rij met een `Uint8Array`-kolom wegschrijven en teruglezen. De bytes komen er
|
||||||
|
ongeschonden uit en het proces sluit schoon af, zonder defect en zonder te blijven hangen
|
||||||
|
|
||||||
|
## Fase 2 - de opdrachtregel
|
||||||
|
|
||||||
|
- [ ] Een eigenaar aanmaken en bewaren: mnemonic plus database in een eigen map, in een pad dat
|
||||||
|
gitignored is. Het `OwnerId` wordt afgedrukt, de mnemonic niet
|
||||||
|
- [ ] Een register van eigenaars: aanmaken, opsommen, teruglezen, weggooien
|
||||||
|
- [ ] Verbinden met de relay via `createOwnerWebSocketTransport`, met het adres uit een instelling
|
||||||
|
- [ ] Een blob schrijven met een instelbare omvang, en teruglezen
|
||||||
|
- [ ] De losse WebSocket-poging die de HTTP-statuscode wél te zien krijgt; zie [OPEN.md](OPEN.md) punt 1
|
||||||
|
|
||||||
|
## Fase 3 - het bedieningsvlak
|
||||||
|
|
||||||
|
- [ ] De HTTP-server met de pagina, in de stijl van de twee statuspagina's
|
||||||
|
- [ ] De lijst met eigenaars, met per eigenaar de toestand en de knoppen uit [PLAN.md](PLAN.md) §4b
|
||||||
|
- [ ] Het log, met tijdstip per gebeurtenis
|
||||||
|
|
||||||
|
## Fase 4 - de proeven
|
||||||
|
|
||||||
|
De zes proeven staan in [PLAN.md](PLAN.md) §4c. Ze zijn handwerk en hebben een draaiende relay nodig, dus
|
||||||
|
ze komen na fase 2 en kunnen deels vóór fase 3.
|
||||||
|
|
||||||
|
- [ ] Onbekende eigenaar met het venster dicht: 401, en een regel in de weigerlijst
|
||||||
|
- [ ] Toelaten en opnieuw verbinden: komt de hele geschiedenis alsnog binnen
|
||||||
|
- [ ] Blokkeren tijdens een lopende verbinding: blijft hij schrijven tot de volgende verbinding
|
||||||
|
- [ ] Het venster van twee minuten met de pagina dicht
|
||||||
|
- [ ] Eén blob boven `RELAY_MAX_WRITE_BYTES`, en meerdere kleinere die samen groter zijn
|
||||||
|
- [ ] Twee eigenaars tegelijk verbonden
|
||||||
|
- [ ] **De uitkomsten terugschrijven** naar §10 van
|
||||||
|
[Upstream-evolu-relay.md](../../../Referenties/Upstream-evolu-relay.md) en naar open punt 10 van het
|
||||||
|
plan **Umbrelapp**. Dit is de opbrengst van het hele plan; zonder deze stap blijft het gereedschap
|
||||||
|
zonder resultaat
|
||||||
|
|
||||||
|
## Fase 5 - de toetsen
|
||||||
|
|
||||||
|
Wat automatisch kan is klein, en [PLAN.md](PLAN.md) §7 zegt waarom.
|
||||||
|
|
||||||
|
- [ ] Het register: aanmaken, teruglezen, een naam die al bestaat, een half aangelegde map
|
||||||
|
- [ ] Een vaste mnemonic met een vast verwacht `OwnerId`. Dit is de toets die omvalt als Evolu onder ons
|
||||||
|
iets anders gaat doen
|
||||||
|
- [ ] Het lezen van de opdrachtregel
|
||||||
|
- [ ] De nieuwe toetsen mutatie-testen, zoals `HomeGit/CLAUDE.md` voorschrijft
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
// Toetst de testcliënt zonder relay: tools/relay-client/src/client.js
|
||||||
|
//
|
||||||
|
// Draaien: node tests/test_client_lokaal.mjs
|
||||||
|
//
|
||||||
|
// Anders dan de rest van deze map heeft deze toets wél afhankelijkheden nodig,
|
||||||
|
// namelijk die van tools/relay-client. Staat `node_modules` daar niet, dan slaat
|
||||||
|
// hij zichzelf over en meldt dat, zoals de certificaattoetsen doen als de
|
||||||
|
// certificaatwinkel van het besturingssysteem ontbreekt. Installeren gaat met
|
||||||
|
// `npm install` in tools/relay-client.
|
||||||
|
//
|
||||||
|
// Waarom dit bestand er is: `@evolu/nodejs` bevat geen cliënt, dus de
|
||||||
|
// afhankelijkheden voor `createEvolu` worden in src/evolu-node.js zelf
|
||||||
|
// samengesteld. Dat is de plek die bij een versieverhoging van Evolu als eerste
|
||||||
|
// omvalt, en hij faalt stil: alle vier de fouten die dat bestand tijdens de bouw
|
||||||
|
// opleverde gaven een instantie die het lijkt te doen, tot de eerste
|
||||||
|
// schrijfactie. **Deze toets doet dus een schrijfactie**, en aanmaken alleen is
|
||||||
|
// geen bewijs.
|
||||||
|
//
|
||||||
|
// Geen relay: `openStore` zonder `relayUrl`. Wat hier getoetst wordt is de
|
||||||
|
// cliënt, en die is los van het netwerk te beproeven. Het toegangsbeleid van de
|
||||||
|
// relay staat in test_limiter.mjs.
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
import { mkdtempSync, rmSync } from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
|
||||||
|
// Een wachthond, en die is hier niet voor de sier. Gaat er iets mis in de
|
||||||
|
// samenstelling, dan komt dat er meestal uit als een schrijfactie die nooit
|
||||||
|
// antwoordt in plaats van een fout: het defect zit in een worker en de
|
||||||
|
// `loadQuery` erna blijft hangen. Zonder deze wachthond blijft de suite dus
|
||||||
|
// staan in plaats van rood te worden, en dat is bij de mutatietoets van
|
||||||
|
// 09-09-2026 letterlijk gebeurd.
|
||||||
|
const WACHTHOND_SECONDEN = 30;
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log(`FOUT: niets meer gehoord na ${WACHTHOND_SECONDEN} seconden.`);
|
||||||
|
console.log(' Dat wijst op een defect in een worker; zie tools/relay-client/src/evolu-node.js.');
|
||||||
|
console.log('');
|
||||||
|
console.log('afgebroken');
|
||||||
|
process.exit(1);
|
||||||
|
}, WACHTHOND_SECONDEN * 1000).unref();
|
||||||
|
|
||||||
|
let goed = 0;
|
||||||
|
let fout = 0;
|
||||||
|
|
||||||
|
const toets = (omschrijving, voorwaarde) => {
|
||||||
|
if (voorwaarde) {
|
||||||
|
goed += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fout += 1;
|
||||||
|
console.log(`FOUT: ${omschrijving}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Zijn de afhankelijkheden er? ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
let client;
|
||||||
|
try {
|
||||||
|
client = await import('../tools/relay-client/src/client.js');
|
||||||
|
} catch (oorzaak) {
|
||||||
|
console.log('OVERGESLAGEN: de pakketten van tools/relay-client ontbreken.');
|
||||||
|
console.log(` Draai daar eerst \`npm install\`. (${oorzaak.message})`);
|
||||||
|
console.log('');
|
||||||
|
console.log('0 goed, 0 fout');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { eigenaarUitMnemonic, nieuweEigenaar, openStore } = client;
|
||||||
|
|
||||||
|
// ── Een mnemonic levert altijd hetzelfde OwnerId op ───────────────────────────
|
||||||
|
|
||||||
|
{
|
||||||
|
// Dit is de toets die omvalt als Evolu onder ons iets anders gaat doen met de
|
||||||
|
// afleiding van een eigenaar. Vaste mnemonic, vast antwoord. Het is geen
|
||||||
|
// geheim: deze eigenaar bestaat alleen in deze toets, en het is bovendien de
|
||||||
|
// bekendste testmnemonic die er is.
|
||||||
|
const MNEMONIC =
|
||||||
|
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
||||||
|
|
||||||
|
const eerste = eigenaarUitMnemonic(MNEMONIC);
|
||||||
|
const tweede = eigenaarUitMnemonic(MNEMONIC);
|
||||||
|
|
||||||
|
toets('dezelfde mnemonic geeft hetzelfde OwnerId', eerste.id === tweede.id);
|
||||||
|
toets('en een OwnerId is een niet-lege string', typeof eerste.id === 'string' && eerste.id.length > 0);
|
||||||
|
toets('de eigenaar houdt zijn mnemonic bij zich', eerste.mnemonic === MNEMONIC);
|
||||||
|
toets('een verse eigenaar krijgt een ander OwnerId', nieuweEigenaar().id !== eerste.id);
|
||||||
|
|
||||||
|
let geweigerd = false;
|
||||||
|
try {
|
||||||
|
eigenaarUitMnemonic('dit is geen mnemonic');
|
||||||
|
} catch {
|
||||||
|
geweigerd = true;
|
||||||
|
}
|
||||||
|
// Het niet-gelukkige pad, en dat is hier het belangrijkste: stil een andere
|
||||||
|
// eigenaar maken zou een proef opleveren die nergens over gaat.
|
||||||
|
toets('een onleesbare mnemonic wordt geweigerd', geweigerd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Schrijven en teruglezen, zonder relay ─────────────────────────────────────
|
||||||
|
|
||||||
|
const map = mkdtempSync(join(tmpdir(), 'relay-client-toets-'));
|
||||||
|
let store = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const defecten = [];
|
||||||
|
store = await openStore({
|
||||||
|
directory: map,
|
||||||
|
owner: nieuweEigenaar(),
|
||||||
|
onDefect: (defect) => {
|
||||||
|
defecten.push(defect);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
toets('zonder relayUrl is er geen transport', store.transportUrl === null);
|
||||||
|
|
||||||
|
const bytes = new Uint8Array([0, 1, 2, 253, 254, 255]);
|
||||||
|
const geschreven = store.schrijf('toets', bytes);
|
||||||
|
toets('een blob wegschrijven geeft een id terug', typeof geschreven?.id === 'string');
|
||||||
|
|
||||||
|
const rijen = await store.lees();
|
||||||
|
toets('en die blob komt er weer uit', rijen.length === 1);
|
||||||
|
toets('met hetzelfde label', rijen[0]?.label === 'toets');
|
||||||
|
toets(
|
||||||
|
'en met de bytes ongeschonden',
|
||||||
|
rijen[0]?.body?.length === bytes.length
|
||||||
|
&& [...(rijen[0]?.body ?? [])].every((waarde, plek) => waarde === bytes[plek]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const sluiten = store.sluit;
|
||||||
|
store = null;
|
||||||
|
await sluiten();
|
||||||
|
|
||||||
|
// Afsluiten hoort stil te zijn. Ging dat mis, dan bleef er een worker draaien
|
||||||
|
// die zijn eigen run al kwijt was, en dat merk je pas als een proces blijft
|
||||||
|
// hangen. Dat was de vierde valstrik uit PLAN.md §4e.
|
||||||
|
toets(`afsluiten levert geen defect op (${defecten.length} gezien)`, defecten.length === 0);
|
||||||
|
} finally {
|
||||||
|
if (store) await store.sluit();
|
||||||
|
rmSync(map, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
console.log(`${goed} goed, ${fout} fout`);
|
||||||
|
process.exit(fout === 0 ? 0 : 1);
|
||||||
Generated
+616
@@ -0,0 +1,616 @@
|
|||||||
|
{
|
||||||
|
"name": "whatsnext-relay-client",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "whatsnext-relay-client",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@evolu/common": "8.7.0",
|
||||||
|
"@evolu/nodejs": "3.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=24"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@evolu/common": {
|
||||||
|
"version": "8.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@evolu/common/-/common-8.7.0.tgz",
|
||||||
|
"integrity": "sha512-52bfvAJZaOmAAmiaOdkGn4MtYbIK1xf5sSb9fjzdRRAD6XbJtRT4yqlJ5MxBacsUHDtMXjUW02XgYlbUXyJ04A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/ciphers": "^2.3.0",
|
||||||
|
"@noble/hashes": "^2.3.0",
|
||||||
|
"@scure/bip39": "^2.3.0",
|
||||||
|
"@standard-schema/spec": "^1.1.0",
|
||||||
|
"kysely": "^0.29.5",
|
||||||
|
"random": "5.5.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=24.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@evolu/nodejs": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@evolu/nodejs/-/nodejs-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-xw1eYjsWNbQfJsDQ0pTe/2iYrxuzDG0cHMhObyCbHwICdJzXvEH2iVGzw4qPfxW4fikZNcUTsAHyzblW8tJkqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"better-sqlite3": "^12.11.1",
|
||||||
|
"ws": "^8.21.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=24.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@evolu/common": "^8.0.0",
|
||||||
|
"@evolu/oxlint-config": "^0.1.0",
|
||||||
|
"@evolu/typescript-config": "^0.1.0",
|
||||||
|
"oxlint": "^1.79.0",
|
||||||
|
"oxlint-tsgolint": "7.0.2001",
|
||||||
|
"typescript": ">=7.0.0",
|
||||||
|
"vite": ">=8.0.0",
|
||||||
|
"webpack": ">=5.108.4"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@evolu/oxlint-config": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@evolu/typescript-config": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"oxlint": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"oxlint-tsgolint": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vite": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"webpack": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@noble/ciphers": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-AnjFn0Jv92laAkvMrghlFZq4qQCIN/4DxFV/eooqtC2YTjB7kBeLMS2T9KJX4Dn+ZVXLOwK0lSgqDtx9gvxtiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@noble/hashes": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@scure/bip39": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-82dxFbZUYboyOf0AXiydsQrFQ5Q4h9mX+O2UkE91ROYmsc0BKMGZLwDmy96Jpa2+vrtoxomjUhy1RPIgH/r2nA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/hashes": "2.4.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@standard-schema/spec": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/base64-js": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/better-sqlite3": {
|
||||||
|
"version": "12.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz",
|
||||||
|
"integrity": "sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bindings": "^1.5.0",
|
||||||
|
"prebuild-install": "^7.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bindings": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"file-uri-to-path": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bl": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer": "^5.5.0",
|
||||||
|
"inherits": "^2.0.4",
|
||||||
|
"readable-stream": "^3.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/buffer": {
|
||||||
|
"version": "5.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||||
|
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.3.1",
|
||||||
|
"ieee754": "^1.1.13"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chownr": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/decompress-response": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mimic-response": "^3.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/deep-extend": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/detect-libc": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/end-of-stream": {
|
||||||
|
"version": "1.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||||
|
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"once": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/expand-template": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||||
|
"license": "(MIT OR WTFPL)",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/file-uri-to-path": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/fs-constants": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/github-from-package": {
|
||||||
|
"version": "0.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||||
|
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/ieee754": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/ini": {
|
||||||
|
"version": "1.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||||
|
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/kysely": {
|
||||||
|
"version": "0.29.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.29.5.tgz",
|
||||||
|
"integrity": "sha512-ooa+eSbBNPTo3MycPEuW5jdrxQdQwdtB3LC3h43FiXQbIry5tR0C5lDG7eealK0E4D7XjrnOP5DIUg/LyjRMYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mimic-response": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/minimist": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mkdirp-classic": {
|
||||||
|
"version": "0.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||||
|
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/napi-build-utils": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/node-abi": {
|
||||||
|
"version": "3.96.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz",
|
||||||
|
"integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"semver": "^7.3.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/once": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prebuild-install": {
|
||||||
|
"version": "7.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||||
|
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
||||||
|
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.0",
|
||||||
|
"expand-template": "^2.0.3",
|
||||||
|
"github-from-package": "0.0.0",
|
||||||
|
"minimist": "^1.2.3",
|
||||||
|
"mkdirp-classic": "^0.5.3",
|
||||||
|
"napi-build-utils": "^2.0.0",
|
||||||
|
"node-abi": "^3.3.0",
|
||||||
|
"pump": "^3.0.0",
|
||||||
|
"rc": "^1.2.7",
|
||||||
|
"simple-get": "^4.0.0",
|
||||||
|
"tar-fs": "^2.0.0",
|
||||||
|
"tunnel-agent": "^0.6.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"prebuild-install": "bin.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pump": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"end-of-stream": "^1.1.0",
|
||||||
|
"once": "^1.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/random": {
|
||||||
|
"version": "5.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/random/-/random-5.5.1.tgz",
|
||||||
|
"integrity": "sha512-2TIJKf/BGjy0FGeZaan2+a/vq37afbSyscquADMFC13KBjFmg+10T/TkACpP9GMHfbEruKYbHqq0rjCA9x8gNA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rc": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||||
|
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
||||||
|
"dependencies": {
|
||||||
|
"deep-extend": "^0.6.0",
|
||||||
|
"ini": "~1.3.0",
|
||||||
|
"minimist": "^1.2.0",
|
||||||
|
"strip-json-comments": "~2.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"rc": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/readable-stream": {
|
||||||
|
"version": "3.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
|
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/safe-buffer": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/semver": {
|
||||||
|
"version": "7.8.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||||
|
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/simple-concat": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/simple-get": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"decompress-response": "^6.0.0",
|
||||||
|
"once": "^1.3.1",
|
||||||
|
"simple-concat": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/string_decoder": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "~5.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strip-json-comments": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tar-fs": {
|
||||||
|
"version": "2.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz",
|
||||||
|
"integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chownr": "^1.1.1",
|
||||||
|
"mkdirp-classic": "^0.5.2",
|
||||||
|
"pump": "^3.0.0",
|
||||||
|
"tar-stream": "^2.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tar-stream": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bl": "^4.0.3",
|
||||||
|
"end-of-stream": "^1.4.1",
|
||||||
|
"fs-constants": "^1.0.0",
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"readable-stream": "^3.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tunnel-agent": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/util-deprecate": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/wrappy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.21.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
|
||||||
|
"integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "whatsnext-relay-client",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"description": "Testcliënt voor Evolu Relay: meerdere eigenaars, elk met eigen sleutels, om het toegangsbeleid en de opslag te beproeven.",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=24"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "node src/cli.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@evolu/common": "8.7.0",
|
||||||
|
"@evolu/nodejs": "3.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
// De testcliënt zelf: eigenaars aanmaken en blobs door de relay duwen.
|
||||||
|
//
|
||||||
|
// Dit is de publieke kant van dit gereedschap. De opdrachtregel en straks de
|
||||||
|
// pagina praten hiermee en niet rechtstreeks met Evolu; `evolu-node.js` ernaast
|
||||||
|
// doet alleen de platformafhankelijkheden en heeft geen weet van eigenaars.
|
||||||
|
//
|
||||||
|
// Eén eigenaar is één mnemonic plus één database. Evolu leidt de bestandsnaam af
|
||||||
|
// uit `appName` en de eigenaar, dus alle eigenaars kunnen in dezelfde map staan
|
||||||
|
// zonder elkaar te raken; wat ze deelt is het proces, en dat is precies wat de
|
||||||
|
// gedeelde worker aankan (zie evolu-node.js).
|
||||||
|
//
|
||||||
|
// Wat hier NIET in hoort: iets dat de gegevens van Trezor Suite probeert te
|
||||||
|
// lezen. Deze cliënt maakt eigen sleutels en leest zijn eigen gegevens; zie de
|
||||||
|
// niet-doelen in het plan Testclient.
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
import {
|
||||||
|
AppName,
|
||||||
|
createAppOwner,
|
||||||
|
createEvolu,
|
||||||
|
createOwnerSecret,
|
||||||
|
createOwnerWebSocketTransport,
|
||||||
|
createQueryBuilder,
|
||||||
|
createRandomBytes,
|
||||||
|
createRun,
|
||||||
|
id,
|
||||||
|
mnemonicToOwnerSecret,
|
||||||
|
NonEmptyTrimmedString100,
|
||||||
|
Uint8Array as Uint8ArrayType,
|
||||||
|
} from '@evolu/common';
|
||||||
|
import { installPolyfills } from '@evolu/common/polyfills';
|
||||||
|
|
||||||
|
import { createNodeEvoluDeps } from './evolu-node.js';
|
||||||
|
|
||||||
|
// Node 24 mist `Map.prototype.getOrInsertComputed`, dat Evolu wel gebruikt.
|
||||||
|
// Zonder deze aanroep valt niet het aanmaken om maar de eerste schrijfactie, en
|
||||||
|
// dat is precies het soort fout dat je pas op het verkeerde moment vindt. Hier en
|
||||||
|
// niet in het startbestand, zodat elke ingang van dit gereedschap hem meekrijgt.
|
||||||
|
installPolyfills();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* De naam waaronder deze cliënt zijn databases wegzet.
|
||||||
|
*
|
||||||
|
* Onderdeel van de bestandsnaam, dus veranderen betekent dat elke eigenaar zijn
|
||||||
|
* lokale gegevens kwijt is. Dat maakt op de relay niets uit (die kent alleen het
|
||||||
|
* `OwnerId`), maar de geschiedenis moet dan opnieuw opgehaald worden.
|
||||||
|
*/
|
||||||
|
export const APP_NAME = AppName.orThrow('WhatsnextRelayClient');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Het schema: één tabel met een label en een blob.
|
||||||
|
*
|
||||||
|
* Bewust klein. Wat hier beproefd wordt is de relay, niet een gegevensmodel. Het
|
||||||
|
* label is er om in een uitdraai te kunnen zien wélke rij je terugkrijgt, en de
|
||||||
|
* blob is waar de omvang in zit die de bytegrens van de relay moet raken.
|
||||||
|
*/
|
||||||
|
export const Schema = {
|
||||||
|
blob: {
|
||||||
|
id: id('Blob'),
|
||||||
|
label: NonEmptyTrimmedString100,
|
||||||
|
body: Uint8ArrayType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const allesQuery = createQueryBuilder(Schema)((db) =>
|
||||||
|
db.selectFrom('blob').selectAll().orderBy('createdAt'),
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Maakt een nieuwe eigenaar met verse sleutels. */
|
||||||
|
export const nieuweEigenaar = () =>
|
||||||
|
createAppOwner(createOwnerSecret({ randomBytes: createRandomBytes() }));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Herstelt een eigenaar uit zijn mnemonic.
|
||||||
|
*
|
||||||
|
* Dit is de enige weg terug: de mnemonic is wat bewaard wordt, het `OwnerId` is
|
||||||
|
* er een afgeleide van. Een onleesbare mnemonic gooit, en dat hoort: stil een
|
||||||
|
* andere eigenaar maken zou een proef opleveren die nergens over gaat.
|
||||||
|
*/
|
||||||
|
export const eigenaarUitMnemonic = (mnemonic) => createAppOwner(mnemonicToOwnerSecret(mnemonic));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opent de opslag van één eigenaar.
|
||||||
|
*
|
||||||
|
* `relayUrl` weglaten of leeg laten geeft een instantie zonder synchronisatie.
|
||||||
|
* Dat is niet alleen voor toetsen nuttig: het is ook hoe je een eigenaar met
|
||||||
|
* gegevens vult vóórdat je hem voor het eerst laat verbinden, en dat is wat de
|
||||||
|
* proef "toelaten en opnieuw verbinden" nodig heeft.
|
||||||
|
*
|
||||||
|
* @param {{directory: string, owner: object, relayUrl?: string, onDefect?: Function, consoleLevel?: string}} config
|
||||||
|
*/
|
||||||
|
export const openStore = async ({ directory, owner, relayUrl, onDefect, consoleLevel = 'error' }) => {
|
||||||
|
const { deps, dispose } = createNodeEvoluDeps({ directory, consoleLevel, onDefect });
|
||||||
|
const run = createRun(deps);
|
||||||
|
|
||||||
|
// Het OwnerId gaat mee in de URL, en dat is wat de relay bij de
|
||||||
|
// WebSocket-upgrade leest om te beslissen of deze eigenaar erin mag. Zonder dat
|
||||||
|
// is er niets te beslissen en is de hele allowlist niet te beproeven; zie §10
|
||||||
|
// van Docs/Referenties/Upstream-evolu-relay.md.
|
||||||
|
const transports = relayUrl
|
||||||
|
? [createOwnerWebSocketTransport({ url: relayUrl, ownerId: owner.id })]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const uitkomst = await run(
|
||||||
|
createEvolu(Schema, { appName: APP_NAME, appOwner: owner, transports }),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!uitkomst.ok) {
|
||||||
|
await run[Symbol.asyncDispose]();
|
||||||
|
await dispose();
|
||||||
|
throw new Error(`Evolu wilde niet starten: ${JSON.stringify(uitkomst.error)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const evolu = uitkomst.value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
ownerId: owner.id,
|
||||||
|
/** De URL waarmee verbonden wordt, of null bij een instantie zonder sync. */
|
||||||
|
transportUrl: transports[0]?.url ?? null,
|
||||||
|
|
||||||
|
/** Schrijft een blob weg. Geeft het id van de nieuwe rij terug. */
|
||||||
|
schrijf: (label, body) => evolu.insert('blob', { label, body }),
|
||||||
|
|
||||||
|
/** Alle blobs van deze eigenaar, oudste eerst. */
|
||||||
|
lees: () => evolu.loadQuery(allesQuery),
|
||||||
|
|
||||||
|
/** Voor wie meer wil dan schrijven en lezen. */
|
||||||
|
evolu,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sluit af. Eerst de run, dan de afhankelijkheden: de run bezit de instantie
|
||||||
|
* en die gebruikt de workers die in de afhankelijkheden zitten. Andersom
|
||||||
|
* eindigt elk afsluiten met een SuppressedError zonder oorzaak.
|
||||||
|
*/
|
||||||
|
sluit: async () => {
|
||||||
|
await run[Symbol.asyncDispose]();
|
||||||
|
await dispose();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
// Een Evolu-cliënt op Node in elkaar zetten.
|
||||||
|
//
|
||||||
|
// Dit is het stuk waarvan het plan zei dat het eerst geverifieerd moest worden,
|
||||||
|
// en het antwoord was niet wat we hoopten: **`@evolu/nodejs` bevat geen cliënt.**
|
||||||
|
// Het pakket levert de relay plus losse bouwstenen (de better-sqlite3-driver, een
|
||||||
|
// BroadcastChannel, een klok), maar er is geen `createEvoluDeps` voor Node zoals
|
||||||
|
// `@evolu/web` die voor de browser heeft. Wie op Node een cliënt wil, zet de
|
||||||
|
// afhankelijkheden zelf in elkaar, en dat is wat hier gebeurt.
|
||||||
|
//
|
||||||
|
// Nagetrokken op 09-09-2026 in @evolu/common 8.7.0 en @evolu/nodejs 3.1.0. Wat
|
||||||
|
// `createEvolu` verlangt is `EvoluPlatformDeps`, en dat is de som van zes dingen:
|
||||||
|
//
|
||||||
|
// createDbWorker de database-worker
|
||||||
|
// sharedWorker de gedeelde worker, die de synchronisatie doet
|
||||||
|
// createBroadcastChannel, createMessageChannel, lockManager, reloadApp
|
||||||
|
//
|
||||||
|
// Alles draait hier in één proces. De "worker" en de "shared worker" zijn dus de
|
||||||
|
// in-memory varianten uit `@evolu/common`, en dat is precies waar die voor
|
||||||
|
// bestaan: hun eigen documentatie noemt ze de terugvaloptie voor platforms zonder
|
||||||
|
// echte workers. Voor een testcliënt is dat geen concessie, want er is niets te
|
||||||
|
// winnen met een tweede draad.
|
||||||
|
//
|
||||||
|
// `lockManager` komt van `navigator.locks`. Dat is er sinds Node 24, en het is de
|
||||||
|
// reden dat `package.json` die ondergrens stelt; de documentatie van Evolu noemt
|
||||||
|
// "Web and Node.js 24+" bij dit onderdeel met zoveel woorden.
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
import { join } from 'node:path';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createConsole,
|
||||||
|
createConsoleStoreOutput,
|
||||||
|
createMessageChannel,
|
||||||
|
createMessagePort,
|
||||||
|
createRun,
|
||||||
|
createSharedWorker,
|
||||||
|
createWebSocket,
|
||||||
|
createWorker,
|
||||||
|
} from '@evolu/common';
|
||||||
|
import { createEvoluDeps, initSharedWorker, startDbWorker } from '@evolu/common/local-first';
|
||||||
|
import { createBetterSqliteDriver, createBroadcastChannel } from '@evolu/nodejs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* De SQLite-driver, maar met alle bestanden in één map.
|
||||||
|
*
|
||||||
|
* `createBetterSqliteDriver` maakt `${name}.db` in de werkmap, waarbij `name` de
|
||||||
|
* instantienaam is die Evolu uit `appName` en de eigenaar afleidt. Elke eigenaar
|
||||||
|
* krijgt dus al zijn eigen bestand; wat ontbreekt is een map om ze in te zetten.
|
||||||
|
* Dit voorvoegsel doet dat zonder `process.chdir`, want dat is procesbreed en
|
||||||
|
* deze cliënt draait meerdere eigenaars naast elkaar.
|
||||||
|
*/
|
||||||
|
const createSqliteDriverIn = (directory) => (name, options) =>
|
||||||
|
createBetterSqliteDriver(join(directory, name), options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zet de afhankelijkheden neer die `createEvolu` op Node nodig heeft.
|
||||||
|
*
|
||||||
|
* Geeft naast de afhankelijkheden een `dispose` terug. Die hoort aangeroepen te
|
||||||
|
* worden bij het afsluiten: de runs die hieronder gemaakt worden bezitten de
|
||||||
|
* workers, en een worker die blijft staan houdt het proces open.
|
||||||
|
*
|
||||||
|
* @param {{directory: string, consoleLevel?: string}} config
|
||||||
|
*/
|
||||||
|
export const createNodeEvoluDeps = ({ directory, consoleLevel = 'warn', onDefect }) => {
|
||||||
|
const console = createConsole({ level: consoleLevel });
|
||||||
|
const consoleStoreOutput = createConsoleStoreOutput();
|
||||||
|
|
||||||
|
// De basis die zowel de database-worker als de gedeelde worker verlangt.
|
||||||
|
// `consoleStoreOutputEntry` is hoe een worker zijn logregels naar buiten geeft;
|
||||||
|
// wij lezen dat niet uit, maar de afhankelijkheid moet er zijn.
|
||||||
|
const workerDeps = {
|
||||||
|
console,
|
||||||
|
consoleStoreOutputEntry: consoleStoreOutput.entry,
|
||||||
|
createMessagePort,
|
||||||
|
};
|
||||||
|
|
||||||
|
const lockManager = globalThis.navigator?.locks;
|
||||||
|
if (!lockManager) {
|
||||||
|
throw new Error(
|
||||||
|
'navigator.locks ontbreekt. Dit vraagt Node 24 of hoger; zie de kop van dit bestand.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Een defect in een worker is standaard een uitzondering uit een microtaak, en
|
||||||
|
// die komt dus nergens terecht waar je hem kunt lezen: het proces valt om met
|
||||||
|
// "An error was suppressed during disposal" en zonder oorzaak. Deze cliënt
|
||||||
|
// bestaat om fouten zichtbaar te maken, dus dat wordt hier omgeleid.
|
||||||
|
const reportDefect = (defect) => {
|
||||||
|
if (onDefect) {
|
||||||
|
onDefect(defect);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.error('[evolu] defect', defect);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Twee runs, want de twee workers hebben verschillende afhankelijkheden. Ze
|
||||||
|
// worden hier vastgehouden zodat `dispose` ze allebei kan opruimen.
|
||||||
|
const dbRun = createRun({
|
||||||
|
...workerDeps,
|
||||||
|
createBroadcastChannel,
|
||||||
|
lockManager,
|
||||||
|
createSqliteDriver: createSqliteDriverIn(directory),
|
||||||
|
reportDefect,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sharedRun = createRun({
|
||||||
|
...workerDeps,
|
||||||
|
createBroadcastChannel,
|
||||||
|
createMessageChannel,
|
||||||
|
createWebSocket,
|
||||||
|
lockManager,
|
||||||
|
reportDefect,
|
||||||
|
});
|
||||||
|
|
||||||
|
const createDbWorker = () =>
|
||||||
|
createWorker((self) => {
|
||||||
|
void dbRun(startDbWorker(self));
|
||||||
|
});
|
||||||
|
|
||||||
|
// `initSharedWorker` geeft een AsyncDisposableStack terug, en die moet iemand
|
||||||
|
// bezitten. Laat je hem vallen, dan wordt hij pas opgeruimd als de run eronder
|
||||||
|
// al weg is, en dat is precies de "Cannot use a disposed object" die deze
|
||||||
|
// cliënt bij elk afsluiten liet zien.
|
||||||
|
let sharedWorkerResources = null;
|
||||||
|
const sharedWorker = createSharedWorker((self) => {
|
||||||
|
void Promise.resolve(sharedRun(initSharedWorker(self))).then((result) => {
|
||||||
|
if (result.ok) sharedWorkerResources = result.value;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// En dan door `createEvoluDeps` heen, want dat is geen optionele verpakking.
|
||||||
|
// Dáár wordt de gedeelde worker aan de database-worker geknoopt en wordt de
|
||||||
|
// "tab leader" aangekondigd; zonder die stap breekt de gedeelde worker af met
|
||||||
|
// "Expected tab leader port." zodra de eerste schrijfactie binnenkomt. Dat is
|
||||||
|
// de tweede valstrik van vandaag: de typen laten `EvoluPlatformDeps` toe waar
|
||||||
|
// `EvoluDeps` hoort, dus het compileert en het faalt pas als je iets doet.
|
||||||
|
//
|
||||||
|
// Eén keer aanroepen en hergebruiken voor alle eigenaars, zoals de
|
||||||
|
// documentatie voorschrijft. Dat is ook precies goed voor deze cliënt: in een
|
||||||
|
// browser bedient één gedeelde worker alle instanties van alle tabbladen, en
|
||||||
|
// hier bedient hij alle eigenaars in dit proces.
|
||||||
|
const evoluDeps = createEvoluDeps({
|
||||||
|
console,
|
||||||
|
createDbWorker,
|
||||||
|
createBroadcastChannel,
|
||||||
|
createMessageChannel,
|
||||||
|
lockManager,
|
||||||
|
// Een cliënt zonder venster heeft niets te herladen. Evolu roept dit aan als
|
||||||
|
// hij de app wil laten herstarten; hier is dat een logregel waard en verder
|
||||||
|
// niets, want het proces herstarten zou de proef die loopt weggooien.
|
||||||
|
reloadApp: () => {
|
||||||
|
console.warn('[evolu] reloadApp aangeroepen, genegeerd in deze cliënt');
|
||||||
|
},
|
||||||
|
sharedWorker,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
deps: evoluDeps,
|
||||||
|
/**
|
||||||
|
* Ruimt de workers op. Asynchroon, en dat moet.
|
||||||
|
*
|
||||||
|
* Het afsluiten van een worker is zelf een taak op de run die hem draagt.
|
||||||
|
* Wordt die run meteen weggegooid, dan loopt dat afsluiten tegen "Cannot use
|
||||||
|
* a disposed object" aan, en dat komt naar buiten als een SuppressedError
|
||||||
|
* zonder oorzaak. De volgorde is dus: eerst de afhankelijkheden, dan de runs
|
||||||
|
* afwachten in plaats van weggooien.
|
||||||
|
*/
|
||||||
|
dispose: async () => {
|
||||||
|
evoluDeps[Symbol.dispose]();
|
||||||
|
// De in-memory workers bezorgen hun berichten asynchroon, dus op dit punt
|
||||||
|
// staat er nog post in de wacht die bij aankomst zijn eigen run gebruikt.
|
||||||
|
// Even doorlaten voordat die runs weggaan.
|
||||||
|
await new Promise((klaar) => {
|
||||||
|
setImmediate(klaar);
|
||||||
|
});
|
||||||
|
if (sharedWorkerResources) await sharedWorkerResources[Symbol.asyncDispose]();
|
||||||
|
await sharedRun[Symbol.asyncDispose]();
|
||||||
|
await dbRun[Symbol.asyncDispose]();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user