GenomeJS
Reference

Error Reference

Understand GenomeJS graph errors, unknown traits, token failures, subscriber errors, and browser setup failures.

GenomeJS reports invalid dependency graphs while a Genome is being constructed.

Other failures may occur later during token resolution, mutation, subscription notification, or browser-environment binding.

Error categories

CategoryTypical timing
Circular dependencyGenome construction or scope creation
Unresolved DNA referenceGenome construction or scope creation
Unknown traitgetTrait() or framework snapshot read
Token function failureConstruction or mutation
Subscriber failureMutation after resolution
Browser API failureBinding setup or environment change
Invalid targetCSS expression or scope creation

CircularDependencyError

CircularDependencyError is thrown when the derived-token graph cannot be resolved because token relationships form a cycle.

Import

import { CircularDependencyError } from "@genomejs/core";

Class shape

class CircularDependencyError extends Error {
  readonly cycle: string[];
}

Public properties

PropertyTypeDescription
name"CircularDependencyError"Error class name
messagestringFormatted dependency path
cyclestring[]Ordered token names associated with the detected cycle

Example

const genome = new Genome({
  primitives: {},

  tokens: {
    first: (dna) => Number(dna.second),

    second: (dna) => Number(dna.first),
  },
});

Example message:

Circular token dependency: first -> second -> first

Handle the error

import { CircularDependencyError, Genome } from "@genomejs/core";

try {
  const genome = new Genome(config);
} catch (error) {
  if (error instanceof CircularDependencyError) {
    console.error("Circular token graph:", error.cycle);
  } else {
    throw error;
  }
}

Common causes

Two-token cycle

first → second → first

Longer cycle

surface

foreground

border

surface

Indirect helper relationships

A cycle may be less obvious when token functions are far apart in the configuration.

const tokens = {
  cardSurface: (dna) => dna.elevatedSurface,

  elevatedSurface: (dna) => dna.shadowColor,

  shadowColor: (dna) => dna.cardSurface,
};

Fixing a cycle

Identify the value that should be a source rather than a result.

Before:

surface → foreground
foreground → surface

After:

baseSurface ──→ surface
baseForeground ──→ foreground
surface ──→ foreground

Place stable source values in primitives.


UnresolvedTokenError

UnresolvedTokenError is thrown when a derived token reads one or more DNA properties that are not known primitives or derived functions.

Import

import { UnresolvedTokenError } from "@genomejs/core";

Class shape

class UnresolvedTokenError extends Error {
  readonly token: string;

  readonly missing: string[];
}

Public properties

PropertyTypeDescription
name"UnresolvedTokenError"Error class name
messagestringToken and missing names
tokenstringDerived token whose dependencies are unresolved
missingstring[]Missing DNA property names

Example

const genome = new Genome({
  primitives: {},

  tokens: {
    surface: (dna) => dna.missingSurface,
  },
});

Message:

Token "surface" references undefined token(s): missingSurface

Multiple missing references

const genome = new Genome({
  primitives: {},

  tokens: {
    border: (dna) => `${dna.missingWidth} solid ${dna.missingColor}`,
  },
});

The error’s missing property contains both names.

Handle the error

import { Genome, UnresolvedTokenError } from "@genomejs/core";

try {
  const genome = new Genome(config);
} catch (error) {
  if (error instanceof UnresolvedTokenError) {
    console.error(`Token ${error.token} is missing:`, error.missing);
  } else {
    throw error;
  }
}

Common causes

Spelling mistake

primitives: {
  background: "#ffffff",
},

tokens: {
  surface: (dna) =>
    dna.backgound,
}

Reading context from dna

Incorrect:

const tokens = {
  surface: (dna) => (dna.mode === "dark" ? "#000000" : "#ffffff"),
};

Runtime context is the second parameter:

const tokens = {
  surface: (_dna, context) => (context.mode === "dark" ? "#000000" : "#ffffff"),
};

Static value placed under tokens

The current public type permits:

tokens: {
  baseRadius: 12,
}

However, the current compiler only includes function-valued entries when building and resolving the derived-token graph.

Use:

primitives: {
  baseRadius: 12,
}

A derived function reading a static value placed under tokens may therefore report that name as unresolved.


Unknown trait error

Calling getTrait() with a name absent from the current resolved DNA throws a standard Error.

genome.getTrait("missingTrait");

Message:

Unknown token: "missingTrait"

This is not currently an instance of:

UnresolvedTokenError;

Handle an unknown trait

try {
  const value = genome.getTrait(name);
} catch (error) {
  if (error instanceof Error && error.message.startsWith("Unknown token:")) {
    console.error("Check the trait name.");
  } else {
    throw error;
  }
}

The public API does not currently provide:

genome.hasTrait(name);

Prefer known configuration keys rather than using exceptions as ordinary control flow.

Framework adapter behavior

All adapters ultimately read through:

genome.getTrait(name);

An unknown name may therefore surface during:

  • React render or snapshot reading
  • Vue composable initialization
  • Svelte wrapper initialization
  • A later adapter subscription update

Example:

const value = useGenomeTrait(genome, "missingTrait");

This throws rather than returning undefined.


Token function errors

GenomeJS does not wrap ordinary exceptions thrown by token functions.

const tokens = {
  spacing: (_dna, context) => {
    if (typeof context.scale !== "number") {
      throw new Error("scale must be a number");
    }

    return context.scale * 16;
  },
};

This may throw:

  • During initial resolution in the constructor
  • During mutate()
  • During child creation with scope()

Tracking pass and actual resolution

GenomeJS first runs derived functions with a recording DNA proxy to discover property reads.

Exceptions from this tracking pass are ignored after touched dependencies have been recorded.

The token function is then run normally during actual resolution.

An exception during actual resolution propagates to the caller.

This allows dependency tracking to continue even when an inert tracking value is unsuitable for part of a token’s calculation.

It does not suppress real runtime failures.

Defensive context handling

Prefer fallbacks and type narrowing:

const scale = typeof context.scale === "number" ? context.scale : 1;

Use explicit errors when silent fallback would hide invalid application state:

function requireNumber(value: unknown, name: string): number {
  if (typeof value !== "number" || !Number.isFinite(value)) {
    throw new TypeError(`${name} must be a finite number.`);
  }

  return value;
}

Subscriber errors

Subscriber callbacks are called synchronously after token resolution and CSS expression.

GenomeJS does not catch subscriber exceptions.

genome.subscribe(() => {
  throw new Error("External synchronization failed.");
});

A later mutation propagates the error:

genome.mutate({
  mode: "dark",
});

Safer subscriber

genome.subscribe(() => {
  try {
    synchronizeExternalSystem();
  } catch (error) {
    console.error("Synchronization failed:", error);
  }
});

A failing subscriber may prevent later subscribers in the current iteration from running, depending on where the exception occurs.

Keep subscribers small and predictable.


Browser environment errors

window is unavailable

This fails during server rendering:

bindMediaQueries(genome);

Possible error:

ReferenceError: window is not defined

Call it inside a browser lifecycle.

matchMedia is unavailable

bindMediaQueries() expects:

window.matchMedia;

An environment without it may throw a TypeError.

ResizeObserver is unavailable

This fails in environments without ResizeObserver:

bindContainerSize(genome, element);

Possible error:

ReferenceError: ResizeObserver is not defined

Use a browser implementation, test mock, or appropriate polyfill.

Invalid target

The Genome constructor accepts:

HTMLElement | null;

And scope() accepts:

HTMLElement;

Passing an invalid value outside TypeScript may fail when GenomeJS accesses:

target.style.setProperty(...)

Validate dynamically obtained targets:

const element = document.querySelector<HTMLElement>("[data-theme-root]");

if (!element) {
  throw new Error("Theme target was not found.");
}

Utility input failures

Invalid contrast colors

contrastRatio() and lockContrast() do not currently provide dedicated validation errors.

Unsupported color strings may produce incorrect values or NaN.

Validate six-digit hexadecimal input:

function assertHexColor(value: string): void {
  if (!/^#[0-9a-f]{6}$/i.test(value)) {
    throw new TypeError(`Invalid hex color: ${value}`);
  }
}

Invalid fluidScale() ranges

The function does not validate equal or reversed viewport ranges.

Avoid:

fluidScale(16, 32, 640, 640);

Validate dynamic values before calling it.


Error timing summary

During new Genome()

Possible failures:

  • UnresolvedTokenError
  • CircularDependencyError
  • Token function exception
  • Invalid target during CSS expression
  • CSS target method exception

During mutate()

Possible failures:

  • Token function exception
  • CSS expression exception
  • Subscriber exception

During getTrait()

Possible failure:

  • Standard unknown-token Error

During scope()

Possible failures:

  • Graph errors during child construction
  • Token errors during initial child construction
  • Token errors during inherited-context mutation
  • Invalid target errors
  • Subscriber errors created during child setup

During browser bindings

Possible failures:

  • Missing browser APIs
  • Token errors caused by binding mutations
  • Subscriber errors caused by binding mutations

Error-handling recommendations

  • Let graph errors fail during development.
  • Validate external theme configuration before construction.
  • Narrow runtime context values inside tokens.
  • Validate external colors and numeric ranges.
  • Keep subscribers small.
  • Use browser bindings only after mounting.
  • Always clean up listeners and observers.
  • Log the structured fields on GenomeJS error classes.
  • Avoid hiding graph errors behind broad catch blocks.

On this page