NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Go modules · #1720 by repository stars
Last release 1 months ago
23 Aug 2026
Ships fairly regularly
a new release about every 3 weeks
Rarely documented
notes for 10 of 57 stable releases
Nothing withdrawn
no release was ever pulled
3 years old
102 releases · first in 2024
Nothing published for this version
Compared to v0.40.0 , Neva v0.41.0 establishes one canonical, zero-config format for Neva source, exposes that formatter both through the CLI and a pu
Compared to v0.40.0, Neva v0.41.0 establishes one canonical, zero-config format for Neva source, exposes that formatter both through the CLI and a public Go package, adds a portable type-description substrate for future runtime tooling, and hardens several runtime and installation paths.
neva fmt is now built into the Neva CLI. It formats valid Neva source with one fixed, syntax-only style and is available for stdin, files, and recursively scanned directories.pkg/formatter provides the same formatter to tools and integrations. It parses and renders standalone Neva source without module resolution, analysis, desugaring, or code generation.std/reflect.Type and TypeNode expose a portable finite, indexed description of a Neva type. Recursive edges are represented as indexes into the same descriptor.Turn, Match, and Select now receive independent inputs concurrently, eliminating the sequential-receive deadlock risk when senders arrive in a different order.sudo; set NEVA_INSTALL_DIR to choose an explicit destination.Use neva fmt before committing, or make it a repository gate:
neva fmt -w .
neva fmt --check .neva fmt is deliberately opinionated and has zero configuration: each valid source file has exactly one canonical layout. It is syntax-preserving, so semantic style rules and automated refactorings remain separate tooling concerns.
The design drew on gofmt, rustfmt, Prettier, Kotlin, Swift, Zig, and Odin formatters, while settling on Neva's own canon: compact layouts where they remain readable, vertical sequences when they do not, and no ambiguous alternatives for imports, indentation, or spacing.
For example, it sorts imports into their canonical groups and normalizes indentation:
Before
import {
@:zeta
third:omega
fmt
@:alpha
third:alpha
reflect
third:zeta
@:omega
runtime
}
After
import {
fmt
reflect
runtime
third:alpha
third:omega
third:zeta
@:alpha
@:omega
@:zeta
}
It also makes a single deterministic choice when a breakable sequence crosses 80 columns:
Before
type ExtremelyLongAlias very_long_package.SomeVeryLongGeneric<first_type, second_type, third_type>
interface VeryLongInterfaceName<first_very_long_type_parameter, second_very_long_type_parameter, third_very_long_type_parameter> () ()
After
type ExtremelyLongAlias very_long_package.SomeVeryLongGeneric<
first_type,
second_type,
third_type,
>
interface VeryLongInterfaceName<
first_very_long_type_parameter,
second_very_long_type_parameter,
third_very_long_type_parameter,
>() ()
For scripting and editor integration, the command supports standard output, in-place writes, diffs, file listing, parse-error continuation, and deterministic directory traversal:
neva fmt -d path/to/file.neva
neva fmt -l .Go-based tools can import the same public package used by the CLI:
import "github.com/nevalang/neva/pkg/formatter"
formatted, err := formatter.Format(source)std/reflect.Type is a compact graph representation for carrying a resolved Neva type as data. Its public shape is a list of TypeNode values; the relevant node forms include scalar values and indexes to composite children:
pub type Type list<TypeNode>
pub type TypeNode union {
String
List int
Struct list<StructField>
Union list<UnionCase>
}
The root is node 0; composite relationships point to other nodes by index. For example, the Neva type list<string> is carried at runtime as:
const list_of_strings reflect.Type = [
reflect.TypeNode::List(1),
reflect.TypeNode::String,
]
This makes recursive types representable without a global registry or per-message metadata.
This release deliberately provides the descriptor substrate only: it is a foundation for future reflection-aware tooling. It does not add TypeOf, #bind_type, JSON conversion, or a global type registry.
Turn, Match, and Select preserve their existing behavior while accepting independent inputs in either sender order.sudostd/reflect type descriptorOne column per month.
Nothing published for this version
Compared to v0.39.0 , Neva v0.40.0 redesigns streams as an explicit tagged-union protocol, expands immutable list operations, and makes common scalar
Compared to v0.39.0, Neva v0.40.0 redesigns streams as an explicit tagged-union protocol, expands immutable list operations, and makes common scalar collections materially faster without changing their public Neva types.
stream<T> is now the tagged union Open | Data T | Close, rather than a struct carrying an implicit index and terminal flag. Stream boundaries and payloads are explicit, and an index is created only when a program asks for one with streams.Enumerate<T>.streams.Just<T> adapts one value to a stream, while streams.Enumerate<T> adds explicit zero-based indexes when they are needed.lists.Append<T> is the renamed lists.Push<T> operation. Together with new Prepend<T> and Concat<T>, it provides immutable list updates.list<T> and dict<T> values now use native typed runtime storage where possible. Scalar-list traversal is 75–87% faster; constructing a 512-int list is 82% faster, uses 64% less memory, and makes 255 fewer allocations.neva tool <name> provides a consistent CLI entry point for an installed neva-* developer tool, including neva tool lsp.neva.yml or neva.yaml, and recommend neva upgrade.Previously, every item in a stream<T> carried control metadata even when a program only needed the value:
pub type stream<T> struct {
data T
idx int
last bool
}
Now a stream is a tagged union with exactly three possible states:
pub type stream<T> union {
Open
Data T
Close
}
Open marks the beginning of a stream, Data carries a value, and Close marks completion. Sources such as streams.Range, streams.FromList, and streams.Just emit this protocol; ordinary user code normally passes streams to standard combinators rather than handling the three cases itself.
This is Neva's return to the canonical FBP stream design: bracket information packets delimit a sequence of ordinary data packets. It follows the model described by FBP pioneer J. Paul Morrison, with whom Neva's author corresponded while developing the language. The earlier indexed structure was an intermediate design; experience showed that the explicit FBP protocol is the more idiomatic and robust model.
streams.Map, Filter, ForEach, and other combinators use Switch<stream<T>> internally: they forward Open, transform or handle Data, and forward Close. The three paths are intentionally shown only as a schematic here — the actual Map graph also has a FanIn and ordering/backpressure wiring that must not be omitted:
Open -> ... -> Open
Data -> handler -> Data
Close -> ... -> Close
The model makes the protocol visible in the type system, removes a stored index from the default representation, and keeps indexing as an opt-in operation.
streams.Just<T>pub def Just<T>(data T) (res stream<T>)
Just adapts one value to an API that expects a stream. Given 42, it emits Open, Data(42), then Close.
This matters when a higher-level API is stream-oriented but the caller has one value. For example, the PNG example creates one image.Pixel, turns it into a one-item stream with Just, and passes that stream to image.New:
new image.New
just streams.Just<image.Pixel>
---
newPixel -> just -> new
streams.Enumerate<T>pub type Enumerated<T> struct {
idx int
item T
}
pub def Enumerate<T>(data stream<T>) (res stream<Enumerated<T>>)
Use Enumerate when an index is part of the program's logic. It attaches 0, 1, 2, … to Data items instead of making every stream item retain an index by default.
std/listsThis release adds three list operations. As with the rest of Neva's value operations, each returns a new list; the input list is not mutated.
lists.Append<T> — renamed from Push<T>pub def Append<T>(lst list<T>, data T) (res list<T>)
The dataflow below appends 3 to [1, 2] and produces [1, 2, 3].
append lists.Append<int>
---
[1, 2] -> append:lst
3 -> append:data
append:res -> :res // produces [1, 2, 3]
lists.Prepend<T>pub def Prepend<T>(lst list<T>, data T) (res list<T>)
Prepending 1 to [2, 3] produces [1, 2, 3].
lists.Concat<T>pub def Concat<T>(left list<T>, right list<T>) (res list<T>)
Joining [1, 2] and [3, 4] produces [1, 2, 3, 4].
concat lists.Concat<int>
---
[1, 2] -> concat:left
[3, 4] -> concat:right
concat:res -> :res // produces [1, 2, 3, 4]
This release includes a runtime optimization for common scalar collections. On the measured hot path, scalar-list traversal is 75–87% faster; constructing a 512-int list is 82% faster and uses 64% less memory. At the language level, list<T> and dict<T> are unchanged.
Internally, homogeneous scalar values no longer have to be stored as a generic runtime-message box per element when a typed representation is available.
Conceptually, the runtime can now keep an integer list or dictionary in native typed storage:
before: []Msg{Int(1), Int(2), Int(3)}
after: []int64{1, 2, 3}
before: map[string]Msg{"answer": Int(42)}
after: map[string]int64{"answer": 42}
Heterogeneous and non-scalar values retain the existing generic representation. The runtime boxes values only at boundaries that actually require generic messages.
On an Apple M1 Pro with Go 1.26.5, the benchmarked implementation delivers:
list_at and list_slice paths: 3–4% faster with one to two fewer allocations;The detailed benchmark method and raw comparison are in PR #1127.
Run an installed language server through the Neva CLI:
neva tool lspneva tool is a dispatcher for installed executables named neva-<name>; it does not install or update tools itself.
neva upgrade.union { ... } types rather than the removed untagged-union syntax.streams.EnumerateDocumentation, CI, benchmark, and engineering-harness improvements in the same release window are included as maintenance work.
Compared to v0.38.0 , this release expands the standard library with practical OS and file-handle capabilities, improves parser error handling, and ti
Compared to v0.38.0, this release expands the standard library with practical OS and file-handle capabilities, improves parser error handling, and tightens release/version maintenance.
std/io file handles: File, Open, Create, Close, ReadAllFile, and WriteAllFile.std/io.ReadAll and std/io.WriteAll remain available as convenience APIs for whole-file reads and writes.std/os with typed environment, process, filesystem, and temp-path primitives.std/os.Args runtime list construction..codex/.std/io now supports explicit open/create/read/write/close flows for programs that need to keep file access state visible in the graph.
import { bytes, io }
def WriteExample(start any) (stop any, err error) {
create io.Create?
from_string bytes.FromString
write io.WriteAllFile?
close io.Close?
---
:start -> [
'out.txt' -> create:filename,
'hello' -> from_string:data
]
from_string:res -> write:data
create:res -> write:file
write:res -> close:file
close:res -> :stop
}
std/os now covers common environment, process, filesystem, and temporary-path operations through typed, dataflow-friendly components. Query-style components are signal-triggered so programs stay explicit about when OS data is read.
import { os }
def ListCwd(start any) (res list<os.DirEntry>, err error) {
getwd os.Getwd?
read_dir os.ReadDir?
---
:start -> getwd:sig
getwd:res -> read_dir:path
read_dir:res -> :res
}
compiler.Error.std/os runtime helpers are covered by targeted unit and e2e tests.0.39.0 across checked-in manifests, examples, docs, generated headers, and test expectations.release-neva skill now documents repo-wide version bump requirements..agent, .claude/rules, and .opencode harness roots were consolidated under .codex/.Compared to v0.37.1 , this release makes generic node usage stricter and more explicit, improves runtime safety around edge cases, and continues inter
Compared to v0.37.1, this release makes generic node usage stricter and more explicit, improves runtime safety around edge cases, and continues internal tooling groundwork.
any fallback removed).list_at out-of-bounds flow and ArrayInport.Select behavior.pkg/view now exposes port order and DI args for downstream visual/tooling work.any semantics and container specialization policy.This change is specifically about generic nodes at initialization/callsites.
If a node is generic, pass <T> explicitly when you instantiate it.
Before (now invalid, missing type arg):
import {
fmt
runtime
}
def Main(start any) (stop any) {
println fmt.Println
panic runtime.Panic
---
:start -> "hello" -> println:data
println:res -> :stop
println:err -> panic
}
After (valid):
import {
fmt
runtime
}
def Main(start any) (stop any) {
println fmt.Println<string>
panic runtime.Panic
---
:start -> "hello" -> println:data
println:res -> :stop
println:err -> panic
}
view tests were strengthened.0.38.0 across release-related files.Nothing published for this version
Nothing published for this version
Patch release after v0.37.0 focused on trace output consistency and JSONL cleanup.
Patch release after v0.37.0 focused on trace output consistency and JSONL cleanup.
receiver <- sender, branch lines).new now propagates signal causes, so causal chains can reach :start.port.Index when it is null (kept only for array ports).Compared to v0.36.1 , this release starts the final language-completion track: runtime tracing first, then visual tooling, then debugger stack.
Compared to v0.36.1, this release starts the final language-completion track: runtime tracing first, then visual tooling, then debugger stack.
pkg/view foundation primitives for upcoming visual tooling.In control-flow languages, production failures are explained by stack traces.
In Neva, execution is dataflow, so failure context is a causality graph of messages. This release makes that graph observable in runtime output.
When panic happens, Neva now prints a panic cause dataflow trace to stderr.
import {
fmt
runtime
}
def Main(start any) (stop any) {
panic runtime.Panic
printf fmt.Printf
---
:start -> [
'value=$1' -> printf:tpl,
10 -> printf:args[0]
]
printf:err -> panic
printf:sig -> :stop
}
Pretty trace view (target format):
panic:data <- printf:err
├─ printf:args[0] <- __newv2__2
│ └─ __newv2__2 <- :start
└─ printf:tpl <- __newv2__1
└─ __newv2__1 <- :start
import {
fmt
runtime
strconv
}
def Main(start any) (stop any) {
panic runtime.Panic
printf fmt.Printf
left_atoi strconv.Atoi
right_atoi strconv.Atoi
---
:start -> [
'left=$0 right=$1 extra=$2' -> printf:tpl,
'10' -> left_atoi,
'20' -> right_atoi
]
[left_atoi:err, right_atoi:err, printf:err] -> panic
left_atoi:res -> printf:args[0]
right_atoi:res -> printf:args[1]
printf:sig -> :stop
}
Pretty trace view (target format):
panic:data <- __fan_in__2
└─ __fan_in__2:data[2] <- printf:err
├─ printf:tpl <- __newv2__7
│ └─ __newv2__7 <- :start
├─ printf:args[0] <- left_atoi:res
│ └─ left_atoi:data <- __newv2__8
│ └─ __newv2__8 <- :start
└─ printf:args[1] <- right_atoi:res
└─ right_atoi:data <- __newv2__9
└─ __newv2__9 <- :start
Note:
Current runtime output still exposes internal synthetic node names (for example __newv2__*) and may stop short of :start for some runtime funcs. The next tracing patch will align runtime output with the target view above.
Trace events written to file are standardized as JSONL (v=2, one event per line).
Real sample from trace.log:
{"message":{},"port":{"Path":"","Port":"start"},"event":"sent","causeIndexes":null,"v":2,"index":1}
{"message":"value=$1","port":{"Path":"__newv2__1","Port":"res"},"event":"sent","causeIndexes":null,"v":2,"index":4}
{"message":"value=$1","port":{"Path":"printf","Port":"tpl"},"event":"recv","v":2,"index":4}
{"message":10,"port":{"Path":"printf","Port":"args","Index":0},"event":"recv","v":2,"index":5}This release also includes initial pkg/view projection primitives used by the read-only visual tooling track.
The previous "Engineered Stability" release v0.36.0 tightened delivery quality gates.
The previous "Engineered Stability" release v0.36.0 tightened delivery quality gates.
v0.36.1 is a focused patch release that continues this maintenance track: wider runtime operator coverage, benchmark surface expansion, lint debt reduction, and CI/release automation cleanup.
golangci-lint rollout with targeted suppression cleanup via refactors.Runtime/operator work filled missing paths and removed stale suppression-heavy patterns:
nolint usage in runtime funcsA delayed-echo example synchronization bug was fixed by correcting wait-group count wiring.
This release significantly expands reproducible benchmark slices for runtime behavior:
These changes improve visibility into runtime performance trends without introducing new language syntax.
.agent layout cleanup to keep harness guidance consistentNo language-level syntax or type-system semantic changes were introduced in this patch window.
Full Changelog: v0.36.0...v0.36.1
Release binaries were re-uploaded from main commit f9dbb8e6 to fix CLI-reported compiler version metadata (neva version now reports 0.36.1).
Nothing published for this version
Nothing published for this version
A dedicated sweep addressed reachable vulnerability findings and upgraded affected modules:
The previous "Pragmatic Power" release v0.35 focused on language and stdlib leverage.
v0.36 is about making that leverage safer and more repeatable in day-to-day work: stricter release builds, stronger quality gates, cleaner CI lanes, security/toolchain refresh, and reduced maintenance noise.
go1.26.1 (including CI).lint -> unit -> e2e) and uses shared e2e binary caching.lint, test-unit, vulncheck, quality-gate, quality-gate-ci).fieldalignment cleanup was applied.A dedicated sweep addressed reachable vulnerability findings and upgraded affected modules:
github.com/go-git/go-git/v5 v5.11.0 -> v5.16.5github.com/cloudflare/circl v1.3.7 -> v1.6.3golang.org/x/crypto v0.21.0 -> v0.45.0golang.org/x/net v0.23.0 -> v0.47.0go mod tidy was re-run to normalize the module graph after the update.
Neva now targets:
go 1.26toolchain go1.26.1CI workflows were aligned to the same version to keep local/CI behavior consistent.
Release build flags are now standardized in shared backend helpers and aligned with Make targets:
-trimpath-buildvcs=false-ldflags="-s -w"This keeps end-user binaries compact and more reproducible across release paths (install, native backend, wasm backend, and release builds).
Release operations were tightened further around lightweight repo-local guidance and post-publish automation:
release-neva skill now codifies the draft-first release flow and version-bump decision pointsThe test workflow was restructured into ordered jobs:
lintunit_testse2e_testspkg/e2e now supports shared binary caching with safe fallback behavior, reducing repetitive rebuild work during e2e runs.
The repo now exposes a clearer local/CI quality path:
make lintmake test-unitmake vulncheckmake quality-gatemake quality-gate-cigovulncheck also runs as a dedicated workflow job, and optional Go-only hooks can mirror the same checks locally before commit.
After introducing safer e2e/cache mechanics, the temporary explicit -p 2 cap for e2e/examples was removed in CI. The lane now relies on default Go package parallelism.
Documentation/comments were tightened around stdlib cache invalidation:
embed.FS constraints)nolint suppressions across the repo.govet field alignment checks.make nilaway to keep the default developer path focused on production code.No language-level breaking changes were introduced in this release window. The focus of v0.36 is delivery quality and operational stability.
Full Changelog: v0.35.0...v0.36.0
The previous "Back to Dataflow" release v0.34 has cleaned up the language surface and removed a lot of accidental complexity.
The previous "Back to Dataflow" release v0.34 has cleaned up the language surface and removed a lot of accidental complexity.
Current "Pragmatic Power" releasev0.35 is what comes next: making that simpler core actually pleasant and powerful in real programs. This release is more about real leverage.
bytes builtin data-type for effective IO operations. We no longer pretend that all bytes are strings.streams/dicts/strings/bytes).data/res/err/sig defaults) across many components to make APIs more predictable.maybe type is no longer special case for type-system, it's just union now.[*] (instead of =>), which is much clearer and less magical.pkg/e2e package for end-to-end testing.bytesbytes is now a first-class builtin type.
Minimal snippet (nodes + network only):
read_all io.ReadAll?
bytes_to_string strings.FromBytes
println fmt.Println<any>?
---
:start -> 'bytes_roundtrip.txt' -> read_all:filename -> bytes_to_string -> println -> :stop
A concrete stdlib API example using bytes:
#extern(write_all)
pub def WriteAll(filename string, data bytes) (res any, err error)
It makes code more consistent and easier to reason about for LLMs.
// before (now invalid)
import { fmt }
def Main(start any) (stop any) {
fmt.Println
...
}
// after
import { fmt }
def Main(start any) (stop any) {
println fmt.Println
...
}
maybe/error modeling cleanupmaybe<T> is now regular tagged-union modeling in std/builtin.
pub type maybe<T> union {
Some T
None
}
pub type error struct {
text string
child maybe<error>
}
No special type-system path is needed for optional/error chaining.
We finished the remaining language cleanup from v0.34 by removing deferred connection syntax.
Before, you could write sugar like a -> { b -> c }, where delivery from b to c was deferred by a. It worked, but
c and not sending by b, which is clear using explicit locks - what desugarer was doing under the hood)So now this is explicit wiring via builtin.Lock:
lock Lock<string>
---
a -> lock:sig
b -> lock:data
lock -> c
[*]Array bypass used to use =>. Now it is explicit port-slot wildcard syntax on both sides:
// before
in:items => out:items
// after
in:items[*] -> out:items[*]
This might seem like a opinionated change but actually it's just simplification we didn't see possible before - at AST level we used to have 2 kinds of connection, a normal one and array bypass one. Now array bypass is just a special case of normal connection where array slot index is * (which is encoded as 255 - reserved uint8 value). So all connections are "normal" now. I.e. there are just "connections". Also [*] feels more consistent with [i] (e.g. [0]) rather than using different kind of arrow =>.
A lot of stdlib APIs were normalized to follow port naming convention with (data, res, err, sig) with boundary exceptions only when domain naming adds real value. The convention itself was finalized in the docs/style_guide.md document.
This is not a flashy feature, but it helps to form idiomatic conventions for the language and its standard library. This particular change should make it a little bit easier to reason about the port names. We expect you to just follow the convention without asking yourself a lot about "how do I name this port?". Also should help LLMs with codegen predictability.
streams.FromString(data string) (res stream<string>)streams.FromDict<T>(data dict<T>) (res stream<DictEntry<T>>)dicts.FromStream<T>(data stream<DictEntry<T>>) (res dict<T>)strings.FromBytes(data bytes) (res string)This introduces idiomatic convention for data-type convertors. We have decided to continue follow "small core" philosophy and made type convertors simple components rather than language feature.
dict -> streamconst dict_value dict<string> = {
a: 'one',
b: 'two'
}
...
dict_to_stream streams.FromDict<string>
for_each_println streams.ForEach<DictEntry<string>>{fmt.Println<any>}?
wait streams.Wait
---
:start -> $dict_value -> dict_to_stream -> for_each_println -> wait -> :stop
stream -> dict (last write wins)const dict_entries list<DictEntry<string>> = [
{ key: 'dup', value: 'one' },
{ key: 'dup', value: 'forty-two' }
]
...
list_to_stream streams.FromList<DictEntry<string>>
stream_to_dict dicts.FromStream<string>
println fmt.Println<any>?
---
:start -> $dict_entries -> list_to_stream -> stream_to_dict -> println -> :stop
Builtin scalar converters now explicitly document intent (aligned with Go):
Int(float) -> int (truncate toward zero)Float(int) -> floatString(int) -> string (Unicode code point)This gives a sane, predictable baseline while keeping non-total parsing/formatting in stdlib (strconv style APIs).
Bytes(string) -> bytes and String(bytes) -> string in builtin are in progress.
go fix disciplineRepository now targets:
go 1.26toolchain go1.26.0CI now enforces go fix ./... cleanliness. If you haven't read about go fix then do it. It's awesome tool that automatically rewrites legacy Go code to its modern version respecting language and stdlib changes. Now every Neva release language is going to be better and better also because of this, among with many-many other reasons.
A lot of groundwork landed to make this split working:
pkg/ast, pkg/core, pkg/indexer, pkg/typesystem,cmd/lsp removed,nevalang/neva-lsp.This is important for velocity in neva-lsp and vscode-neva: compiler core stays focused, language tooling can evolve in its own repo.
pkg/* APIs are now usable from external Go modulesExpose public APIs for external LSP extraction means you can now import Neva AST/typesystem packages directly from another Go module.
Minimal example:
package main
import (
"fmt"
src "github.com/nevalang/neva/pkg/ast"
ts "github.com/nevalang/neva/pkg/typesystem"
)
func main() {
var _ src.Component
var _ ts.Expr
fmt.Println("neva ast/typesystem imported successfully")
}Core editor features landed:
textDocument/definition - jump to symbol definition.textDocument/references - find all usages.textDocument/rename (+ prepare rename) - safe symbol rename.textDocument/hover - quick symbol/type info.textDocument/documentSymbol - file outline navigation.textDocument/completion - entities, ports, and imports.textDocument/semanticTokens/full - syntax-aware highlighting.references, implementations) - inline code navigation counts.This is the baseline for vscode-neva and future visual tooling over LSP transport.
main was cleaned while preparing this release baseline (staticcheck + wastedassign findings).#1030) was a small but important bug-fix: pretty spacing no longer mutates string payload contents.Core to this release window:
Full Changelog: v0.34.0...v0.35.0
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
This release is a turning point toward the 2026 Q1 priority: a polished, “wow‑factor” working prototype. To get there we needed to correct language de
This release is a turning point toward the 2026 Q1 priority: a polished, “wow‑factor” working prototype. To get there we needed to correct language design mistakes before moving forward. v0.34 finishes that simplification effort and brings Neva back to its roots: a small core and pure dataflow. We’re no longer trying to mimic conventional programming languages — we’re doubling down on what makes Neva different.
With this foundation in place, we can finally shift focus to the visual node editor. The language syntax/semantics are now kinda stable: expect small tweaks, but no major reshaping. The next frontier is stdlib APIs and patterns rather than the language itself.
This release continues the language simplification work: composite sender syntax is removed in favor of explicit stdlib components, and const/literal senders are now strictly chain-only. Alongside the language changes, you get a new neva install command, clearer compiler diagnostics, and updated stdlib APIs.
Neva is a pure dataflow language: messages flow between nodes over connections. Composite senders (expressions, range syntax, ternary, switch syntax, union wrapping syntax) pull references from the network in sender position, which conflicts with chain-only semantics (constants/literals/union tags must be triggered). This created:
1..10 implies streams.Range and auto-imports).Switch note below).v0.34.0 removes these composite senders and requires explicit wiring. This makes the textual format a faithful serialization of the graph and restores a single, consistent rule: senders are nodes or chain-only literals/consts triggered by a signal.
The following language-level constructs are removed in v0.34.0:
-x, a + b, a && b).switch language construct.1..10).Type::Tag(data) as a sender).Use explicit stdlib components instead. This keeps the text format aligned with the visual dataflow model.
// before
x + y -> out:data
// after (builtin is implicit)
add Add
---
:x -> add:left
:y -> add:right
add -> :res
// before
cond ? a : b -> out:data
// after (builtin is implicit)
ternary Ternary<string>
---
:cond -> ternary:if
:a -> ternary:then
:b -> ternary:else
ternary -> :res
switch construct// before
value -> switch {
case1 -> handle1
case2 -> handle2
_ -> handle_default
}
// after (builtin is implicit)
switch Switch<string>
---
:value -> switch:data
:start -> [
'Alice' -> switch:case[0] -> :out1,
'Bob' -> switch:case[1] -> :out2
]
switch:else -> :out_default
// before
1..10 -> stream:data
// after
import { streams }
range streams.Range
---
:start -> [
1 -> range:from,
10 -> range:to
]
range:res -> :stop
// before
type MyUnion union { Tag int }
MyUnion::Tag(value) -> handler
// after (builtin is implicit)
type MyUnion union { Tag int }
union Union<MyUnion>
---
:data -> union:data
:start -> MyUnion::Tag -> union:tag
union -> :res
Standalone const or literal senders are no longer allowed. They must appear in a chain triggered by a signal.
// before (invalid)
$answer -> print:data
42 -> print:data
// after
:start -> [
$answer -> print:data,
42 -> print:data
]
Switch<T> and the Type placeholder (type-system note)Removing the language switch construct exposes a deeper type-system reality: union elimination is not expressible as a normal generic component. A true pattern match needs different output types per branch, but Neva’s type system assigns a single type T to Switch<T> ports. A naive Switch<T> therefore cannot statically refine types by tag.
To keep the language minimal and preserve switch-like behavior, Neva keeps Switch as a stdlib component, while the compiler applies special typing rules to its case outputs. The signature uses a placeholder type Type to indicate “compiler-determined output type” per case:
pub def Switch<T>(data T, [case] T) ([case] Type, else T)
Type is not a real runtime type. It is a documentation/typing placeholder that signals compiler participation. This is a deliberate, constrained “compiler magic” to keep the surface language small while acknowledging that union elimination is not soundly expressible as a normal generic function today (see #969 for details).
Comments are now allowed inside import { ... } blocks and as trailing comments on import lines.
import {
fmt
strings // used for joining
}
Multi-line struct literals can now end with a trailing comma.
const person Person = {
name: 'Ada',
}
neva install commandBuild and install a Neva program to your Go bin path (GOBIN, GOPATH/bin, or ~/go/bin) using the package directory name as the binary name.
neva install path/to/pkgReal‑world examples:
# Install a CLI from a local module
neva install ./cmd/hello
# Install the main package from a module root
neva install .
# Then run it
helloA new --debug-runtime-validation flag is available for language developers. It emits a validation helper into the generated runtime to print unconnected senders/receivers for wiring inspection, without shipping that helper in the repo runtime by default.
# Inspect runtime wiring during development
neva run --debug-runtime-validation path/to/pkgbuiltin.<Name> when appropriate.std/os/dotenv loaders no longer return dictionaries. They load into the process environment and send a completion signal.
import {
os/dotenv
}
def Main(start any) (stop any) {
load dotenv.Load
---
:start -> load -> :stop
}
Load a specific file via LoadFrom, and use the override variants when needed:
import {
os/dotenv
}
def Main(start any) (stop any) {
load dotenv.LoadFrom
---
:start -> './config/.env' -> load:data -> load:res -> :stop
}
import {
os/dotenv
}
def Main(start any) (stop any) {
load dotenv.LoadFromOverride
---
:start -> './config/.env' -> load:data -> load:res -> :stop
}
Available components in std/os/dotenv:
LoadLoadFromLoadOverrideLoadFromOverrideos.EnvironRetrieve the current process environment as list<string> in KEY=VALUE form. This mirrors Go’s os.Environ() API, which returns a slice of KEY=VALUE strings rather than a map, so Neva exposes the same shape for consistency with the runtime implementation.
import {
fmt
os
}
def Main(start any) (stop any) {
environ os.Environ
println fmt.Println<list<string>>
---
:start -> environ -> println -> :stop
}
streams.Range is now explicit (breaking)The .. syntax and sig-gated Range variant are removed. Use streams.Range with explicit wiring.
import { streams }
def Main(start any) (stop any) {
range streams.Range
---
:start -> [
1 -> range:from,
10 -> range:to
]
range -> :stop
}
Union senders are removed. Use the Union<T> component.
type MyUnion union { Tag int }
def WrapUnion(start any, data int) (res MyUnion) {
union Union<MyUnion>
---
:data -> union:data
:start -> MyUnion::Tag -> union:tag
union -> :res
}
Unary/binary expressions -> builtin operators (e.g., Add, Sub, Eq)
ternary -> Ternary<T>
switch {...} -> Switch<T>
1..10 -> streams.Range (from/to wiring)
Type::Tag(data) sender -> Union<T>
Standalone literals/consts -> Chain from a signal
Dotenv Load returning dict -> signal-only loaders + os.Environ
Added or changed components in v0.34.0:
os.Environ (new)os/dotenv.Load, LoadFrom, LoadOverride, LoadFromOverride (changed to signal-style)streams.Range (moved to streams, single explicit variant)Union<T> (unchanged component; now required for union wrapping instead of union senders)golangci-lint configuration with an explicit allowlist and new quality/security linters enabled. Highlights include gosec, govet with fieldalignment, depguard, gochecknoglobals, gochecknoinits, gocritic, revive, errorlint, nilerr/nilnil, copyloopvar, and tparallel. This tightened bar took focused cleanup work and materially improves correctness, security, and maintainability..checksum file under ~/neva/std. If it matches, the existing on-disk stdlib is reused; if it differs, the stdlib is fully re-extracted and the checksum updated. This prevents stale stdlib copies after upgrades while keeping fast startup when nothing changed.Pull requests: #951, #953, #955, #956, #958, #974, #978, #981, #991, #992, #993, #994, #995, #997, #998, #1005, #1006, #1009
Full Changelog: v0.33.0...v0.34.0
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →