Status: current direction, everything provisional.
A package declares its target in alder.jsonc:
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 |
cloudflareruns on workerd.standaloneruns on the embedded runtime inside thealderbinary, with no platform underneath.- A CLI is
fn main(). A proposed TUI importstuiand callstui.run(App); a proposed server importshttpand callshttp.serve(handler).awaitinsideasync fn main(). Same target, same toolchain; these modules require explicit imports.http.serveis implemented; TUI rendering remains deferred. - The web framework switches on when
src/routes/exists. A purely client-side app is the web framework withssr = falseandprerender = trueon 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 arestandalone-only; KV, D1, and the other bindings arecloudflare-only; the web-standard surface is both. Importingcloudflare/kvin astandalonepackage 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.
- 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]isa | null. Nested options box the inner value.Numberis a JS number,BigInta JS bigint,Arraya 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.
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.
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.
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;standaloneadds file system and raw network access on top. alder runexecutesstandalonetargets with no external runtime installed. Astandalonebuild can also emit a self-contained binary (asdeno compiledoes), and a container image is that binary; there is no external runtime to pick.- Macros and
comptimeblocks 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, andcli.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
externuntil 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 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 towrangler devor Vite.standalonetargets use deno_core with HMR.
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.
{ "type": "application", "target": "cloudflare" } // or "standalone"