Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
90d95d6
fix: Reconcile wrpc hygiene fixes with old metacom plan drift (Ф0)
Alex-Dolid Aug 9, 2026
f4dfe3c
feat: Harden the WebSocket engine with backpressure and RFC 6455/7692…
Alex-Dolid Aug 10, 2026
1ede6e1
docs: Remove nightly CI mention after dropping the workflow
Alex-Dolid Aug 10, 2026
164c9e3
feat: Replace metarhia application coupling with a router-based serve…
Alex-Dolid Aug 10, 2026
1620fa4
feat: Add server adapters for uWebSockets.js, fastify, and express
Alex-Dolid Aug 10, 2026
4f553f0
build: Report bundle size for every published subpath, not just the b…
Alex-Dolid Aug 10, 2026
44ab64f
fix: Update bench/support/wrpc-echo.js to the router-based Server API
Alex-Dolid Aug 10, 2026
9906348
feat: Add maxBodySize to the fastify adapter as a per-route bodyLimit
Alex-Dolid Aug 10, 2026
e3db432
feat: Add rooms, client→server events, scaling backplane, and client …
Alex-Dolid Aug 11, 2026
7e9ecc5
feat: Add subscriptions, batching, cancellation, and SSE transport (F5)
Alex-Dolid Aug 11, 2026
5883697
fix: Close 4 open F5 review defects (CORS SSE, batch abort, packet id…
Alex-Dolid Aug 11, 2026
c910025
feat: Add typed client, codegen CLI, and TanStack Query bindings (F6)
Alex-Dolid Aug 11, 2026
f7d1a06
fix: Deliver onEnd to callback-based subscriptions on client.close()
Alex-Dolid Aug 11, 2026
62961af
docs: Add full documentation site, README, and release checklist (F7)
Alex-Dolid Aug 11, 2026
95cb161
test: Add manual Redis/socket.io/tRPC verification coverage from plan…
Alex-Dolid Aug 11, 2026
2180f19
feat: Replace the console option with a structured logger (F8)
Alex-Dolid Aug 11, 2026
7b3ef35
feat: Add client logging and cover the silent failure paths (F9)
Alex-Dolid Aug 11, 2026
480a597
feat: Add OpenTelemetry traces and metrics to the server core (F10)
Alex-Dolid Aug 11, 2026
64ffa8b
feat: Propagate trace context over the wrpc wire (F11)
Alex-Dolid Aug 11, 2026
a42f7d0
docs: Document observability, telemetry, and the trace-context field …
Alex-Dolid Aug 11, 2026
f0031d1
feat!: harden security/resilience/scale, add hooks and pluggable ids
Alex-Dolid Aug 16, 2026
c820d50
fix(bench): send events over the wire again, guard against silent hangs
Alex-Dolid Aug 16, 2026
c64040b
feat: cluster layer (presence, introspection, commands) and acks
Alex-Dolid Aug 18, 2026
e3b6e1f
perf: hand-optimize hot-path allocations, add Performance conventions…
Alex-Dolid Aug 18, 2026
d7e80fe
docs: add Node.js-styled branding, mermaid diagrams, and 8 new guide …
Alex-Dolid Aug 18, 2026
a177d73
feat!: declarative REST bridge, injected ajv/fjs, transport fallback,…
Alex-Dolid Aug 20, 2026
e485fa0
feat!: context call identity, static introspection, unit.vN versions …
Alex-Dolid Aug 20, 2026
5a2f14f
Update CHANGELOG.md
Alex-Dolid Aug 20, 2026
c31290c
feat!: authenticate/refresh hooks, connection headers + per-call meta…
Alex-Dolid Aug 22, 2026
fa233a0
feat(rpc): accept S3-style x-wrpc-meta-<key> headers for per-request …
Alex-Dolid Aug 22, 2026
bd32de6
feat!: client-side prefixed meta headers, kebab-normalized keys, cors…
Alex-Dolid Aug 22, 2026
2d86210
feat!: client resilience, auth carrier hardening, cluster-scale prese…
Alex-Dolid Aug 23, 2026
997a005
docs: release checklist fixes, unreleased-note placement, and a chat …
Alex-Dolid Aug 23, 2026
a236559
Update client.resilience.test.js
Alex-Dolid Aug 23, 2026
c19f1a9
Update client.resilience.test.js
Alex-Dolid Aug 23, 2026
173765a
Update client.resilience.test.js
Alex-Dolid Aug 23, 2026
11ffc71
Update CHANGELOG.md
Alex-Dolid Aug 23, 2026
586bf74
test: await contract subtests and hold the loop for unref
Alex-Dolid Aug 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ jobs:

test:
runs-on: ubuntu-latest
# A leaked native listen socket (a uws engine that a failing test never
# tore down) wedges `node --test` instead of failing it — cap the job
# rather than let a hang burn the six-hour default.
timeout-minutes: 20
strategy:
matrix:
node-version: [22, 24]
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,9 @@ dist
docs/.vitepress/dist
docs/.vitepress/cache
/.claude/launch.json

# Autobahn testsuite output
scripts/autobahn/reports/

# examples/chat's build.js output — regenerate with `node build.js`
examples/chat/public/wrpc.browser.js
632 changes: 612 additions & 20 deletions CHANGELOG.md

Large diffs are not rendered by default.

86 changes: 71 additions & 15 deletions CLAUDE.md

Large diffs are not rendered by default.

193 changes: 193 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# Contributing

Thanks for looking. This file covers how to work on `@alexify/wrpc`, the house
rules that are not obvious from the code, and the release checklist.

## Setup

```bash
pnpm install
pnpm test
```

Requires **Node.js ≥ 22** and **pnpm** (`packageManager` pins the version).
There is no build step: `src/` ships as-is.

## Commands

```bash
pnpm test # node --test, recursive over tests/
pnpm test:coverage # c8 over src/ — thresholds 95 lines / 95 statements / 90 branches / 95 functions
pnpm test:types # tsd against the .d.ts files
pnpm test:perf # the 1 GiB stream memory guard
pnpm lint # oxlint
pnpm format # oxfmt (format:check in CI)
pnpm size # bundle-size report; browser budgets fail the run
pnpm bench # every script in bench/
pnpm docs:dev # the VitePress site (docs:build / docs:preview too)
```

Run a single file with `node --test tests/smoke.test.js`, or filter by name
with `--test-name-pattern`. `node --test` takes **files**, not directories — a
targeted run of a folder needs a glob (`node --test tests/adapters/*.test.js`).

## House rules

**Zero runtime dependencies.** `package.json` has no `dependencies` field, and
no `peerDependencies` or `optionalDependencies` either. Anything reused from
another package is copied in and adapted, not installed. Optional integrations
(uWebSockets.js, Redis, fastify, express, TanStack) are **injected by the
caller** and validated structurally — duck typing, never `instanceof` against
an imported class. Nothing under `src/` may `require()` a framework.

**A `.d.ts` change and its `tsd` test land together.** The declarations are
hand-maintained, one file per subpath, and they drift silently otherwise. Every
new subpath brings a root shim, a root `.d.ts`, an `exports` entry, a `files`
entry and a `tests/<name>.test-d.ts`.

**`tsd` type-checks with `@tsd/typescript` (5.9), not the repo's own
`typescript`.** A type relying on newer inference has to be checked against
both.

**Wire-protocol changes are documented in
[`docs/reference/protocol.md`](./docs/reference/protocol.md)**, which is frozen
at 1.0: additive, optional fields within a major version; anything else is a
major.

**Style** is enforced by oxlint/oxfmt: 2-space indent, single quotes,
semicolons, 120-column lines. The `correctness` category is intentionally off,
and `no-void` is intentionally not enabled — `return void fn()` is the idiom
this codebase uses for explicit fire-and-forget.

### Tests

Tests live in `tests/`, run by Node's built-in runner. Files that are **not**
`*.test.js` are shared helpers, not tests: `tests/websocket/mockSocket.js`,
`tests/websocket/protocolClient.js`, `tests/engine/engineContract.js` (the
engine port contract suite), and the swap-test pair `tests/adapters/boots.js` +
`tests/adapters/spec.js` — one behavioural spec replayed against every way of
standing wrpc up.

Adapter tests **skip** (never fail) when a framework is missing, so a machine
without a working `uWebSockets.js` binary can still run the suite. CI has them
all.

> **A leaked uws engine hangs `node --test` forever.** A standalone engine holds
> a native listen socket; if a test fails an assertion before its cleanup line
> runs, the socket is never released and the whole run wedges instead of
> reporting a failure. Register teardown with `t.after(...)` **immediately after
> booting**, never as a trailing `await close()` at the end of the test body.

## CI

Three jobs (`.github/workflows/ci.yml`):

- **lint** (Node 22) — `lint`, `format:check`, `size`
- **test** (Node 22 and 24) — `test:coverage`, `test:types`
- **docs** (Node 22) — `docs:build`, which fails on dead links

Lint and format deliberately target `src tests scripts bench bin` only, so
`docs/` is not covered by them.

Three checks are deliberately **not** in CI — run them by hand:

- `node scripts/autobahn/run.js` — the RFC 6455/7692 conformance suite against
the engine in `src/websocket/`. Needs docker; several minutes for 500+
cases. `FAILED` and `WRONG CODE` fail the run, `NON-STRICT` and
`INFORMATIONAL` do not.
- `pnpm test:perf` — the 1 GiB stream memory guard.
- `REDIS_URL=redis://127.0.0.1:6379 node --test tests/scaling/redis.integration.test.js`
— the scaling backplane against a real Redis. `pnpm test` already covers the
adapter's contract through an in-repo ioredis-shaped fake
(`tests/scaling/redis.test.js`); this is only useful when you want to check
a live server, and the file skips itself without `REDIS_URL`.

## Stability and deprecation

The published surface is stable under semver, with two carve-outs marked
`@experimental` in the `.d.ts` files:

- the **telemetry** writer shapes and metric set (`telemetry` option) — the
signals will keep improving in minors;
- the **engine port** internals beyond the documented `WrpcSocket` contract
(`capabilities` in particular).

An `@experimental` API may change in a minor release, with the change
described in the CHANGELOG. Everything else follows the usual rule: removal
or breaking change of a stable API needs (1) a deprecation note in the
CHANGELOG and the docs for at least one minor release, and (2) a major
version to actually remove it. The wire protocol has its own, stronger
promise — see [protocol.md](./docs/reference/protocol.md#stability): packet
shapes never break inside a major, and the `wrpc.v1` subprotocol names the
revision on the wire.

## Release checklist

Releases are **manual**. Nothing in the tooling bumps a version, tags, or
publishes.

1. **Green everything** on the release commit:
```bash
pnpm lint && pnpm format:check && pnpm test:coverage && pnpm test:types && pnpm size && pnpm docs:build
```
`tests/package/consistency.test.js` already fails this step if
`docs/.vitepress/config.mts`'s keywords or nav version label drift from
`package.json` — there is no separate manual sync step for either.
2. **Move `[Unreleased]` to the new version** in `CHANGELOG.md`, with the date,
and open a fresh empty `[Unreleased]` above it. Before the first publish,
`[Unreleased]` carries a "not published to npm yet" note right under its
heading — delete that note as part of this move; it does not apply again
after v1.0.0 ships.
3. **Bump `version` in `package.json`, and confirm it differs from what npm
already has.** Semver against the **JavaScript API**; the wire protocol has
[its own promise](./docs/reference/protocol.md#stability). Before the first
publish there is nothing on the registry to compare against, so this half
of the step is a no-op; from the second release on, run
`npm view @alexify/wrpc version` and make sure the bumped value doesn't
match it — publishing an already-used version is a rejected `npm publish`,
not a warning.
4. **Check what would ship.** `files` in `package.json` is an explicit
allowlist, so a new root shim or `.d.ts` that was not added to it silently
disappears from the tarball:
```bash
npm publish --dry-run
```
Verify the file list contains every root shim, every `.d.ts`, `bin/`, `src/`,
and **not** `docs/`, `tests/`, `bench/`, `scripts/`, `examples/` or
`coverage/`.
5. **Verify the tarball resolves**, CommonJS and ESM alike. In a scratch
directory:
```bash
npm pack
npm install ./alexify-wrpc-<version>.tgz
node -e "require('@alexify/wrpc'); require('@alexify/wrpc/engine'); require('@alexify/wrpc/query')"
node --input-type=module -e "import { Server, connect } from '@alexify/wrpc'; import { createNodeEngine } from '@alexify/wrpc/engine'; void Server; void connect; void createNodeEngine;"
npx wrpc --version
```
and that a bundler-resolution consumer type-checks against the shipped
`.d.ts` files (`tsd` only ever checks `tests/`, never the installed
tarball):
```bash
printf "import { connect } from '@alexify/wrpc';\nvoid connect;\n" > /tmp/wrpc-esm-smoke.ts
npx tsc --moduleResolution bundler --module esnext --noEmit /tmp/wrpc-esm-smoke.ts
```
6. **Commit, tag, push**: `git tag v<version> && git push --follow-tags`.
7. **Publish**: `npm publish` (`publishConfig.access` is already `public`).
8. **Release notes** on GitHub from the CHANGELOG entry.
9. **The docs site deploys from `main` through Vercel** (`vercel.json` pins
the build command and output directory) — no separate step, but confirm it
actually landed before announcing anything:
```bash
curl -o /dev/null -sw '%{http_code}\n' https://wrpc.vercel.app/
curl -o /dev/null -sw '%{http_code}\n' https://wrpc.vercel.app/reference/protocol
```
Both must answer `200` — the site has previously 404'd on everything while
the deploy was pointed at a branch without the docs on it.
10. **Refresh the size numbers quoted in prose.** README's bundle-size table,
`docs/guide/browser.md`, and any "under N KB" claim in `docs/index.md` or
`docs/guide/client.md` are hand-copied from the `pnpm size` output already
produced in step 1 — there is no guard test for prose, so they drift the
moment a budget changes and nothing catches it until someone notices.

Releases stay a person running these commands by hand, on purpose: no
GitHub Actions release workflow, no publish provenance step.
Loading
Loading