Thanks for your interest. This document is the contributor handbook –
project philosophy, repo layout, dev workflow, CI, release flow, and the
rules that apply to PRs. End-user documentation lives in
README.md and at zapo.to (source:
vinikjkkj/zapo-docs).
By contributing you also agree to the Code of Conduct – in particular the AI-Assisted Contributions section, which covers disclosure, human ownership of AI output, the verification rule against bundle-hallucination, and the ban on hidden prompt-injection payloads.
For deep protocol-level guidance (coordinator patterns, store contracts,
performance rules, anti-patterns), see AGENTS.md. This file
covers the higher-level workflow.
- What Makes This Project Different
- Core Principles
- Setup
- Architecture at a Glance
- Useful Scripts
- Continuous Integration
- Versioning and Releases
- GitHub Release Notes
- Protobuf Generation
- Documentation Site
- Contribution Rules
zapo is an independent runtime implementation (not a wrapper/fork of an
existing WhatsApp library).
- No wrappers around third-party WhatsApp SDKs
- No forks of existing WhatsApp client libraries
- No copied protocol abstractions from community libraries
- The entire
spec/folder (protobuf, MEX schema, app-state schemas) is vendored fromvinikjkkj/wa-specand compiled locally for runtime/types
The protocol source of truth is the deobfuscated WhatsApp Web. The target is behavior parity with WhatsApp Web, while improving internal performance and memory efficiency.
index-first: validate protocol behavior against WhatsApp Web before implementing anythingperformance-first: optimize for low CPU, low RAM, low allocations, and zero-copy in hot pathsasync-first: I/O and network operations are async. Crypto follows the same rule only where it pays off - X25519/Ed25519 (curves) andpbkdf2stay async because they're genuinely heavy and benefit from Node's libuv thread pool; hashes, AES, HMAC, and HKDF were measured to be faster sync (no thread-pool hop, no microtask cost) and converted.
Requirements:
- Node.js
>= 20.9.0 - npm
Install + run the real-flow example (pairs against the real WhatsApp Web):
git clone https://github.com/vinikjkkj/zapo
cd zapo
npm install
npm run exampleScan the QR code emitted via auth_qr, then send ping to the connected
session - the example replies with pong. Auth state is persisted in
.auth/state.sqlite.
- Coordinator-first feature design in
src/client/coordinators/ - Pure node builders in
src/transport/node/builders/for reusable protocol stanzas - Incoming parsers/normalizers in
src/client/events/, with coordinators handling orchestration only - Typed store contracts in
src/store/contracts/; persistent + cache providers live under@zapo-js/store-*packages - Protocol constants in
src/protocol/usingObject.freeze({...} as const)
Uint8Arrayeverywhere for binary data (Bufferis avoided)- Zero-copy (
subarray, byte views) in critical paths - Bounded in-memory structures to prevent unbounded growth
- Path aliases (
@client,@crypto,@store, etc.), no relative../imports across modules - Named exports only, no default exports
- No enums (
Object.freeze+as constinstead)
See AGENTS.md for the full architecture spec, including
coordinator responsibilities, store contracts, the performance rules
catalog, and the review anti-patterns list.
npm run build- build CJS, ESM, and typesnpm run test- run unit tests (non-flow)npm run test:flow- run real-flow testsnpm run test:coverage- run coverage reportnpm run typecheck- type-check corenpm run typecheck:all- core + examples + bench + packages (run before opening a PR)npm run lint- lint source filesnpm run format- format codebasenpm run proto:generate- regenerate protobuf runtime/types fromspec/proto/WAProto.protonpm run changeset- create a versioning entry (patch/minor/major)npm run changeset:status- show pending versioning entriesnpm run version:packages- apply pending versions and updateCHANGELOG.mdnpm run release:publish- build and publish to npm with Changesets
Every push to master and every PR run
ci.yml, which fans out into:
lint,format,typecheckover corebuild-core(CJS + ESM + types)typecheck-packagesacross every@zapo-js/*workspace- per-provider test jobs:
test-core,test-fake-server,test-media-utils,test-sqlite,test-mcp-server,test-mysql,test-postgres,test-redis,test-mongo - a final
all-checksjob that gates merges - branch protection only needs to require this single job
Two PR automations also run on every pull request:
pr-validate-title.ymlenforces a conventional-commit PR title (feat:,fix:,chore:, ...) viaamannn/action-semantic-pull-request. Required because release notes group PRs by that prefix.pr-auto-label.ymlattaches the matchingtype:<prefix>label so the release-notes workflow can categorize.
Versioning is managed with
Changesets. From 1.0.0 the
public API is stable - breaking changes ship only in a major bump.
npm run changeset
npm run changeset:status
npm run version:packages
npm run release:publishNotes:
- Changesets are stored in
.changeset/*.md - Multiple changesets are merged automatically into the next release
- SemVer is manual and intentional:
patch,minor,major
Release notes are generated automatically (grouped by conventional-commit type, with contributor list) when a version tag is pushed.
- Workflow:
.github/workflows/github-release.yml - Categories config:
.github/release.yml
Trigger example:
git tag v1.0.0
git push origin v1.0.0If the tag contains - (example: v1.0.0-rc.0), the release is marked as
prerelease.
The entire spec/ folder - WAProto.proto, the MEX GraphQL
schema, and the app-state schema descriptors - is vendored from
vinikjkkj/wa-spec. Update that
upstream first when the protocol changes, then regenerate locally.
npm run proto:generate runs scripts/generate-proto.cjs, which:
- Ensures proto tooling dependencies are installed in
spec/proto/ - Generates and minifies
spec/proto/index.js - Regenerates compact typings at
spec/proto/index.d.ts
End-user guides and the API reference at zapo.to are
built from vinikjkkj/zapo-docs.
Fixes to guides, examples, or wording on the site go there. PRs in this
repo cover code, JSDoc, READMEs, and AGENTS.md.
Before opening a PR:
- Validate behavior against WhatsApp Web (and
wa-mobfor mobile-only flows) before writing code. Don't invent tags/attrs/namespaces. The deobfuscated bundles aren't vendored - seeAGENTS.md§1.1 for how to obtain them locally. - Keep performance and memory constraints in mind - the rules in
AGENTS.md§7 (Uint8Array-only, zero-copy in hot paths, bounded structures, scratch buffer reuse) are not suggestions. - Keep node building/parsing aligned with project patterns
(
@transport/node/builders/*+ pure parsers in@client/events/*). - Avoid API changes that diverge from observed WhatsApp Web behavior.
- Test real flows when touching auth, transport, app state, retry, or
signal paths (
npm run test:flow). - Run
npm run typecheck:all,npm run lint,npm run format:check,npm run test, andnpm run test:flowlocally before pushing. - Add a changeset (
npm run changeset) when your change affects public API or behavior.
PR title must follow conventional commits (feat:, fix:, chore:,
perf:, refactor:, docs:, test:, build:, ci:, style:,
deps:, revert:) - CI enforces this and uses it for auto-labeling and
release-notes grouping.