{"_id":"@crescendolab/override-proxy","_rev":"4-86b471e62f1941874ee44515228f111f","name":"@crescendolab/override-proxy","dist-tags":{"latest":"0.1.4"},"versions":{"0.1.0":{"name":"@crescendolab/override-proxy","version":"0.1.0","keywords":["mock","proxy","override","websocket","reverse-proxy"],"author":{"name":"Crescendo Lab"},"license":"Apache-2.0","_id":"@crescendolab/override-proxy@0.1.0","maintainers":[{"name":"vdustr","email":"VdustR@gmail.com"},{"name":"joseph_chang_tw","email":"k7451797@gmail.com"},{"name":"jacklee814","email":"jacklee82814@gmail.com"},{"name":"yunchao","email":"yunchao@cresclab.com"}],"homepage":"https://github.com/crescendolab-open/override-proxy#readme","bugs":{"url":"https://github.com/crescendolab-open/override-proxy/issues"},"bin":{"override-proxy":"dist/cli.js"},"dist":{"shasum":"2977be15defd5d4b43cabc7d83b0066b0e559587","tarball":"https://registry.npmjs.org/@crescendolab/override-proxy/-/override-proxy-0.1.0.tgz","fileCount":39,"integrity":"sha512-11rtHO9HlW4UXonfLuOHTj1scDQWciG6u3Fx08l6WeiWY+3HP3l2XSLcu51OJ3bLYVDotKSozHO2WSh6IdimBQ==","signatures":[{"sig":"MEQCIBwxkl9UVWTQG0jCtma2aHsoaEspJ0M1b0lxabHZ3pwxAiBR9ym1mJfbvP3NaasjlAxmB0zkSLcEGD3I5ARw+XmgUw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":250054},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.19.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./cli":{"types":"./dist/cli.d.ts","import":"./dist/cli.js","require":"./dist/cli.js"},"./main":{"types":"./dist/main.d.ts","import":"./dist/main.js","require":"./dist/main.js"}},"gitHead":"344493e3bcc21763b3da617eedab5bd38feafb9e","scripts":{"dev":"node scripts/dev.mjs","test":"node --import tsx --test tests/*.test.ts","build":"tsc -p tsconfig.build.json","prepack":"tsc -p tsconfig.build.json","release":"pnpm run build && changeset publish","changeset":"changeset","typecheck":"tsc --noEmit","changeset:version":"changeset version"},"_npmUser":{"name":"vdustr","email":"VdustR@gmail.com"},"repository":{"url":"git+https://github.com/crescendolab-open/override-proxy.git","type":"git"},"_npmVersion":"11.13.0","description":"Override-first local mock + proxy server","directories":{},"_nodeVersion":"24.5.0","dependencies":{"ws":"^8.20.0","tsx":"^4.20.4","cors":"^2.8.5","chalk":"^5.3.0","pathe":"^1.1.2","express":"^4.21.2","fs-extra":"^11.2.0","get-port":"^7.1.0","es-toolkit":"^1.39.10","@dotenvx/dotenvx":"^1.22.0","http-proxy-middleware":"^3.0.5"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.14.0","devDependencies":{"nodemon":"^3.1.4","prettier":"^3.3.0","@types/ws":"^8.18.1","typescript":"^5.5.0","@types/cors":"^2.8.17","@types/express":"^4.17.21","@changesets/cli":"^2.31.0","@types/fs-extra":"^11.0.4","@tsconfig/node24":"^24.0.0","@tsconfig/strictest":"^2.0.0"},"_npmOperationalInternal":{"tmp":"tmp/override-proxy_0.1.0_1777370440207_0.20283547172043637","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@crescendolab/override-proxy","version":"0.1.2","keywords":["mock","proxy","override","websocket","reverse-proxy"],"author":{"name":"Crescendo Lab"},"license":"Apache-2.0","_id":"@crescendolab/override-proxy@0.1.2","maintainers":[{"name":"vdustr","email":"VdustR@gmail.com"},{"name":"joseph_chang_tw","email":"k7451797@gmail.com"},{"name":"jacklee814","email":"jacklee82814@gmail.com"},{"name":"yunchao","email":"yunchao@cresclab.com"}],"homepage":"https://github.com/crescendolab-open/override-proxy#readme","bugs":{"url":"https://github.com/crescendolab-open/override-proxy/issues"},"bin":{"override-proxy":"dist/cli.js"},"dist":{"shasum":"4075d726af5f3133ae1206198c9c61521390541a","tarball":"https://registry.npmjs.org/@crescendolab/override-proxy/-/override-proxy-0.1.2.tgz","fileCount":44,"integrity":"sha512-9iao/bUNROfxxRD1XS8vOwmDDku2cbJ4WFRr4SAJpFKx/8iXcABS8Iaj++9UgqzQIJVXRcqrP0po0pW5WNCO/Q==","signatures":[{"sig":"MEYCIQDLS+RivApwVZimYcLHahy37poyzWHA9Edk+6xba2mnbQIhALGi5rceR57h74/EIQ6CsYyez2YYnUkB0L5Gs5fKrPEK","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":272003},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.19.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./cli":{"types":"./dist/cli.d.ts","import":"./dist/cli.js","require":"./dist/cli.js"},"./main":{"types":"./dist/main.d.ts","import":"./dist/main.js","require":"./dist/main.js"}},"gitHead":"d829da424101ca1496c9decaf432066dd6fe6f28","scripts":{"dev":"node scripts/dev.mjs","test":"node --import tsx --test tests/*.test.ts","build":"tsc -p tsconfig.build.json","prepack":"tsc -p tsconfig.build.json","release":"pnpm run build && changeset publish","changeset":"changeset","typecheck":"tsc --noEmit","changeset:version":"changeset version"},"_npmUser":{"name":"vdustr","email":"VdustR@gmail.com"},"repository":{"url":"git+https://github.com/crescendolab-open/override-proxy.git","type":"git"},"_npmVersion":"11.13.0","description":"Override-first local mock + proxy server","directories":{},"_nodeVersion":"24.5.0","dependencies":{"ws":"^8.20.0","tsx":"^4.20.4","cors":"^2.8.5","chalk":"^5.3.0","pathe":"^1.1.2","express":"^4.21.2","fs-extra":"^11.2.0","get-port":"^7.1.0","es-toolkit":"^1.39.10","@dotenvx/dotenvx":"^1.22.0","http-proxy-middleware":"^3.0.5"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.14.0","devDependencies":{"nodemon":"^3.1.4","prettier":"^3.3.0","@types/ws":"^8.18.1","typescript":"^5.5.0","@types/cors":"^2.8.17","@types/express":"^4.17.21","@changesets/cli":"^2.31.0","@types/fs-extra":"^11.0.4","@tsconfig/node24":"^24.0.0","@tsconfig/strictest":"^2.0.0"},"_npmOperationalInternal":{"tmp":"tmp/override-proxy_0.1.2_1777381456759_0.6248147425818529","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@crescendolab/override-proxy","version":"0.1.3","keywords":["mock","proxy","override","websocket","reverse-proxy"],"author":{"name":"Crescendo Lab"},"license":"Apache-2.0","_id":"@crescendolab/override-proxy@0.1.3","maintainers":[{"name":"vdustr","email":"VdustR@gmail.com"},{"name":"joseph_chang_tw","email":"k7451797@gmail.com"},{"name":"jacklee814","email":"jacklee82814@gmail.com"},{"name":"yunchao","email":"yunchao@cresclab.com"}],"homepage":"https://github.com/crescendolab-open/override-proxy#readme","bugs":{"url":"https://github.com/crescendolab-open/override-proxy/issues"},"bin":{"override-proxy":"dist/cli.js"},"dist":{"shasum":"3721e388aff25b5d9766d19c51dfc5b829572e95","tarball":"https://registry.npmjs.org/@crescendolab/override-proxy/-/override-proxy-0.1.3.tgz","fileCount":44,"integrity":"sha512-ca5TNVTDgnzC/8Ot8Yp9wE4BIWPWpI2t+KKNZJpmwpZcJ69p6ztHUa5RYSicN1dzJK7myz4lvohSP7ZL9E8g0w==","signatures":[{"sig":"MEYCIQCRs8kJbCw8Ved9T0vJQDUGOb6e7RdKVUOqJQem70y1AgIhAIe2tEUBXKE24rG2WEHnFbMcLM4W3FWh6DF5zqdwRGza","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":275500},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.19.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./cli":{"types":"./dist/cli.d.ts","import":"./dist/cli.js","require":"./dist/cli.js"},"./main":{"types":"./dist/main.d.ts","import":"./dist/main.js","require":"./dist/main.js"}},"scripts":{"dev":"node scripts/dev.mjs","test":"node --import tsx --test tests/*.test.ts","build":"tsc -p tsconfig.build.json","prepack":"tsc -p tsconfig.build.json","release":"pnpm run build && changeset publish","changeset":"changeset","typecheck":"tsc --noEmit","changeset:version":"changeset version"},"_npmUser":{"name":"vdustr","email":"VdustR@gmail.com"},"repository":{"url":"git+https://github.com/crescendolab-open/override-proxy.git","type":"git"},"_npmVersion":"11.13.0","description":"Override-first local mock + proxy server","directories":{},"_nodeVersion":"24.5.0","dependencies":{"ws":"^8.20.0","tsx":"^4.20.4","cors":"^2.8.5","chalk":"^5.3.0","pathe":"^1.1.2","express":"^4.21.2","fs-extra":"^11.2.0","get-port":"^7.1.0","es-toolkit":"^1.39.10","@dotenvx/dotenvx":"^1.22.0","http-proxy-middleware":"^3.0.5"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.14.0","devDependencies":{"nodemon":"^3.1.4","prettier":"^3.3.0","@types/ws":"^8.18.1","typescript":"^5.5.0","@types/cors":"^2.8.17","@types/express":"^4.17.21","@changesets/cli":"^2.31.0","@types/fs-extra":"^11.0.4","@tsconfig/node24":"^24.0.0","@tsconfig/strictest":"^2.0.0"},"_npmOperationalInternal":{"tmp":"tmp/override-proxy_0.1.3_1777448937796_0.27951591904376816","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"_id":"@crescendolab/override-proxy@0.1.4","bin":{"override-proxy":"dist/cli.js"},"bugs":{"url":"https://github.com/crescendolab-open/override-proxy/issues"},"dist":{"shasum":"954747f9e79309752daf9742e504311a004f1dfd","tarball":"https://registry.npmjs.org/@crescendolab/override-proxy/-/override-proxy-0.1.4.tgz","fileCount":44,"integrity":"sha512-CYnZ/hTlr2v27IpF8eQFQUrs05sf5giIXyHGzA7xROUB5CyWvS5iIzzuTPaJKsqAA8+U5FdW+COKbxkZhIMsPg==","signatures":[{"sig":"MEYCIQCLiDfHUbu/C/l1kdcj92pakYvVUPdrXAIKB6D2j9R2BQIhALi9WF1spJFaXIBLKdHLEtTQSRk22JAWt69wyIPYzQT6","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHg0c2cz5SroSavr4PnavxR/9pmmnV7a+r+mmFydRM2AAiBZY6KcIJdu4xI7INaaN1/39m9m33UPPyqgnXDhjF3Ltw=="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@crescendolab%2foverride-proxy@0.1.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":277106},"main":"./dist/index.js","name":"@crescendolab/override-proxy","type":"module","_from":"file:crescendolab-override-proxy-0.1.4.tgz","types":"./dist/index.d.ts","author":{"name":"Crescendo Lab"},"engines":{"node":">=20.19.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./cli":{"types":"./dist/cli.d.ts","import":"./dist/cli.js","require":"./dist/cli.js"},"./main":{"types":"./dist/main.d.ts","import":"./dist/main.js","require":"./dist/main.js"}},"license":"Apache-2.0","scripts":{"dev":"node scripts/dev.mjs","test":"node --import tsx --test tests/*.test.ts","build":"tsc -p tsconfig.build.json","release":"pnpm run build && changeset publish","changeset":"changeset","typecheck":"tsc --noEmit","changeset:version":"changeset version"},"version":"0.1.4","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:af985551-4467-4e01-97b8-074f7a5ffc47"}},"homepage":"https://github.com/crescendolab-open/override-proxy#readme","keywords":["mock","proxy","override","websocket","reverse-proxy"],"_resolved":"/tmp/1d5d03e8f33b3f64743eedd7937053e5/crescendolab-override-proxy-0.1.4.tgz","_integrity":"sha512-CYnZ/hTlr2v27IpF8eQFQUrs05sf5giIXyHGzA7xROUB5CyWvS5iIzzuTPaJKsqAA8+U5FdW+COKbxkZhIMsPg==","repository":{"url":"git+https://github.com/crescendolab-open/override-proxy.git","type":"git"},"_npmVersion":"11.5.1","description":"Override-first local mock + proxy server","directories":{},"maintainers":[{"name":"vdustr","email":"VdustR@gmail.com"},{"name":"joseph_chang_tw","email":"k7451797@gmail.com"},{"name":"jacklee814","email":"jacklee82814@gmail.com"},{"name":"yunchao","email":"yunchao@cresclab.com"}],"_nodeVersion":"24.5.0","dependencies":{"ws":"^8.20.0","tsx":"^4.20.4","cors":"^2.8.5","chalk":"^5.3.0","pathe":"^1.1.2","express":"^4.21.2","fs-extra":"^11.2.0","get-port":"^7.1.0","es-toolkit":"^1.39.10","@dotenvx/dotenvx":"^1.22.0","http-proxy-middleware":"^3.0.5"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"nodemon":"^3.1.4","prettier":"^3.3.0","@types/ws":"^8.18.1","typescript":"^5.5.0","@types/cors":"^2.8.17","@types/express":"^4.17.21","@changesets/cli":"^2.31.0","@types/fs-extra":"^11.0.4","@tsconfig/node24":"^24.0.0","@tsconfig/strictest":"^2.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/override-proxy_0.1.4_1789879019266_0.12209678530627088"}}},"time":{"created":"2026-04-28T10:00:40.101Z","modified":"2026-09-20T04:36:59.660Z","0.1.0":"2026-04-28T10:00:40.365Z","0.1.2":"2026-04-28T13:04:16.896Z","0.1.3":"2026-04-29T07:48:58.010Z","0.1.4":"2026-09-20T04:36:59.346Z"},"bugs":{"url":"https://github.com/crescendolab-open/override-proxy/issues"},"author":{"name":"Crescendo Lab"},"license":"Apache-2.0","homepage":"https://github.com/crescendolab-open/override-proxy#readme","keywords":["mock","proxy","override","websocket","reverse-proxy"],"repository":{"url":"git+https://github.com/crescendolab-open/override-proxy.git","type":"git"},"description":"Override-first local mock + proxy server","maintainers":[{"name":"vdustr","email":"VdustR@gmail.com"},{"name":"joseph_chang_tw","email":"k7451797@gmail.com"},{"name":"jacklee814","email":"jacklee82814@gmail.com"},{"name":"yunchao","email":"yunchao@cresclab.com"}],"readme":"# override-proxy\n\nPluggable local development server that serves rule-based HTTP and WebSocket overrides first, then proxies unmatched traffic to upstream targets.\n\nKey features:\n\n- Override-first: if a rule matches, respond immediately; otherwise proxy.\n- Multi-server, route-scoped config with root and subdirectory routes.\n- Inline HTTP and WebSocket rules through config imports.\n- Raw WebSocket direct proxy or bridge mode with bidirectional message actions.\n- CLI entry with config discovery, `serve`, `validate`, and legacy fallback.\n- Layered environment loading via `dotenvx` (`.env.local` then `.env.default`).\n\n## Documentation\n\n| Document                                                                 | Purpose                                            |\n| ------------------------------------------------------------------------ | -------------------------------------------------- |\n| [README.md](README.md)                                                   | User guide and overview (you are here)             |\n| [AGENTS.md](AGENTS.md)                                                   | Detailed guide for AI agents                       |\n| [docs/TOOLS.md](docs/TOOLS.md)                                           | Development commands and verification workflow     |\n| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)                             | Visual diagrams and code location index            |\n| [docs/design/config.md](docs/design/config.md)                           | Config model for multi-server routing              |\n| [docs/design/websocket.md](docs/design/websocket.md)                     | WebSocket proxy and rule semantics                 |\n| [docs/design/cli.md](docs/design/cli.md)                                 | CLI behavior                                       |\n| [docs/design/implementation-plan.md](docs/design/implementation-plan.md) | Ordered implementation checklist                   |\n| [docs/EXAMPLES.md](docs/EXAMPLES.md)                                     | Copy-paste examples for common scenarios           |\n| [docs/PATTERNS.md](docs/PATTERNS.md)                                     | Best practices and common pitfalls                 |\n| [docs/DOC-WRITING-GUIDE.md](docs/DOC-WRITING-GUIDE.md)                   | Documentation writing standards (for contributors) |\n| [skills/override-proxy/SKILL.md](skills/override-proxy/SKILL.md)         | Codex skill for agent-assisted usage               |\n\n## Development Tools\n\nThe workflow is config-driven: import rule values in config, validate the config,\nthen run focused tests or the built CLI. See [docs/TOOLS.md](docs/TOOLS.md) for\nthe current command list.\n\n## Codex Skill\n\nThis repository includes an installable Codex skill at\n`skills/override-proxy`. Install it from this GitHub repository with the\nskill-installer workflow by using repo `crescendolab-open/override-proxy` and\npath `skills/override-proxy`.\n\nThe skill guides agents toward a project-local devDependency setup, then helps\nthem author configs or rules with the same commands the repository will use.\n\n## Quick Start\n\nInstall it in your app or mock workspace:\n\n```bash\npnpm install -D @crescendolab/override-proxy\n```\n\nCreate `override-proxy.config.ts`:\n\n```ts\nimport { defineConfig, rule } from \"@crescendolab/override-proxy\";\n\nconst Ping = rule(\"GET\", \"/__ping\", (_req, res) => {\n  res.json({ ok: true, source: \"override-proxy\" });\n});\n\nexport default defineConfig({\n  servers: [\n    {\n      port: 4000,\n      routes: [\n        {\n          path: \"/\",\n          target: \"https://pokeapi.co/api/v2/\",\n          http: { rules: [Ping] },\n        },\n      ],\n    },\n  ],\n});\n```\n\nValidate and serve:\n\n```bash\npnpm exec override-proxy validate\npnpm exec override-proxy serve\ncurl http://localhost:4000/__ping\n```\n\nFor repeatable team usage, add package scripts in the consuming project:\n\n```json\n{\n  \"scripts\": {\n    \"proxy:validate\": \"override-proxy validate\",\n    \"proxy:serve\": \"override-proxy serve\"\n  }\n}\n```\n\nThen run `pnpm run proxy:validate` and `pnpm run proxy:serve`.\n\n## Repository Development\n\nFrom this source checkout:\n\n```bash\npnpm install\npnpm dev\n```\n\n`pnpm dev` runs the CLI serve path through `nodemon`.\n\nValidate a config file without listening:\n\n```bash\npnpm exec tsx cli.ts validate\npnpm exec tsx cli.ts validate --config ./override-proxy.config.ts\n```\n\nBuild the standalone package entrypoints:\n\n```bash\npnpm run build\nnode dist/cli.js validate\n```\n\nRelease workflow:\n\n```bash\npnpm changeset\n```\n\nEvery user-facing change should include a changeset. After changes land on\n`main`, the Release workflow uses Changesets to open a version PR. Merging that\nversion PR publishes to npm through `pnpm release`; the repository must provide\nan `NPM_TOKEN` secret for publishing.\n\n## Environment Variables\n\nLoad order (first wins, no overwrite): `.env.local` → `.env.default`\n\nSample `.env.default` (do not put secrets here):\n\n```dotenv\nPROXY_TARGET=https://pokeapi.co/api/v2/\nPORT=4000\n# CORS_ORIGINS=http://localhost:3000,https://your-app.local\n```\n\n| Name         | Description                                     | Default                      |\n| ------------ | ----------------------------------------------- | ---------------------------- |\n| PROXY_TARGET | Upstream target when no rule matches            | <https://pokeapi.co/api/v2/> |\n| PORT         | Preferred port (auto-increments if busy)        | 4000                         |\n| CORS_ORIGINS | Allowed origins (comma list, empty = allow all) | (empty)                      |\n\n> Put secrets only in `.env.local` (ignored by git). `.env.default` is committed and should remain non-sensitive.\n\n## CLI And Config Files\n\nThe CLI command defaults to `serve`. In this source checkout, run it through `tsx`:\n\n```bash\npnpm exec tsx cli.ts\npnpm exec tsx cli.ts serve --config ./override-proxy.config.ts\npnpm exec tsx cli.ts validate --config ./override-proxy.config.ts\n```\n\nAfter `pnpm run build`, the package exposes `override-proxy` from `./dist/cli.js`. Installed package usage should go through the consuming project's local dependency:\n\n```bash\npnpm exec override-proxy\npnpm exec override-proxy serve --config ./override-proxy.config.ts\npnpm exec override-proxy validate --config ./override-proxy.config.ts\n```\n\nWhen consuming the built or installed package, config files can import helpers from `@crescendolab/override-proxy`. In this source checkout before building, import from local source files such as `./config.js`.\n\nDefault config discovery checks the current working directory for:\n\n1. `override-proxy.local.config.ts`\n2. `override-proxy.local.config.mts`\n3. `override-proxy.local.config.js`\n4. `override-proxy.local.config.mjs`\n5. `override-proxy.config.local.ts`\n6. `override-proxy.config.local.mts`\n7. `override-proxy.config.local.js`\n8. `override-proxy.config.local.mjs`\n9. `override-proxy.config.ts`\n10. `override-proxy.config.mts`\n11. `override-proxy.config.js`\n12. `override-proxy.config.mjs`\n\nLocal config names are ignored by the repository's default `.gitignore`.\n\nIf no config file exists, override-proxy runs in legacy proxy mode using `PROXY_TARGET`, `PORT`, and `CORS_ORIGINS`.\n\nExample multi-route config:\n\n```ts\nimport { defineConfig } from \"./config.js\";\nimport { ApiUser } from \"./rules/api-user.js\";\nimport { RootFallback } from \"./rules/root-fallback.js\";\n\nexport default defineConfig({\n  servers: [\n    {\n      name: \"main\",\n      host: \"127.0.0.1\",\n      port: 4000,\n      routes: [\n        {\n          name: \"api\",\n          path: \"/api\",\n          target: \"https://api.example.com\",\n          http: {\n            rules: [ApiUser],\n          },\n          rewrite: { stripPrefix: true },\n        },\n        {\n          name: \"root\",\n          path: \"/\",\n          target: \"https://www.example.com\",\n          http: {\n            rules: [RootFallback],\n          },\n        },\n      ],\n    },\n  ],\n});\n```\n\nRoutes are matched by pathname with priority, longest segment-aware prefix, declaration order, and root fallback.\n\nConfig exports can be objects, factories, or async factories:\n\n```ts\nimport { readFile } from \"node:fs/promises\";\nimport { LocalRule } from \"./rules/local.js\";\n\nexport default defineConfig(async () => {\n  const fixture = JSON.parse(await readFile(\"./fixtures/user.json\", \"utf8\"));\n\n  return {\n    servers: [\n      {\n        routes: [{ path: \"/\", http: { rules: [LocalRule(fixture)] } }],\n      },\n    ],\n  };\n});\n```\n\n## Rule System\n\nRules are ordinary JavaScript values attached to config. Put them inline or import them from any module; config may be an object, function, or async function, so filesystem reads and other setup belong in userland config code.\n\nInterface:\n\n```ts\ninterface OverrideRule {\n  name?: string;\n  enabled?: boolean; // default true\n  methods: [Method, ...Method[]]; // non-empty, uppercase\n  test(req: Request): boolean;\n  handler(\n    req: Request,\n    res: Response,\n    next: NextFunction,\n  ): void | Promise<void>;\n}\n```\n\nHelper creation styles:\n\n1. Overload form:\n\n```ts\nrule(method: Method | readonly Method[], path: string | RegExp, handler, options?)\n```\n\n1. Config object form:\n\n```ts\nrule({ path?: string|RegExp, test?: (req)=>boolean, methods?: readonly Method[], name?, enabled?, handler })\n```\n\nConstraints:\n\n- Provide either `path` or `test` (if both given, `test` augments path match logic you control).\n- If `methods` omitted in config form it defaults to `[\"GET\"]`.\n- First matching enabled rule short-circuits.\n\nAuthoring patterns:\n\n1. `export const SomeRule = rule(...)` and import it from config.\n2. Export arrays for scenario packs, then spread them into `http.rules` or `ws.rules`.\n3. Use `name` when logs need a stable display value; otherwise the helper derives one from `path` when possible.\n\n## WebSocket Rules\n\nWebSocket support targets raw WebSocket traffic. It does not implement Socket.IO protocol semantics.\n\nRoute config supports three modes:\n\n| Mode     | Behavior                                                                 |\n| -------- | ------------------------------------------------------------------------ |\n| `direct` | Transparent WebSocket proxy using the upstream target                    |\n| `bridge` | Accept client socket, optionally connect upstream, and run message rules |\n| `mock`   | Accept client socket without opening an upstream connection              |\n\nFor WebSocket routes, set `ws.target` to the upstream origin or base path. The\nclient request path is appended after route rewrites, and bridge mode forwards\nthe client query string to the upstream URL.\n\nBridge and mock message rules use `wsRule()`:\n\n```ts\nimport { wsRule } from \"../../utils.js\";\n\nexport const PatchChatMessage = wsRule({\n  test: (ctx) =>\n    ctx.direction === \"client\" && ctx.jsonObject?.[\"type\"] === \"message\",\n  handler: (ctx) => {\n    ctx.emitToClient({ type: \"proxy:seen\" });\n    return ctx.forward({\n      ...ctx.jsonObject,\n      patchedByProxy: true,\n    });\n  },\n});\n```\n\nEach message context includes `raw`, `text`, `json`, `jsonObject`, `direction`, route metadata, request headers, and action helpers. Supported actions are `forward`, `skip`, `emitToClient`, `emitToUpstream`, `close`, and `fail`.\n\nUse `wsConnectionRule()` when the proxy should send messages without waiting for client or upstream traffic, such as welcome events, heartbeat pings, or server-push mocks:\n\n```ts\nimport { wsConnectionRule } from \"../../utils.js\";\n\nexport const Heartbeat = wsConnectionRule({\n  onConnect: (ctx) => {\n    ctx.client.send({ type: \"proxy:ready\" });\n\n    ctx.every(30_000, () => {\n      ctx.client.send({ type: \"proxy:ping\", at: Date.now() });\n    });\n  },\n});\n```\n\nThe connection context exposes typed `client` and optional `upstream` peers with `send`, `close`, and `readyState`. Advanced rules can use `ctx.raw.client` / `ctx.raw.upstream` for the underlying `ws` sockets. Timers registered with `ctx.every()` and disposers returned from `onConnect()` are cleaned up when the connection closes.\n\n## Examples\n\n### 4.1 Simple path\n\n```ts\nimport { rule } from \"../utils.js\";\nexport default rule({\n  name: \"ping\",\n  path: \"/__ping\",\n  methods: [\"GET\"],\n  handler: (_req, res) => res.json({ ok: true, t: Date.now() }),\n});\n```\n\n### 4.2 RegExp capture\n\n```ts\nimport { rule } from \"../utils.js\";\nexport default rule({\n  name: \"user-detail\",\n  path: /^\\/api\\/users\\/(\\d+)$/,\n  methods: [\"GET\"],\n  handler: (req, res) => {\n    const match = /^\\/api\\/users\\/(\\d+)$/.exec(req.path);\n    if (!match) {\n      res.status(404).json({ error: \"not_found\" });\n      return;\n    }\n    const [, id] = match;\n    res.json({ id, name: `User ${id}`, from: \"override\" });\n  },\n});\n```\n\n### 4.3 Custom test\n\n```ts\nimport { rule } from \"../utils.js\";\nexport const rules = [\n  rule({\n    name: \"feature-core\",\n    test: (req) =>\n      req.method === \"GET\" &&\n      req.path === \"/feature-controls\" &&\n      req.query[\"only\"] === \"core\",\n    handler: (_req, res) =>\n      res.json({ features: [\"core-a\", \"core-b\"], ts: Date.now() }),\n  }),\n];\n```\n\n### 4.4 Disabled rule\n\n```ts\nexport default rule({\n  name: \"temp-off\",\n  path: \"/disabled\",\n  enabled: false,\n  handler: (_r, res) => res.json({ off: true }),\n});\n```\n\n## Built-in Endpoints\n\n| Path          | Method | Description                           |\n| ------------- | ------ | ------------------------------------- |\n| `/__env`      | GET    | Legacy non-sensitive environment info |\n| `/__override` | GET    | Config-mode server and route snapshot |\n| `*`           | ANY    | Route-specific proxy fallback         |\n\nRoute CORS settings apply to route traffic, not built-in control endpoints.\n\nLogging pattern: `[id] -> METHOD path` / `match ruleName` / completion line with status & source.\n\n## Development Workflow\n\n1. Add or edit rule modules.\n2. Import the active rules from `override-proxy.config.ts`.\n3. Run `pnpm exec tsx cli.ts validate`.\n4. Start with `pnpm dev` and send requests to validate behavior.\n\nChange upstream: set `PROXY_TARGET` in `.env.local`  \nRestrict CORS: `CORS_ORIGINS=http://localhost:3000,https://dev.example.com`\n\n## Project Structure\n\n```text\n.\n├─ cli.ts\n├─ index.ts\n├─ config.ts\n├─ server-runtime.ts\n├─ http-app.ts\n├─ ws-direct-proxy.ts\n├─ ws-bridge.ts\n├─ main.ts\n├─ utils.ts\n├─ tests/\n├─ .env.default\n├─ package.json\n├─ tsconfig.json\n├─ tsconfig.build.json\n└─ nodemon.json\n```\n\n## Common Scenarios\n\n- Simulate latency: `await new Promise(r => setTimeout(r, 800));`\n- Conditional override: `test: (req)=> req.path === \"/api/users\" && req.headers[\"x-mock-mode\"] === \"1\"`\n- Header trigger: `test: (req)=> req.headers[\"x-mock-mode\"] === \"1\"`\n- WebSocket mock event: `ctx.emitToClient({ type: \"proxy:ready\" }); return ctx.skip();`\n\n## Security Notes\n\n- Keep secrets only in `.env.local`.\n- Remove or protect `/__env` if exposing externally.\n- Rules execute arbitrary code: review sources.\n- Avoid exposing this service directly to the public Internet.\n\n## Extension Ideas\n\n| Feature                 | Description                      |\n| ----------------------- | -------------------------------- |\n| /\\_\\_rules              | List rules + status + hit counts |\n| Runtime toggle          | Enable/disable via PATCH         |\n| Hot replace             | chokidar-based in-process swap   |\n| Fault / delay injection | Simulate 4xx/5xx/timeout         |\n| Stats                   | hit count / last hit timestamp   |\n| Priority control        | Explicit rule ordering           |\n\n## Rule Organization & Archival\n\nYou can still keep rule modules under `rules/`, but runtime does not scan that directory. Config decides exactly which rule values are active.\n\n### 11.1 Group Related Rules\n\n- Group by feature / domain / scenario using either subfolders _and/or_ multi-export files.\n- Import the packs you want in `override-proxy.config.ts`.\n\n### 11.2 Disable Single Rule\n\nTo temporarily disable a single rule without deleting it, add `enabled: false` to the rule configuration:\n\n```ts\nexport const UserDetail = rule({\n  methods: ['GET'],\n  path: /^\\/api\\/users\\/\\d+$/,\n  enabled: false,\n  handler: (req, res) => res.json({ ... })\n});\n```\n\nThe rule remains in config but won't match requests.\n\n### 11.3 Disable an Entire Group\n\nRemove that pack from the config array, or branch inside an async config factory:\n\n```ts\nconst rules = process.env[\"MOCK_PACK\"] === \"checkout\" ? checkoutRules : [];\n```\n\n### 11.4 Shareable by Design\n\n- Committed config and rule modules are instantly shared—teammates restart and get the same overrides.\n- Avoid secrets / PII in responses. Use env vars or synthetic placeholders if needed.\n- Scenario-oriented packs let you prepare multiple demo states and enable exactly one by config import or factory branch.\n\n### 11.5 Personal / WIP Rules\n\n- For scratch work you _do not_ want committed, use `override-proxy.local.config.ts` and keep it git-ignored.\n\n### 11.6 Naming Guidance\n\n- Module names: concise, kebab-case domain or scenario (`billing-refunds`, `chat-surge-test`).\n- Rule `name` (shown in logs): stable identifier (PascalCase or kebab-case) reflecting purpose.\n\n### 11.7 Quick Lifecycle Table\n\n| Action              | Steps                                 |\n| ------------------- | ------------------------------------- |\n| Add feature pack    | Create module, import rules in config |\n| Disable single rule | Add `enabled: false` to rule config   |\n| Disable rule group  | Remove pack from config or branch env |\n| Share               | Push config/rule modules and restart  |\n\n### 11.8 Why Inline over Runtime Scanning?\n\nInline config keeps runtime simple and makes TypeScript point directly at missing imports, wrong rule shapes, and dead code.\n\n## Comparison with MSW\n\n`override-proxy` and [MSW](https://mswjs.io/) both solve API interception/mocking but sit at different layers: this project is a standalone reverse proxy that applies override rules first and transparently forwards the rest; MSW runs inside your runtime (Service Worker in the browser or a Node process). They are often complementary (team‑wide shared partial overrides via `override-proxy`; fully deterministic isolated tests & Storybook via MSW).\n\n| Aspect                   | override-proxy                                                   | MSW                                                               | When to favor override-proxy                           | When to favor MSW                             |\n| ------------------------ | ---------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------ | --------------------------------------------- |\n| Deployment form          | Standalone Node reverse proxy                                    | In-process (Service Worker / Node)                                | Need one shared layer for Web, Mobile, backend scripts | Only JS app/tests, want zero base URL changes |\n| Override strategy        | First matching rule short-circuits, rest passthrough             | All requests potentially intercepted; passthrough needs opting in | Partial mock + keep real behavior for the rest         | Fully controlled, offline, deterministic data |\n| Upstream realism         | Unmatched hits real upstream (reduced mock drift)                | All data must be defined/generative                               | Want to reduce divergence between mock and prod        | Want fully stable replayable fixtures         |\n| Team sharing             | Point base URL; everyone instantly uses same overrides           | Must add handlers per repo                                        | Fast alignment “what’s overridden today”               | Single codebase control is enough             |\n| Client languages         | Any (JS, iOS, Android, backend) via HTTP                         | Primarily JavaScript ecosystems                                   | Multi-language integration workflows                   | Pure JS/UI workflows                          |\n| Logging & observability  | Centralized request log (latency, status, source, rule)          | Distributed per environment                                       | Need mixed real+mock traffic insight                   | Local test verbosity sufficient               |\n| CORS / network semantics | Real browser/network semantics preserved                         | Simulated inside SW/Node                                          | Need to validate real cookies/CORS/TLS                 | Network realism not required                  |\n| Adoption cost            | Run one process + point base URL                                 | Install lib + configure handlers in each env                      | Want zero code intrusion                               | Prefer inline mocks in tests                  |\n| Extensibility surface    | Natural spot for caching, record/replay, fault/latency injection | Built-in REST/GraphQL/WebSocket already                           | Need proxy aggregation / caching                       | Need protocol breadth immediately             |\n| Non-JS test integration  | Any stack via HTTP                                               | Requires JS runtime                                               | Mixed polyglot E2E                                     | JS-only test matrix                           |\n\n### Key strengths of this project\n\n1. Override‑first with transparent passthrough: author only what you need to change; everything else stays real, reducing maintenance & data drift.\n2. Cross‑client sharing: any device or language adopts overrides by switching a base URL (or system proxy).\n3. Low intrusion: no library embedded in the app—easy to adopt or discard.\n4. Real network conditions: genuine CORS, cookies, caching, TLS; good for integration sanity checks.\n5. Flexible rules: an override is just an Express handler—inject latency, errors, dynamic data, conditional passthrough.\n6. Layered env loading: safe defaults in `.env.default`, secrets in `.env.local` (git‑ignored).\n7. Evolution friendly: ideal anchor point for future record & replay, metrics, runtime toggles, chaos/fault injection, priority control.\n8. Short learning curve: minimal API (`defineConfig()` + `rule()` / `wsRule()`); experienced Node/Express users are productive immediately.\n\n### Typical combined workflow with MSW\n\n- Day-to-day team development: run `override-proxy` for shared partial overrides + live upstream behavior.\n- Test / CI: use MSW for 100% deterministic, offline, fast tests.\n- Demo / Storybook: point at `override-proxy` for realistic hybrid data; fall back to MSW when full offline determinism needed.\n\n> Summary: `override-proxy` is a shared, real-network, partial-override layer; MSW is an in-process, fully controllable interception layer. They complement rather than exclude each other.\n\n### Architecture & Flow (Mermaid)\n\n```mermaid\nflowchart LR\n  subgraph Client\n    A[Request]\n  end\n  A --> B[override-proxy]\n  B -->|rule match| C[Override handler]\n  B -->|no match| U[(Upstream API)]\n  C --> R[Response]\n  U --> R\n  R --> A\n  %% Behaviors: dynamic JSON, latency, error injection\n  classDef proxy fill:#0d6efd,stroke:#084298,stroke-width:1px,color:#fff;\n  class B proxy;\n```\n\n### Complementary Usage with MSW\n\n```mermaid\nsequenceDiagram\n  participant DevApp as Frontend App\n  participant OP as override-proxy\n  participant Up as Upstream API\n  participant MSW as MSW (test env)\n\n  Note over DevApp,OP: Local dev (shared partial overrides)\n  DevApp->>OP: GET /api/items\n  OP->>OP: Match rule?\n  alt Rule matches\n    OP-->>DevApp: Mocked JSON\n  else No match\n    OP->>Up: Forward request\n    Up-->>OP: Real response\n    OP-->>DevApp: Real JSON\n  end\n  Note over DevApp,MSW: Test/CI (fully mocked)\n  DevApp->>MSW: GET /api/items\n  MSW-->>DevApp: Deterministic mocked JSON\n```\n\n## License\n\nApache License 2.0 © 2025 Crescendo Lab. See `LICENSE` for full text.\n\n---\n\nAuthor: Crescendo Lab — 2025\n\nNeed extras (rule listing, runtime toggles, latency/error injection)? Open an issue or ask.\n","readmeFilename":"README.md"}