Releases
dev.yuri4n version 0.2.0-alpha.1 is published in the GitHub Packages Maven registry for yuri4n/h34s. The prerelease and successful release workflow record publication of all nine artifacts. The same workflow then resolved the Kyo, ZIO, Kyo Schema and ZIO Schema consumers from the authenticated registry, and each one served real h3 requests.
0.2.0-alpha.1 changes the module set and the handler API: Kyo is the core runtime, the ZIO modules adapt it, and every module builds with Scala 3.9.0. Its release notes summarize the changes. The tables and commands below describe 0.2.0-alpha.1. Where 0.1.0-alpha.1 differs, this page says so.
The older 0.1.0-alpha.1 stays published, with eleven artifacts and the older API. Its prerelease and release workflow record that publication, and its release notes list what that version contains.
The reusable JavaScript tooling is published separately as @yuri4n/h34s-nuxt@0.2.0-alpha.1 in its own npm release. See npm build tooling for package installation, shared configuration, and local tarball review. This package version registers a new Nitro plugin that the 0.2.0-alpha.1 Scala libraries need for runtime config, storage mounts, and application-scoped resources. The 0.1.0-alpha.1 package does not have it. Use @yuri4n/h34s-nuxt at the Scala libraries' version or later. With the 0.1.0-alpha.1 libraries, use the matching @yuri4n/h34s-nuxt@0.1.0-alpha.1. The npm release workflow checked that the registry download matches the tested archive. The migration guide maps the 0.1.0-alpha.1 API to the new one.
What 0.2.0-alpha.1 provides
| Capability | Kyo | ZIO |
|---|---|---|
Handlers as Request => body functions | KHandler.define | ZHandler.define |
| Response head as local state, written once | Var[ResponseHead] | ZState[ResponseHead] |
Wire format {data} / {errors:[{message,path}]} / 204, configurable | HandlerOptions | HandlerOptions |
| Bounded body reads with 400/413/415/422 | request.body.json, text | same |
| Safe errors, opaque 500s, development and debug diagnostics | yes | yes, with Cause.prettyPrint |
Runtime config from Scala, Secret | RuntimeConfig | ZRuntimeConfig |
| Application-scoped resources released on Nitro close | Shared | ZShared |
| Streams with cancellation that waits for cleanup | kyo.Stream | ZStream |
| Nitro storage | KHubKv.mount | ZHubKv.mount |
| One schema for native values, JSON, YAML and validation | Kyo NativeSchema | ZIO NativeSchema (no YAML on Scala.js) |
| Testing without h3 | Request.fake + Var.runTuple | Request.fake + ZIO.stateful |
On main, for the next release: handlers declare their routes in Scala with .at(Route...), and Nuxt types $fetch from generated declarations. See routes and typed $fetch. In 0.2.0-alpha.1, mount each handler with a one-line route file. Also on main: handlers send streams with Response.sendStream instead of returning them, and tests call KHandler.test or ZHandler.test; see streaming and testing. The models that your routes return also get TypeScript names, which your app imports from #h34s/models; see use the models in your app.
Toolchain
Use Node 24, pnpm 10.34.4, sbt 1.11.7, Scala.js 1.22.0, and Java 25. Every module compiles with Scala 3.9.0. The Kyo modules use Kyo 1.0.0-RC7; the ZIO modules use ZIO 2.1.26, the version that kyo-zio pins. Kyo's macros require Java 25 at build time, so build your application with Java 25 too. Java and sbt are build tools; the application runs inside Nitro/Node.
In 0.1.0-alpha.1, the native and ZIO modules used Scala 3.5.2 and only the Kyo modules used Scala 3.9.0.
Scala 3 dependency names have the _sjs1_3 suffix. Use sbt's %%% syntax to select it. A Kyo-only application never resolves ZIO. A ZIO application resolves Kyo too, because the ZIO adapter runs on the Kyo core.
Authenticate package reads
The repository is public, and so are its Maven packages: GitHub's Maven registry gives a package the visibility of its repository. Every download from GitHub Packages still needs a token with package-read permission, also for a public package. Installing without a token waits for the public registries planned after the coordinate decision in issue #21 (ADR-0013). On a developer machine, use a GitHub personal access token (classic) with read:packages from any GitHub account. If organization SSO applies, authorize the token for that organization.
Provide GITHUB_PACKAGES_USER and GITHUB_PACKAGES_TOKEN through your shell or secret manager. Keep the token out of committed sbt files, .env files included in builds, and command output. In a consumer's build.sbt, add:
ThisBuild / resolvers += "h34s GitHub Packages" at
"https://maven.pkg.github.com/yuri4n/h34s"
ThisBuild / credentials ++= (for {
user <- sys.env.get("GITHUB_PACKAGES_USER")
token <- sys.env.get("GITHUB_PACKAGES_TOKEN")
} yield Credentials(
"GitHub Package Registry",
"maven.pkg.github.com",
user,
token,
)).toSeq
GitHub's Maven registry uses repository-scoped permissions. The h34s repository's own workflows can publish and verify these packages with their GITHUB_TOKEN.
A different repository, including yurian.land, needs a classic PAT with read:packages. A token from any GitHub account works, because the Maven packages are public. Store it as that consumer repository's PACKAGES_READ_TOKEN Actions secret and provide the token owner's username as the PACKAGES_USER Actions variable. GitHub reserves the GITHUB_ prefix for saved secret and variable names. Map the saved values into the environment names read by sbt:
env:
GITHUB_PACKAGES_TOKEN: ${{ secrets.PACKAGES_READ_TOKEN }}
GITHUB_PACKAGES_USER: ${{ vars.PACKAGES_USER }}
The consumer's own GITHUB_TOKEN and the granular package “Manage Actions access” settings do not establish cross-repository Maven access.
HTTP 401 usually means missing or invalid credentials. HTTP 403, or a package appearing absent despite a successful release, can mean that the account or workflow repository lacks access. Check the registry URL, package version, token scope, and package access before changing dependency versions.
Add the API to a Scala.js application
Pin the build tools:
# project/build.properties
sbt.version=1.11.7
// project/plugins.sbt
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.22.0")
For ZIO, add the registry and credentials above, then configure your application module:
import org.scalajs.linker.interface.ModuleKind
lazy val server = project.in(file("scala-server"))
.enablePlugins(ScalaJSPlugin)
.settings(
scalaVersion := "3.9.0",
libraryDependencies +=
"dev.yuri4n" %%% "zio-h3-ext" % "0.2.0-alpha.1",
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.ESModule)),
)
For Kyo, replace zio-h3-ext with kyo-h3-ext. Both APIs use Scala 3.9.0; in 0.1.0-alpha.1 the ZIO API used Scala 3.5.2. Keep Java 25 selected when compiling. These dependencies bring their runtime and the shared native layer transitively.
Also create the .jvmopts file from the adoption guide. The verified consumer builds use it; a build without it has not been tested.
The current h3-ext has hand-written, internal h3 and Nitro facades. Your handlers receive a Request value and return a body; they do not call facades. 0.1.0-alpha.1 instead packaged ScalablyTyped-generated facades under dev.yuri4n.h34s.facades.h3 and dev.yuri4n.h34s.facades.std. 0.2.0-alpha.1 removes those packages.
Install h3 in the Nuxt application as a runtime dependency:
pnpm add h3@1.15.4
Maven dependencies supply Scala.js code, while pnpm supplies the JavaScript module imported by that code. See adoption for linking output, mounting exports alongside TypeScript routes, and composing application services outside handlers. See development for the complete repository example and Kyo for its independent application entry point.
Independently consumable modules
Every row uses organization dev.yuri4n and Scala.js coordinates selected by %%%. 0.2.0-alpha.1 publishes these nine library modules:
| Artifact | Purpose | Brings |
|---|---|---|
h3-ext | Native layer: request snapshot, response head, safe errors, Secret, internal h3/Nitro facades | No effect runtime |
kyo-h3-ext | The core: KHandler, body reading, RuntimeConfig, Shared, streaming | Kyo |
kyo-nuxthub-ext | Optional Kyo storage over Nitro mounts (KHubKv.mount) | Kyo |
native-converter-kyo-schema-ext | Optional derives NativeSchema from a Kyo Schema | Kyo Schema |
zio-h3-ext | The ZIO adapter: ZHandler, ZRuntimeConfig, ZShared, ZStream bodies | ZIO and Kyo |
zio-nuxthub-ext | Optional ZIO storage over Nitro mounts (ZHubKv.mount) | ZIO and Kyo |
native-converter-zio-ext | NativeConverter support for ZIO data types | ZIO, no Kyo |
native-converter-zio-json-ext | NativeConverter support for ZIO JSON values | ZIO JSON, no Kyo |
native-converter-zio-schema-ext | Optional derives NativeSchema from a ZIO Schema | ZIO Schema, no Kyo |
0.1.0-alpha.1 published eleven artifacts. 0.2.0-alpha.1 removes three of them and adds one:
zio-js-extis removed. It no longer contained code.nuxthub-extis removed. The storage adapters open a Nitro mount by name (KHubKv.mount("kv")) through the h34s Nitro plugin, so your application does not supply a native KV object.zio-h3-schema-extis removed. Usenative-converter-zio-schema-extwithderives Schema, NativeSchemaand read the body withrequest.body.json[A].native-converter-kyo-schema-extis new.
See NuxtHub KV for storage configuration and driver limitations.
Schema support stays optional. Add native-converter-zio-schema-ext (with "dev.zio" %%% "zio-schema-derivation" % "1.7.5") or native-converter-kyo-schema-ext next to your handler module when your models need one schema for validation and conversion. Plain derives NativeConverter models need neither. The schema bridges do not choose a JSON or YAML library for you: add kyo-schema-json, kyo-schema-yaml, or zio-schema-json yourself when you need those formats. See schema for supported forms and validation behavior.
The root project, aggregates, applications, and examples, including zioMigrationsExt in examples/zio-migrations, are excluded from publication. Database services, configuration, models, and AppHandler composition stay in application modules. The reusable libraries do not require the example's Skunk database.
Releases include binary and source jars. build.sbt still omits Scaladoc jars. In 0.1.0-alpha.1 the reason was that Scala 3.5's documentation generator failed on the generated inline h3 facades. Those facades are gone, but publishing Scaladoc again has not been tested. The documentation website and source jars remain the API guides.
Verified, and how
On macOS x86_64 with JDK 25 and Node 24.19.0: 276 Scala tests across the native layer, the Kyo core, the ZIO adapter, both schema bridges, storage and the ZIO demo; the npm package's Node tests; fresh Maven consumers for Kyo, ZIO and both schema bridges (Kyo consumers resolve no ZIO); a production build of the demo serving the Kyo and ZIO routes, debug mode with NUXT_H34S_DEBUG=true, and releasing the shared pool on SIGTERM; and nuxt dev with development diagnostics and Kyo stacks mapped to Scala sources. For the generated routes on main, the demo has no route files: a production build served the Kyo and ZIO routes from the manifests, a type check proved typed $fetch from the declarations, and the packed module mounted a route added while nuxt dev ran.
In a Claude Code cloud session on Ubuntu 24.04.5, x86_64 (2026-10-19): a production build of the demo; the ZIO Notes routes against PostgreSQL 16 with scram-sha-256, and the release of the pooled sessions on SIGTERM; and the kyo-sql code of the database guide, which passed its 5 tests against PostgreSQL 16 in Node through sbt.
In CI, on GitHub's Linux x86_64 runners: every Scala test suite, production linking and the four fresh Maven consumers, on the merge commit and again in the release workflow; the packed npm module in Nuxt 4.3.1 with development and production HTTP checks; and, after publication, the four consumers resolved from GitHub Packages and a byte-for-byte check of the downloaded npm archive. The postgres job runs the kyo-sql suite, the demo's production build and the Notes routes against PostgreSQL 16 on pull requests and on pushes to main that touch the libraries or the example. It skips release tags, so it does not gate a release. Details and run links are in compatibility.
Not verified yet
- Relinking the real demo under
nuxt dev, and breakpoint stepping (diagnostics and source maps undernuxt devwere checked). - kyo-sql inside Nitro: the database guide code ran in Node through sbt, not in a Nitro bundle.
- NuxtHub KV as a Nitro storage mount.
- Linux beyond the CI checks, Windows, Nix, and non-Node Nitro presets.
Limits to plan around
- Generated types: response bodies only. Request bodies and query strings have no generated type, and a converter that does not describe itself gives
unknown. - Streaming: in
0.2.0-alpha.1, direct Node HTTP only; onmain, also Nitro's in-process local fetch. No end-to-end memory bound, because h3 does not wait fordrain. - ZIO Schema: no enums, sealed traits or maps yet; no YAML on Scala.js.
- Finalizers: failures are logged and do not change the response, in Kyo and in ZIO.
- Locale data: Kyo brings a large currency database; exclude it in applications that do not need it.
- Build memory: Nitro bundles the linked Scala.js twice. With
scalaPrerender: 'stub', which the demo sets, the demo'snuxt buildpassed with a 4096 MiB heap limit. The demo's scripts keep--max-old-space-size=8192as a margin; see build memory.
Planned
- Nix and sbt-native-packager paths over the same build pipeline.
- Interactive documentation: compiler-derived types, symbol links and runnable examples.
The documentation shows static source examples. It has no semantic hovers, no editable compiler and no hosted playground yet.
About this documentation
This documentation is written with AI assistance under the maintainer's direction, and errors are tracked as issues.
Publish a release
Maintainers validate, tag and publish releases with the procedure in the maintainer release guide.