Skip to content

v0.3.0 — six packages become one, and the credential boundary becomes a build failure

Choose a tag to compare

@tonyabracadabra tonyabracadabra released this 26 Aug 15:09
· 54 commits to main since this release
eac619a

Important

Not on npm yet. npm install realtime-avatar will 404 until 2026-08-27 10:32 UTC.
Every name was unpublished on 2026-08-26, and npm blocks a fully-unpublished name for 24
hours — no version number gets around it. The release pipeline is verified green and waiting;
this tag marks the code, not the registry state.

One package

npm install realtime-avatar gives you the whole SDK. realtime-avatar-react is gone — its
entries are now subpaths.

Import What it is
realtime-avatar · /server Server client. Holds your API key. 18.8 KB
/nextjs /hono /express /tanstack-start Route adapters. Key-holding
/react /react-native React and Expo bindings. Never hold a key
/browser /tools Mic + playback, and the browser tool plane

realtime-avatar-mcp stays a separate name because it is a CLI you run with npx and never install.

The credential boundary is now a build failure

Importing a key-holding entry into a browser bundle throws at bundle time. The six server
subpaths carry browser and react-native export conditions pointing at a module whose only
statement is a throw.

Verified on the built package: a browser bundle importing both halves contains 0 occurrences
of Bearer or apiKey.

This replaced a naming convention with a mechanism. The previous shape rested on the claim that an
exports condition "chooses which file is bundled, never whether the package is" — measured on a
fixture, that is false.

Two traps, if you copy this pattern. "browser": null is worse than nothing: webpack errors,
but Vite 8 / rolldown ignores it and bundles the server file with the secret. And
"sideEffects": false lets a bundler treeshake a throw-only module away — the first version of
this guard measured as doing nothing until sideEffects was narrowed to name it.

Fixed

  • Eight demo apps threw MODULE_NOT_FOUND at startup and the Next.js quickstart imported a
    package that 404s — they resolved npm names that had been unpublished.
  • AvatarCall could not be used by anyone. It required client: RealtimeAvatarClient, a class
    the browser half deliberately does not export, so the prop named a type no consumer could
    produce. It now takes AvatarSessionClient, a five-method interface satisfiable by a plain object.
  • Rule 5 of the agent contract told you to import a subpath that throws ERR_PACKAGE_PATH_NOT_EXPORTED.
  • The published packages shipped with no README.

Types now derive from the published contract

spec/realtime-avatar.openapi.json is vendored from the live OpenAPI document and generated into
types that the public surface indexes into, so a contract change is a typecheck failure rather than
a silent divergence. npm run spec:check reports drift and exits 0 offline.

Internals

before after
Packages an app installs 6 1
Unreachable source files 1,230 lines 0 — now a build gate
tsup builds 7 1
npm run build 22.0s 7.9s
realtime-avatar/react exports 82 31

npm run reachable walks the real import graph and fails if any source file is reachable from
nothing. 24 LiveKit re-exports were removed — they are peer dependencies, so import them from
LiveKit and get the version you installed.

Known gap

shouldReplayPendingTurn is a documented predicate that nothing calls and nothing tests: a turn
sent while the session is reconnecting is not replayed today. Annotated rather than deleted.