Simplify Installation: Bundle OTel SDK Dependencies

by Admin 52 views
Simplify Installation: Bundle OTel SDK Dependencies

Hey guys! Let's dive into how we're making it easier to install and use the @atrim/instrument-node package by streamlining its dependencies, specifically those related to OpenTelemetry (OTel). We've been seeing some runtime errors pop up because of how peer dependencies are handled, and we're tackling this head-on. The goal? To make your experience smoother and less prone to those pesky "cannot find package" errors. This change aims to simplify the installation process and ensure a more stable experience for users, especially when integrating with libraries like Effect-TS. We're going to explore the problem, our solution, and the steps we're taking to make this a reality. This is all about making the package more user-friendly and reducing common installation headaches.

The Problem: Resolving Peer Dependencies

So, what's the deal, and why are we even bothering with this? Well, the main issue revolves around how our package declares its dependencies, particularly concerning OpenTelemetry packages like @effect/opentelemetry. Currently, some of these packages are listed in both dependencies and peerDependencies within our package.json file. This is where things get tricky, especially when using package managers like pnpm, which have strict dependency isolation. Guys, this situation creates a conflict. pnpm's setup means the peer dependency should take precedence, but it isn't automatically installed, leading to that frustrating "cannot find package" error. Imagine trying to build a house, and some of the essential materials (like the foundation) aren't automatically included in the basic package. That's essentially what's happening. The core problem lies in the current dependency setup. This leads to the root cause: users face runtime errors because peer dependencies, which are critical for the package to function correctly, are not being resolved properly during installation and usage. This can be a real pain for developers, so we're working hard to make this process seamless.

Diving Deeper into the Root Cause

The root cause is quite simple: The way our package.json file is structured. When a package is listed in both dependencies and peerDependencies, it can cause conflicts, especially with package managers like pnpm, which prioritize peer dependencies but don't automatically install them. This means that even if you install our package, certain dependencies that it relies on might not be available, leading to runtime errors. This is particularly problematic with OpenTelemetry and Effect packages, as they are crucial for instrumentation and integration. This issue is something that we noticed when developing and testing our library.

The Solution: A Hybrid Bundling Approach

To solve these dependency woes, we're adopting a hybrid bundling approach. This strategy carefully balances what we bundle directly into our package and what we declare as peer dependencies. We're aiming for a solution that simplifies installation while maintaining flexibility. We will be bundling specific OpenTelemetry SDK packages, while keeping the API and Effect packages as peer dependencies. This method is all about making things simpler for you, the user, by reducing the number of dependencies you need to manage directly. This hybrid approach will give us the best of both worlds. The idea is to make installation as simple as possible while still offering the flexibility for users to integrate with other libraries.

Dependency Strategy Breakdown

Here’s how we're breaking it down:

  • @opentelemetry/api: Peer dependency (required). This package is essential because it provides the core API for OpenTelemetry, including the tracer provider. Think of it as the foundation of your instrumentation. It's a global singleton, meaning there should only be one instance shared across your application. That’s why we MUST keep it as a peer dependency, so everyone uses the same instance.
  • @opentelemetry/sdk-*: Bundle. These packages contain the OpenTelemetry SDK implementations. They don't have singleton issues, so bundling them is safe and simplifies installation.
  • @opentelemetry/auto-instrumentations-*: Bundle. These are implementation details used for automatic instrumentation. Bundling them makes sense because they're not meant to be shared or customized by the end-user.
  • @opentelemetry/exporter-*: Bundle. These are used for exporting trace data. Again, they’re implementation details best bundled with the package.
  • effect: Peer dependency (optional). Effect-TS is a powerful library, but users might already have their own version installed. We keep it as a peer dependency to avoid conflicts.
  • @effect/opentelemetry: Peer dependency (optional). This package integrates OpenTelemetry with Effect-TS, and its version often depends on the Effect-TS version. Keeping it as a peer dependency gives you control.
  • @effect/platform: Peer dependency (optional). Similar to @effect/opentelemetry, this package’s version is tied to Effect-TS. It's optional so users can manage it.

Why This Specific Strategy? The Rationale Behind Our Choices

Why are we doing it this way, guys? The key is to balance simplicity and flexibility. The OpenTelemetry API uses a global state for the tracer provider. If we bundled @opentelemetry/api, users who also have it installed would end up with two separate instances, which would break the instrumentation. This strategy ensures everyone shares the same core API instance, which is crucial for consistency. We also want to keep the Effect integration optional. Many users may not use Effect-TS, so we don't want to force them to install it. By making Effect packages peer dependencies, we give users the choice. This approach yields several benefits:

  • Simpler Installation: Users only need to install @opentelemetry/api for core features, making the initial setup much easier.
  • No Singleton Conflicts: We avoid any issues with multiple instances of the OpenTelemetry API.
  • Effect Integration Remains Optional: Users can choose to integrate with Effect-TS without being forced to install it.
  • Smaller Surface Area of Required Peer Dependencies: We reduce the number of packages that must be installed separately, minimizing potential conflicts.

Implementation Tasks: What's Changing?

So, what's involved in putting this plan into action? We've broken down the implementation into several key tasks. Each step is designed to address a specific aspect of the dependency issue and improve the overall user experience. This is a multi-step process, but each step is vital to solving the problem. We'll be updating our package configuration and writing helpful error messages to make sure everything works smoothly. Here's what we're tackling:

  • Phase 1: Fix Dependency Configuration: This is the initial step and includes tweaking the packages/node/package.json file. We'll be removing the OpenTelemetry and Effect packages from the dependencies section. We'll keep them in peerDependencies with the correct version ranges and mark the Effect packages as optional using peerDependenciesMeta.
  • Phase 2: Configure tsup for Hybrid Bundling: We’re using tsup (a TypeScript bundler) to manage our dependencies. We'll update the tsup.config.ts file to specify which packages should be bundled (noExternal) and which ones should remain as peer dependencies (external). This way, we can make the instrumentation process as seamless as possible.
  • Phase 3: Add Runtime Dependency Validation: We'll add error messages to warn users if the required peer dependencies are missing. These messages will also include easy-to-follow installation instructions to guide users on what to do. The goal is to make any potential issues clear and easy to resolve.
  • Phase 4: Update Documentation: We'll update the documentation to provide clear installation instructions, showing the required versus optional dependencies. We'll also provide a version compatibility matrix to ensure users know which versions of our package work with which versions of OpenTelemetry and Effect-TS. We are making sure that the users know all the information.
  • Phase 5: Test Scenarios: We'll thoroughly test the solution in various scenarios. This includes testing cases where users have no Effect-TS installed (ensuring core features work), and cases where users do have Effect-TS installed (ensuring the integration works correctly). We’ll also test different OpenTelemetry API versions (1.0, 1.4, and 1.9) to make sure everything is compatible.

Updated package.json Structure: The New Configuration

Here’s a sneak peek at the new package.json structure. You'll notice the changes in how we handle dependencies and peer dependencies. We've refined the dependencies to ensure that our package functions correctly while giving users the flexibility they need. This new structure is designed to be more efficient and less prone to errors. This will drastically improve the installation experience. Let's take a look:

{
  "dependencies": {
    "yaml": "^2.3.0",
    "zod": "^3.22.0"
  },
  "peerDependencies": {
    "@opentelemetry/api": "^1.0.0",
    "effect": "^3.0.0",
    "@effect/opentelemetry": ">=0.40.0",
    "@effect/platform": ">=0.70.0"
  },
  "peerDependenciesMeta": {
    "effect": { "optional": true },
    "@effect/opentelemetry": { "optional": true },
    "@effect/platform": { "optional": true }
  }
}

Updated tsup.config.ts: Bundling Configuration

Here's how the tsup.config.ts file will be configured to handle the bundling. This configuration ensures that specific packages are included in the bundle, while others are treated as peer dependencies. We’re explicitly telling tsup which packages to bundle and which to leave out. This configuration strikes the perfect balance. This is very important for the entire project.

export default defineConfig({
  // ... existing config

  external: [
    '@opentelemetry/api',       // Global singleton
    'effect',                    // User provides
    '@effect/opentelemetry',     // User provides
    '@effect/platform',          // User provides
    '@effect/platform-node'      // User provides
  ],

  noExternal: [
    '@opentelemetry/sdk-node',
    '@opentelemetry/sdk-trace-base',
    '@opentelemetry/sdk-trace-node',
    '@opentelemetry/exporter-trace-otlp-http',
    '@opentelemetry/auto-instrumentations-node',
    '@opentelemetry/instrumentation',
    '@opentelemetry/resources',
    '@opentelemetry/semantic-conventions'
  ]
})

User Installation After This Change: How to Get Started

So, after these changes, how do you install our package? It's simple. We're making sure it's as easy as possible. Here’s how you'll install the package:

# Required (core features)
npm install @atrim/instrument-node @opentelemetry/api

# Optional (Effect-TS integration)
npm install effect @effect/opentelemetry @effect/platform

Install the required core features and optional integration packages. These instructions will ensure you get everything you need to start using the package effectively, whether you're using Effect-TS or not.

Version Compatibility: Staying Up-to-Date

We'll also provide a version compatibility matrix. This table will make sure you know which versions of our package work with which versions of OpenTelemetry and Effect-TS. Keeping the software up-to-date and compatible with other technologies is critical. Here's a quick look at the compatibility:

@atrim/instrument-node @opentelemetry/api effect
0.6.x ^1.0.0 ^3.0.0
2.0.x (future) ^2.0.0 ^3.x+

See full analysis: tmp/2025-12-04/otel-dependency-bundling-strategy.md