Library / SDK
evoluhq/evolu avatar
evoluhq/evolu

Evolu refuses to build until the SQLite wasm matches its pinned hash

Local-first platform and TypeScript library

1,909 stars74 forksTypeScriptMIT

At a glance

What is it?
Evolu is a local-first TypeScript platform built on CRDTs and SQLite wasm, published as a set of scoped packages including a relay and a React Native binding. What the repository documents in depth is the contributor workflow rather than consumer usage, and the strictest part of it is a WebAssembly file that must hash to a pinned value or the playgrounds refuse to start.
Who is it for?
Adopt Evolu if you are building an offline-first application that wants SQLite on the client and CRDT merge semantics without designing a sync protocol, and you can live with a wasm binary in your toolchain. Do not evaluate it from this repository alone, since the README documents contributor setup rather than how to consume the library, which lives on evolu.dev.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • evoluhq/evolu1,909 ★ · +2
Star history
Repository2026-10-162026-10-23
evoluhq/evolu1.9k1.9k

2 Stars in the last 7 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Local-first with CRDTs over SQLite in the browser

Evolu describes itself in one line as a local-first platform and TypeScript library, and the repository topics name the three mechanisms: crdt, local-first and sqlite3, plus react and react-native.

That combination is the design in miniature. State lives in SQLite, which runs in the browser as WebAssembly, and the reason it can run in every client is that the same database is the unit of storage on each device. Merging is handled by CRDT semantics rather than by a server assigning order, which is what lets two clients work offline and converge afterwards.

The published packages are scoped under @evolu, and the release feed shows them versioning independently: @evolu/relay at 4.2.15, @evolu/react-native at 16.1.6 and @evolu/oxlint-config at 0.3.2, all released on 2026-10-15. A relay server is part of the system, so this is a platform rather than a passive store, and the example applications show the client side running as a PWA, inside Electron and inside Expo.

The wasm file is downloaded and hash-checked, never committed

The single most concrete rule in the README concerns the WebAssembly binary. The playgrounds in apps/web, the examples, and the integration, bundle and end to end tests all load packages/sqlite-wasm/wasm/sqlite3.wasm. That file is never committed to the repository and must be fetched:

sh
pnpm sqlite-wasm:download

The script takes the wasm from the published @evolu/sqlite-wasm package and writes it only if its sha256 matches the pinned value. The playgrounds and tests fail when the file is missing or is not the pinned build. The examples are the deliberate exception, since they exist as templates to copy into a project rather than as part of this repository's test surface.

That is a supply chain decision worth respecting. A database engine compiled to WebAssembly is exactly the kind of artifact where an unpinned download would be invisible, and the check makes the pin enforceable instead of advisory.

When no release holds the pinned wasm, you build it yourself

The download script fails informatively rather than silently when the pinned hash is not in any published release. In that case the README tells you to build it with an emsdk that has Emscripten 6.0.3 installed and activated, following the instructions in the @evolu/sqlite-wasm README:

sh
EMSDK=/path/to/emsdk node packages/sqlite-wasm/scripts/build-wasm.mts

So there is a hard dependency on a specific Emscripten version at the point where contributors fork off from published artifacts. Anyone contributing who is not on that version will produce a binary that fails its own hash check, which is at least a loud failure rather than a subtly different database.

A second constraint in the same area is platform related rather than technical. On macOS Tahoe, the web build may need the Launch Services limits raised, and the README notes the shell ulimit -n is not enough:

sh
sudo launchctl limit maxfiles 262144 262144

One dev command starts the relay and the web app together

The contributor setup assumes the latest Node.js LTS with pnpm, installed through Corepack so the version comes from package.json. The root .nvmrc selects the Node version:

sh
nvm install
npm install --global corepack@latest
corepack enable pnpm
pnpm install
pnpm playwright:install

The playwright install is not optional decoration. Playwright browsers back the browser-based Vitest projects, and the README asks you to repeat the step after a Playwright update or if the browser cache is cleared.

For day to day work, pnpm dev starts the relay and the web servers together, while pnpm relay starts only the relay, which is what the iOS and Android example commands need since those are told to start the relay first. That split tells you the relay is a separate process with its own lifecycle, not a library call inside the client.

Two build steps are marked required once after a clone or a pull: pnpm build for package types so the IDE resolves them, and pnpm build:docs, which runs the TypeScript project build, typedoc and a fix-up step inside apps/web.

Coverage floors and randomized test order

The test commands encode the project's standards more explicitly than any prose would. The full suite is pnpm test, which chains unit tests, the sqlite-wasm tests, integration tests, bundle tests and the documentation example tests.

The unit run passes numeric floors as flags rather than leaving them to configuration:

json
"test:unit": "pnpm test:node --test-coverage-branches=97 --test-coverage-functions=91 --test-coverage-lines=90 --test-reporter=@evolu/nodejs/TestOverviewReporter \"{apps/web/src,bench,packages/*/src,packages/*/scripts,scripts,test/jsdoc}/**/*.test.{ts,mts}\""

The sqlite-wasm project is held to a stricter standard still, running with 100 percent on lines, branches and functions, and failing below that. Bundle tests exist to catch tree-shaking regressions, with test:bundle:update to regenerate the size snapshots, and test:jsdoc compiles and runs the examples embedded in the documentation, so a wrong snippet in the docs fails the build.

One more practice stands out: unit, integration and bundle suites randomize execution order and print a seed that reproduces the same ordering. Ordering independence is what makes a green run mean something in a database library.

Structure enforced by tooling, releases by changesets

Monorepo conventions are linted rather than trusted. pnpm lint runs everything, pnpm lint:oxlint runs Oxlint over the code with the root .oxlintrc.json, and pnpm lint:sherif checks the monorepo structure itself with Sherif, which catches the class of problems where one workspace package depends on another in a way that breaks the build graph.

Formatting is Prettier with a shared config in prettier.config.mjs, and pnpm format:check is the version that fails rather than writes. Type checking is a single command that runs several tsc project references in sequence, including apps/relay, apps/web, scripts, bench and test.

Releases go through Changesets, with pnpm changeset describing a change for the release log and a .changeset/ directory holding pending entries. That matches the independent version numbers across scoped packages seen in the release feed.

A single pnpm verify runs build, lint and test locally, and the README states that CI runs the same checks on pull requests and pushes to main. There is also a flake.nix and flake.lock, so a Nix environment is available for anyone who wants the toolchain pinned.

Where this repository will not tell you what you need

The honest limitation of this project as documentation: the README is a contributor document. It tells you how to run the playgrounds, the examples and the test suites, and it points to evolu.dev for usage information and examples. There is no consumer install command here, no API description and no data model documentation, so this article does not invent any.

That matters for evaluation. Whether Evolu fits your application depends on how its schema is defined and how queries are written, and neither is visible from the repository. Plan to read the site documentation before committing.

The examples give you a coverage map if you prefer to read code: react-nextjs, react-vite-pwa, svelte-vite-pwa, vue-vite-pwa, react-electron, react-expo and angular-vite-pwa. That is a broad claim to being framework-neutral rather than React-first, and each one is described as a template to copy rather than a test subject. Note that examples run against the local packages by default, and pnpm examples:toggle-deps switches them to the published ones, which is how you check your consumers actually resolve.

The project is MIT licensed, with SECURITY.md and an AGENTS.md at the root, and the last push was on 2026-10-16, so development is current.

Editorial conclusion

Adopt Evolu if you are building an offline-first application that wants SQLite on the client and CRDT merge semantics without designing a sync protocol, and you can live with a wasm binary in your toolchain. Do not evaluate it from this repository alone, since the README documents contributor setup rather than how to consume the library, which lives on evolu.dev. Verify first which scoped package you need, since the release feed shows relay, react-native and oxlint-config versions moving independently on the same day.

Frequently asked questions

How do I set up the Evolu monorepo for development?

Install the Node version from .nvmrc with nvm install, enable Corepack and pnpm, run pnpm install, fetch the pinned wasm with pnpm sqlite-wasm:download, and install Playwright browsers with pnpm playwright:install. Then pnpm dev starts the relay and web servers.

Why do the Evolu playgrounds fail with a missing sqlite3.wasm?

The wasm file is never committed. Run pnpm sqlite-wasm:download, which takes it from the published @evolu/sqlite-wasm package and writes it only if its sha256 matches the pin. If no release holds the pinned hash, build it with Emscripten 6.0.3 using packages/sqlite-wasm/scripts/build-wasm.mts.

Which packages does the Evolu project publish?

Scoped packages under @evolu, released independently. The most recent releases include @evolu/relay, @evolu/react-native and @evolu/oxlint-config, all published on the same day, plus @evolu/sqlite-wasm, which supplies the WebAssembly database build.

How does Evolu test its library?

pnpm test chains unit, sqlite-wasm, integration, bundle and documentation example tests. Unit runs carry coverage floors of 97 percent branches, 91 percent functions and 90 percent lines, the sqlite-wasm suite requires 100 percent, and the suites randomize execution order with a printed seed.

Official sources

  1. evoluhq/evolu on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/evoluhq-evolu.svg)](https://hysenlabs.com/projects/evoluhq-evolu)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/evoluhq-evolu.svg)](https://hysenlabs.com/projects/evoluhq-evolu)

Was this useful?