From 29c24a2367d7539020e2186f71eb7144e9aa921b Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 4 Oct 2026 02:48:10 +0200 Subject: [PATCH] The TypeScript SDK's state reaches the runtime, a narrowed watch only its keys (novox/hq ADR 0201) --- node-tools/internal/runtime/state_test.go | 41 ++++++++++++++++++++ node-tools/test/fixtures/state-sdk.mjs | 13 +++++++ node-tools/test/fixtures/state-sdk.serve.mjs | 5 +++ 3 files changed, 59 insertions(+) create mode 100644 node-tools/test/fixtures/state-sdk.mjs create mode 100755 node-tools/test/fixtures/state-sdk.serve.mjs diff --git a/node-tools/internal/runtime/state_test.go b/node-tools/internal/runtime/state_test.go index f89bb71..d0f258d 100644 --- a/node-tools/internal/runtime/state_test.go +++ b/node-tools/internal/runtime/state_test.go @@ -135,3 +135,44 @@ func waitFor(t *testing.T, path, line string) { return errorf("%s has no %q yet: %s", filepath.Base(path), line, strings.Join(read(t, path), " | ")) }) } + +// The TypeScript SDK against the runtime (ADR 0201): two watches of one state, each handed only its +// own changes, the current values handled before the module's import goes on. +func TestTheSDKsStateReachesTheRuntime(t *testing.T) { + mesh := mt.New(t) + kv := mesh.Bucket(t, "sdkkeeper_servers") + if _, err := kv.Put("all.one", []byte(`{"url":"http://one"}`)); err != nil { + t.Fatal(err) + } + m := mt.MembershipOf("sdkkeeper", "anchor", false, nil) + m.State = []bus.StateIssued{{Name: "servers", Bucket: "sdkkeeper_servers", Writes: true}} + mesh.Issue(t, m) + nodeTools := connect(t, "node-tools", "anchor") + asker := connect(t, "console", "workstation") + log := filepath.Join(t.TempDir(), "sdk.log") + said := &lines{} + stop, err := Run(nodeTools, []Served{{Module: "sdkkeeper", Entrypoints: []string{mt.Fixture("state-sdk.serve.mjs")}}}, + map[string]map[string]string{"sdkkeeper": {"STATE_LOG": log}}, said.logf) + if err != nil { + t.Fatalf("%v\n%s", err, said.all()) + } + t.Cleanup(stop) + waitFor(t, log, "current") + if got := strings.Join(read(t, log), " | "); got != "was put all.one | current" { + t.Fatalf("the import went on with %q", got) + } + if _, err := call(t, asker, "sdkkeeper.register@anchor", map[string]any{"key": "anchor.two", "url": "http://two"}); err != nil { + t.Fatal(err) + } + waitFor(t, log, "now put anchor.two") + waitFor(t, log, "narrow put anchor.two") + if _, err := call(t, asker, "sdkkeeper.register@anchor", map[string]any{"key": "all.three", "url": "http://three"}); err != nil { + t.Fatal(err) + } + waitFor(t, log, "now put all.three") + for _, l := range read(t, log) { + if l == "narrow put all.three" { + t.Fatalf("a narrowed watch was handed a key outside it: %s", strings.Join(read(t, log), " | ")) + } + } +} diff --git a/node-tools/test/fixtures/state-sdk.mjs b/node-tools/test/fixtures/state-sdk.mjs new file mode 100644 index 0000000..164c946 --- /dev/null +++ b/node-tools/test/fixtures/state-sdk.mjs @@ -0,0 +1,13 @@ +// A module that keeps state through the SDK (novox/hq ADR 0201): it watches its servers as it is +// imported, and writes each change it is handed to STATE_LOG; one tool registers a server. +import { appendFileSync } from "node:fs"; +import { state } from "@novox/mesh-sdk/state"; +import { registerModuleTools } from "@novox/mesh-sdk/tools"; + +const servers = state("servers"); +await servers.watch((c) => appendFileSync(process.env.STATE_LOG, `${c.current ? "was" : "now"} ${c.op} ${c.key}\n`)); +await servers.watch((c) => appendFileSync(process.env.STATE_LOG, `narrow ${c.op} ${c.key}\n`), { key: "anchor.*" }); +appendFileSync(process.env.STATE_LOG, "current\n"); +registerModuleTools("sdkkeeper", () => [ + { name: "register", description: "put a server", input: {}, run: async (a) => servers.put(a.key, { url: a.url }) }, +]); diff --git a/node-tools/test/fixtures/state-sdk.serve.mjs b/node-tools/test/fixtures/state-sdk.serve.mjs new file mode 100755 index 0000000..ff30e9c --- /dev/null +++ b/node-tools/test/fixtures/state-sdk.serve.mjs @@ -0,0 +1,5 @@ +#!/usr/bin/env node +// The launcher the builder writes beside an entrypoint (novox/hq ADR 0193), for state-sdk.mjs. +import { serveRegisteredOverStdio } from "@novox/mesh-sdk/stdio"; +await import("./state-sdk.mjs"); +await serveRegisteredOverStdio();