{"id":41328808,"url":"https://github.com/visible/cruel","last_synced_at":"2026-01-28T11:00:35.974Z","repository":{"id":334029906,"uuid":"1139743680","full_name":"visible/cruel","owner":"visible","description":"chaos testing with zero mercy","archived":false,"fork":false,"pushed_at":"2026-01-22T15:53:49.000Z","size":50,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-25T20:47:51.194Z","etag":null,"topics":["chaos","chaos-engineering","chaos-testing","fault-injection","nodejs","reliability","resilience","testing","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/visible.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-22T11:02:32.000Z","updated_at":"2026-01-25T06:24:42.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/visible/cruel","commit_stats":null,"previous_names":["visible/cruel"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/visible/cruel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/visible%2Fcruel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/visible%2Fcruel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/visible%2Fcruel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/visible%2Fcruel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/visible","download_url":"https://codeload.github.com/visible/cruel/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/visible%2Fcruel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28771523,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T08:38:24.014Z","status":"ssl_error","status_checked_at":"2026-01-26T08:38:22.080Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chaos","chaos-engineering","chaos-testing","fault-injection","nodejs","reliability","resilience","testing","typescript"],"created_at":"2026-01-23T06:12:03.214Z","updated_at":"2026-01-26T09:02:51.474Z","avatar_url":"https://github.com/visible.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"```\n┌──────────────────────────────────────────────────────────────┐\n│                                                              │\n│   ▄████▄  ██▀███  █    ██ ▓█████  ██▓                        │\n│  ▒██▀ ▀█ ▓██ ▒ ██▒██  ▓██▒▓█   ▀ ▓██▒                        │\n│  ▒▓█    ▄▓██ ░▄█ ▓██  ▒██░▒███   ▒██░                        │\n│  ▒▓▓▄ ▄██▒██▀▀█▄ ▓▓█  ░██░▒▓█  ▄ ▒██░                        │\n│  ▒ ▓███▀ ░██▓ ▒██▒▒█████▓ ░▒████▒░██████▒                    │\n│  ░ ░▒ ▒  ░ ▒▓ ░▒▓░▒▓▒ ▒ ▒ ░░ ▒░ ░░ ▒░▓  ░                    │\n│                                                              │\n│   chaos testing with zero mercy                              │\n│                                                              │\n└──────────────────────────────────────────────────────────────┘\n```\n\n```bash\n\u003e what is this?\n\n  a chaos engineering library for testing resilience\n  inject failures, latency, timeouts into any function\n  works with fetch, ai sdks, databases, anything async\n\n\u003e install?\n\n  bun add cruel\n  npm install cruel\n  pnpm add cruel\n\n\u003e quick start?\n\n  import { cruel } from \"cruel\"\n\n  const api = cruel(fetch, {\n    fail: 0.1,\n    delay: [100, 500],\n    timeout: 0.05,\n  })\n\n  const res = await api(\"https://api.example.com\")\n\n\u003e shorthand helpers?\n\n  cruel.fail(fn, 0.1)      // 10% failures\n  cruel.slow(fn, 500)      // add 500ms delay\n  cruel.timeout(fn, 0.1)   // 10% never resolve\n  cruel.flaky(fn)          // random chaos\n  cruel.unreliable(fn)     // more chaos\n  cruel.nightmare(fn)      // maximum chaos\n\n\u003e network chaos?\n\n  cruel.network.latency(fn, [100, 500])\n  cruel.network.packetLoss(fn, 0.1)\n  cruel.network.disconnect(fn, 0.05)\n  cruel.network.dns(fn, 0.02)\n  cruel.network.slow(fn)\n  cruel.network.unstable(fn)\n  cruel.network.offline(fn)\n\n\u003e http chaos?\n\n  cruel.http.status(fn, 500, 0.1)\n  cruel.http.status(fn, [500, 502, 503])\n  cruel.http.rateLimit(fn, 0.1)\n  cruel.http.serverError(fn, 0.1)\n  cruel.http.clientError(fn, 0.1)\n  cruel.http.badGateway(fn)\n  cruel.http.serviceUnavailable(fn)\n  cruel.http.gatewayTimeout(fn)\n\n\u003e stream chaos?\n\n  cruel.stream.cut(fn, 0.1)\n  cruel.stream.pause(fn, 500)\n  cruel.stream.corrupt(fn, 0.1)\n  cruel.stream.truncate(fn, 0.1)\n  cruel.stream.slow(fn)\n  cruel.stream.flaky(fn)\n\n\u003e ai chaos?\n\n  cruel.ai.rateLimit(fn, 0.1)\n  cruel.ai.overloaded(fn, 0.05)\n  cruel.ai.contextLength(fn, 0.02)\n  cruel.ai.contentFilter(fn, 0.01)\n  cruel.ai.modelUnavailable(fn)\n  cruel.ai.slowTokens(fn, [50, 200])\n  cruel.ai.streamCut(fn, 0.1)\n  cruel.ai.partialResponse(fn, 0.1)\n  cruel.ai.invalidJson(fn, 0.05)\n  cruel.ai.realistic(fn)\n  cruel.ai.nightmare(fn)\n\n\u003e ai sdk v6 integration?\n\n  import { aisdk, wrapModel, wrapProvider } from \"cruel\"\n  import { generateText, streamText } from \"ai\"\n  import { openai } from \"@ai-sdk/openai\"\n\n  // wrap entire provider\n  const chaosOpenAI = wrapProvider(openai, {\n    rateLimit: 0.1,\n    overloaded: 0.05,\n    delay: [100, 500],\n  })\n\n  // use with generateText\n  const result = await generateText({\n    model: chaosOpenAI(\"gpt-4\"),\n    prompt: \"hello\",\n  })\n\n  // or wrap individual model\n  const model = wrapModel(openai(\"gpt-4\"), aisdk.presets.realistic)\n\n  // streaming with chaos\n  const { textStream } = await streamText({\n    model: wrapModel(openai(\"gpt-4\"), {\n      streamCut: 0.1,\n      slowTokens: [50, 200],\n    }),\n    prompt: \"hello\",\n  })\n\n\u003e ai sdk middleware?\n\n  import { aisdk } from \"cruel\"\n  import { wrapLanguageModel } from \"ai\"\n  import { openai } from \"@ai-sdk/openai\"\n\n  // create chaos middleware\n  const chaosMiddleware = aisdk.middleware({\n    rateLimit: 0.1,\n    overloaded: 0.05,\n    streamCut: 0.1,\n    log: true,\n  })\n\n  // wrap model with middleware\n  const model = wrapLanguageModel({\n    model: openai(\"gpt-4\"),\n    middleware: chaosMiddleware,\n  })\n\n\u003e ai sdk presets?\n\n  aisdk.presets.realistic    // light, production-like\n  aisdk.presets.unstable     // medium chaos\n  aisdk.presets.harsh        // aggressive chaos\n  aisdk.presets.nightmare    // extreme chaos\n  aisdk.presets.apocalypse   // everything fails\n\n\u003e ai sdk errors?\n\n  import {\n    RateLimitError,\n    OverloadedError,\n    ContextLengthError,\n    ContentFilterError,\n    ModelUnavailableError,\n    InvalidApiKeyError,\n    QuotaExceededError,\n    StreamCutError,\n  } from \"cruel\"\n\n  try {\n    await generateText({ model, prompt })\n  } catch (e) {\n    if (e instanceof RateLimitError) {\n      console.log(\"retry after:\", e.retryAfter)\n    }\n    if (e instanceof OverloadedError) {\n      console.log(\"model overloaded, try later\")\n    }\n  }\n\n\u003e wrap ai tools?\n\n  import { wrapTools } from \"cruel\"\n\n  const tools = wrapTools({\n    search: { execute: searchFn },\n    calculate: { execute: calcFn },\n  }, {\n    toolFailure: 0.1,\n    toolTimeout: 0.05,\n    delay: [50, 200],\n  })\n\n\u003e circuit breaker?\n\n  const api = cruel.circuitBreaker(fetch, {\n    threshold: 5,     // open after 5 failures\n    timeout: 30000,   // try again after 30s\n    onOpen: () =\u003e console.log(\"circuit opened\"),\n    onClose: () =\u003e console.log(\"circuit closed\"),\n  })\n\n  await api(\"...\")\n  api.getState() // { state: \"closed\", failures: 0 }\n\n\u003e retry with backoff?\n\n  const api = cruel.retry(fetch, {\n    attempts: 3,\n    delay: 1000,\n    backoff: \"exponential\",  // fixed, linear, exponential\n    maxDelay: 10000,\n    onRetry: (attempt, error) =\u003e console.log(`retry ${attempt}`),\n    retryIf: (error) =\u003e error.status !== 404,\n  })\n\n\u003e bulkhead?\n\n  const api = cruel.bulkhead(fetch, {\n    maxConcurrent: 10,   // max parallel requests\n    maxQueue: 100,       // max queued requests\n    onReject: () =\u003e console.log(\"rejected\"),\n  })\n\n\u003e timeout wrapper?\n\n  const api = cruel.withTimeout(fetch, {\n    ms: 5000,\n    onTimeout: () =\u003e console.log(\"timed out\"),\n  })\n\n\u003e fallback?\n\n  const api = cruel.fallback(fetch, {\n    fallback: cachedData,  // or () =\u003e fetchBackup()\n    onFallback: (error) =\u003e console.log(\"using fallback\"),\n  })\n\n\u003e combine patterns?\n\n  import { cruel } from \"cruel\"\n\n  // chaos + circuit breaker + retry\n  const resilientApi = cruel.retry(\n    cruel.circuitBreaker(\n      cruel(fetch, { fail: 0.1, delay: [100, 500] }),\n      { threshold: 5, timeout: 30000 }\n    ),\n    { attempts: 3, backoff: \"exponential\" }\n  )\n\n\u003e presets?\n\n  cruel.enable(cruel.presets.development)\n  cruel.enable(cruel.presets.staging)\n  cruel.enable(cruel.presets.production)\n  cruel.enable(cruel.presets.harsh)\n  cruel.enable(cruel.presets.nightmare)\n  cruel.enable(cruel.presets.apocalypse)\n\n\u003e global mode?\n\n  cruel.enable({ fail: 0.1, delay: [100, 500] })\n  cruel.disable()\n  cruel.toggle()\n  cruel.isEnabled()\n\n\u003e scoped chaos?\n\n  await cruel.scope(async () =\u003e {\n    await api(\"...\")\n  }, { fail: 0.2 })\n\n\u003e scenarios?\n\n  cruel.scenario(\"outage\", {\n    chaos: { fail: 1 },\n    duration: 5000,\n  })\n\n  await cruel.play(\"outage\")\n  cruel.stop()\n\n  // built-in scenarios\n  await cruel.play(\"networkPartition\")\n  await cruel.play(\"highLatency\")\n  await cruel.play(\"degraded\")\n  await cruel.play(\"recovery\")\n\n\u003e intercept fetch?\n\n  cruel.patchFetch()\n\n  cruel.intercept(\"api.openai.com\", {\n    rateLimit: { rate: 0.1, retryAfter: 60 },\n    delay: [100, 500],\n  })\n\n  cruel.intercept(/api\\.anthropic\\.com/, {\n    fail: 0.1,\n    status: [529],\n  })\n\n  cruel.unpatchFetch()\n\n\u003e profiles?\n\n  cruel.profile(\"testing\", { fail: 0.2, delay: 100 })\n  cruel.useProfile(\"testing\")\n\n\u003e stats?\n\n  cruel.stats()\n  // { calls, failures, timeouts, delays, ... }\n  cruel.resetStats()\n\n\u003e deterministic?\n\n  cruel.seed(12345)\n  cruel.coin(0.5) // same result every time\n\n\u003e utilities?\n\n  cruel.coin(0.5)\n  cruel.pick([1, 2, 3])\n  cruel.between(10, 100)\n  cruel.maybe(value, 0.5)\n  await cruel.delay(500)\n\n\u003e fluent api?\n\n  cruel.wrap(fn).fail(0.1)\n  cruel.wrap(fn).slow(500)\n  cruel.wrap(fn).timeout(0.05)\n  cruel.wrap(fn).flaky()\n  cruel.wrap(fn).nightmare()\n\n\u003e factory?\n\n  import { createCruel } from \"cruel\"\n\n  const myCruel = createCruel({ delay: 100 })\n\n\u003e errors?\n\n  import {\n    CruelError,\n    CruelTimeoutError,\n    CruelNetworkError,\n    CruelHttpError,\n    CruelRateLimitError,\n    CruelAIError,\n  } from \"cruel\"\n\n\u003e cli?\n\n  cruel test https://api.example.com --fail 0.1 --count 20\n  cruel test https://api.example.com --preset nightmare\n  cruel scenario outage --duration 5000\n  cruel presets\n\n\u003e testing?\n\n  import { describe, test, beforeEach } from \"bun:test\"\n  import { cruel } from \"cruel\"\n\n  beforeEach(() =\u003e {\n    cruel.reset()\n    cruel.seed(12345)\n  })\n\n  test(\"handles failures\", async () =\u003e {\n    cruel.enable({ fail: 1 })\n    await expect(api()).rejects.toThrow()\n  })\n\n\u003e features?\n\n  ✓ chaos injection\n  ✓ network simulation\n  ✓ http status codes\n  ✓ stream manipulation\n  ✓ ai sdk v6 integration\n  ✓ middleware support\n  ✓ circuit breaker\n  ✓ retry with backoff\n  ✓ bulkhead isolation\n  ✓ timeout wrapper\n  ✓ fallback support\n  ✓ fetch interception\n  ✓ presets and profiles\n  ✓ scenarios\n  ✓ statistics\n  ✓ deterministic mode\n  ✓ cli tool\n  ✓ typescript native\n  ✓ zero dependencies\n  ✓ works everywhere\n\n\u003e license?\n\n  mit\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvisible%2Fcruel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvisible%2Fcruel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvisible%2Fcruel/lists"}