Skip to content

v1.0.0-RC4

Choose a tag to compare

@fwbrasil fwbrasil released this 18 Jun 19:24

v1.0.0-RC4

This release supersedes v1.0.0-RC3, which was discarded for a compilation performance regression that has since been reverted.

v1.0.0-RC4 makes Kyo a place to build and drive the web. kyo-ui turns a web UI into a pure value that runs unchanged on the client, on the server, or as static HTML, and kyo-browser drives a real browser to exercise it, both on JVM, JS, Native, and now WebAssembly πŸš€

The theme is one source reaching more targets, now even past Kyo itself: kyo-ffi binds native C libraries once and enables calling them in JVM, JS, and Native, kyo-compat enables shipping a single library to six effect backends, and WebAssembly joins as a fourth platform, now published to Maven Central. The framework is fully documented now, every module carrying a README whose examples compile in CI.

The new getkyo.io is itself a Kyo app, generated from those same docs.

New Features

New modules

  • kyo-ui (README) is a web UI framework where a signal stands in for a plain value in most places the API takes one, so a setter or child slot taking A also takes Signal[A] or SignalRef[A] with no wrapper type and no binder operator. Invalid markup does not compile, and updates are fine-grained with no virtual DOM, re-rendering only the subtree bound to a changed signal. The same UI value runs unchanged across three runners (UI.runMount for a Scala.js client, UI.runHandlers for a server-driven app, UI.runRender for an HTML stream), and markup reaches past HTML to typed SVG and a typed Chart layer that lowers to it. (by @fwbrasil in #1649, #1654, #1664, #1665, #1671)

  • kyo-browser (README) is cross-platform browser automation built directly on kyo-http's WebSocket transport speaking Chrome DevTools Protocol, with no WebDriver and no Node-driver shim, so it compiles on JVM, JS, Native, and Wasm. Every page-mutating action routes through a settlement pipeline (frame-lifecycle tracking, a mutation observer, a stability sampler, per-element actionability checks) so a call returns only when the page is ready for the next action, which is what makes it more suitable as the substrate for AI-agent browser driving. It replaces the JVM-only kyo-playwright. (by @fwbrasil in #1644, #1671)

  • kyo-compat (README) lets you write a library once against the kyo.compat.* surface and ship it to six effect backends (Kyo, ZIO, Cats Effect, scala.concurrent.Future, Ox, Twitter Future), selected by the consumer at deploy time. CIO[+A] is a per-backend opaque type and every method is an inline def lowering to the backend primitive, so there is no dispatch and no adapter, and each kyo-compat-<backend> artifact depends only on its target library, so a non-Kyo consumer pulls zero Kyo code. Backend support is not uniform: Future, Ox, and Twitter Future are JVM-only; Cats Effect is JVM and JS; Kyo and ZIO are all three. (by @fwbrasil in #1627, #1637, @ghostdogpr in #1661, @DamianReeves in #1677)

  • kyo-ffi (README) lets you bind a C library once with typed Scala signatures and call it from JVM (Panama), JS (koffi), and Native. You declare a trait X extends Ffi of typed method signatures (primitives, case-class structs, enums, unions, handles, buffers, callbacks) and build-time codegen generates the per-platform implementation. The runtime layers a safe tier over an explicit AllowUnsafe tier, models absence and failure with Maybe and Result, and runs blocking JS calls on a fiber. (by @fwbrasil in #1668, #1674)

  • kyo-tasty (README) is cross-platform TASTy reflection (JVM, JS, Native, Wasm) that answers reflection questions about Scala 3 code without a live JVM, Class.forName, or an agent. A classpath is bound for a block with Tasty.withClasspath(roots) { ... } with no Context threaded, and navigation, member traversal, subtyping, and rendering are pure instance methods on a sealed, immutable model so matches stay exhaustive. Cold load is optimized via memory-mapped reads, parallel decode, lazy symbol-body decode, and a binary snapshot cache. (by @fwbrasil in #1669)

  • kyo-test (README) is a Kyo-native test framework that replaces ScalaTest and now runs most of the project's own test suites. You extend Test[S] and register cases with "name" - { ... } and "name" in { ... } whose body is a captured Kyo computation, so async and sync tests share one shape and an extra effect is declared in S and discharged with .handle. It ships a power-assert with subexpression diagrams, property-based testing with shrinking and seeded replay, snapshot testing, decorators (retry, timeout, flaky, pending, platform gates, tags, focus), and a runner with console, TAP, and JUnit-XML reporters. (by @fwbrasil in #1658)

  • kyo-slack (README) is a Slack Socket Mode client built around one scope-managed entry point, Slack.run(config)(handler), which streams typed SlackEnvelope values into the handler. The handler returns one SlackAck and the framework emits exactly one wire ack from it, so forgetting to ack and double-acking are both unrepresentable. Reconnect defaults to overlap with a seen-id window and residue drain so no envelope is lost or duplicated. It is cross-platform across JVM, JS, Native, and Wasm. (by @fwbrasil in #1673)

  • kyo-doctest (README) is a kyo-agnostic sbt plugin plus JVM runner that compiles (does not run) every fenced Scala block in a Markdown file against its module's classpath and validates every internal link, replacing mdoc. A block-level DSL in HTML-comment modifiers controls scope, expectation, target platforms, and setup prelude, and a content-hash cache keeps per-block cost near zero. Every module now carries a README, so the whole project is documented. (by @fwbrasil in #1645)

  • kyo-case-app (README) connects case-app to Kyo entrypoints (KyoCaseApp[T], KyoCommand[T]) across JVM, JS, Native, and Wasm, so you can build a declarative CLI with options, positionals, and subcommands and register effectful work after parsing. It also introduces KyoAppRunner in kyo-core as the shared, public API for running Kyo effects after a third-party framework has started. KyoApp is refactored onto it, and it is made public so external entrypoints (decline, zio-cli, Spark-style mains) can use it without copying runner code. (by @DamianReeves in #1619)

  • kyo-website (README) is a Scala 3 static-site generator plus a kyo-ui and kyo-http single-page bundle that replaces the hand-maintained docsify docs site. Content reads the live repo: the root README's Modules table drives the catalog and sidebar, each module README renders to a page, and a shared SiteApp renderer produces both build-time HTML (JVM) and the browser's first paint (JS). (by @fwbrasil in #1676)

Platform parity

  • WebAssembly (WasmGC) as a fourth cross-build target: a WasmPlatform is added to the sbt cross-build (Scala.js with the experimental WebAssembly linker and ESModule output), and JS and Wasm share platform code through a js-wasm source directory. Every module targeting Scala.js now also targets Wasm, except the two cats-effect bridges (kyo-cats, kyo-compat-ce, which lack upstream Wasm support), and the full stack up to and including kyo-http servers runs on WasmGC, serving real HTTP and HTTPS over Node sockets. WebAssembly artifacts are published to Maven Central as kyo-<module>_sjs1-wasm_3, alongside the JVM, JS, and Native coordinates. Requires Node 24 or later. (by @fwbrasil in #1663)

Improvements

Concurrency and scheduler

  • Reliable fiber interrupt delivery: A lost-update race on the single @volatile Task.state could clobber a just-set interrupt bit, so a fiber interrupt could be delayed to reach the worker via the fiber state. Interruption is now sourced from IOPromise, Task.requestInterrupt is removed and Task.state packs only runtime and preemption, so there is no cross-thread interrupt write to race. (by @fwbrasil in #1628)

  • Interrupt starvation under queue pressure: a CPU-bound fiber preempted into a worker's run queue and then interrupted could be starved before observing the interrupt, because a completed task did not reset its accumulated runtime and the heap did not re-sift an in-place priority change. A completed task now calls Task.resetRuntime(), and each worker rebalances when the global interrupt epoch advances, re-heapifying so the reset task surfaces within a bounded, load-independent delay; the generic queue is replaced by a Task-specialized 4-ary min-heap. (by @fwbrasil in #1667)

  • Fatal Throwable no longer kills a worker: IOTask.eval's panic catch went through Result.Panic.apply, which rethrows fatal Throwables (LinkageError, OOM), so the rethrow escaped the catch, the worker thread died, the IOPromise was never completed, and awaiters hung forever. An inner try/catch now falls back to the Result.Panic constructor so the promise is always completed and the panic surfaces through the normal fiber result channel. This is a best-effort fix at the scheduler boundary. (by @gcsolaroli in #1528)

  • Signal reactive combinators: Signal[A] exposed only map and streaming helpers, leaving the standard reactive combinators missing. It now carries switchMap, zip, and combineLatest on the instance and Signal.awaitAny, zipAll, and combineLatestAll on the companion, named after their Rx equivalents because they do not satisfy the monad laws. A bug where the first subscriber to a fresh SignalRef got different interrupt semantics than every later one is fixed as well. (by @fwbrasil in #1648)

  • Clock.withTimeControl shares one timeline in nested scopes: a nested withTimeControl scope used to create a fresh controlled clock with a separate timeline. It now reuses the current local TimeControl when one is already active, so nested scopes share one clock timeline. (by @cerredz in #1601)

General

  • Sync.acquireReleaseWith bracket operator: adds a lightweight acquire/use/release bracket on Sync that releases only after a successful acquire, covering success, use-side panic cleanup, and acquire-side panic. (by @cerredz in #1604)

  • KyoApp accepts non-Throwable aborts: KyoApp used to accept only Abort[Throwable]. Its boundary now widens to Abort[Any], converting non-Throwable abort failures to a typed KyoApp.FailureException at the app boundary while keeping the existing Throwable and panic handling. (by @cerredz in #1605)

Streams, parsing, and data

  • Parse.repeat drops from quadratic to linear: ParseInput.remaining returned tokens.drop(position), materializing the entire remaining input on every call, so Parse.repeat over n tokens made O(n) calls each copying O(n) data. ParseInput now caches an indexed chunk derived once at construction and reads positions in constant time. (by @fwbrasil in #1672)

  • Stream#zip: Stream lacked the standard zip method. It now takes values chunk-at-a-time, emits zipped chunks, and completes when one stream is done with no leftovers remaining; there is no N-ary variant, since deriving the tag for every combination is impractical. (by @johnhungerford in #1539)

  • Path confinement, piped subprocess stdin, and Stream.find: three additive gaps surfaced building filesystem-server features. Path gains cwd, ancestors, realPath (resolves symlinks), and confinedTo(root), which realPath-resolves both sides and aborts if the target is not under the root, making it the recommended way to validate untrusted paths. Process.Input.Pipe and Command.pipeStdin stream content into a subprocess' stdin, and Stream.find short-circuits upstream on the first match. (by @fwbrasil in #1656)

  • Windows drive letter dropped by Path reconstruction: kyo.Path modeled an absolute path as a driveless segment list, but on Windows the JVM keeps the drive in getRoot, so parts for C:\Windows\System32 dropped the drive and any reconstruction silently re-anchored the path to the process's current drive, corrupting I/O and making confinedTo drive-blind (a multi-drive sandbox-escape footgun). The jvm-native PathPlatformSpecific.parts now emits the drive designator as the leading root segment (gated on Platform.isWindows), reconstruction is drive-rooted, and the js-wasm variant round-trips identically, so paths preserve their drive and confinedTo enforces confinement per-drive. (by @DamianReeves in #1679)

  • Schedule combinator fixes: Schedule.linear(base) produced a doubling sequence instead of a linear one, one of nine combinator bugs of similar shape. Linear now carries (current, step), Jitter clamps its factor before multiplying (it was biasing the mean about 12.5% high), Repeat counts emissions and restarts so fixed(X).repeat(N) emits N, caps are respected, and several smart-constructor reductions are corrected, so the combinators now match their docstrings. (by @fwbrasil in #1632)

  • Frame.calleeName: Frame.methodName returned the enclosing method's name with no accessor for the syntactic name of the called function. methodName is renamed to callerName, and a sibling calleeName is added that reads the source backwards from the splice. (by @fwbrasil in #1642)

Schema, HTTP, and integrations

  • kyo-schema YAML support: kyo-schema had no YAML, and a produced CST could be edited and rendered but not consumed as a decode source. Full YAML support now covers parse and visit APIs, schema encode and decode, YAML 1.2 defaults with a 1.1 scalar mode, document-stream selection and merging, tags, anchors, aliases, block scalars, and flow collections, plus a direct decode path with bounded source replay and no JSON bridge. A parsed-and-edited CST becomes a pipeline source that visit, render, parse, and decode accept, with comments intact. (by @DamianReeves in #1652, #1655)

  • kyo-schema Ion codec: adds Amazon Ion encode and decode entry points plus IonReader and IonWriter, supporting schema-shaped Ion text (structs, lists, maps, blobs, typed nulls, comments, symbols as strings, timestamps, annotations as metadata). Ion.decode is a single-value schema decode API; Ion annotations are accepted and validated as input but not preserved or emitted. (by @hearnadam in #1659)

  • kyo-schema derivation fixes: Schema.derived[F[A]] crashed at expansion for any generic case class with a default-valued field, and discriminator-tagged sum types nested inside another case class lost their discriminator on JSON and Protobuf round-trips. Default-method references now apply the case class's type arguments before lifting, and nested-field serialization routes through the transform-aware dispatch layer so the discriminator survives. (by @fwbrasil in #1641)

  • caliban GraphQL subscriptions over WebSockets: kyo-caliban had no WebSocket integration, so GraphQL subscriptions did not work over the kyo-http server. A WebSocket endpoint at ${config.path}/ws now bridges kyo-http's HttpWebSocket to caliban's CalibanPipe, supporting both the graphql-transport-ws and legacy graphql-ws subprotocols and all six WebSocketHooks. Three pre-existing non-WebSocket bugs are fixed as well: malformed POST, SSE, and upload bodies returned HTTP 500 instead of a GraphQL error envelope. (by @fwbrasil in #1634)

  • kyo-http WebSocket frame-size enforcement and reader failures: HttpWebSocket.Config.maxFrameSize was not enforced during inbound decoding, its 64 KiB default was too low for realistic single-frame payloads, and an aborted reader silently discarded its typed HttpException. The cap is now enforced while decoding inbound frames on both server and client read loops, the default is raised to 16 MiB, and both monitor fibers pattern-match the reader's result and log the typed exception instead of discarding it. (by @NguyenCong2k in #1625, @fwbrasil in #1643)

  • kyo-http client filter scopes: the client side could not apply cross-cutting concerns (auth, request IDs, logging, tracing, metrics) consistently across convenience calls and WebSocket handshakes. clientFilter is added to HttpClientConfig with filter/filters builders and HttpClient.withFilter/withFilters for scoped configuration; filters compose in order and apply to WebSocket upgrade handshakes as well as HTTP requests. (by @DamianReeves in #1662)

STM and direct

  • kyo-stm coverage and five concurrency fixes: kyo-stm carried several latent concurrency bugs. TRef.use now consults the write lock and aborts on a write-locked ref (it used to permit the read, violating opacity), bounded barging lets a writer make progress under sustained reader pressure, and STM.run commits a TRef holding null. TTable.insert now draws ids from a lock-free counter that skips upsert-occupied ids while TTable.Indexed.upsert drops stale index entries, so concurrent inserts no longer contend, overwrite upserted records, or leak index entries. (by @fwbrasil in #1629)

  • kyo-stm TMap.initWith and contains: TMap.initWith did not apply or return the provided function's result, and TMap.contains(key) checked only map non-emptiness instead of the requested key. Both are fixed. (by @NguyenCong2k in #1624)

  • kyo-direct macro hygiene and Validate edge cases: a val whose right-hand side used .now inside a direct { ... } block emitted trees with the wrong owner for the synthetic monad given, failing dotty's owner-consistency check, and three Validate bugs rejected valid programs. The inner cps.await quote is now built with the correct owner, and Validate handles multi-statement async-shifted lambdas, pattern-bound effect-typed identifiers, and by-name-parameter .now with a clear error. (by @fwbrasil in #1633)

Documentation and tooling

  • Unified scaladocs and public-surface cleanup: scaladoc rendering was broken across modules (Scala 2 wiki syntax rendered as literal text, scalafmt collapsing docstrings) and kyo.* carried implementation-detail and platform-internal traits. This adds sbt-unidoc with a Scala 3 fix and a workflow that publishes scaladocs on release tags, bulk-converts wiki syntax to Markdown across all modules, and relocates a surgical set of types off the public surface (7 or more traits to kyo.internal; Rendered to Render.Rendered, Subject to Actor.Subject, HttpFilterFactory to HttpFilter.Factory, and others). (by @fwbrasil in #1635)

  • -Werror on Scala 3: no -Werror gate existed on Scala 3, so warnings accumulated silently across modules. -Werror is now appended for Scala 3 (Scala 2.13 kyo-scheduler excluded), the resulting warnings are fixed, deprecated FFI shim files and the kyo.IO/kyo.Resource top-level aliases are removed, and the codebase is warning-clean. (by @fwbrasil in #1675, #1636)

  • Explicit JVM suffix on sbt project ids: the JVM variant of each cross-platform module used the bare module name (kyo-core) while JS, Native, and Wasm carried suffixes, a recurring source of confusion. Every JVM project id now carries a JVM suffix (kyo-core becomes kyo-coreJVM); published Maven coordinates are unchanged, so only sbt project ids are affected. (by @fwbrasil in #1670)

  • Doc clarifications: the docsify site's deep module-README links resolved instead of 404'ing through a repository basePath (later superseded by the kyo-website SSG), and the Memo README example is clearer about Memo.run's return type. (by @rbobillot in #1651, @cerredz in #1602)

Breaking changes

  • Frame.methodName renamed to Frame.callerName (no alias; sibling calleeName added). (by @fwbrasil in #1642)
  • The Text type is removed: all Text signatures become String, Render[A].asText becomes Render[A].asString, t"" now returns String, and the Ansi Text overloads are gone. (by @fwbrasil in #1646, supersedes @cerredz in #1603)
  • kyo.IO and kyo.Resource top-level deprecated aliases removed (use Sync and Scope). (by @fwbrasil in #1675)
  • Public-surface relocations: Rendered to Render.Rendered, Subject to Actor.Subject, HttpFilterFactory to HttpFilter.Factory, Schema.Builder/Compare/Focus/Modify nested under Schema, and CalibanRunner and Image relocated; 7 or more traits moved to kyo.internal. (by @fwbrasil in #1635)
  • sbt project id change (not a Maven-coordinate change): cross-platform JVM project ids gain a JVM suffix (kyo-core becomes kyo-coreJVM), so <module>/test no longer resolves; use <module>JVM/test. (by @fwbrasil in #1670)
  • Module removed: kyo-playwright (replaced by kyo-browser). (by @fwbrasil in #1644)
  • Native behavior change: trampoline depth lowered from 512 to 256 to match Wasm (native frames are larger). (by @fwbrasil in #1663)
  • Wasm build requires Node 24 or later. (by @fwbrasil in #1663)

New Contributors

Full Changelog: v1.0.0-RC2...v1.0.0-RC4