You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
TLS support in the net package, built on the bundled BoringSSL. TCPSocket.connect_tls(host, port) establishes a verified connection (certificate chain and hostname checked against the system CA bundle); connect_tls_with / upgrade_tls / accept_tls cover custom configs and server-side handshakes. TLSConfig.client() / .insecure() / .server(cert, key) / .with_ca(ca) configure verification and credentials. Certificates and keys are in-memory Crypto.Certificate / Crypto.PrivateKey values parsed from PEM text, validated up front — a swapped cert/key pair or an encrypted key fails with a clear message instead of a handshake error. The HTTP client routes https:// URLs through TLS automatically, so HTTP.get("https://...") works out of the box.
koja run now executes projects with the interpreter by default — no --backend=llvm required. The Process entry runs in-process with argv passing, sockets, TLS, binary pattern matching, and receive over lifecycle signals (SIGTERM / SIGINT / SIGHUP) and after timeouts, so real services run unmodified with millisecond startup. Process features the interpreter does not cover yet (spawn, cross-process messaging) report a clear runtime error with a --backend=llvm hint.
Implicit numeric widening into the default types: sized integers (Int8 / Int16 / Int32 / UInt8 / UInt16 / UInt32) now flow into Int slots without explicit conversion, and Float32 into Float — at call arguments, fields, payloads, returns, bindings, and assignments. Widening is always lossless. Sideways widening (Int8 to Int16), narrowing, UInt64 to Int, binary operators, and generic inference still require exact types.
Checked numeric narrowing methods: Int.to_int8 / to_int16 / to_int32 / to_uint8 / to_uint16 / to_uint32 / to_uint64 and UInt64.to_int, each returning Result<T, NumericConversionError> with OutOfRange when the value does not fit. Float.to_float32 is total and rounds to the nearest representable value.
koja test --trace prints each test grouped by struct with its path:line and per-test timing instead of progress dots. Names print before the test runs, so a crashing test leaves its name as the last line of output. Trace runs skip the per-binary timeout for long debugging sessions.
koja test now enforces a 60-second per-binary timeout, so a hung test process fails fast instead of stalling the suite.
Fd.read_binary(count) and TCPSocket.read_binary(count) -- read up to count bytes as a Binary for binary wire protocols.
Binary.at(index) -> Option<Int> and Binary.slice(range) -> Binary -- O(1) byte access and inclusive-range byte slicing (endpoints clamp). The byte-oriented complement to String.get / String.slice, whose codepoint indexing is O(n) per call; scanners and parsers should step over text.to_binary() instead.
Changed
Breaking: Koja moved from affine ownership to value semantics. Every binding, parameter, return, and field is now an independent value: assignment and argument passing copy, and a value stays usable for as long as it is in scope. Copies are cheap — heap-backed values are reference-counted, shared until mutated (copy-on-write), and reclaimed deterministically at scope exit, with no garbage collector. The move keyword, borrow-by-default parameters, use-after-move errors, and the fn(move T) -> U syntax are gone. Remove move from your signatures; code that previously had to satisfy move/borrow rules now just works.
Breaking: the net package reports failures with typed errors instead of strings. Socket operations return Result<_, Net.SocketError>, a cause-based enum (ConnectionRefused, TimedOut, NameNotFound, ..., Unknown(errno)) — match on the cause instead of parsing message text. TLS-capable operations return Result<_, Net.SocketError | Net.TLSError>, with TLSError.VerificationFailed(VerificationError) carrying why certificate verification failed. Crypto.Certificate.parse / Crypto.PrivateKey.parse return Result<_, Crypto.PEMError>. All error enums expose message() for display. TCPEvent.Error now carries a SocketError, and TLSSession.read is removed — use TCPSocket.read, which decodes UTF-8 and reports SocketError.InvalidUTF8.
Breaking: Int.parse and Float.parse (and the String.to_int / String.to_float wrappers) now return Result<T, NumericConversionError> instead of Result<T, String>: InvalidFormat for malformed text, OutOfRange for well-formed numbers that don't fit — including an integer overflowing 64 bits and a float like 1e999 that would round to infinity (previously parsed as inf). The inf / nan tokens are rejected as InvalidFormat.
Fd.write and TCPSocket.write now accept data: Binary | String (a String is encoded as UTF-8), unifying text and binary writes under one method.
Removed
Breaking: fn main is no longer a program entry point. Every compiled program's entry is a type implementing Process<C, M, R>, named by entry = "App" in koja.toml; the program exits with the code mapped from the entry's StopReason. Standalone .koja files can no longer be built or run — use a .kojs script for entry-free programs, or a project for compiled binaries (koja check file.koja still works). koja new scaffolds a Process entry, and the koja test harness runs as one too.
Breaking: The Clone protocol and .clone() method are removed. Under value semantics every binding is already an independent value, so explicit cloning is unnecessary — replace x.clone() with x.
Fixed
Fixed a scheduler race that could intermittently crash message-heavy programs with a segfault when a yielded process was resumed from a stale stack pointer.
Fixed a stack overflow in long-running processes: a self-recursive tail call written as the value of an if / match / receive (notably the receive ... after -> self.loop() actor loop) was not tail-call-optimized, so the loop grew the stack until it crashed. These loops now run in constant stack.
Passing a heap-owning local (e.g. a String built with <>, or a List) to a function or method that stores it no longer crashes or returns corrupt data under --backend=llvm.
Fixed an unbounded memory leak on common patterns: a heap-backed temporary passed as an argument (f(a <> b)), used as a receiver in a method chain (make().foo().bar()), or produced by a literal or constructor was never released. Long-running programs no longer bleed memory on routine work.
Message payloads that are never delivered — sent to a dead process, or still queued when the program exits — are now reclaimed instead of leaked.
Fixed a race in the I/O reactor that could leave a process blocked forever on a readiness event delivered between fd registration and the blocking state transition.
Closing a file descriptor that another process is blocked on now wakes that process with an error instead of stranding it forever; a Fd.watch owner of the closed fd receives a synthetic readiness-error event so its handler observes the hangup.
Closing a file descriptor now drops it from the reactor's watch maps, preventing spurious wakeups when the kernel recycles the fd.
Package-level functions are now callable from other packages with qualified syntax (HTTP.get(url)), as the 0.11.0 changelog advertised — previously this failed with unknown identifier. A call to a missing function in a known package now reports package Xhas no functiony``.
JSON.Decoder.decode is now O(n): it scans the input's UTF-8 bytes instead of indexing codepoints (which walked from the start of the string on every call, making the old decoder quadratic). Decoding a 200 KB payload drops from ~8 s to milliseconds.
The interpreter no longer deep-copies String / Binary payloads on every variable read and argument pass — buffers are now shared via reference counting, matching compiled binaries. Workloads that move large payloads through functions (e.g. decoding a big HTTPS JSON response) run several times faster.
koja format now lays out match / cond / receive arms consistently: when any arm body is long enough to wrap, every sibling arm breaks onto its own indented line instead of leaving short siblings inline next to a wrapped one.