effect-start 0.9.0 → 0.10.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 (44) hide show
  1. package/package.json +12 -13
  2. package/src/BundleHttp.test.ts +1 -1
  3. package/src/Commander.test.ts +15 -15
  4. package/src/Commander.ts +58 -88
  5. package/src/EncryptedCookies.test.ts +4 -4
  6. package/src/FileHttpRouter.test.ts +81 -12
  7. package/src/FileHttpRouter.ts +115 -26
  8. package/src/FileRouter.ts +60 -162
  9. package/src/FileRouterCodegen.test.ts +250 -64
  10. package/src/FileRouterCodegen.ts +13 -56
  11. package/src/FileRouterPattern.test.ts +116 -0
  12. package/src/FileRouterPattern.ts +59 -0
  13. package/src/FileRouter_path.test.ts +63 -102
  14. package/src/FileSystemExtra.test.ts +226 -0
  15. package/src/FileSystemExtra.ts +24 -60
  16. package/src/HttpUtils.test.ts +68 -0
  17. package/src/HttpUtils.ts +15 -0
  18. package/src/HyperHtml.ts +24 -5
  19. package/src/JsModule.test.ts +1 -1
  20. package/src/NodeFileSystem.ts +764 -0
  21. package/src/Random.ts +59 -0
  22. package/src/Route.test.ts +471 -0
  23. package/src/Route.ts +298 -153
  24. package/src/RouteRender.ts +38 -0
  25. package/src/Router.ts +11 -33
  26. package/src/RouterPattern.test.ts +629 -0
  27. package/src/RouterPattern.ts +391 -0
  28. package/src/Start.ts +14 -52
  29. package/src/bun/BunBundle.test.ts +0 -3
  30. package/src/bun/BunHttpServer.ts +246 -0
  31. package/src/bun/BunHttpServer_web.ts +384 -0
  32. package/src/bun/BunRoute.test.ts +341 -0
  33. package/src/bun/BunRoute.ts +326 -0
  34. package/src/bun/BunRoute_bundles.test.ts +218 -0
  35. package/src/bun/BunRuntime.ts +33 -0
  36. package/src/bun/BunTailwindPlugin.test.ts +1 -1
  37. package/src/bun/_empty.html +1 -0
  38. package/src/bun/index.ts +2 -1
  39. package/src/testing.ts +12 -3
  40. package/src/Datastar.test.ts +0 -267
  41. package/src/Datastar.ts +0 -68
  42. package/src/bun/BunFullstackServer.ts +0 -45
  43. package/src/bun/BunFullstackServer_httpServer.ts +0 -541
  44. package/src/jsx-datastar.d.ts +0 -63
@@ -0,0 +1,246 @@
1
+ import * as HttpApp from "@effect/platform/HttpApp"
2
+ import * as HttpServer from "@effect/platform/HttpServer"
3
+ import * as HttpServerError from "@effect/platform/HttpServerError"
4
+ import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
5
+ import * as Socket from "@effect/platform/Socket"
6
+ import * as Bun from "bun"
7
+ import * as Config from "effect/Config"
8
+ import * as Context from "effect/Context"
9
+ import * as Deferred from "effect/Deferred"
10
+ import * as Effect from "effect/Effect"
11
+ import * as Exit from "effect/Exit"
12
+ import * as FiberSet from "effect/FiberSet"
13
+ import * as Layer from "effect/Layer"
14
+ import * as Option from "effect/Option"
15
+ import type * as Scope from "effect/Scope"
16
+ import * as Random from "../Random.ts"
17
+ import * as Router from "../Router.ts"
18
+ import EmptyHTML from "./_empty.html"
19
+ import {
20
+ makeResponse,
21
+ ServerRequestImpl,
22
+ WebSocketContext,
23
+ } from "./BunHttpServer_web.ts"
24
+ import * as BunRoute from "./BunRoute.ts"
25
+
26
+ type FetchHandler = (
27
+ request: Request,
28
+ server: Bun.Server<WebSocketContext>,
29
+ ) => Response | Promise<Response>
30
+
31
+ /**
32
+ * Basically `Omit<Bun.Serve.Options, "fetch" | "error" | "websocket">`
33
+ * TypeScript 5.9 cannot verify discriminated union types used in
34
+ * {@link Bun.serve} so we need to define them explicitly.
35
+ */
36
+ interface ServeOptions {
37
+ readonly port?: number
38
+ readonly hostname?: string
39
+ readonly reusePort?: boolean
40
+ readonly ipv6Only?: boolean
41
+ readonly idleTimeout?: number
42
+ readonly development?: boolean
43
+ }
44
+
45
+ export type BunServer = {
46
+ readonly server: Bun.Server<WebSocketContext>
47
+ readonly addRoutes: (routes: BunRoute.BunRoutes) => void
48
+ // TODO: we probably don't want to expose these methods publicly
49
+ readonly pushHandler: (fetch: FetchHandler) => void
50
+ readonly popHandler: () => void
51
+ }
52
+
53
+ export const BunServer = Context.GenericTag<BunServer>(
54
+ "effect-start/BunServer",
55
+ )
56
+
57
+ export const make = (
58
+ options: ServeOptions,
59
+ ): Effect.Effect<
60
+ BunServer,
61
+ never,
62
+ Scope.Scope
63
+ > =>
64
+ Effect.gen(function*() {
65
+ const port = yield* Config.number("PORT").pipe(
66
+ Effect.catchTag("ConfigError", () => Effect.succeed(3000)),
67
+ )
68
+ const hostname = yield* Config.string("HOSTNAME").pipe(
69
+ Effect.catchTag("ConfigError", () => Effect.succeed(undefined)),
70
+ )
71
+
72
+ const handlerStack: Array<FetchHandler> = [
73
+ function(_request, _server) {
74
+ return new Response("not found", { status: 404 })
75
+ },
76
+ ]
77
+
78
+ let currentRoutes: BunRoute.BunRoutes = {}
79
+
80
+ // Bun HMR doesn't work on successive calls to `server.reload` if there are no routes
81
+ // on server start. We workaround that by passing a dummy HTMLBundle [2025-11-26]
82
+ // see: https://github.com/oven-sh/bun/issues/23564
83
+ currentRoutes[`/.BunEmptyHtml-${Random.token(6)}`] = EmptyHTML
84
+
85
+ const websocket: Bun.WebSocketHandler<WebSocketContext> = {
86
+ open(ws) {
87
+ Deferred.unsafeDone(ws.data.deferred, Exit.succeed(ws))
88
+ },
89
+ message(ws, message) {
90
+ ws.data.run(message)
91
+ },
92
+ close(ws, code, closeReason) {
93
+ Deferred.unsafeDone(
94
+ ws.data.closeDeferred,
95
+ Socket.defaultCloseCodeIsError(code)
96
+ ? Exit.fail(
97
+ new Socket.SocketCloseError({
98
+ reason: "Close",
99
+ code,
100
+ closeReason,
101
+ }),
102
+ )
103
+ : Exit.void,
104
+ )
105
+ },
106
+ }
107
+
108
+ const server = Bun.serve({
109
+ port,
110
+ hostname,
111
+ ...options,
112
+ routes: currentRoutes,
113
+ fetch: handlerStack[0],
114
+ websocket,
115
+ })
116
+
117
+ yield* Effect.addFinalizer(() =>
118
+ Effect.sync(() => {
119
+ server.stop()
120
+ })
121
+ )
122
+
123
+ const reload = () => {
124
+ server.reload({
125
+ fetch: handlerStack[handlerStack.length - 1],
126
+ routes: currentRoutes,
127
+ websocket,
128
+ })
129
+ }
130
+
131
+ return BunServer.of({
132
+ server,
133
+ pushHandler(fetch) {
134
+ handlerStack.push(fetch)
135
+ reload()
136
+ },
137
+ popHandler() {
138
+ handlerStack.pop()
139
+ reload()
140
+ },
141
+ addRoutes(routes) {
142
+ currentRoutes = {
143
+ ...currentRoutes,
144
+ ...routes,
145
+ }
146
+ reload()
147
+ },
148
+ })
149
+ })
150
+
151
+ export const layer = (
152
+ options?: ServeOptions,
153
+ ): Layer.Layer<BunServer> => Layer.scoped(BunServer, make(options ?? {}))
154
+
155
+ export const makeHttpServer: Effect.Effect<
156
+ HttpServer.HttpServer,
157
+ never,
158
+ Scope.Scope | BunServer
159
+ > = Effect.gen(function*() {
160
+ const bunServer = yield* BunServer
161
+
162
+ return HttpServer.make({
163
+ address: {
164
+ _tag: "TcpAddress",
165
+ port: bunServer.server.port!,
166
+ hostname: bunServer.server.hostname!,
167
+ },
168
+ serve(httpApp, middleware) {
169
+ return Effect.gen(function*() {
170
+ const runFork = yield* FiberSet.makeRuntime<never>()
171
+ const runtime = yield* Effect.runtime<never>()
172
+ const app = HttpApp.toHandled(
173
+ httpApp,
174
+ (request, response) =>
175
+ Effect.sync(() => {
176
+ ;(request as ServerRequestImpl).resolve(
177
+ makeResponse(request, response, runtime),
178
+ )
179
+ }),
180
+ middleware,
181
+ )
182
+
183
+ function handler(
184
+ request: Request,
185
+ server: Bun.Server<WebSocketContext>,
186
+ ) {
187
+ return new Promise<Response>((resolve, _reject) => {
188
+ const fiber = runFork(Effect.provideService(
189
+ app,
190
+ HttpServerRequest.HttpServerRequest,
191
+ new ServerRequestImpl(
192
+ request,
193
+ resolve,
194
+ removeHost(request.url),
195
+ server,
196
+ ),
197
+ ))
198
+ request.signal.addEventListener("abort", () => {
199
+ runFork(
200
+ fiber.interruptAsFork(HttpServerError.clientAbortFiberId),
201
+ )
202
+ }, { once: true })
203
+ })
204
+ }
205
+
206
+ yield* Effect.acquireRelease(
207
+ Effect.sync(() => {
208
+ bunServer.pushHandler(handler)
209
+ }),
210
+ () =>
211
+ Effect.sync(() => {
212
+ bunServer.popHandler()
213
+ }),
214
+ )
215
+ })
216
+ },
217
+ })
218
+ })
219
+
220
+ export const layerServer = (
221
+ options?: ServeOptions,
222
+ ): Layer.Layer<HttpServer.HttpServer | BunServer> =>
223
+ Layer.provideMerge(
224
+ Layer.scoped(HttpServer.HttpServer, makeHttpServer),
225
+ layer(options ?? {}),
226
+ )
227
+
228
+ export function layerRoutes() {
229
+ return Layer.effectDiscard(Effect.gen(function*() {
230
+ const bunServer = yield* BunServer
231
+ const router = yield* Effect.serviceOption(Router.Router)
232
+
233
+ if (Option.isSome(router)) {
234
+ const bunRoutes = yield* BunRoute.routesFromRouter(router.value)
235
+ bunServer.addRoutes(bunRoutes)
236
+ }
237
+ }))
238
+ }
239
+
240
+ const removeHost = (url: string) => {
241
+ if (url[0] === "/") {
242
+ return url
243
+ }
244
+ const index = url.indexOf("/", url.indexOf("//") + 2)
245
+ return index === -1 ? "/" : url.slice(index)
246
+ }
@@ -0,0 +1,384 @@
1
+ import * as Cookies from "@effect/platform/Cookies"
2
+ import type * as FileSystem from "@effect/platform/FileSystem"
3
+ import * as Headers from "@effect/platform/Headers"
4
+ import * as HttpApp from "@effect/platform/HttpApp"
5
+ import * as HttpIncomingMessage from "@effect/platform/HttpIncomingMessage"
6
+ import type { HttpMethod } from "@effect/platform/HttpMethod"
7
+ import * as HttpServerError from "@effect/platform/HttpServerError"
8
+ import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
9
+ import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
10
+ import type * as Multipart from "@effect/platform/Multipart"
11
+ import type * as Path from "@effect/platform/Path"
12
+ import * as Socket from "@effect/platform/Socket"
13
+ import * as UrlParams from "@effect/platform/UrlParams"
14
+ import type {
15
+ Server as BunServerInstance,
16
+ ServerWebSocket,
17
+ } from "bun"
18
+ import * as Deferred from "effect/Deferred"
19
+ import * as Effect from "effect/Effect"
20
+ import * as FiberSet from "effect/FiberSet"
21
+ import * as Inspectable from "effect/Inspectable"
22
+ import * as Option from "effect/Option"
23
+ import type { ReadonlyRecord } from "effect/Record"
24
+ import * as Runtime from "effect/Runtime"
25
+ import type * as Scope from "effect/Scope"
26
+ import * as Stream from "effect/Stream"
27
+
28
+ export interface WebSocketContext {
29
+ readonly deferred: Deferred.Deferred<ServerWebSocket<WebSocketContext>>
30
+ readonly closeDeferred: Deferred.Deferred<void, Socket.SocketError>
31
+ readonly buffer: Array<Uint8Array | string>
32
+ run: (_: Uint8Array | string) => void
33
+ }
34
+
35
+ export class ServerRequestImpl extends Inspectable.Class
36
+ implements HttpServerRequest.HttpServerRequest
37
+ {
38
+ readonly [HttpServerRequest.TypeId]: HttpServerRequest.TypeId
39
+ readonly [HttpIncomingMessage.TypeId]: HttpIncomingMessage.TypeId
40
+
41
+ constructor(
42
+ readonly source: Request,
43
+ public resolve: (response: Response) => void,
44
+ readonly url: string,
45
+ private bunServer: BunServerInstance<WebSocketContext>,
46
+ public headersOverride?: Headers.Headers,
47
+ private remoteAddressOverride?: string,
48
+ ) {
49
+ super()
50
+ this[HttpServerRequest.TypeId] = HttpServerRequest.TypeId
51
+ this[HttpIncomingMessage.TypeId] = HttpIncomingMessage.TypeId
52
+ }
53
+
54
+ toJSON(): unknown {
55
+ return HttpIncomingMessage.inspect(this, {
56
+ _id: "@effect/platform/HttpServerRequest",
57
+ method: this.method,
58
+ url: this.originalUrl,
59
+ })
60
+ }
61
+
62
+ modify(
63
+ options: {
64
+ readonly url?: string | undefined
65
+ readonly headers?: Headers.Headers | undefined
66
+ readonly remoteAddress?: string | undefined
67
+ },
68
+ ) {
69
+ return new ServerRequestImpl(
70
+ this.source,
71
+ this.resolve,
72
+ options.url ?? this.url,
73
+ this.bunServer,
74
+ options.headers ?? this.headersOverride,
75
+ options.remoteAddress ?? this.remoteAddressOverride,
76
+ )
77
+ }
78
+
79
+ get method(): HttpMethod {
80
+ return this.source.method.toUpperCase() as HttpMethod
81
+ }
82
+
83
+ get originalUrl() {
84
+ return this.source.url
85
+ }
86
+
87
+ get remoteAddress(): Option.Option<string> {
88
+ return this.remoteAddressOverride
89
+ ? Option.some(this.remoteAddressOverride)
90
+ : Option.fromNullable(this.bunServer.requestIP(this.source)?.address)
91
+ }
92
+
93
+ get headers(): Headers.Headers {
94
+ this.headersOverride ??= Headers.fromInput(this.source.headers)
95
+ return this.headersOverride
96
+ }
97
+
98
+ private cachedCookies: ReadonlyRecord<string, string> | undefined
99
+ get cookies() {
100
+ if (this.cachedCookies) {
101
+ return this.cachedCookies
102
+ }
103
+ return this.cachedCookies = Cookies.parseHeader(this.headers.cookie ?? "")
104
+ }
105
+
106
+ get stream(): Stream.Stream<Uint8Array, HttpServerError.RequestError> {
107
+ return this.source.body
108
+ ? Stream.fromReadableStream(
109
+ () => this.source.body as ReadableStream<Uint8Array>,
110
+ (cause) =>
111
+ new HttpServerError.RequestError({
112
+ request: this,
113
+ reason: "Decode",
114
+ cause,
115
+ }),
116
+ )
117
+ : Stream.fail(
118
+ new HttpServerError.RequestError({
119
+ request: this,
120
+ reason: "Decode",
121
+ description: "can not create stream from empty body",
122
+ }),
123
+ )
124
+ }
125
+
126
+ private textEffect:
127
+ | Effect.Effect<string, HttpServerError.RequestError>
128
+ | undefined
129
+
130
+ get text(): Effect.Effect<string, HttpServerError.RequestError> {
131
+ if (this.textEffect) {
132
+ return this.textEffect
133
+ }
134
+ this.textEffect = Effect.runSync(Effect.cached(
135
+ Effect.tryPromise({
136
+ try: () => this.source.text(),
137
+ catch: (cause) =>
138
+ new HttpServerError.RequestError({
139
+ request: this,
140
+ reason: "Decode",
141
+ cause,
142
+ }),
143
+ }),
144
+ ))
145
+ return this.textEffect
146
+ }
147
+
148
+ get json(): Effect.Effect<unknown, HttpServerError.RequestError> {
149
+ return Effect.tryMap(this.text, {
150
+ try: (_) => JSON.parse(_) as unknown,
151
+ catch: (cause) =>
152
+ new HttpServerError.RequestError({
153
+ request: this,
154
+ reason: "Decode",
155
+ cause,
156
+ }),
157
+ })
158
+ }
159
+
160
+ get urlParamsBody(): Effect.Effect<
161
+ UrlParams.UrlParams,
162
+ HttpServerError.RequestError
163
+ > {
164
+ return Effect.flatMap(this.text, (_) =>
165
+ Effect.try({
166
+ try: () => UrlParams.fromInput(new URLSearchParams(_)),
167
+ catch: (cause) =>
168
+ new HttpServerError.RequestError({
169
+ request: this,
170
+ reason: "Decode",
171
+ cause,
172
+ }),
173
+ }))
174
+ }
175
+
176
+ private multipartEffect:
177
+ | Effect.Effect<
178
+ Multipart.Persisted,
179
+ Multipart.MultipartError,
180
+ Scope.Scope | FileSystem.FileSystem | Path.Path
181
+ >
182
+ | undefined
183
+
184
+ get multipart(): Effect.Effect<
185
+ Multipart.Persisted,
186
+ Multipart.MultipartError,
187
+ Scope.Scope | FileSystem.FileSystem | Path.Path
188
+ > {
189
+ if (this.multipartEffect) {
190
+ return this.multipartEffect
191
+ }
192
+ this.multipartEffect = Effect.runSync(Effect.cached(
193
+ Effect.die("Multipart not implemented"),
194
+ ))
195
+ return this.multipartEffect
196
+ }
197
+
198
+ get multipartStream(): Stream.Stream<
199
+ Multipart.Part,
200
+ Multipart.MultipartError
201
+ > {
202
+ return Stream.die("Multipart stream not implemented")
203
+ }
204
+
205
+ private arrayBufferEffect:
206
+ | Effect.Effect<ArrayBuffer, HttpServerError.RequestError>
207
+ | undefined
208
+ get arrayBuffer(): Effect.Effect<ArrayBuffer, HttpServerError.RequestError> {
209
+ if (this.arrayBufferEffect) {
210
+ return this.arrayBufferEffect
211
+ }
212
+ this.arrayBufferEffect = Effect.runSync(Effect.cached(
213
+ Effect.tryPromise({
214
+ try: () => this.source.arrayBuffer(),
215
+ catch: (cause) =>
216
+ new HttpServerError.RequestError({
217
+ request: this,
218
+ reason: "Decode",
219
+ cause,
220
+ }),
221
+ }),
222
+ ))
223
+ return this.arrayBufferEffect
224
+ }
225
+
226
+ get upgrade(): Effect.Effect<Socket.Socket, HttpServerError.RequestError> {
227
+ return Effect.flatMap(
228
+ Effect.all([
229
+ Deferred.make<ServerWebSocket<WebSocketContext>>(),
230
+ Deferred.make<void, Socket.SocketError>(),
231
+ Effect.makeSemaphore(1),
232
+ ]),
233
+ ([deferred, closeDeferred, semaphore]) =>
234
+ Effect.async<Socket.Socket, HttpServerError.RequestError>((resume) => {
235
+ const success = this.bunServer.upgrade(
236
+ this.source,
237
+ {
238
+ data: {
239
+ deferred,
240
+ closeDeferred,
241
+ buffer: [],
242
+ run: wsDefaultRun,
243
+ },
244
+ },
245
+ )
246
+ if (!success) {
247
+ resume(Effect.fail(
248
+ new HttpServerError.RequestError({
249
+ request: this,
250
+ reason: "Decode",
251
+ description: "Not an upgradeable ServerRequest",
252
+ }),
253
+ ))
254
+ return
255
+ }
256
+ resume(Effect.map(Deferred.await(deferred), (ws) => {
257
+ const write = (chunk: Uint8Array | string | Socket.CloseEvent) =>
258
+ Effect.sync(() => {
259
+ if (typeof chunk === "string") {
260
+ ws.sendText(chunk)
261
+ } else if (Socket.isCloseEvent(chunk)) {
262
+ ws.close(chunk.code, chunk.reason)
263
+ } else {
264
+ ws.sendBinary(chunk)
265
+ }
266
+
267
+ return true
268
+ })
269
+ const writer = Effect.succeed(write)
270
+ const runRaw = Effect.fnUntraced(
271
+ function*<RR, EE, _>(
272
+ handler: (
273
+ _: Uint8Array | string,
274
+ ) => Effect.Effect<_, EE, RR> | void,
275
+ opts?: { readonly onOpen?: Effect.Effect<void> | undefined },
276
+ ) {
277
+ const set = yield* FiberSet.make<unknown, EE>()
278
+ const run = yield* FiberSet.runtime(set)<RR>()
279
+ function runRawInner(data: Uint8Array | string) {
280
+ const result = handler(data)
281
+ if (Effect.isEffect(result)) {
282
+ run(result)
283
+ }
284
+ }
285
+ ws.data.run = runRawInner
286
+ ws.data.buffer.forEach(runRawInner)
287
+ ws.data.buffer.length = 0
288
+ if (opts?.onOpen) yield* opts.onOpen
289
+ return yield* FiberSet.join(set)
290
+ },
291
+ Effect.scoped,
292
+ Effect.onExit((exit) => {
293
+ ws.close(exit._tag === "Success" ? 1000 : 1011)
294
+ return Effect.void
295
+ }),
296
+ Effect.raceFirst(Deferred.await(closeDeferred)),
297
+ semaphore.withPermits(1),
298
+ )
299
+
300
+ const encoder = new TextEncoder()
301
+ const run = <RR, EE, _>(
302
+ handler: (_: Uint8Array) => Effect.Effect<_, EE, RR> | void,
303
+ opts?: {
304
+ readonly onOpen?: Effect.Effect<void> | undefined
305
+ },
306
+ ) =>
307
+ runRaw(
308
+ (data) =>
309
+ typeof data === "string"
310
+ ? handler(encoder.encode(data))
311
+ : handler(data),
312
+ opts,
313
+ )
314
+
315
+ return Socket.Socket.of({
316
+ [Socket.TypeId]: Socket.TypeId,
317
+ run,
318
+ runRaw,
319
+ writer,
320
+ })
321
+ }))
322
+ }),
323
+ )
324
+ }
325
+ }
326
+
327
+ function wsDefaultRun(this: WebSocketContext, _: Uint8Array | string) {
328
+ this.buffer.push(_)
329
+ }
330
+
331
+ export function makeResponse(
332
+ request: HttpServerRequest.HttpServerRequest,
333
+ response: HttpServerResponse.HttpServerResponse,
334
+ runtime: Runtime.Runtime<never>,
335
+ ): Response {
336
+ const fields: {
337
+ headers: globalThis.Headers
338
+ status?: number
339
+ statusText?: string
340
+ } = {
341
+ headers: new globalThis.Headers(response.headers),
342
+ status: response.status,
343
+ }
344
+
345
+ if (!Cookies.isEmpty(response.cookies)) {
346
+ for (const header of Cookies.toSetCookieHeaders(response.cookies)) {
347
+ fields.headers.append("set-cookie", header)
348
+ }
349
+ }
350
+
351
+ if (response.statusText !== undefined) {
352
+ fields.statusText = response.statusText
353
+ }
354
+
355
+ if (request.method === "HEAD") {
356
+ return new Response(undefined, fields)
357
+ }
358
+ const ejectedResponse = HttpApp.unsafeEjectStreamScope(response)
359
+ const body = ejectedResponse.body
360
+ switch (body._tag) {
361
+ case "Empty": {
362
+ return new Response(undefined, fields)
363
+ }
364
+ case "Uint8Array":
365
+ case "Raw": {
366
+ if (body.body instanceof Response) {
367
+ for (const [key, value] of fields.headers.entries()) {
368
+ body.body.headers.set(key, value)
369
+ }
370
+ return body.body
371
+ }
372
+ return new Response(body.body as BodyInit, fields)
373
+ }
374
+ case "FormData": {
375
+ return new Response(body.formData as FormData, fields)
376
+ }
377
+ case "Stream": {
378
+ return new Response(
379
+ Stream.toReadableStreamRuntime(body.stream, runtime),
380
+ fields,
381
+ )
382
+ }
383
+ }
384
+ }