# ADR 0120: Published declarations follow ESM resolution

> Source: https://github.com/max-listov/stitchkit/blob/master/docs/decisions/0120-published-declarations-follow-esm-resolution.md

---

# ADR 0120 — Published declarations follow ESM resolution

## Context

Stitchkit sources use TypeScript's bundler resolution and extensionless relative
imports. The JavaScript build is bundled, so those source specifiers do not
survive into published runtime entrypoints. Declaration emit is different:
TypeScript preserves each specifier in `.d.ts`.

An ESM consumer using `moduleResolution: NodeNext` requires explicit relative
file extensions. A declaration such as `export * from './contract'` is therefore
not a portable package reference even though bundler-mode consumers resolve it.
The package also exported the root Socket.IO surface through a constraint imported
from an optional peer. An HTTP-only consumer consequently needed Socket.IO types
just to check a client that never opened a socket.

## Decision

The declaration build resolves every emitted relative module specifier against
the completed declaration tree and writes its ESM runtime spelling:

- `./browser/client` becomes `./browser/client.js`;
- `./contract` becomes `./contract/index.js` when the declaration target is an
  index module;
- an unresolved relative specifier fails the build instead of being published.

The browser-safe root owns its generic event-map constraint. Optional Socket.IO
packages remain necessary only for code that opts into their runtime or dedicated
server declarations; they are not a declaration dependency of `createClient`.

The packed consumer lane includes a clean ESM project with
`module: NodeNext`, `moduleResolution: NodeNext`, `skipLibCheck: false`, only
`stitchkit` and `zod` installed, and both typecheck and Node runtime execution.

## Consequences

- Package declarations follow the same ESM resolution rules as their published
  JavaScript targets.
- The peer-free root is proven from an installed tarball rather than inferred
  from the workspace dependency graph.
- Source authoring remains on bundler resolution; the package build owns the
  translation at the artifact boundary.
- Optional entrypoints may still name their declared peers. The guarantee is
  that an unrelated root import does not require them.
