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

@agent-scope/core

v1.20.0

Published

Core types, schemas, and serialization for Scope — zero dependencies

Readme

@agent-scope/core

Zero-dependency type foundation for the Scope React instrumentation platform.

@agent-scope/core defines every shared TypeScript type, the serialize() function, a complete set of runtime type-guards, and schema version constants. Nothing in this package references the DOM, React, or any browser API — it is safe to import in any environment (browser, Node.js, edge runtimes, test runners).


Installation

npm install @agent-scope/core

What it does / when to use it

@agent-scope/core is the contract layer of the Scope monorepo. You need it when:

  • Building a consumer of PageReport objects (analytics, DevTools, CI reporters) — import the types and type-guards.
  • Serialising arbitrary React props or state — call serialize() to produce a safely-serialised SerializedValue that handles circular references, depth limiting, and every JavaScript type.
  • Validating untrusted data — use the is* guards to verify that a stored report still matches the current schema before processing it.

@agent-scope/runtime and @agent-scope/sourcemap both depend on this package and re-export the types they need.


API Reference

PageReport

The top-level transport and storage unit. Every other type is a node or sub-node of this structure.

interface PageReport {
  /** Full URL at capture time (no fragment). */
  url: string;

  /**
   * Router-level metadata. null when no routing adapter is installed or
   * the URL could not be matched to a known route.
   */
  route: RouteInfo | null;

  /** Unix timestamp (ms) when the capture was initiated. */
  timestamp: number;

  /**
   * Wall-clock time (ms) from capture start to PageReport assembly.
   * Use this to monitor Scope runtime overhead.
   */
  capturedIn: number;

  /** Root node of the captured React component tree. */
  tree: ComponentNode;

  /**
   * All JS errors observed during the capture window:
   * unhandled exceptions, unhandled rejections, ErrorBoundary catches,
   * and console.error calls attributed to an error.
   */
  errors: CapturedError[];

  /** All <Suspense> boundaries, flattened for O(1) access. */
  suspenseBoundaries: SuspenseBoundaryInfo[];

  /** All console.* calls intercepted during the capture window, ascending by timestamp. */
  consoleEntries: ConsoleEntry[];
}

Example payload (from src/__tests__/types.test.ts):

const report: PageReport = {
  url: "https://example.com/dashboard",
  route: {
    pattern: "/dashboard",
    params: null,
    query: {},
    name: "dashboard",
  },
  timestamp: 1700000000000,
  capturedIn: 42,
  tree: {
    id: 1,
    name: "App",
    type: "function",
    source: { fileName: "/app/src/Foo.tsx", lineNumber: 10, columnNumber: 5 },
    props: { type: "object", value: {}, preview: "{}" },
    state: [
      {
        type: "useState",
        name: null,
        value: { type: "string", value: "hello", preview: '"hello"' },
        deps: null,
        hasCleanup: null,
      },
    ],
    context: [
      {
        contextName: "ThemeContext",
        value: { type: "string", value: "hello", preview: '"hello"' },
        didTriggerRender: false,
      },
    ],
    renderCount: 1,
    renderDuration: 0.5,
    children: [],
  },
  errors: [
    {
      message: "Something went wrong",
      name: "Error",
      stack: "Error: Something\n  at App.tsx:42",
      source: { fileName: "/app/src/Foo.tsx", lineNumber: 10, columnNumber: 5 },
      componentName: "App",
      timestamp: 1700000000000,
      capturedBy: "boundary",
    },
  ],
  suspenseBoundaries: [
    {
      id: 5,
      componentName: "LazySection",
      isSuspended: false,
      suspendedDuration: null,
      fallbackName: "Spinner",
      source: { fileName: "/app/src/Foo.tsx", lineNumber: 10, columnNumber: 5 },
    },
  ],
  consoleEntries: [
    {
      level: "warn",
      args: [{ type: "string", value: "hello", preview: '"hello"' }],
      timestamp: 1700000000001,
      componentName: "App",
      preview: "Something is odd",
    },
  ],
};

ComponentNode

One node in the captured component tree, corresponding to a single React fiber.

interface ComponentNode {
  /** Fiber's internal numeric ID. Stable for the lifetime of a mounted component. */
  id: number;

  /**
   * Resolved display name.
   * Resolution order: displayName → function/class name → JSX tag → "Anonymous".
   */
  name: string;

  /** Component implementation strategy. */
  type: ComponentType; // "function" | "class" | "forward_ref" | "memo" | "host"

  /**
   * Source location from babel's __source prop or DevTools fiber data.
   * null in production builds or for host elements.
   */
  source: SourceLocation | null;

  /** Serialized snapshot of the component's props at capture time. */
  props: SerializedValue;

  /**
   * Ordered hook slots (hook call-order, slot 0 = first use* call).
   * Empty for class components and host elements.
   */
  state: HookState[];

  /** All React contexts consumed by this component during the capture window. */
  context: ContextConsumption[];

  /** Number of renders during the capture window (>1 indicates re-renders). */
  renderCount: number;

  /**
   * Total wall-clock time (ms) in this component's render, excluding children.
   * Measured from beginWork to completeWork.
   */
  renderDuration: number;

  /** Ordered child nodes (depth-first). Empty array = leaf node. */
  children: ComponentNode[];
}

type ComponentType = "function" | "class" | "forward_ref" | "memo" | "host";

LightweightComponentNode

A stripped-down node emitted when capture({ lightweight: true }) is used. Omits props, state, context, source, renderCount, and renderDuration, producing ~99% smaller payloads for large trees.

interface LightweightComponentNode {
  id: number;
  name: string;
  type: ComponentType;
  /** Total hooks count (no values serialised). */
  hookCount: number;
  /** Hook types in call order, no values. */
  hookTypes: HookType[];
  /** Equals children.length. */
  childCount: number;
  /** Tree depth; root = 0. */
  depth: number;
  children: LightweightComponentNode[];
}

HookState

A snapshot of a single hook slot.

interface HookState {
  /** Hook type. "custom" for third-party / project-local hooks. */
  type: HookType;

  /**
   * Inferred name of a custom hook (e.g. "useTheme").
   * null for built-in hooks where type is already informative.
   */
  name: string | null;

  /**
   * Serialized current value.
   * - useState/useReducer: current state
   * - useMemo/useCallback: memoized value / function ref
   * - useRef: .current value
   * - useContext: current context value
   * - effect hooks: undefined (no meaningful value)
   */
  value: SerializedValue;

  /**
   * Serialized dependency array.
   * Present for useEffect, useLayoutEffect, useMemo, useCallback, useSyncExternalStore.
   * null for hooks that do not accept deps.
   */
  deps: SerializedValue[] | null;

  /**
   * Whether the effect registered a cleanup function on its last run.
   * true = cleanup returned. false = no cleanup. null = not applicable.
   */
  hasCleanup: boolean | null;
}

type HookType =
  | "useState" | "useReducer" | "useEffect" | "useLayoutEffect"
  | "useMemo" | "useCallback" | "useRef" | "useContext"
  | "useId" | "useSyncExternalStore" | "useTransition" | "useDeferredValue"
  | "custom";

Example (from src/__tests__/types.test.ts):

// useState
{ type: "useState", name: null, value: { type: "number", value: 42 }, deps: null, hasCleanup: null }

// useEffect with deps and cleanup
{ type: "useEffect", name: null, value: { type: "undefined" }, deps: [{ type: "string", value: "dep1" }], hasCleanup: true }

// custom hook
{ type: "custom", name: "useTheme", value: { type: "object", preview: '{ mode: "dark" }' }, deps: null, hasCleanup: null }

SerializedValue

An envelope for any JavaScript value that may not round-trip through JSON (functions, symbols, circular references, etc.).

interface SerializedValue {
  /** Original JavaScript type discriminant. */
  type: SerializedValueType;

  /**
   * Round-trippable value for primitives and simple composites.
   * Absent for function, symbol, circular, truncated — use preview instead.
   */
  value?: unknown;

  /**
   * Human-readable string for display. Always present when value is absent.
   */
  preview?: string;
}

type SerializedValueType =
  | "string" | "number" | "boolean" | "null" | "undefined"
  | "object" | "array" | "function" | "symbol" | "bigint"
  | "date" | "map" | "set"
  | "circular"   // circular reference was detected at this position
  | "truncated"; // value exceeded depth/size limits and was cut off

Examples (from src/__tests__/serialization.test.ts):

// Primitives
{ type: "null",    value: null,  preview: "null" }
{ type: "boolean", value: true,  preview: "true" }
{ type: "number",  value: 42,    preview: "42" }
{ type: "string",  value: "hi",  preview: '"hi"' }
{ type: "bigint",  value: "123n", preview: "123n" }

// Function (no value)
{ type: "function", preview: "function myFunc(a, b) { return a + …" }

// Date
{ type: "date", value: "2024-01-01T00:00:00.000Z", preview: "2024-01-01T00:00:00.000Z" }

// Map
{ type: "map", value: [/* entry objects */], preview: "Map(2)" }

// Set
{ type: "set", value: [/* items */], preview: "Set(3)" }

// Circular reference
{ type: "circular" }

// Depth-exceeded
{ type: "truncated", preview: "Array(50)" }

serialize(value, options?)

Convert any JavaScript value to a SerializedValue snapshot.

function serialize(value: unknown, options?: SerializeOptions): SerializedValue;

interface SerializeOptions {
  /** Maximum recursion depth for nested objects/arrays. Default: 5. */
  maxDepth?: number;
  /** Maximum string length before truncation. Default: 200. */
  maxStringLength?: number;
  /** Maximum array items to serialize. Default: 100. */
  maxArrayLength?: number;
  /** Maximum object properties to serialize. Default: 50. */
  maxProperties?: number;
}

Behaviour summary:

| Input | Output type | Notes | |---|---|---| | null | "null" | value: null | | undefined | "undefined" | no value field | | boolean | "boolean" | | | number | "number" | NaN, Infinity included | | string | "string" | truncated at maxStringLength with "..." suffix | | bigint | "bigint" | stored as "123n" string | | Symbol | "symbol" | preview only, no value | | function | "function" | raw source preview, truncated at 50 chars | | Date | "date" | ISO 8601 string in value and preview | | Map | "map" | entries as objects, truncated at maxProperties | | Set | "set" | items as array, truncated at maxArrayLength | | Array | "array" | truncated at maxArrayLength | | object | "object" | {key: SerializedValue} map, truncated at maxProperties | | Error | "object" | {name, message, stack} extracted | | WeakMap/WeakSet/WeakRef | "object" | opaque, no entries | | circular ref | "circular" | no value or preview | | depth exceeded | "truncated" | preview describes the cut-off value |

Circular reference detection uses a WeakSet that tracks the current ancestor chain. The same object can legitimately appear in sibling branches without being flagged as circular (verified by tests).

Depth counting — depth 0 is the root call. An object value encountered at depth >= maxDepth is returned as { type: "truncated" } rather than being expanded.

// Self-referential object
const obj: Record<string, unknown> = { a: 1 };
obj.self = obj;
serialize(obj);
// → { type: "object", value: { a: { type: "number", ... }, self: { type: "circular" } } }

// Deep nesting with maxDepth: 2
const deep = { a: { b: { c: "leaf" } } };
serialize(deep, { maxDepth: 2 });
// root(depth=0) → a(depth=1) → b is { type: "truncated" } at depth=2

// Shared object in sibling branches (NOT flagged as circular)
const shared = { x: 1 };
serialize({ left: shared, right: shared });
// → { left: { type: "object", ... }, right: { type: "object", ... } }  ← both are "object"

Type Guards

All guards accept unknown and return a type predicate. Use them to validate data crossing process or storage boundaries.

import {
  isPageReport, isPageReportDeep,
  isComponentNode, isComponentNodeDeep,
  isCapturedError, isSuspenseBoundaryInfo,
  isConsoleEntry, isContextConsumption,
  isHookState, isRouteInfo,
  isSerializedValue, isSourceLocation,
} from "@agent-scope/core";

| Guard | Validates | Depth | |---|---|---| | isPageReport(v) | Full PageReport shape | Shallow ComponentNode | | isPageReportDeep(v) | Full PageReport + entire tree | Recursive | | isComponentNode(v) | Single ComponentNode (children not recursed) | O(1) per node | | isComponentNodeDeep(v) | ComponentNode + all descendants | O(n) | | isCapturedError(v) | CapturedError | — | | isSuspenseBoundaryInfo(v) | SuspenseBoundaryInfo | — | | isConsoleEntry(v) | ConsoleEntry | — | | isContextConsumption(v) | ContextConsumption | — | | isHookState(v) | HookState | — | | isRouteInfo(v) | RouteInfo | — | | isSerializedValue(v) | SerializedValue | — | | isSourceLocation(v) | SourceLocation | — |


Constants

Readonly arrays useful for exhaustive switch/if checks and validation loops.

import {
  SCHEMA_VERSION,     // "0.1.0" — bump on breaking schema changes
  HOOK_TYPES,         // readonly HookType[]
  COMPONENT_TYPES,    // readonly ComponentType[]
  SERIALIZED_VALUE_TYPES, // readonly SerializedValueType[]
  CONSOLE_LEVELS,     // readonly ConsoleLevel[]
} from "@agent-scope/core";

SCHEMA_VERSION follows semver; store it in PageReport metadata to detect schema drift when loading reports from storage.


Supporting Types

interface SourceLocation {
  /** Absolute or relative path to the source file. */
  fileName: string;
  /** 1-based line number of the JSX element. */
  lineNumber: number;
  /** 1-based column number of the JSX element. */
  columnNumber: number;
}

Populated from React's __source prop (injected by babel-plugin-transform-react-jsx-source in development mode).

interface ContextConsumption {
  /**
   * The context's displayName. null when the context has no display name.
   */
  contextName: string | null;

  /** Serialized snapshot of the context value at capture time. */
  value: SerializedValue;

  /**
   * Whether this context consumption triggered a re-render during the
   * capture window (i.e. value changed between previous and current render).
   */
  didTriggerRender: boolean;
}
interface CapturedError {
  message: string;
  name: string; // e.g. "TypeError", "RangeError"
  stack: string | null; // raw V8 stack; not source-mapped
  source: SourceLocation | null; // parsed from top stack frame
  componentName: string | null; // nearest React component
  timestamp: number; // Unix ms
  capturedBy: "boundary" | "unhandled" | "rejection" | "console";
}
interface RouteInfo {
  pattern: string | null; // e.g. "/users/:id"
  params: Record<string, string> | null; // e.g. { id: "42" }
  query: Record<string, string>; // parsed query string
  name: string | null; // named route from router config
}
interface SuspenseBoundaryInfo {
  id: number;
  componentName: string; // nearest named ancestor
  isSuspended: boolean;
  suspendedDuration: number | null; // ms suspended; null if not triggered
  fallbackName: string | null; // name of the fallback element
  source: SourceLocation | null;
}
interface ConsoleEntry {
  level: ConsoleLevel; // "log" | "warn" | "error" | "info" | "debug" | "group" | ...
  args: SerializedValue[]; // each argument, serialized
  timestamp: number; // Unix ms
  componentName: string | null; // attributed React component
  preview: string; // single-line condensed text, trimmed to 200 chars
}

Internal Architecture

src/
├── index.ts            ← barrel export (types, guards, serialize, constants)
├── serialization.ts    ← serialize() + SerializeOptions
├── guards.ts           ← all is*() type-guard functions
├── constants.ts        ← SCHEMA_VERSION, HOOK_TYPES, COMPONENT_TYPES, …
└── types/
    ├── page-report.ts              ← PageReport
    ├── component-node.ts           ← ComponentNode, ComponentType
    ├── lightweight-component-node.ts ← LightweightComponentNode
    ├── hook-state.ts               ← HookState, HookType
    ├── serialized-value.ts         ← SerializedValue, SerializedValueType
    ├── source-location.ts          ← SourceLocation
    ├── context.ts                  ← ContextConsumption
    ├── errors.ts                   ← CapturedError
    ├── route.ts                    ← RouteInfo
    ├── console.ts                  ← ConsoleEntry, ConsoleLevel
    └── suspense.ts                 ← SuspenseBoundaryInfo

The package has zero runtime dependencies. It ships both ESM (dist/index.js) and CJS (dist/index.cjs) builds via tsup.


Used by

| Package | How | |---|---| | @agent-scope/runtime | Imports all types; calls serialize() for props, state, and context values | | @agent-scope/sourcemap | Imports ComponentNode, PageReport to type the resolution input/output |