Skip to content
This repository was archived by the owner on Sep 17, 2026. It is now read-only.

Latest commit

 

History

History
184 lines (154 loc) · 9.22 KB

File metadata and controls

184 lines (154 loc) · 9.22 KB

Alder Runtime and Targets

Status: current direction, everything provisional.

Targets

A package declares its target in alder.jsonc:

{ "type": "application", "target": "cloudflare" } // or "standalone"

There are two targets because there are two runtimes. Everything else is a library or a framework switch, not a target.

you write main entry generated from src/routes/
cloudflare a worker with a fetch handler the web framework on Workers
standalone CLI, TUI, self-hosted HTTP server the web framework self-hosted
  • cloudflare runs on workerd. standalone runs on the embedded runtime inside the alder binary, with no platform underneath.
  • A CLI is fn main(). A proposed TUI imports tui and calls tui.run(App); a proposed server imports http and calls http.serve(handler).await inside async fn main(). Same target, same toolchain; these modules require explicit imports. http.serve is implemented; TUI rendering remains deferred.
  • The web framework switches on when src/routes/ exists. A purely client-side app is the web framework with ssr = false and prerender = true on the root layout, producing static files; there is no separate browser target.
  • One proposed standard library with target-gated modules (like Rust cfg). fs, tui, and raw sockets are standalone-only; KV, D1, and the other bindings are cloudflare-only; the web-standard surface is both. Importing cloudflare/kv in a standalone package would be a compile error.
  • Library packages are target-neutral unless they declare a target; the compiler checks that only target-neutral code is reachable from them.
  • Multiple entry points (a worker, a migration CLI, a TUI admin) are multiple workspace members. Workspaces already exist in alder-config.
  • Web packages additionally split server and client code within one package. See web.md.

JavaScript output

  • One JS module per Alder module, bundled with rolldown (as a Rust library inside the compiler) for a Vite-like experience with no separate tool.
  • Option[a] is a | null. Nested options box the inner value.
  • Number is a JS number, BigInt a JS bigint, Array a JS array, records are plain objects, and enums are tagged objects. Unit variants are frozen { $: "Name" } singletons, tuple variants use _0, _1, … fields, and record variants retain their field names.
  • Trait dispatch is dictionary passing resolved at compile time where the type is known. Open: representation for HKT dictionaries.

Kernel

The runtime is a hand-written TypeScript kernel shipped by the compiler, exposed to the Alder stdlib through extern (Elm's kernel model). It contains the value ABI, structural equality, Option and Result helpers, collection primitives, the test registry, M4's task/fiber scheduler, and M6's signals, stores, DOM/SSR/hydration, routing, resource and HTTP orchestration. Render owners capture runtime provider context, including asynchronous SSR. Static context-availability/provider checking remains deferred; runtime context inheritance is not a claim that the services/layers redesign has landed.

Everything above the kernel is written in Alder.

Tasks, fibers, and Promise externs

A task is a reusable lazy factory for a JavaScript generator. Calling an Alder function inferred as asynchronous constructs the task without running its body. $runMain starts task-producing main and test entries. The scheduler resumes generators through small tagged operations, processes at most 1,024 operations per turn, and yields to the host timer queue so ready work cannot starve I/O.

Every fiber owns a scope. A child is registered before it starts; completing a parent interrupts and joins live children before running LIFO finalizers. Interruption is cooperative and remains pending through uninterruptible cleanup. fiber.all is ordered and fail-fast for defects/interruption; fiber.race selects the first exit, interrupts every loser, and waits for loser cleanup. Typed Alder Err values are data, not fiber defects.

A non-kernel extern declared Task[a] is the explicit Promise boundary. The compiler emits a lazy thunk passed to $tryPromise; no ordinary extern result is dynamically promoted. Fulfillment produces a, while synchronous foreign throws, malformed returns, and raw rejections produce contextual foreign defects. A Promise fulfilling with Result[a, e] retains the ordinary typed .await? path.

#[extern("module", "symbol", "abort")] opts into cancellation: the bridge creates an AbortController before invoking the thunk and appends its signal to the call. Other Promises are not claimed to be cancellable. Interruption invalidates their waiter while the bridge continues observing eventual rejection.

The lifecycle, scope, scheduling, and Promise invariants were reimplemented with Effect v4 commit bd393d63c19bdd0ab212d95576cec89051c8501c (4.0.0-rc.112, MIT) as the semantic reference. Alder keeps a much smaller generator ABI and does not adopt Effect's instruction algebra, Cause model, Context API, tracing, or public fiber-ref system. See docs/effects-internals.md for exact behavior and divergences.

Embedded runtime

The alder binary embeds V8 through one exact compatible Deno crate family. The versions are intentionally pinned in lockstep:

crate version
deno_core 0.403.0
deno_webidl 0.250.0
deno_web 0.281.0
deno_crypto 0.264.0
deno_fetch 0.274.0
deno_fs 0.160.0
deno_net 0.242.0
deno_http 0.248.0
deno_websocket 0.255.0

In this Deno family URL and console implementations live in deno_web, so there are no separate deno_url or deno_console crates. Alder installs its web globals in its own extension bootstrap and explicitly selects AWS-LC as Rustls's process provider, avoiding feature-unification-dependent TLS startup. deno_node is not embedded: Node compatibility is a non-goal.

  • That surface is, by design, the same one Cloudflare Workers expose (fetch, Request/Response, URL, streams, crypto.subtle, timers, WebSocket). The kernel and stdlib are written once against it; standalone adds file system and raw network access on top.
  • alder run executes standalone targets with no external runtime installed. A standalone build can also emit a self-contained binary (as deno compile does), and a container image is that binary; there is no external runtime to pick.
  • Macros and comptime blocks execute in the same embedded V8 at build time.
  • TUI I/O is provided from Rust (terminal raw mode, events, layout).
  • CLI argument parsing is a stdlib derive, not a compiler feature: #[derive(Args)] on a record and #[derive(Subcommand)] on an enum, with doc comments as help text, optional fields as optional flags, and cli.parse() typed by annotation (clap's derive model).
  • Binary size (~100MB) is accepted.
  • npm packages that need Node built-ins are out of scope for extern until wrapped by a first-party package.

The only direct generated-entry/host boundary is the frozen, non-enumerable globalThis.__alderHost object (including args, exit, HTTP serving, and the structured build-report sink). Alder modules use stdlib/kernel functions rather than Deno ops. Standalone execution loads the bundled ESM as the main module and drives V8's event loop to completion.

Cloudflare

Cloudflare concepts are ordinary types implementing traits, marked with attributes. The bundled cloudflare module supplies the types and traits; the compiler extracts their metadata and emits Worker adapters and wrangler.jsonc. Exact working declarations, supported binding handles, Durable Object storage, queues, and workflows are in cloudflare-web.md.

  • Binding aliases (KV, D1, R2, Queues, Hyperdrive, Workflows) retain distinct provider identities and are installed by the generated request entry point. Declaring a handle does not implement an entire service-specific data API; the D1/Hyperdrive query layer remains M7.
  • Development runs on pinned Miniflare installed with alder cloudflare setup, never by delegating to wrangler dev or Vite. standalone targets use deno_core with HMR.

Deployment

alder deploy PATH --name EXACT_WORKER --account-id EXACT_ACCOUNT builds a Cloudflare web application and invokes the pinned Wrangler deployment path with generated config, no rebundling/autoconfiguration, and --keep-vars. Credentials remain in Wrangler's auth environment/store, not generated client code. --dry-run validates and packages locally without an upload. Existing Durable Object migration history can be supplied explicitly with --migrations; the compiler does not invent destructive history. New object classes use the declarative exports config.

Choose a dedicated preview Worker explicitly before deploying. D1/Hyperdrive schema migrations, standalone container-image builds, and named environment inheritance are not implemented M6 deploy features. Standalone built servers run directly with alder run dist/server.mjs -- --port 3000. See web-development.md for exact commands and external gates.