- A Perry checkout. The commit Coop is built against is pinned in
perry-main.lockat the repository root — thecommitfield, not the version string. Perry's version does not change on every commit, so it cannot tell you whether your checkout matches. - The Rust toolchain named in
rust-toolchain.tomlat the repository root. It is a dated nightly, not stable:perry-runtimeuses nightly-only float intrinsics, and because.perry-mainis excluded from this workspace Perry's own toolchain file does not apply to builds run from here. rustup installs it on first use. Also LLVM 22, for Perry's in-process backend. - Node, for the developer tooling and test fixtures.
./scripts/build-perry-libraries.shThis is the step that makes Coop different from an ordinary Perry build. It
temporarily switches perry-runtime to crate-type = ["dylib"], builds it with
the symbol-suppression stdlib feature, and links coop-perry-stdlib-shared
against it. The outputs are .dylib/.so files — not .a archives, so the
usual "rebuild the static wrapper crates" advice from Perry's own docs does not
apply here.
The script restores the manifest on exit via a trap, including on failure.
To confirm the build actually produced new artifacts, compare the provider
sha256 and the manifest's recorded perry_commit — not file timestamps.
cargo build --release -p coop-daemon -p coop-worker -p coop-cli./target/release/coop-cli dev ./examples/landingdev compiles the deployment and serves it locally. The first compile is slow —
Perry is doing real code generation — and subsequent ones are cached by content.
A deployment is a directory containing coop.toml and your handlers:
myapp/
├── coop.toml
└── handlers/
└── hello.ts
name = "myapp"
version = "0.1.0"
[hosts]
domains = ["myapp.test"]
[[handlers]]
file = "handlers/hello.ts"
path = "/hello"
method = "GET"// handlers/hello.ts
import { CoopRequest, respond } from "@coop/runtime";
export function handle(reqJson: string): string {
const req = new CoopRequest(reqJson);
return respond(200, { "content-type": "text/plain" }, `hello from ${req.path}`);
}Then:
./target/release/coop-cli dev ./myapp
curl -H 'Host: myapp.test' http://127.0.0.1:PORT/helloThe worker refuses to load the app. You will see an ABI mismatch naming two Perry identities. That is the provider guard working: the application was compiled against a different runtime than the one loaded. Rebuild the providers (step 1) and recompile the app. See Shared runtime providers.
A handler compiles under tsc but not under Perry. Perry implements a subset.
See The TypeScript subset.
Tests appear to pass but nothing ran. Several suites self-skip when fixtures
are missing, and a skip is an early return — indistinguishable from a pass in
the summary line. Run with --nocapture and grep for skip:.