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

@chaos-maker/core

v0.8.0

Published

A lightweight, framework-agnostic toolkit for injecting chaos into web applications to test frontend resilience

Readme

@chaos-maker/core

Core chaos engine for web applications. Intercepts fetch, XMLHttpRequest, WebSocket, EventSource, DOM mutations, and Service Worker fetches to inject controlled failures, latency, aborts, corruption, drops, closes, and UI disruptions.

Framework-agnostic. Works with Playwright, Cypress, Selenium, or any browser environment.

Install

npm install @chaos-maker/core

Usage

Programmatic (ESM/CJS)

import { ChaosMaker } from '@chaos-maker/core';

const chaos = new ChaosMaker({
  network: {
    failures: [{ urlPattern: '/api', statusCode: 503, probability: 0.5 }],
    latencies: [{ urlPattern: '/api', delayMs: 2000, probability: 0.3 }]
  }
});

chaos.start();
// All matching fetch/XHR calls are now intercepted

chaos.on('network:failure', (event) => {
  console.log(`${event.detail.url} → ${event.applied ? 'failed' : 'passed'}`);
});

chaos.stop(); // restores original fetch/XHR

Browser (UMD)

<script src="chaos-maker.umd.js"></script>
<script>
  window.chaosUtils.start({
    network: {
      failures: [{ urlPattern: '/api', statusCode: 500, probability: 1.0 }]
    }
  });
</script>

Presets

Presets are reusable bundles of rules. Drop them into a config by name with the presets field, and the engine merges them at construction time.

import { ChaosMaker } from '@chaos-maker/core';

const chaos = new ChaosMaker({
  presets: ['slow-api'],
  network: {
    failures: [{ urlPattern: '/api/checkout', statusCode: 500, probability: 1 }],
  },
});
chaos.start();

Built-in catalog

| camelCase name | Kebab alias | Behavior | | ----------------------- | --------------- | ----------------------------------------------------------------- | | slowNetwork | slow-api | 2000ms latency on every request | | flakyConnection | flaky-api | 5% aborts plus 3000ms latency on 10% of requests | | offlineMode | offline-mode | Force CORS failure on every request | | unstableApi | high-latency | 10% failures + 20% 1000ms latency, scoped to /api/ | | degradedUi | | 20% disable buttons, 10% hide links | | unreliableWebSocket | | 10% drops, 500ms inbound delay, 5% inbound truncation | | unreliableEventStream | | 5% drops, 200ms delay, 2% close after 2000ms |

Kebab-case aliases (slow-api, flaky-api, offline-mode, high-latency) are registry-only. They resolve via presets: ['slow-api'] and new PresetRegistry().get('slow-api'). They are NOT keys on the legacy presets record export - presets['slow-api'] is undefined by design. Use the camelCase key (presets.slowNetwork) when reading from the record.

Custom presets

Register your own bundle inline via customPresets. Names collide fail-fast against built-ins and against each other.

new ChaosMaker({
  customPresets: {
    'team-flow': {
      network: {
        failures: [{ urlPattern: '/checkout', statusCode: 503, probability: 1 }],
      },
    },
  },
  presets: ['team-flow'],
});

Custom preset values may carry only rule arrays plus the optional groups field - presets, customPresets, seed, and debug are rejected at validation. Dependency chains are out of scope.

Builder helper

import { ChaosConfigBuilder } from '@chaos-maker/core';

const config = new ChaosConfigBuilder()
  .usePreset('slow-api')
  .failRequests('/api/checkout', 500, 1)
  .build();

Validation

Unknown preset names, chain attempts, forbidden subfields, duplicate registrations, and group-name collisions across preset+user all surface as ChaosConfigError at construction time, never at runtime.

Mutability

Built-in preset configs are deep-frozen - presets.slowNetwork.network!.latencies![0].delayMs = 1 throws. Your own custom presets passed via customPresets are NOT frozen - keep treating them as your data. The engine takes a deep clone at expansion, so any tweaks you make after construction are not observed.

Legacy spread

import { presets } from '@chaos-maker/core';

new ChaosMaker({ ...presets.slowNetwork, network: { failures: [{ urlPattern: '/api', statusCode: 500, probability: 1 }] } });

Still supported for migration. Prefer the declarative presets: field for new code.

Config Builder

import { ChaosConfigBuilder } from '@chaos-maker/core';

const config = new ChaosConfigBuilder()
  .failRequests('/api/checkout', 500, 0.5)
  .addLatency('/api/', 2000, 0.3)
  .abortRequests('/api/upload', 1.0, 5000)
  .corruptResponses('/api/data', 'malformed-json', 0.2)
  .simulateCors('/external-api/', 1.0)
  .assaultUi('button.submit', 'disable', 0.1)
  .build();

Chaos Types

| Type | Config Key | Description | |------|-----------|-------------| | Failure | network.failures | Force HTTP error responses | | Latency | network.latencies | Add delays to requests | | Abort | network.aborts | Cancel requests (immediate or timed) | | Corruption | network.corruptions | Corrupt response bodies | | CORS | network.cors | Simulate CORS errors | | UI Assault | ui.assaults | Disable, hide, or remove DOM elements | | WebSocket | websocket.* | Drop, delay, corrupt, or close socket messages | | SSE | sse.* | Drop, delay, corrupt, or close EventSource events | | GraphQL | graphqlOperation | Target one operation on a shared endpoint |

Matchers

Every network, WebSocket, and SSE rule accepts targeting matchers alongside urlPattern and methods:

  • hostname (string or RegExp, case-insensitive on strings)
  • queryParams (record of string | RegExp | boolean)
  • requestHeaders (network only; case-insensitive keys)
  • resourceTypes (network only; ['fetch' | 'xhr'])

A top-level matchers registry holds reusable named matchers so one matcher can target network, WebSocket, and SSE without per-transport duplication:

new ChaosMaker({
  matchers: {
    customers: { hostname: 'api.example.com', urlPattern: '/api/customers' },
  },
  network: {
    failures: [{ matcher: 'customers', statusCode: 503, probability: 1 }],
  },
});

A rule supplies either inline matcher fields OR matcher: 'name', never both. Mixing surfaces matcher_inline_conflict at validation time.

Built-in matchers

Three matchers ship preregistered and resolve by name without any matchers entry:

  • graphql (urlPattern: '/graphql')
  • apiRequests (urlPattern: '/api')
  • authRequests (requestHeaders: { authorization: true })
new ChaosMaker({
  network: {
    latencies: [{ matcher: 'graphql', delayMs: 1200, probability: 1 }],
  },
});

BUILT_IN_MATCHERS is exported from @chaos-maker/core and every adapter. A user matchers entry of the same name transparently overrides a built-in. authRequests is meaningful on network rules only - WebSocket and SSE rules cannot target request headers, so a stream rule referencing it matches every connection.

See the Matchers concept for the full surface, validation codes, and debug attribution.

Configuration Reference

NetworkFailureConfig

| Field | Type | Required | Description | |-------|------|----------|-------------| | urlPattern | string | Yes | Substring match against request URL | | statusCode | number | Yes | HTTP status code (100-599) | | probability | number | Yes | 0.0-1.0 chance of applying | | methods | string[] | No | HTTP methods to match (default: all) | | graphqlOperation | string \| RegExp | No | Operation name matcher for GraphQL requests | | body | string | No | Custom response body | | statusText | string | No | Custom status text | | headers | Record<string, string> | No | Custom response headers |

NetworkLatencyConfig

| Field | Type | Required | Description | |-------|------|----------|-------------| | urlPattern | string | Yes | Substring match against request URL | | delayMs | number | Yes | Delay in milliseconds | | probability | number | Yes | 0.0-1.0 chance of applying | | methods | string[] | No | HTTP methods to match | | graphqlOperation | string \| RegExp | No | Operation name matcher for GraphQL requests |

NetworkAbortConfig

| Field | Type | Required | Description | |-------|------|----------|-------------| | urlPattern | string | Yes | Substring match against request URL | | probability | number | Yes | 0.0-1.0 chance of applying | | timeout | number | No | ms before abort (0 or omitted = immediate) | | methods | string[] | No | HTTP methods to match | | graphqlOperation | string \| RegExp | No | Operation name matcher for GraphQL requests |

NetworkCorruptionConfig

| Field | Type | Required | Description | |-------|------|----------|-------------| | urlPattern | string | Yes | Substring match against request URL | | strategy | CorruptionStrategy | Yes | 'truncate' | 'malformed-json' | 'empty' | 'wrong-type' | | probability | number | Yes | 0.0-1.0 chance of applying | | methods | string[] | No | HTTP methods to match | | graphqlOperation | string \| RegExp | No | Operation name matcher for GraphQL requests |

NetworkCorsConfig

| Field | Type | Required | Description | |-------|------|----------|-------------| | urlPattern | string | Yes | Substring match against request URL | | probability | number | Yes | 0.0-1.0 chance of applying | | methods | string[] | No | HTTP methods to match | | graphqlOperation | string \| RegExp | No | Operation name matcher for GraphQL requests |

UiAssaultConfig

| Field | Type | Required | Description | |-------|------|----------|-------------| | selector | string | Yes | CSS selector | | action | string | Yes | 'disable' | 'hide' | 'remove' | | probability | number | Yes | 0.0-1.0 chance of applying |

SSEConfig

sse: {
  drops: [{ urlPattern: '/events', eventType: 'token', probability: 0.1 }],
  delays: [{ urlPattern: '/events', delayMs: 500, probability: 1 }],
  corruptions: [{ urlPattern: '/events', strategy: 'truncate', probability: 0.05 }],
  closes: [{ urlPattern: '/events', afterMs: 2000, probability: 0.02 }],
}

eventType defaults to message; use a named event or '*' for all data events.

GraphQL operation matching

network: {
  failures: [{
    urlPattern: '/graphql',
    graphqlOperation: 'GetUser',
    statusCode: 503,
    probability: 1,
  }],
}

graphqlOperation is an additional matcher on top of urlPattern and methods.

Event System

chaos.on('network:failure', (event) => { /* ... */ });
chaos.on('*', (event) => { /* all events */ });
chaos.off('network:failure', listener);

const log = chaos.getLog();   // all events since start
chaos.clearLog();

Event types: network:failure, network:latency, network:abort, network:corruption, network:cors, ui:assault, websocket:drop, websocket:delay, websocket:corrupt, websocket:close, sse:drop, sse:delay, sse:corrupt, sse:close

Config Validation

All configs are validated with Zod in strict mode. Unknown keys are rejected by default. Invalid values throw ChaosConfigError whose issues is a ValidationIssue[] with structured path / code / ruleType / message / expected / received.

import { validateChaosConfig, ChaosConfigError } from '@chaos-maker/core';

try {
  validateChaosConfig({
    network: { failures: [{ urlPattern: '', statusCode: 999, probability: 2 }] },
  });
} catch (e) {
  if (e instanceof ChaosConfigError) {
    for (const issue of e.issues) {
      console.log(issue.path, issue.code, issue.message);
    }
    // legacy v0.4.x string array still available:
    console.log(e.messages);
  }
}

validateChaosConfig(input, opts?) accepts:

  • unknownFields: 'reject' | 'warn' | 'ignore' - strict by default. 'warn' and 'ignore' strip unknowns from the returned config; 'warn' emits exactly one aggregated console.warn per call.
  • customValidators: Partial<Record<RuleType, (rule, ctx) => ValidationIssue[] | void>> - run extra checks per rule type.
  • onDeprecation: (issue) => void - receive ValidationIssue events for deprecated fields. The registry is empty for this release.

A JSON Schema artifact ships at node_modules/@chaos-maker/core/dist/chaos-config.schema.json for IDE / "$schema" autocomplete plus a sidecar chaos-config.schema.notes.md listing parity caveats. The artifact is a tooling approximation - runtime canonical validation is always Zod via validateChaosConfig.

See the Rule Validation concept page for the full pipeline, brand semantics, and migration notes.

Lifecycle and isolation

start() and stop() are the only entry points to the patched runtime. On stop() each restore step - fetch, XMLHttpRequest, WebSocket, EventSource, and the DOM observer - runs inside its own try / catch, so one failing step does not block the others from running. The failing step is reported via a cleanup-step-failed:<step> debug event and a console.warn. Some edge cases (frozen prototypes, third-party code that re-wraps a global between start() and stop(), host objects that reject property writes) may still leave a global patched; treat the diagnostics surface as the source of truth rather than assuming an absolute restore guarantee.

const chaos = new ChaosMaker(config);
chaos.start();
try {
  // ... drive the page ...
} finally {
  chaos.stop(); // safe to call twice; idempotent.
}

Concurrent instances against the same target are rejected. A second start() on a target that already has an active instance throws [chaos-maker] target already has an active runtime instance so the first instance keeps owning the patched globals. Use one ChaosMaker per realm (page, worker, jsdom) and call stop() before constructing a replacement.

Leak diagnostics

When debug mode is enabled, the engine emits structured invariant events whenever it sees signs of a leaked runtime - patched globals on start, stale wrapper handles, or another instance owning the target.

const chaos = new ChaosMaker(config, { debug: true });
chaos.start();
// ...
chaos.stop();

const issues = chaos.getLog().filter((event) =>
  event.type === 'debug' &&
  (event.detail.reason?.includes('already-patched') ||
   event.detail.reason?.includes('stale') ||
   event.detail.reason?.includes('orphaned') ||
   event.detail.reason === 'active-instance-conflict'),
);

Reasons emitted include target-fetch-already-patched, target-xhr-open-already-patched, target-xhr-send-already-patched, target-websocket-already-patched, target-eventsource-already-patched, stale-websocket-handle, stale-eventsource-handle, orphaned-dom-observer, active-instance-conflict, and cleanup-step-failed:<step>. The same reasons appear with phase: 'engine:stop' when a global stays patched after stop() runs.

Diagnostics are surfaced through getLog() only when debug: true; the runtime never throws on these conditions (the active-instance check is the one exception). They are intended for CI noise reduction and bug reports, not control flow.

Service Worker chaos

Chaos applies to SW-originated fetches via the @chaos-maker/core/sw subpath. Zod + UI + builder are excluded from this bundle so it stays small enough for production SW deploys.

Classic SW (one line):

// user's sw.js
importScripts('/path/to/chaos-maker-sw.js'); // auto-installs

Module SW:

import { installChaosSW } from '@chaos-maker/core/sw';
installChaosSW({ source: 'message' });

Page-side config is delivered via postMessage + MessageChannel ack. Use the adapter helpers (injectSWChaos / removeSWChaos / getSWChaosLog) in @chaos-maker/{playwright,cypress,webdriverio,puppeteer}.

Limitations: caches.match hits bypass chaos; push/sync events not covered; cross-origin SWs not supported.

License

MIT