npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@roku-sdk/stack-decoder

v2.19.1

Published

TypeScript API for decoding Roku Hermes/JS stack traces to original source positions

Readme

@roku-sdk/stack-decoder

TypeScript API for decoding Roku Hermes/JS stack traces to original TypeScript source positions. Designed for programmatic use in custom tooling and monitoring integrations (Sentry, Bugsnag, etc.).

Installation

pnpm add @roku-sdk/stack-decoder

Quick Start

Auto-detect source map from build directory

import { decodeStack } from '@roku-sdk/stack-decoder';

const rawStack = `
at renderComponent (index.hbc:1234:56)
at updateView (index.hbc:890:12)
`;

const decoded = await decodeStack(rawStack, {
  contextLines: 5
});

console.log(decoded.frames[0].original?.source);
// => "/Users/dev/project/src/components/Grid.tsx"

console.log(decoded.frames[0].sourceLine);
// => "    const items = data.map(item => ("

Provide source map directly

Bypass filesystem loading by passing a source map object. Useful for remote source maps, CI pipelines, or serverless environments.

import { decodeStack } from '@roku-sdk/stack-decoder';
import fs from 'fs/promises';

const sourceMapContent = await fs.readFile('path/to/sourcemap.json', 'utf-8');
const sourceMap = JSON.parse(sourceMapContent);

const decoded = await decodeStack(rawStack, {
  sourceMap,
  contextLines: 3
});

Fetch source map from remote server

import { decodeStack } from '@roku-sdk/stack-decoder';

async function decodeProductionStack(stackTrace: string, sha: string) {
  const response = await fetch(`https://source-maps.example.com/${sha}.json`);
  const sourceMap = await response.json();

  return await decodeStack(stackTrace, {
    sourceMap,
    sha // Still provide SHA for output metadata
  });
}

API Reference

decodeStack(stackTrace, options?)

Decode a Roku Hermes/JS stack trace to original TypeScript source positions.

Parameters:

| Parameter | Type | Description | |-----------|------|-------------| | stackTrace | string | Raw stack trace text containing index.hbc or index.js frames | | options | DecodeOptions | Optional decoding options |

Returns: Promise<DecodedStack>

loadSourceMap(buildDir, sha?, cwd?)

Load source maps from a build directory. Useful for caching across multiple decodeStack calls in high-throughput scenarios.

Parameters:

| Parameter | Type | Description | |-----------|------|-------------| | buildDir | string | Path to the build directory containing source maps | | sha | string | Optional source map SHA to locate specific build artifacts | | cwd | string | Optional working directory (defaults to process.cwd()) |

Returns: Promise<LoadedBundle> — a pre-parsed bundle that can be passed to decodeStack via the bundle option.

Types

interface DecodeOptions {
  sha?: string;           // Source map SHA (auto-detect if omitted)
  buildDir?: string;      // Build directory (auto-detect if omitted)
  sourceMap?: RawSourceMap; // Provide source map directly (bypasses filesystem)
  bundle?: LoadedBundle;  // Pre-loaded bundle from loadSourceMap (highest priority)
  contextLines?: number;  // Source context lines (default: 3)
  cwd?: string;           // Working directory for resolving build artifacts (default: process.cwd())
}

interface RawSourceMap {
  version: number;
  sources: string[];
  names: string[];
  mappings: string;
  file?: string;
  sourceRoot?: string;
  sourcesContent?: string[];
}

interface DecodedStack {
  sourceMapSha: string;
  buildDir: string;
  frames: DecodedFrame[];
}

interface DecodedFrame {
  functionName: string;
  bytecode: {
    file: string;
    line: number;
    column: number;
  };
  original: {
    source: string;
    line: number;
    column: number;
    name: string | null;
  } | null;
  sourceLine?: string;      // The source line at the mapped position
  sourceContext?: string[];  // Surrounding source lines (raw text)
  error?: string;            // Set when frame could not be decoded
}

Error Handling

The StackDecoderError class is thrown for input validation failures:

import { decodeStack, StackDecoderError } from '@roku-sdk/stack-decoder';

try {
  const decoded = await decodeStack(rawStack, { sourceMap: invalidMap });
} catch (err) {
  if (err instanceof StackDecoderError) {
    console.error(`${err.code}: ${err.message}`);
    // "INVALID_SOURCE_MAP: sourceMap.mappings must be a string"
  }
}

Error codes:

  • INVALID_OPTIONS — Invalid option values (e.g., contextLines: NaN)
  • INVALID_SOURCE_MAP — Malformed source map object passed to sourceMap option
  • SOURCE_MAP_NOT_FOUND — No source maps found in the build directory (filesystem path only)

Frames that fail to decode do not throw. Instead, the frame's original is null and error contains the failure reason.

Integration Examples

Sentry

import { decodeStack } from '@roku-sdk/stack-decoder';
import * as Sentry from '@sentry/node';

Sentry.init({
  async beforeSend(event) {
    if (event.exception?.values?.[0]?.stacktrace) {
      const rawStack = formatSentryStack(event.exception.values[0].stacktrace);

      try {
        const decoded = await decodeStack(rawStack);
        event.exception.values[0].stacktrace = formatDecodedForSentry(decoded);
      } catch {
        // Fall back to original stack if decoding fails
      }
    }

    return event;
  }
});

Bugsnag

import { decodeStack } from '@roku-sdk/stack-decoder';
import Bugsnag from '@bugsnag/node';

Bugsnag.start({
  async onError(event) {
    const rawStack = event.errors[0].stacktrace
      .map(f => `at ${f.method} (${f.file}:${f.lineNumber}:${f.columnNumber})`)
      .join('\n');

    try {
      const decoded = await decodeStack(rawStack, {
        sourceMap: await fetchSourceMap(event.app.version)
      });

      event.errors[0].stacktrace = decoded.frames.map(f => ({
        file: f.original?.source ?? f.bytecode.file,
        lineNumber: f.original?.line ?? f.bytecode.line,
        columnNumber: f.original?.column ?? f.bytecode.column,
        method: f.functionName
      }));
    } catch {
      // Fall back to original stack
    }
  }
});

High-Throughput: Cache Source Maps

For scenarios processing many stack traces against the same build, load the source maps once:

import { decodeStack, loadSourceMap } from '@roku-sdk/stack-decoder';

// Load once
const bundle = await loadSourceMap('./dist-build/bundle', 'abc123');

// Decode many — pass the pre-loaded bundle directly
for (let i = 0; i < incomingTraces.length; i++) {
  const decoded = await decodeStack(incomingTraces[i], { bundle });
  process.stdout.write(JSON.stringify(decoded) + '\n');
}

Requirements

  • Node.js >= 20.0.0
  • @roku-sdk/tooling (installed automatically as a dependency)