Flowstate runs in WASM today: the whole pkg tree compiles under wasip1 and js/wasm (design record + slice plan)
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 9
- Forks
- 0
- Avg merge
- 3h 3m
- Merged PRs (30d)
- 509
Description
Feasibility pass on running the local driver — the Temporal-free execution path — under WebAssembly. The headline is better than expected and independently verified:
GOOS=wasip1 GOARCH=wasm go build ./pkg/flowstate/v1/ ./pkg/flowstate/v1/flowfile/ # exit 0
GOOS=js GOARCH=wasm go build ./pkg/flowstate/v1/ ./pkg/flowstate/v1/flowfile/ # exit 0
The whole ./pkg/... tree compiles under both targets today, including pkg/flowstate/v1/plugin/. Every WASM incompatibility in this codebase is a runtime boundary, not a build boundary. That reshapes the effort estimate entirely: slice 1 is not "make it compile," it is "prove it runs, and lock the property so it can't regress."
Why it already works
The local driver's import set is context/errors/fmt/math/reflect/strings/time + cel-go + the expr protos — all pure Go. cel-go pulls no Temporal and no gRPC runtime into the core. And the seams that WASM needs were already injected for other reasons: the clock (NewContextWithClock, VirtualClock), signals (SignalWaiter), the task registry (NewContextWithRegistry), secrets (TaskRuntime), and compile-from-bytes (flowfile.Parse is byte-based and fs-free — only ParseFile and call: resolution touch the filesystem, by design, at the authoring edge). The plugin host already has a proc_unix.go/proc_other.go build-tag seam, so WASM compiles against stubs and simply never wires a host.
Invariant 3 holds by construction: grep for //go:build / runtime.GOOS across the core execution path returns nothing — a WASM build is the existing local driver on a new GOOS, not a third driver. Same RunWithInputs→eval→runNodes, byte-for-byte the same logic.
The three targets, disambiguated
(a) Compile / validate / fix in WASM — highest value/effort ratio. Pure and byte-based; compiles today. Unlocks a browser playground, LSP-in-browser, runnable docs. call: degrades to a clean diagnostic (no fs), not a crash.
(b) No-egress local execution in WASM — a real near-term slice. log + CEL transforms + loop/for_each/if/parallel + waits with a VirtualClock (so sleep: 24h is instant) + embedder-registered custom Go tasks. The work is a thin //go:build wasm entrypoint and JS glue — not core surgery, because targets (a) and (b) are essentially "call the embed facade (#230/#232) from a wasm main."
(c) http via browser fetch (js/wasm) — feasible, highest effort, separate PR. Under js/wasm an http.Client whose transport does not override DialContext uses Fetch automatically — and the blocker is precisely that netpolicy.newClient() hardcodes a net.Dialer. Needs an injectable transport seam. Fail-closed is load-bearing here: a WASM build with no egress shim must deny http, not silently no-op it — the absent-transport case returns a deny error, matching the embed facade's own nil-EgressPolicy-means-default-deny posture.
Plugins are categorically out (no subprocess, no unix sockets) — so the WASM-expressible capability set is the trusted base plus embedder-registered tasks, which is exactly the right subset for a demo and for embedding.
Slice 1 (one PR)
- A CI build check for
GOOS=wasip1andGOOS=jsover the core + embed packages — locks in the "compiles under wasm" property so it cannot silently regress. Highest signal, lowest cost. - A minimal
//go:build wasmexamplemain: Flowfile bytes →embed.Compile→embed.RunLocalwith aVirtualClockand alog-only registry → outputs. Runnable underwasmtimeas a CI smoke target, honoring the house rule that a capability lands when an example exercises it in CI. - No transport shim, no plugin host,
call:-free — with http either absent from the registry or wired to deny, proving the fail-closed boundary explicitly.
The netpolicy transport seam for target (c) is a deliberate follow-on: it changes a security-sensitive construction path and deserves its own review.
Honest limits (do not oversell)
"Run any workflow in the browser" is not the claim. http needs the transport seam; plugins are impossible; call: needs host-side resolution before the bytes are handed in. What is true is that the compile core and the no-egress execution core already compile under wasm with their seams already injected — the remaining work is packaging and glue.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the existing pkg/flowstate/v1/ and pkg/flowstate/v1/flowfile/ packages, then inspect the embed Compile and RunLocal entry points plus VirtualClock. Run the stated wasip1 and js/wasm builds before adding the CI check and minimal wasm main. Done means the core and embed packages compile for both targets and a call-free, no-egress example runs under wasmtime in CI.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, wasm
- Domain
- build-system, testing-qa, web-dev
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100