Skip to content

fix(highlight.run): make published TypeScript declarations resolvable#629

Open
Vadman97 wants to merge 2 commits into
mainfrom
fix/published-dts-types
Open

fix(highlight.run): make published TypeScript declarations resolvable#629
Vadman97 wants to merge 2 commits into
mainfrom
fix/published-dts-types

Conversation

@Vadman97

@Vadman97 Vadman97 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Problem

Importing @launchdarkly/observability (or @launchdarkly/session-replay, or highlight.run directly) breaks consumer type-checking whenever skipLibCheck is not set to true — and false is TypeScript's default. A customer hit this.

Root cause is in highlight.run's published dist/index.d.ts (the @launchdarkly/* packages are thin re-export wrappers over it). The rolled-up declaration file imports types from specifiers that don't exist in a consumer's node_modules:

Specifier in published .d.ts Why it's unresolvable
@opentelemetry/api dev-only dep, not declared as runtime dep
@highlight-run/rrweb-types workspace dep, not published as a matching runtime dep
graphql-request + graphql-request/build/cjs/types dev-only dep; the deep subpath isn't in its exports map either
stacktrace-js dev-only dep
client/types/observe internal baseUrl path alias that leaked into the published types — not a package at all

In-repo tsc never catches this because inside the monorepo every devDependency and the baseUrl alias resolve. The only faithful test is a clean consumer install (see the new CI guard).

Fix

  • Inline @opentelemetry/api and @highlight-run/rrweb-types into the declaration via vite-plugin-dts bundledPackages.
  • Declare stacktrace-js a runtime dependency. api-extractor can't follow its export = namespace StackFrame symbol, so it can't be bundled; making it a real dep means the existing import { StackFrame } from 'stacktrace-js' resolves (same mechanism that already makes @launchdarkly/js-client-sdk work).
  • Make the three client/types/observe imports relative, so the baseUrl alias stops leaking.
  • Keep the internal GraphQL client off the public surface. The graphqlSDK fields on the SDK classes are now ECMAScript-private (#graphqlSDK). They were the only thing dragging graphql-request (and transitively graphql, cross-fetch, @graphql-typed-document-node/core) into the public types. Bundling graphql-request instead just relocates the leak — its own declarations aren't self-contained, and the deep build/cjs/types import isn't resolvable under bundler/node16 regardless. Making the field #-private removes the whole chain. The sdk unit test now stubs the generated getSdk factory instead of assigning the (now private) field.

CI guard

scripts/check-published-types.mjs (new step in turbo.yml, before publish) packs the freshly built package, installs it into a clean consumer outside the monorepo (so only declared runtime deps are present), and type-checks an import with skipLibCheck: false under both bundler and classic node resolution. It fails the build if any declaration import is unresolvable. This is what caught a first iteration of this PR where bundling graphql-request had relocated the leak — the guard is doing its job. Run locally with yarn check:published-types after a build.

(node16/nodenext are intentionally not exercised: @launchdarkly/js-client-sdk currently ships ESM declarations with extensionless relative imports that those modes reject (TS2834) — unrelated noise from a dep we don't control here.)

Notes

  • The customer mentioned observability-node, but that package is independent (it does not depend on highlight.run) and is unaffected. The broken package is the browser SDK @launchdarkly/observability.
  • The changeset bumps highlight.run (patch); the workspace:* wrappers @launchdarkly/observability and @launchdarkly/session-replay need to republish against the fixed highlight.run for consumers to pick up the corrected types — please confirm the release pipeline cascades those.

🤖 Generated with Claude Code


Note

Medium Risk
Touches published SDK types and adds a runtime dependency (stacktrace-js); runtime behavior is largely unchanged but a bad release would break downstream TypeScript until republished.

Overview
Fixes consumer TypeScript failures when importing highlight.run / @launchdarkly/* browser SDKs with default skipLibCheck: false. The rolled-up dist/index.d.ts was importing types from dev-only deps, workspace packages, internal path aliases, and public graphqlSDK fields that dragged in graphql-request.

Declaration surface: vite-plugin-dts now bundles @opentelemetry/api and @highlight-run/rrweb-types into the published types. stacktrace-js moves from devDependency to runtime dependency so its types resolve in clean installs. client/types/observe imports are switched to relative paths so baseUrl aliases do not leak. Internal GraphQL clients on Highlight, ObserveSDK, and RecordSDK use #graphqlSDK so GraphQL types stay off the public API.

CI: New scripts/check-published-types.mjs packs the built package, installs it in a temp consumer outside the monorepo, and runs tsc with skipLibCheck: false under bundler and node resolution—wired into turbo.yml before publish and as yarn check:published-types.

Tests: SDK unit tests mock getSdk instead of assigning the now-private graphqlSDK field.

Reviewed by Cursor Bugbot for commit d355581. Bugbot is set up for automated code reviews on this repo. Configure here.

@Vadman97 Vadman97 requested a review from a team as a code owner June 16, 2026 18:50
The rolled-up dist/index.d.ts referenced types from packages that are not
runtime dependencies and from an internal path alias, so importing
@launchdarkly/observability / @launchdarkly/session-replay (or highlight.run
directly) failed type-checking whenever skipLibCheck was not enabled — which is
TypeScript's default.

Unresolvable specifiers in the published declarations were:
@opentelemetry/api, @highlight-run/rrweb-types, graphql-request (+ its deep
graphql-request/build/cjs/types), stacktrace-js, and client/types/observe.

Fixes:
- Inline @opentelemetry/api and @highlight-run/rrweb-types into the declaration
  bundle via vite-plugin-dts `bundledPackages`.
- Declare stacktrace-js as a runtime dependency (api-extractor cannot follow its
  `export =` namespace `StackFrame` symbol, so it cannot be bundled).
- Make the three `client/types/observe` imports relative so the baseUrl alias
  no longer leaks into the emitted declarations.
- Keep the internal GraphQL client off the public surface: the `graphqlSDK`
  fields on the SDK classes are now ECMAScript-private (`#`). They are the only
  thing that dragged graphql-request / graphql / cross-fetch into the public
  types, and bundling graphql-request just relocated the leak (its own
  declarations are not self-contained). The sdk unit test now stubs the
  generated `getSdk` factory instead of assigning the (now private) field.

Adds a CI guard (scripts/check-published-types.mjs, wired into turbo.yml) that
packs the built package, installs it into a clean consumer outside the
monorepo, and type-checks an import with skipLibCheck:false under both bundler
and classic node resolution — failing before publish if any declaration import
is unresolvable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Vadman97 Vadman97 force-pushed the fix/published-dts-types branch from 6b92bc5 to e6bf45f Compare June 16, 2026 19:15
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Vadman97 Vadman97 enabled auto-merge (squash) June 16, 2026 22:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants