Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions __tests__/Fetch_vitest.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
open Vitest

describe("Fetch.Response", () => {
open Expect

test("make sets status + headers; body may be null", () => {
let headers = Fetch.Headers.make()
headers->Fetch.Headers.set("Location", "/somewhere")
headers->Fetch.Headers.set("Cache-Control", "public, max-age=3600")
let res = Fetch.Response.make(Nullable.null, {status: 307, headers})
(
res.status,
res.headers->Fetch.Headers.get("Location"),
res.headers->Fetch.Headers.get("Cache-Control"),
)
->expect
->toEqual((307, Some("/somewhere"), Some("public, max-age=3600")))
})

test("redirect sets Location + status", () => {
let res = Fetch.Response.redirect("https://example.com/y", 307)
(res.status, res.headers->Fetch.Headers.get("Location"))
->expect
->toEqual((307, Some("https://example.com/y")))
})
})
24 changes: 22 additions & 2 deletions src/Fetch.res
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,31 @@ module Headers = {
// missing forEach, getSetCookie
}

module FetchResponse = {
// The web `Response` — one Web API type, so one binding for both its *read*
// side (what `fetch` resolves to) and its *construct* side (what an App Router
// route handler returns). The latter is why `make` / `redirect` live here:
// `next/navigation`'s `redirect` / `notFound` are control-flow throwers for
// pages (no custom headers; `notFound` renders the HTML page), and
// `NextServer.NextResponse`'s status is a closed 200/400/403/404/500 variant.
module Response = {
type t = {
ok: bool,
status: int,
headers: Headers.t,
arrayBuffer: unit => promise<Js.TypedArray2.ArrayBuffer.t>,
text: unit => promise<string>,
json: unit => promise<JSON.t>,
}

// Construct: `new Response(body, init)`. Body may be null (redirects, empty
// bodies); every `init` field is optional.
type init = {status?: int, statusText?: string, headers?: Headers.t}
@new external make: (Nullable.t<string>, init) => t = "Response"

// Static `Response.redirect(url, status)`. Sets Location + status only; for
// extra headers (e.g. `Cache-Control`) use `make` with `init.headers`.
@val @scope("Response") external redirect: (string, int) => t = "redirect"

let jsonResult = async response => {
let text = await response.text()
try JSON.parseOrThrow(text)->Result.Ok catch {
Expand All @@ -43,7 +60,10 @@ module FetchResponse = {
let json = async response => await response.json()
}

@val external fetch: (string, {..}) => promise<FetchResponse.t> = "fetch"
// Former name; kept so existing consumers keep compiling. Prefer `Response`.
module FetchResponse = Response

@val external fetch: (string, {..}) => promise<Response.t> = "fetch"

let fetchJson = async (url: string, body: JSON.t) => {
let options = {
Expand Down
Loading