# v2 git bundle
8e0cf9d0f366b6278f51f404c0e58d0aa7a6825e refs/heads/bundle
8e0cf9d0f366b6278f51f404c0e58d0aa7a6825e HEAD

PACK      Kx?tree 132c208074e34e844dba3863c01b8ca912b6e4c0
parent 212ce324805a01280ca3b01a8462b7a9ca0538ad
author MythEclipse <asepharyanalm001@gmail.com> 1778158400 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778158400 +0700

fix: update import path for relay-utils and restore functions configuration in vercel.json
|^fNx0tree b260d37e9b556c24ebf22ccb576e25579d337ccf
parent d8e1e125b15af9903a433f22664717c411e9ec05
author MythEclipse <asepharyanalm001@gmail.com> 1778158192 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778158192 +0700

feat: implement Edge Proxy Relay with request handling and header filtering
W`xEtree aa4701c8ba846de84d57912fdfcc6dbea97368fa
parent 4cf3a520d50a941037e411582bd68ba5585880d9
author MythEclipse <asepharyanalm001@gmail.com> 1778157968 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778157968 +0700

feat: update vercel.json to restore build and install commands, and adjust routing configuration
4igx.tree 4e5fea38ffc53deb285a8d6a2c3cd0c3ec428a0d
parent cfbc4609b59325f7b13e91d62e0f8b94002cd4a4
author MythEclipse <asepharyanalm001@gmail.com> 1778157960 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778157960 +0700

feat: add index.html for Edge Proxy Relay interface with request handling
%ax1N0Eb-VZI;!cdcpb˙ Dq_<25V@t.njɴy=|gW]t;jz!`ّ?wsC淐bq2c=pŴ(_tyymZ=UY.QL{!Dw) Ar=@2\(nrB}ly\	6WGkZ kx:tree 42c57f376aacb019117a6896b6277660fe6ec45c
parent 098eabe5bf58ceded334c84aec21fad22bf01301
author MythEclipse <asepharyanalm001@gmail.com> 1778157571 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778157571 +0700

feat: simplify vercel.json by removing function configuration and updating bunVersion
$fVx4tree e53f17f47db7c4ed24b58311fd833b0fd145369f
parent c49c1d7441bd6114d8ac9b6524c68fae9ec2c4d5
author MythEclipse <asepharyanalm001@gmail.com> 1778157124 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778157124 +0700

feat: add 'api' directory to .gitignore and update build script in package.json
UVbx!tree 13bf95cf078fe41cbe5b712a0246749dfa3c0595
parent 42cf7a89c7dcdf6ba019fa3fc4761de52ffae2c9
author MythEclipse <asepharyanalm001@gmail.com> 1778156931 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778156931 +0700

feat: add schema and bunVersion to vercel.json configuration
\Ôx$tree 16c4490cdaad8731da37b00549f4ed0f837d8722
parent b4f5bad19d991fdf2a385f828552bbf0258539bc
author MythEclipse <asepharyanalm001@gmail.com> 1778156765 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778156765 +0700

feat: add function configuration for Bun runtime in vercel.json
CE\ʐx0tree d174693999c8b1caeac264668058f25af27f5bfd
parent 9448a9cc7adc4296dc00d77f324d062935b0ed89
author MythEclipse <asepharyanalm001@gmail.com> 1778156548 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778156548 +0700

feat: enhance request handling with method validation and target URL checks
`ߝx-tree c66bb43dfece634df9c2966d72f842f199e14fcc
parent 86ca59ad8ec27cdd1e3a830507fc57f77d444808
author MythEclipse <asepharyanalm001@gmail.com> 1778155980 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778155980 +0700

feat: add code-review-graph integration and update project configuration
Ya)xAtree 8706934b5a75bb33997b6198061e8d1bc871db58
parent a4fcbbcca92256fb8df5a160f30215a98ec82d81
author MythEclipse <asepharyanalm001@gmail.com> 1778155966 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778155966 +0700

refactor: restructure project by moving and renaming files, and implementing relay utilities
R(h!x .tree 2aa466ef7705afc32b53317c523af03857c85caf
author MythEclipse <asepharyanalm001@gmail.com> 1778154129 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778154129 +0700

initial commit for proxy-bun
FB?$x31 ҔT@OW;&f&Icd2CDA/=$3=/(͆ M:q:8M.*c8зL}vh˚GVA980_||۾_wg{xCTQ/XQÃ%ʤfĂL7+Yl^4n22sS!_{;*Of(	Ix74_x)G(Cd楤V蕤38cgfP4NUbP3hoێD3[Z߀)HLNL:LWqÎM-*nx4)'3Ag~.^_2L7Y'5(5'R$3d׺>ₖ.\Kdi4ĨdJa0y9_e@))NKL"d}78w1!gү~*,K-JŃ(AnŷrlU~ /xKx4 y*`1SZ"bsddxɅ 95n}xJ 100644 settings.json e\Glj'	f,b40000 skills @2{*PfHnb/x{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|Bash",
        "hooks": [
          {
            "type": "command",
            "command": "code-review-graph update --skip-flows",
            "timeout": 30
          }
        ]
      }
    ],
    "SessionStart": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "code-review-graph status",
            "timeout": 10
          }
        ]
      }
    ]
  }
}
evx K100644 debug-issue.md 8V6/DզK100644 explore-codebase.md zP6.$2!x100644 refactor-safely.md τBW់Է-NƩ100644 review-changes.md kUwpxxr+A.ICx?---
name: Debug Issue
description: Systematically debug issues using graph-powered code navigation
---

## Debug Issue

Use the knowledge graph to systematically trace and debug issues.

### Steps

1. Use `semantic_search_nodes` to find code related to the issue.
2. Use `query_graph` with `callers_of` and `callees_of` to trace call chains.
3. Use `get_flow` to see full execution paths through suspected areas.
4. Run `detect_changes` to check if recent changes caused the issue.
5. Use `get_impact_radius` on suspected files to see what else is affected.

### Tips

- Check both callers and callees to understand the full context.
- Look at affected flows to find the entry point that triggers the bug.
- Recent changes are the most common source of new issues.

## Token Efficiency Rules
- ALWAYS start with `get_minimal_context(task="<your task>")` before any other graph tool.
- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient.
- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens.
0KxJ---
name: Explore Codebase
description: Navigate and understand codebase structure using the knowledge graph
---

## Explore Codebase

Use the code-review-graph MCP tools to explore and understand the codebase.

### Steps

1. Run `list_graph_stats` to see overall codebase metrics.
2. Run `get_architecture_overview` for high-level community structure.
3. Use `list_communities` to find major modules, then `get_community` for details.
4. Use `semantic_search_nodes` to find specific functions or classes.
5. Use `query_graph` with patterns like `callers_of`, `callees_of`, `imports_of` to trace relationships.
6. Use `list_flows` and `get_flow` to understand execution paths.

### Tips

- Start broad (stats, architecture) then narrow down to specific areas.
- Use `children_of` on a file to see all its functions and classes.
- Use `find_large_functions` to identify complex code.

## Token Efficiency Rules
- ALWAYS start with `get_minimal_context(task="<your task>")` before any other graph tool.
- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient.
- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens.
JxR---
name: Refactor Safely
description: Plan and execute safe refactoring using dependency analysis
---

## Refactor Safely

Use the knowledge graph to plan and execute refactoring with confidence.

### Steps

1. Use `refactor_tool` with mode="suggest" for community-driven refactoring suggestions.
2. Use `refactor_tool` with mode="dead_code" to find unreferenced code.
3. For renames, use `refactor_tool` with mode="rename" to preview all affected locations.
4. Use `apply_refactor_tool` with the refactor_id to apply renames.
5. After changes, run `detect_changes` to verify the refactoring impact.

### Safety Checks

- Always preview before applying (rename mode gives you an edit list).
- Check `get_impact_radius` before major refactors.
- Use `get_affected_flows` to ensure no critical paths are broken.
- Run `find_large_functions` to identify decomposition targets.

## Token Efficiency Rules
- ALWAYS start with `get_minimal_context(task="<your task>")` before any other graph tool.
- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient.
- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens.
RBx+---
name: Review Changes
description: Perform a structured code review using change detection and impact
---

## Review Changes

Perform a thorough, risk-aware code review using the knowledge graph.

### Steps

1. Run `detect_changes` to get risk-scored change analysis.
2. Run `get_affected_flows` to find impacted execution paths.
3. For each high-risk function, run `query_graph` with pattern="tests_for" to check test coverage.
4. Run `get_impact_radius` to understand the blast radius.
5. For any untested changes, suggest specific test cases.

### Output Format

Provide findings grouped by risk level (high/medium/low) with:
- What changed and why it matters
- Test coverage status
- Suggested improvements
- Overall merge recommendation

## Token Efficiency Rules
- ALWAYS start with `get_minimal_context(task="<your task>")` before any other graph tool.
- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient.
- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens.
7zxmPMK1ϯzPA<(dt&$N *2o>ޛL'6ĥ7H"*\":~W7Bb: j_owwop.M"Fer5f;؀3FIct+NYfnҶDD`~iWL
s?V|$=n6g_3Hlh?v*x吻3n.t|jxɼyF
F %
x P{
  "mcpServers": {
    "code-review-graph": {
      "command": "uvx",
      "args": [
        "code-review-graph",
        "serve"
      ],
      "type": "stdio"
    }
  }
}
8I,xmVmo6_qS)l	žu]]q%:f"
I6=GJ$@"~]Ob6%1ME2V9-}UL3J6/dȗT`	JSbl6ر{aGCM#v,s'k73ĕiv	G
9- TWz3T9)&ӲiĘګ~\9ЂJoŒ*˴_{	ZicUZ#*EzQ'GT~pGFG1H4u5]lV*LzYM	IES3Zc!y(P鄶|Ga3cȥR8ʓ{n{e>mZ>,8ٙF{zVho|gϷ&97Q+F(|~L
38,y_ЍAp3hS^'mv&\"~CSГcBi-2KϴժSτCwDQ(+]&tZȪ3~ayxU_[6v7F(uxld+T6+ib튵I}ކŞ"_82Ϲ(ù1<&<~m=}A?WbM*#&W}('O$9:ii"5<{1JUa.?owۛo{|^s?jp6
ekӈ`YP%r(
UؕzNV7Պ,B<=}|8lj!D aJh4-K.5`p`ι/J?(YǨ7Y+Yi6vp߯ȳpDʏKb~9"Ƌ'@X˰TyS.mۓp-c9ܧv'0!ak?HV;~s]3fJ?|2XMxf*㬐< [%spwy?EK$WY8p 6<cgT7X?0pv	0ma	=Yt/މjt?`vlW @s2Wl+b@3_71&fz]T=zox!	
<!-- code-review-graph MCP tools -->
## MCP Tools: code-review-graph

**IMPORTANT: This project has a knowledge graph. ALWAYS use the
code-review-graph MCP tools BEFORE using Grep/Glob/Read to explore
the codebase.** The graph is faster, cheaper (fewer tokens), and gives
you structural context (callers, dependents, test coverage) that file
scanning cannot.

### When to use graph tools FIRST

- **Exploring code**: `semantic_search_nodes` or `query_graph` instead of Grep
- **Understanding impact**: `get_impact_radius` instead of manually tracing imports
- **Code review**: `detect_changes` + `get_review_context` instead of reading entire files
- **Finding relationships**: `query_graph` with callers_of/callees_of/imports_of/tests_for
- **Architecture questions**: `get_architecture_overview` + `list_communities`

Fall back to Grep/Glob/Read **only** when the graph doesn't cover what you need.

### Key Tools

| Tool | Use when |
|------|----------|
| `detect_changes` | Reviewing code changes — gives risk-scored analysis |
| `get_review_context` | Need source snippets for review — token-efficient |
| `get_impact_radius` | Understanding blast radius of a change |
| `get_affected_flows` | Finding which execution paths are impacted |
| `query_graph` | Tracing callers, callees, imports, tests, dependencies |
| `semantic_search_nodes` | Finding functions/classes by name or keyword |
| `get_architecture_overview` | Understanding high-level codebase structure |
| `refactor_tool` | Planning renames, finding dead code |

### Workflow

1. The graph auto-updates on file changes (via hooks).
2. Use `detect_changes` for code review.
3. Usie `get_affected_flows` to understand impact.
4. Use `query_graph` pattern="tests_for" to check coverage.
ykxT͎6)A-;z-Y;qlkzAcil1H?u}=$4-o15xbn3^9_d
jzɖfBpa5wkQ`)wF%9q^*=œ쬄8etm!L:shr,3|$O%Iu
J]wW>c);1OWZ9]	m%ưٰrI50gQx@eՋhha$<jo2bWZcGfubTi؎3b'gmЬ~OfޒO6V ҳ#\K+5kZj=&mAvͧW9ӡv<ۨܥW۫u8,WVU/I]aH5a&Us3똉\ӎ^_єTƒF('*mY#}rvxh,2Fa>Y툂rѵp/z$F~x>9NX_;o.)qԳח?SYqU(0%N663'!8$A`,d]J5;%}feSk˶Pڴh3$4a.G;o]`A"a"OzJh藊,x*'%Jqx\WmxV`@\weSa2`CC?a^Ao.:'W(>k!ଂ*H%S{މh3nz:gqLĒI*EY\/"?CxN 100644 index.ts :Hf%K%j@;%#100644 relay-utils.ts Uw>%]eΎ1MY1~2x}RN0<'_HiˁSx lZIA)YRqvİV#Vf=qM<
jZ~ⳡ9Uz&th
$[(^Q'-ʫ8Ə4,.a:K@a1Gli+.u9¸B҄z
{;v;LSAk*9)`x{Yx`'	7_{jK';bv%an說{7Zo joa*wj4Ѓt$p38;=hH?Q*6MS܄govՑ3݅d$!d_{/`$gG~cލ@9(&cxxJ\	isAllowedTargetZG// Only allow safe methods
const ALLOWED_METHODS = new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);

expoFT// Method validation
	if (!ALLOWED_METHODS.has(req.method)) {
		return new Response()ethod not allowed" }), {
			status: 405,
6});
	}
nG// Target validation (SSRF prevention)
	if (!isAllowedTarget(targetUrl)73Target domain not allowed" }),
			{
				status: 403ʆlDx v.wxWmo9auBl=Q-W!gw6ڋm%IǗ;<g^N2%j~aD@X؍%Q{P(R/ǳ|F[=Z-ܡbŒK.Le)|Ciƅŗӷ_ߝ=qWӛ.OVoBkx[r1@~g.LIʔF,J
-ڰ6V.%qUtx8D18 KښmDN`oPx
sv՝l7w:Q
S;'Y"0PXcoc5"^VQ%~>N|>x|IG@C~eR]b	m3,ro֨&Ͻrr߽/rIeo
MClT[\vln+A۟GrJOz9yW)+\vrTA/N(]ۣBr+J*YJ)i!t(5PYO^q	^WA(H۔dÇZIbъ^a9m<層퐌Y(9'a|`EH=䅢P(Dy[P,毨eƞ!Z+]VXTbA,ק'g+)IJv]X~mu:c}$%z?#nh+*˛^jth( ]X^S*N#J͠`o]u4-#.Xlq.xFwg
v8l6 	/2p͓۩D^.NC&\D\
ƵM#ckc1zɪst>X&_,+HE^;Rk ??(I_r!
ҙ5V+#5zz:UΝ5pR{B5xvJ$Y4vpK6oÈZCOٯQa%LW¥4#Q6FECtKe`bz=FFX;G:ԩ#bBᓽ~A7aFU?Q6bbA/81.q!SBv~l}Ϙ!1Y<ࡧSIjc.K<*0/LdfО:Z\U{Fv"h`@Bl)]hNDg=qB]66ʔr/!:dLurE#UBp)~CI]aPBlkw%i\ȨX z
(|2Hc+ZÜƄPUAhUxQBSWY₽lXx}Rj<Bcۭ(sXĄ̿x{
	"$schema": "https://biomejs.dev/schemas/2.4.14/schema.json",
	"vcs": {
		"enabled": true,
		"clientKind": "git",
		"useIgnoreFile": true
	},
	"files": {
		"ignoreUnknown": false
	},
	"formatter": {
		"enabled": true,
		"indentStyle": "tab"
	},
	"linter": {
		"enabled": true,
		"rules": {
			"recommended": true
		}
	},
	"javascript": {
		"formatter": {
			"quoteStyle": "double"
		}
	},
	"assist": {
		"enabled": true,
		"actions": {
			"source": {
				"organizeImports": "on"
			}
		}
	}
}
`WxYHStL*1"21nu}*OV*~?vӤ-h7kk.M{י~t='zlw^V᮲ӎzߞ]Qvf'YvoЗ!;IyL{{|z8N^i6y^УbgE'wzcnaޒ5"?)OCr}*ʤTSvU~|/u,q$y:w똨v;dvʎ&e4Ӓ@!`Ա7JSr6+$0ELIy `%OE_n
>G{fVOΑMi\zËW;ւ)^pR<:'i
b$Ś"C5k/'اH@q+z19)7ᙆ0gx,ZۚZsLhm51r+0.	ysU<|8<-1iq .#@mVY`ТZF0C&-K^+rW-{d9qm7+eθ1T8&Rh9<W<4ĥ,GbӏfCl@|Bf׽rs-SDFMQj&̈́[ޮY[,H\}?1]RJRd3&BJEs+\3I첖2^F/Bh LmS-GKT	I67,-bz:whNm?O6\ɥ7n,U,7d*xU	lR)OȽ(lu@CzX3&p@Y}fr]) Mxjzk+$(J,͂fbcJ3OD+",jPϒKG!5cG;鏩_Aq'WSgtl_>[Z#պVb+ܠÞ5PKڃxnR0eЄ^+'e0v??"(7W޽rI3U[KUHma~jjkQiA ͆D"9Jp)WF8*igJ>PKWfrQ9Wi a	TD*3vX_(!DòYD,ֳ[pZO,\&[cG& 3k$uЗSe)-jkOzmKPҤ?2̄/ePj}_g6xEimport { describe, expect, test } from "bun:test";
import {
	buildRelayRequest,
	createRelayResponse,
	normalizeTargetUrl,
	shouldSendBody,
	stripRelayHeaders,
} from "~/relay-utils";

describe("normalizeTargetUrl", () => {
	test("returns null when target is null", () => {
		expect(normalizeTargetUrl(null, "/")).toBeNull();
	});

	test("returns null when target is empty string", () => {
		expect(normalizeTargetUrl("", "/")).toBeNull();
	});

	test("removes trailing slash from target", () => {
		expect(normalizeTargetUrl("https://httpbin.org/", "/get")).toBe(
			"https://httpbin.org/get",
		);
	});

	test("keeps target without trailing slash", () => {
		expect(normalizeTargetUrl("https://httpbin.org", "/get")).toBe(
			"https://httpbin.org/get",
		);
	});

	test("appends relay path", () => {
		expect(normalizeTargetUrl("https://example.com", "/api/users")).toBe(
			"https://example.com/api/users",
		);
	});

	test("handles nested paths", () => {
		expect(
			normalizeTargetUrl("https://api.example.com", "/v1/users/123/profile"),
		).toBe("https://api.example.com/v1/users/123/profile");
	});
});

describe("stripRelayHeaders", () => {
	test("removes x-relay-target header", () => {
		const headers = new Headers({ "x-relay-target": "https://test.com" });
		const stripped = stripRelayHeaders(headers);
		expect(stripped.get("x-relay-target")).toBeNull();
	});

	test("removes x-relay-path header", () => {
		const headers = new Headers({ "x-relay-path": "/test" });
		const stripped = stripRelayHeaders(headers);
		expect(stripped.get("x-relay-path")).toBeNull();
	});

	test("removes host header", () => {
		const headers = new Headers({ host: "localhost:3000" });
		const stripped = stripRelayHeaders(headers);
		expect(stripped.get("host")).toBeNull();
	});

	test("preserves other headers", () => {
		const headers = new Headers({
			"x-relay-target": "https://test.com",
			"x-custom-header": "value",
			authorization: "Bearer token",
		});
		const stripped = stripRelayHeaders(headers);
		expect(stripped.get("x-custom-header")).toBe("value");
		expect(stripped.get("authorization")).toBe("Bearer token");
	});

	test("handles multiple relay headers", () => {
		const headers = new Headers({
			"x-relay-target": "https://test.com",
			"x-relay-path": "/path",
			host: "test.com",
			"content-type": "application/json",
		});
		const stripped = stripRelayHeaders(headers);
		expect(stripped.get("content-type")).toBe("application/json");
		expect(stripped.get("x-relay-target")).toBeNull();
		expect(stripped.get("x-relay-path")).toBeNull();
		expect(stripped.get("host")).toBeNull();
	});
});

describe("shouldSendBody", () => {
	test("returns false for GET", () => {
		expect(shouldSendBody("GET")).toBe(false);
	});

	test("returns false for HEAD", () => {
		expect(shouldSendBody("HEAD")).toBe(false);
	});

	test("returns true for POST", () => {
		expect(shouldSendBody("POST")).toBe(true);
	});

	test("returns true for PUT", () => {
		expect(shouldSendBody("PUT")).toBe(true);
	});

	test("returns true for PATCH", () => {
		expect(shouldSendBody("PATCH")).toBe(true);
	});

	test("returns true for DELETE", () => {
		expect(shouldSendBody("DELETE")).toBe(true);
	});

	test("is case-sensitive", () => {
		expect(shouldSendBody("get")).toBe(true);
		expect(shouldSendBody("post")).toBe(true);
	});
});

describe("buildRelayRequest", () => {
	test("sets correct method", () => {
		const req = new Request("http://test.com", { method: "POST" });
		const headers = new Headers();
		const result = buildRelayRequest(req, headers);
		expect(result.method).toBe("POST");
	});

	test("sets headers", () => {
		const req = new Request("http://test.com");
		const headers = new Headers({ "x-custom": "value" });
		const result = buildRelayRequest(req, headers);
		expect(result.headers).toBe(headers);
	});

	test("omits body for GET", () => {
		const req = new Request("http://test.com", { method: "GET" });
		const headers = new Headers();
		const result = buildRelayRequest(req, headers);
		expect(result.body).toBeUndefined();
	});

	test("omits body for HEAD", () => {
		const req = new Request("http://test.com", { method: "HEAD" });
		const headers = new Headers();
		const result = buildRelayRequest(req, headers);
		expect(result.body).toBeUndefined();
	});

	test("includes body for POST", () => {
		const body = JSON.stringify({ test: true });
		const req = new Request("http://test.com", { method: "POST", body });
		const headers = new Headers();
		const result = buildRelayRequest(req, headers);
		expect(result.body).toBeDefined();
		expect(result.duplex).toBe("half");
	});

	test("includes body for PUT", () => {
		const body = JSON.stringify({ update: true });
		const req = new Request("http://test.com", { method: "PUT", body });
		const headers = new Headers();
		const result = buildRelayRequest(req, headers);
		expect(result.body).toBeDefined();
	});

	test("includes body for PATCH", () => {
		const body = JSON.stringify({ patch: true });
		const req = new Request("http://test.com", { method: "PATCH", body });
		const headers = new Headers();
		const result = buildRelayRequest(req, headers);
		expect(result.body).toBeDefined();
	});

	test("includes body for DELETE", () => {
		const body = JSON.stringify({ delete: true });
		const req = new Request("http://test.com", { method: "DELETE", body });
		const headers = new Headers();
		const result = buildRelayRequest(req, headers);
		expect(result.body).toBeDefined();
	});
});

describe("createRelayResponse", () => {
	test("preserves status", async () => {
		const mockResponse = new Response("body", { status: 418 });
		const result = createRelayResponse(mockResponse);
		expect(result.status).toBe(418);
	});

	test("preserves headers", async () => {
		const mockResponse = new Response("body", {
			headers: { "x-custom": "value" },
		});
		const result = createRelayResponse(mockResponse);
		expect(result.headers.get("x-custom")).toBe("value");
	});

	test("preserves body", async () => {
		const mockResponse = new Response("test body content");
		const result = createRelayResponse(mockResponse);
		expect(await result.text()).toBe("test body content");
	});

	test("handles different status codes", async () => {
		const mockResponse = new Response(null, { status: 404 });
		const result = createRelayResponse(mockResponse);
		expect(result.status).toBe(404);
	});

	test("handles 500 status", async () => {
		const mockResponse = new Response("error", { status: 500 });
		const result = createRelayResponse(mockResponse);
		expect(result.status).toBe(500);
	});
});

describe("integration: full relay flow", () => {
	test("complete flow with mocked fetch", async () => {
		const target = "https://httpbin.org";
		const relayPath = "/get";
		const targetUrl = normalizeTargetUrl(target, relayPath);

		expect(targetUrl).toBe("https://httpbin.org/get");

		const originalHeaders = new Headers({
			"x-relay-target": target,
			"x-relay-path": relayPath,
			host: "localhost",
			"x-custom": "preserved",
		});

		const strippedHeaders = stripRelayHeaders(originalHeaders);
		expect(strippedHeaders.get("x-relay-target")).toBeNull();
		expect(strippedHeaders.get("x-relay-path")).toBeNull();
		expect(strippedHeaders.get("host")).toBeNull();
		expect(strippedHeaders.get("x-custom")).toBe("preserved");
	});

	test("POST flow with body preservation", () => {
		const req = new Request("http://test.com", {
			method: "POST",
			body: JSON.stringify({ data: "test" }),
			headers: { "content-type": "application/json" },
		});

		const options = buildRelayRequest(req, new Headers());
		expect(options.method).toBe("POST");
		expect(options.body).toBeDefined();
	});

	test("error case: missing target", () => {
		const target = null;
		const relayPath = "/test";
		const result = normalizeTargetUrl(target, relayPath);
		expect(result).toBeNull();
	});
});
P>HxVMlDV~8m;N7MI[
aiW^͊[WnbH)*qAF+$gU/p!ސ8 ZPh%{n
,yf>m^ǲ@Uͱտ=Q*=t PAyYԮ+O j`|M[[՜#zr17H=S`'_FK<l~}3 	C񄅲/o!o1]ῄ+?s嘎aQCê<54yC`> 	9ưEd`7H@!wEsܴfn	G.B5Nh"ŌKy)\A^Kp?L1"6x3oOU9KdoֻU`i"<R~s33M/[>6~qmZ1>4ۗUM4:ѕ8@dSDOzgZ	w=%HD0FQ܉f<<>OYMb8z<|,6n,߈,ƃ1;4LƋ^VL(Kv,N! YeپյfX"'CcUQSn@IpRo%쮾X	UW%Owspw1^jT-L]98HJG-f*.l6eO߃Y.EǦ9+C^\H725g93-(_l9x?[oKqqnig;ʱqͯl瑃SL]4Y䙰KWc[-Pg,Etj#_
PvS2~v&ePw)t($hl&.]A2d?KLw~rzD!w#A	rTn=Sw-(<c3E/`8^eZ'<R5 ./Բ[x@;cy0ҳ#5`_{e̣hDl}Vj`H'f]I9'{BGYtx=MJAɢ7ąHZEܻ5m'!;<+N*|MY;
 R[W0\U@կgH]4vS|{BvG%+ax8&b0)񅃗' i{Y6;S f~X7HT댼ʊN:D'P-hFEBL!&EǞqʞ~/:ZxuAk!+sВB o6:mU1;lZ=,xg)&н1d'V
,:f"U	URd0@R/8:|./WCC TߘPa8Tcc+1@:&P KuVmwB%xV-|MHZK]	|[jWn=m]7.lCDy?rx& 100644 index.html <vJ,!+(w>x}Vmo6_q>FmN_MC$n}EJIEɖڴ_b=sSVr[ r#ts*%Df"r%Ic U2GNmS6m6[NԤD<uP[6wtASLzsgalW ;Ekx#M3RpQ'P(LIRP>k͊%HuU%iɜ\MҸg7/j>G|6o*5û!l)MjByeX%әP)RYաFB!l8_2]gv]sYVvZ%LcDZ6{.χ2)`B	^ԸpMSnf3> wTҕơc襯}ntNw[IVoߕ딦{+ͅ|PH4vBRᣁ'	p8\9_'W^S[,:)"*ȭ-Mwe>&9.R`8`xǿ71ZyLO$}G`[h7F#3%( ۑU	b:6|rѝ<QayDj^ZOZU2yscn@ʧҪN0HGQr[U"Ww~D{$s]RCFO89JIuveTxfu4q8\6wp'L`v-=m5KZy'C6mBRG*(_%Xz=#!s@IQ&e|Fztz6#j]i(S/w"˻[7]`M@XAAKzqNHsz޹JT>BٳGk$xW_aJ߁Bw_Sx api2>x}RMo0WL-T"ݽgZQQNRN4\;b_){n4`Є[;TH~U 0ޢheRWfv6@(}@bUM)xW|O~I<
ySsz%݅QͲ8=,9)6KvJDI62mD	k|5,@lw\d17	q`oQk#Gٽ7(:y+2k`o\c'md>ytTO-	9MbRWh./ 0}&ˠlď0hkݠL~TkBu>6'o-
bߍaYC8w_)	.:.\xe{֝bϣ?>Rxw 100644 index.test.ts  po.ῗ:ș V+5100644 index.ts j(ts.<IR->100644 relay-utils.ts Uw>%]eΎ1MS,$xB{
	"compilerOptions": {
		"lib": ["ESNext"],
		"target": "ESNext",
		"module": "Preserve",
		"moduleDetection": "force",
		"jsx": "react-jsx",
		"allowJs": true,
		"types": ["bun"],

		// Bundler mode
		"moduleResolution": "bundler",
		"allowImportingTsExtensions": true,
		"verbatimModuleSyntax": true,
		"noEmit": true,

		// Best practices
		"strict": true,
		"skipLibCheck": true,
		"noFallthroughCasesInSwitch": true,
		"noUncheckedIndexedAccess": true,
		"noImplicitOverride": true,

		// Path aliases
		"paths": {
			"~/*": ["./src/*"]
		}
	},
	"include": ["src/**/*"]
}
yx5=0 QaA5MGUw=Z-4SspOJ{ܺO]s<Q⠣^)br{gK^6!j
&z)-K]UP.E4'G	Yek~<"x;ϸqRf^qIbNs~nnb^RRiTTib# MQ.xN 100644 index.ts j(ts.<IR->100644 relay-utils.ts Uw>%]eΎ1Mx]
0@+Ipu?t1$%&])N&4<zօzc.A8-G^X(]wgg
ϸ2?FkEzBl
㕹ZR=AӁڥxD: kx mMo
}QxN 5dfQK:	Ho}>QKWe.*%y&8/޽`jflm[U7\]$tx& 100644 index.html '2#HK.Μp^Ax "c<'2>!N#Op{[8lx;8cW- Wx! MoutputDirectory": "distHFͭx5 bbz=w#j<<2_Z*`K#i"Dx5 b?s1c9e.}!v"c<'2>!N#Op{[3n~xǼy|Ă̍+^sx7 ի1iBydRXę \PW|SY^Q̒eGx +$}rc&Lm"*x7 5dfQK:	Hbz=w#j<<2_ZvwwjMxiBƕ +iKx;X=ѧ 2_xccQSc+c޽~ xE { "rewrites": [{ "source": "/(.*)", "destination": "/api/relay" }] }
{xccٰQd)[rg8^LП1  Ϯxw 100644 index.test.ts  po.ῗ:ș V+5100644 index.ts *4^谺X ,ďj[100644 relay-utils.ts X\Jei:+e0@
x[,}}%Vɂ&q'(e(*䥖+x&kd@hMk.N
ԜԒT
ݢԜJݒĢ%j
K2/h.J-)-ʃdnf& ~<x|100644 .gitignore G	OrCK100644 CLAUDE.md vLٝi~~r(	100644 README.md _WS?eO:100644 bun.lock ^YT?yVՈ^100644 index.test.ts T,=\U3f7U%;100644 index.ts ra*P[̵p3q;100644 package.json  wr"Iޕw-Q100644 relay-utils.ts -L^R*aI'100644 tsconfig.json I}rR%100644 vercel.json A
߅mGM"J]x* 340000 src o(l7hAa<x/3P
gEx¼ 
uMx'sB-ʆ

JE.y)yəJV
\
PTRYZ\YPW3x\os+S=D4:S=K=c%% VPJ	48dbDSRPBB-P8#H7+ǰ,*%3ϧ25K<-ϵ20) ;-0-(Ҥ;0%31-$)"'0#ç$4$=|N Vطx 8{
  "name": "proxy-vercel",
  "module": "index.ts",
  "type": "module",
  "private": true,
  "devDependencies": {
    "@types/bun": "latest"
  },
  "peerDependencies": {
    "typescript": "^5"
  }
}
%:-x {
  "compilerOptions": {
    // Environment setup & latest features
    "lib": ["ESNext"],
    "target": "ESNext",
    "module": "Preserve",
    "moduleDetection": "force",
    "jsx": "react-jsx",
    "allowJs": true,
    "types": ["bun"],

    // Bundler mode
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "noEmit": true,

    // Best practices
    "strict": true,
    "skipLibCheck": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,

    // Some stricter flags (disabled by default)
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noPropertyAccessFromIndexSignature": false
  }
}
v@Px< {"rewrites":[{"source":"/(.*)","destination":"/api/relay"}]}b'K#-#M\.DAr