← Back to Blog

Eleven packages, two ecosystems

·7 min read

A pyRPC release ships eleven packages to two registries. Six go to npm, five to PyPI, and they are not independent, the Python packages depend on each other, the TypeScript packages depend on each other, and the two ecosystems share one version number by contract. Understanding the matrix is understanding the release.

The npm side

Six workspaces under packages/, arranged as a dependency chain:

@pyrpc/types, the type boundary (placeholder → generated module)
@pyrpc/client, plain fetch client (Proxy-based procedure dispatch)
@pyrpc/react, React + TanStack Query hooks
@pyrpc/next, React hooks + Next.js extras
@pyrpc/vue, Vue hooks
@pyrpc/svelte, Svelte hooks

The chain is a DAG: types feeds client, client feeds react, react feeds next. Publishing order matters, which is why the workflow expresses it as a needs: graph rather than a flat loop.

The Python side

Five packages, three adapters and two core tools:

pyrpc-core, router, introspection, CLI, watcher, bundlers
pyrpc-codegen, the TypeScript emitter (depends on core)
pyrpc-fastapi, FastAPI adapter (depends on core)
pyrpc-flask, Flask adapter (depends on core)
pyrpc-django-adapter, Django adapter (depends on core)

All five are built and published in one workflow job with python -m build per package, so a single PyPI upload covers the whole Python matrix at once.

The shared version contract

Eleven packages share one version number. The release script enforces it mechanically, and the contract has a purpose beyond tidiness: the ecosystem is consumed as a unit. A user installing @pyrpc/react@0.12.0 alongside pyrpc-core==0.12.0 gets a pair that is guaranteed to interoperate, because both shipped from the same tag. Version lockstep is the poor man's monorepo release, no code-sharing, but perfect compatibility.

Two package-manager topologies

The two sides organize their workspaces differently. npm uses root-level workspaces globbing packages/* (plus examples). uv uses a root pyproject.toml workspace with the same member directories. Both managers resolve local packages locally (npm through symlinked workspaces, uv through editable installs) so cross-package imports during development hit your working tree, not the registry.

The failure that motivates the matrix

Publish half the matrix and the registry is temporarily broken: @pyrpc/react@0.12.0 exists but depends on @pyrpc/client@^0.12.0, which does not. That is why the pipeline is ordered and idempotent, so the window where the matrix is inconsistent is measured in seconds, not until someone notices.