@henriquecosta/chaos-api 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/adapters/express.d.ts +21 -0
  2. package/dist/adapters/express.js +40 -0
  3. package/dist/adapters/fastify.d.ts +12 -0
  4. package/dist/adapters/fastify.js +44 -0
  5. package/dist/adapters/koa.d.ts +12 -0
  6. package/dist/adapters/koa.js +47 -0
  7. package/dist/adapters/nestjs.d.ts +34 -0
  8. package/dist/adapters/nestjs.js +52 -0
  9. package/dist/bin/chaos-api.d.ts +2 -0
  10. package/dist/bin/chaos-api.js +33 -0
  11. package/dist/core/activity-log.d.ts +24 -0
  12. package/dist/core/activity-log.js +26 -0
  13. package/dist/core/scenario-engine.d.ts +12 -0
  14. package/dist/core/scenario-engine.js +38 -0
  15. package/dist/core/state-store.d.ts +26 -0
  16. package/dist/core/state-store.js +69 -0
  17. package/dist/core/types.d.ts +41 -0
  18. package/dist/core/types.js +1 -0
  19. package/dist/dashboard/server/control-api.d.ts +9 -0
  20. package/dist/dashboard/server/control-api.js +140 -0
  21. package/dist/dashboard/server/index.d.ts +10 -0
  22. package/dist/dashboard/server/index.js +49 -0
  23. package/dist/dashboard/ui/app.js +336 -0
  24. package/dist/dashboard/ui/index.html +215 -0
  25. package/dist/guardrail.d.ts +10 -0
  26. package/dist/guardrail.js +20 -0
  27. package/dist/index.d.ts +22 -0
  28. package/dist/index.js +13 -0
  29. package/dist/outbound/chaos-fetch.d.ts +13 -0
  30. package/dist/outbound/chaos-fetch.js +50 -0
  31. package/dist/outbound/index.d.ts +1 -0
  32. package/dist/outbound/index.js +1 -0
  33. package/dist/presets/catalog.d.ts +8 -0
  34. package/dist/presets/catalog.js +157 -0
  35. package/dist/presets/index.d.ts +15 -0
  36. package/dist/presets/index.js +22 -0
  37. package/dist/presets/types.d.ts +17 -0
  38. package/dist/presets/types.js +1 -0
  39. package/dist/scenarios/connection-reset.d.ts +7 -0
  40. package/dist/scenarios/connection-reset.js +8 -0
  41. package/dist/scenarios/delay.d.ts +6 -0
  42. package/dist/scenarios/delay.js +9 -0
  43. package/dist/scenarios/error-response.d.ts +9 -0
  44. package/dist/scenarios/error-response.js +13 -0
  45. package/dist/scenarios/index.d.ts +7 -0
  46. package/dist/scenarios/index.js +7 -0
  47. package/dist/scenarios/malformed-response.d.ts +11 -0
  48. package/dist/scenarios/malformed-response.js +13 -0
  49. package/dist/scenarios/random-error.d.ts +6 -0
  50. package/dist/scenarios/random-error.js +7 -0
  51. package/dist/scenarios/random-timeout.d.ts +7 -0
  52. package/dist/scenarios/random-timeout.js +8 -0
  53. package/dist/scenarios/registry.d.ts +12 -0
  54. package/dist/scenarios/registry.js +20 -0
  55. package/dist/scenarios/stale-response.d.ts +9 -0
  56. package/dist/scenarios/stale-response.js +10 -0
  57. package/dist/scenarios/unavailable-503.d.ts +5 -0
  58. package/dist/scenarios/unavailable-503.js +9 -0
  59. package/dist/scenarios/unavailable.d.ts +7 -0
  60. package/dist/scenarios/unavailable.js +9 -0
  61. package/package.json +59 -0
@@ -0,0 +1,7 @@
1
+ export { delayScenario } from "./delay.js";
2
+ export { errorResponseScenario } from "./error-response.js";
3
+ export { connectionResetScenario } from "./connection-reset.js";
4
+ export { unavailableScenario } from "./unavailable.js";
5
+ export { malformedResponseScenario } from "./malformed-response.js";
6
+ export { staleResponseScenario } from "./stale-response.js";
7
+ export { SCENARIO_REGISTRY } from "./registry.js";
@@ -0,0 +1,11 @@
1
+ import type { ScenarioHandler } from "../core/types.js";
2
+ export interface MalformedResponseOptions {
3
+ statusCode?: number;
4
+ /** Sent as-is; mismatched with the actual body on purpose (default: declares JSON, body is garbled text). */
5
+ contentType?: string;
6
+ /** JSON-serialized then truncated. Omitted = a built-in garbled JSON fragment. */
7
+ body?: unknown;
8
+ /** Fraction of the serialized body to keep, 0..1. Default 0.5 (cut mid-payload). */
9
+ truncateRatio?: number;
10
+ }
11
+ export declare const malformedResponseScenario: ScenarioHandler;
@@ -0,0 +1,13 @@
1
+ const DEFAULT_GARBLED = '{"data": [{"id": 1, "nam';
2
+ function truncate(text, ratio) {
3
+ const clamped = Math.min(1, Math.max(0, ratio));
4
+ return text.slice(0, Math.max(1, Math.floor(text.length * clamped)));
5
+ }
6
+ export const malformedResponseScenario = (ctx, options) => {
7
+ const { statusCode = 200, contentType = "application/json", body, truncateRatio = 0.5, } = options;
8
+ const payload = body !== undefined ? truncate(JSON.stringify(body), truncateRatio) : DEFAULT_GARBLED;
9
+ ctx.res.header("Content-Type", contentType);
10
+ ctx.res.status(statusCode);
11
+ ctx.res.send(payload);
12
+ return "terminated";
13
+ };
@@ -0,0 +1,6 @@
1
+ import type { ScenarioHandler } from "../core/types.js";
2
+ export interface RandomErrorOptions {
3
+ statusCodes?: number[];
4
+ body?: unknown;
5
+ }
6
+ export declare const randomErrorScenario: ScenarioHandler;
@@ -0,0 +1,7 @@
1
+ export const randomErrorScenario = (ctx, options) => {
2
+ const { statusCodes = [500], body } = options;
3
+ const code = statusCodes[Math.floor(Math.random() * statusCodes.length)];
4
+ ctx.res.status(code);
5
+ ctx.res.send(body ?? { error: "chaos-api: injected error", status: code });
6
+ return "terminated";
7
+ };
@@ -0,0 +1,7 @@
1
+ import type { ScenarioHandler } from "../core/types.js";
2
+ /**
3
+ * Simulates a hung upstream: intentionally never writes a response.
4
+ * Adapters must not call `next()`/end the request when this scenario terminates
5
+ * the chain — the connection stays open until the client or server times out.
6
+ */
7
+ export declare const randomTimeoutScenario: ScenarioHandler;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Simulates a hung upstream: intentionally never writes a response.
3
+ * Adapters must not call `next()`/end the request when this scenario terminates
4
+ * the chain — the connection stays open until the client or server times out.
5
+ */
6
+ export const randomTimeoutScenario = () => {
7
+ return "terminated";
8
+ };
@@ -0,0 +1,12 @@
1
+ import type { ScenarioHandler, ScenarioType } from "../core/types.js";
2
+ export interface ScenarioDefinition {
3
+ type: ScenarioType;
4
+ handler: ScenarioHandler;
5
+ }
6
+ /**
7
+ * Single source of truth for the v2 primitives (docs/PRD.md 6.2): type, handler, and
8
+ * apply order for combined scenarios. Array order = fixed priority order.
9
+ * Adding a primitive only touches this file plus the new scenario module — the engine
10
+ * itself doesn't change (docs/PRD.md 6.1 "registry pattern").
11
+ */
12
+ export declare const SCENARIO_REGISTRY: ScenarioDefinition[];
@@ -0,0 +1,20 @@
1
+ import { connectionResetScenario } from "./connection-reset.js";
2
+ import { delayScenario } from "./delay.js";
3
+ import { errorResponseScenario } from "./error-response.js";
4
+ import { malformedResponseScenario } from "./malformed-response.js";
5
+ import { staleResponseScenario } from "./stale-response.js";
6
+ import { unavailableScenario } from "./unavailable.js";
7
+ /**
8
+ * Single source of truth for the v2 primitives (docs/PRD.md 6.2): type, handler, and
9
+ * apply order for combined scenarios. Array order = fixed priority order.
10
+ * Adding a primitive only touches this file plus the new scenario module — the engine
11
+ * itself doesn't change (docs/PRD.md 6.1 "registry pattern").
12
+ */
13
+ export const SCENARIO_REGISTRY = [
14
+ { type: "delay", handler: delayScenario },
15
+ { type: "error-response", handler: errorResponseScenario },
16
+ { type: "connection-reset", handler: connectionResetScenario },
17
+ { type: "unavailable", handler: unavailableScenario },
18
+ { type: "malformed-response", handler: malformedResponseScenario },
19
+ { type: "stale-response", handler: staleResponseScenario },
20
+ ];
@@ -0,0 +1,9 @@
1
+ import type { ScenarioHandler } from "../core/types.js";
2
+ export interface StaleResponseOptions {
3
+ /** The stale/cached payload to serve instead of the real one. */
4
+ body?: unknown;
5
+ statusCode?: number;
6
+ /** Sets the `Age` header (seconds since the cached response was fresh). */
7
+ ageSeconds?: number;
8
+ }
9
+ export declare const staleResponseScenario: ScenarioHandler;
@@ -0,0 +1,10 @@
1
+ export const staleResponseScenario = (ctx, options) => {
2
+ const { body, statusCode = 200, ageSeconds } = options;
3
+ if (ageSeconds !== undefined) {
4
+ ctx.res.header("Age", String(ageSeconds));
5
+ }
6
+ ctx.res.header("X-Chaos-Stale", "true");
7
+ ctx.res.status(statusCode);
8
+ ctx.res.send(body ?? { stale: true });
9
+ return "terminated";
10
+ };
@@ -0,0 +1,5 @@
1
+ import type { ScenarioHandler } from "../core/types.js";
2
+ export interface Unavailable503Options {
3
+ retryAfterSeconds?: number;
4
+ }
5
+ export declare const unavailable503Scenario: ScenarioHandler;
@@ -0,0 +1,9 @@
1
+ export const unavailable503Scenario = (ctx, options) => {
2
+ const { retryAfterSeconds } = options;
3
+ if (retryAfterSeconds !== undefined) {
4
+ ctx.res.header("Retry-After", String(retryAfterSeconds));
5
+ }
6
+ ctx.res.status(503);
7
+ ctx.res.send({ error: "chaos-api: service unavailable" });
8
+ return "terminated";
9
+ };
@@ -0,0 +1,7 @@
1
+ import type { ScenarioHandler } from "../core/types.js";
2
+ export interface UnavailableOptions {
3
+ /** 503 (service unavailable), 507 (insufficient storage), 429 (rate limited), etc. */
4
+ statusCode?: number;
5
+ retryAfterSeconds?: number;
6
+ }
7
+ export declare const unavailableScenario: ScenarioHandler;
@@ -0,0 +1,9 @@
1
+ export const unavailableScenario = (ctx, options) => {
2
+ const { statusCode = 503, retryAfterSeconds } = options;
3
+ if (retryAfterSeconds !== undefined) {
4
+ ctx.res.header("Retry-After", String(retryAfterSeconds));
5
+ }
6
+ ctx.res.status(statusCode);
7
+ ctx.res.send({ error: "chaos-api: service unavailable" });
8
+ return "terminated";
9
+ };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@henriquecosta/chaos-api",
3
+ "version": "1.0.0",
4
+ "description": "Middleware pra simular falhas de producao (delay, erros, timeout, indisponibilidade, respostas malformadas/obsoletas) em APIs Express/Fastify/NestJS/Koa durante desenvolvimento.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "chaos-api": "./dist/bin/chaos-api.js"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ }
19
+ },
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json && node scripts/copy-ui.mjs",
22
+ "dev": "tsx watch src/bin/chaos-api.ts dashboard",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
25
+ "typecheck": "tsc --noEmit"
26
+ },
27
+ "peerDependencies": {
28
+ "express": ">=4",
29
+ "fastify": ">=4",
30
+ "koa": ">=2"
31
+ },
32
+ "peerDependenciesMeta": {
33
+ "express": {
34
+ "optional": true
35
+ },
36
+ "fastify": {
37
+ "optional": true
38
+ },
39
+ "koa": {
40
+ "optional": true
41
+ }
42
+ },
43
+ "devDependencies": {
44
+ "@types/express": "^4.17.21",
45
+ "@types/koa": "^2.15.2",
46
+ "@types/node": "^20.14.0",
47
+ "@types/supertest": "^6.0.2",
48
+ "express": "^4.19.2",
49
+ "fastify": "^4.28.1",
50
+ "koa": "^2.16.4",
51
+ "supertest": "^7.0.0",
52
+ "tsx": "^4.16.2",
53
+ "typescript": "^5.5.4",
54
+ "vitest": "^2.0.5"
55
+ },
56
+ "engines": {
57
+ "node": ">=18"
58
+ }
59
+ }