effect-start 0.14.0 → 0.16.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 (87) hide show
  1. package/package.json +8 -9
  2. package/src/Commander.test.ts +507 -245
  3. package/src/ContentNegotiation.test.ts +603 -0
  4. package/src/ContentNegotiation.ts +542 -0
  5. package/src/Entity.test.ts +592 -0
  6. package/src/Entity.ts +362 -0
  7. package/src/FileRouter.ts +16 -12
  8. package/src/{FileRouterCodegen.test.ts → FileRouterCodegen.todo.ts} +384 -219
  9. package/src/FileRouterCodegen.ts +6 -6
  10. package/src/FileRouterPattern.test.ts +93 -62
  11. package/src/FileRouter_files.test.ts +5 -5
  12. package/src/FileRouter_path.test.ts +121 -69
  13. package/src/FileRouter_tree.test.ts +62 -56
  14. package/src/FileSystemExtra.test.ts +46 -30
  15. package/src/Http.test.ts +319 -0
  16. package/src/Http.ts +167 -0
  17. package/src/HttpAppExtra.test.ts +39 -20
  18. package/src/HttpAppExtra.ts +0 -1
  19. package/src/HttpUtils.test.ts +35 -18
  20. package/src/HttpUtils.ts +2 -0
  21. package/src/PathPattern.test.ts +648 -0
  22. package/src/PathPattern.ts +485 -0
  23. package/src/Route.ts +266 -1069
  24. package/src/RouteBody.test.ts +234 -0
  25. package/src/RouteBody.ts +193 -0
  26. package/src/RouteHook.test.ts +40 -0
  27. package/src/RouteHook.ts +106 -0
  28. package/src/RouteHttp.test.ts +2906 -0
  29. package/src/RouteHttp.ts +427 -0
  30. package/src/RouteHttpTracer.ts +92 -0
  31. package/src/RouteMount.test.ts +481 -0
  32. package/src/RouteMount.ts +470 -0
  33. package/src/RouteSchema.test.ts +427 -0
  34. package/src/RouteSchema.ts +423 -0
  35. package/src/RouteTree.test.ts +494 -0
  36. package/src/RouteTree.ts +219 -0
  37. package/src/RouteTrie.test.ts +322 -0
  38. package/src/RouteTrie.ts +224 -0
  39. package/src/RouterPattern.test.ts +569 -548
  40. package/src/RouterPattern.ts +7 -7
  41. package/src/Start.ts +3 -3
  42. package/src/StreamExtra.ts +21 -1
  43. package/src/TuplePathPattern.ts +64 -0
  44. package/src/Values.test.ts +263 -0
  45. package/src/Values.ts +76 -0
  46. package/src/bun/BunBundle.test.ts +36 -42
  47. package/src/bun/BunBundle.ts +2 -2
  48. package/src/bun/BunBundle_imports.test.ts +4 -6
  49. package/src/bun/BunHttpServer.test.ts +183 -6
  50. package/src/bun/BunHttpServer.ts +72 -32
  51. package/src/bun/BunHttpServer_web.ts +18 -6
  52. package/src/bun/BunImportTrackerPlugin.test.ts +3 -3
  53. package/src/bun/BunRoute.test.ts +124 -442
  54. package/src/bun/BunRoute.ts +146 -286
  55. package/src/{BundleHttp.test.ts → bundler/BundleHttp.test.ts} +34 -60
  56. package/src/{BundleHttp.ts → bundler/BundleHttp.ts} +1 -2
  57. package/src/client/index.ts +1 -1
  58. package/src/{Effect_HttpRouter.test.ts → effect/HttpRouter.test.ts} +69 -90
  59. package/src/experimental/EncryptedCookies.test.ts +125 -64
  60. package/src/experimental/SseHttpResponse.ts +0 -1
  61. package/src/hyper/Hyper.ts +89 -0
  62. package/src/{HyperHtml.test.ts → hyper/HyperHtml.test.ts} +13 -13
  63. package/src/{HyperHtml.ts → hyper/HyperHtml.ts} +2 -2
  64. package/src/{jsx.d.ts → hyper/jsx.d.ts} +1 -1
  65. package/src/index.ts +3 -4
  66. package/src/middlewares/BasicAuthMiddleware.test.ts +29 -19
  67. package/src/{NodeFileSystem.ts → node/FileSystem.ts} +6 -2
  68. package/src/testing/TestHttpClient.test.ts +26 -26
  69. package/src/testing/TestLogger.test.ts +27 -14
  70. package/src/testing/TestLogger.ts +15 -9
  71. package/src/x/datastar/Datastar.test.ts +47 -48
  72. package/src/x/datastar/Datastar.ts +1 -1
  73. package/src/x/tailwind/TailwindPlugin.test.ts +56 -58
  74. package/src/x/tailwind/plugin.ts +1 -1
  75. package/src/FileHttpRouter.test.ts +0 -239
  76. package/src/FileHttpRouter.ts +0 -194
  77. package/src/Hyper.ts +0 -194
  78. package/src/Route.test.ts +0 -1370
  79. package/src/RouteRender.ts +0 -40
  80. package/src/Router.test.ts +0 -375
  81. package/src/Router.ts +0 -255
  82. package/src/bun/BunRoute_bundles.test.ts +0 -219
  83. /package/src/{Bundle.ts → bundler/Bundle.ts} +0 -0
  84. /package/src/{BundleFiles.ts → bundler/BundleFiles.ts} +0 -0
  85. /package/src/{HyperNode.ts → hyper/HyperNode.ts} +0 -0
  86. /package/src/{jsx-runtime.ts → hyper/jsx-runtime.ts} +0 -0
  87. /package/src/{NodeUtils.ts → node/Utils.ts} +0 -0
@@ -1,17 +1,17 @@
1
1
  import * as HttpRouter from "@effect/platform/HttpRouter"
2
- import * as t from "bun:test"
2
+ import * as test from "bun:test"
3
3
  import * as Effect from "effect/Effect"
4
4
  import * as Layer from "effect/Layer"
5
5
  import * as NFS from "node:fs/promises"
6
6
  import * as NOS from "node:os"
7
7
  import * as NPath from "node:path"
8
- import * as Bundle from "../Bundle.ts"
9
- import * as BundleHttp from "../BundleHttp.ts"
8
+ import * as Bundle from "../bundler/Bundle.ts"
9
+ import * as BundleHttp from "../bundler/BundleHttp.ts"
10
10
  import * as TestHttpClient from "../testing/TestHttpClient.ts"
11
11
  import * as BunBundle from "./BunBundle.ts"
12
12
 
13
- t.describe("BunBundle manifest structure", () => {
14
- t.it("should generate manifest with inputs and outputs arrays", async () => {
13
+ test.describe("BunBundle manifest structure", () => {
14
+ test.it("should generate manifest with inputs and outputs arrays", async () => {
15
15
  const tmpDir = await NFS.mkdtemp(
16
16
  NPath.join(NOS.tmpdir(), "effect-start-test-"),
17
17
  )
@@ -41,43 +41,41 @@ export const greeting = "Hello World";`
41
41
  }),
42
42
  )
43
43
 
44
- t
44
+ test
45
45
  .expect(bundle.entrypoints)
46
46
  .toBeObject()
47
- t
47
+ test
48
48
  .expect(bundle.artifacts)
49
49
  .toBeArray()
50
-
51
- t
50
+ test
52
51
  .expect(Object.keys(bundle.entrypoints).length)
53
52
  .toBe(1)
54
- t
53
+ test
55
54
  .expect(bundle.artifacts.length)
56
55
  .toBe(3)
57
56
 
58
57
  const entrypointKeys = Object.keys(bundle.entrypoints)
59
58
  const firstEntrypoint = entrypointKeys[0]
60
59
 
61
- t
60
+ test
62
61
  .expect(firstEntrypoint)
63
62
  .toBeString()
64
- t
63
+ test
65
64
  .expect(bundle.entrypoints[firstEntrypoint])
66
65
  .toBeString()
67
66
 
68
67
  const firstArtifact = bundle.artifacts[0]
69
68
 
70
- t
69
+ test
71
70
  .expect(firstArtifact)
72
71
  .toHaveProperty("path")
73
- t
72
+ test
74
73
  .expect(firstArtifact)
75
74
  .toHaveProperty("type")
76
- t
75
+ test
77
76
  .expect(firstArtifact)
78
77
  .toHaveProperty("size")
79
-
80
- t
78
+ test
81
79
  .expect(firstArtifact.size)
82
80
  .toBeGreaterThan(0)
83
81
  } finally {
@@ -88,7 +86,7 @@ export const greeting = "Hello World";`
88
86
  }
89
87
  })
90
88
 
91
- t.it("should serve manifest via HTTP with correct structure", async () => {
89
+ test.it("should serve manifest via HTTP with correct structure", async () => {
92
90
  const tmpDir = await NFS.mkdtemp(
93
91
  NPath.join(NOS.tmpdir(), "effect-start-test-"),
94
92
  )
@@ -136,46 +134,44 @@ export const greeting = "Hello World";`
136
134
  ),
137
135
  )
138
136
 
139
- t
137
+ test
140
138
  .expect(result)
141
139
  .toHaveProperty("entrypoints")
142
- t
140
+ test
143
141
  .expect(result)
144
142
  .toHaveProperty("artifacts")
145
-
146
- t
143
+ test
147
144
  .expect(result.entrypoints)
148
145
  .toBeObject()
149
- t
146
+ test
150
147
  .expect(result.artifacts)
151
148
  .toBeArray()
152
-
153
- t
149
+ test
154
150
  .expect(Object.keys(result.entrypoints).length)
155
151
  .toBe(1)
156
- t
152
+ test
157
153
  .expect(result.artifacts.length)
158
154
  .toBe(3)
159
155
 
160
156
  const entrypointKeys = Object.keys(result.entrypoints)
161
157
  const firstKey = entrypointKeys[0]
162
158
 
163
- t
159
+ test
164
160
  .expect(firstKey)
165
161
  .toBeString()
166
- t
162
+ test
167
163
  .expect(result.entrypoints[firstKey])
168
164
  .toBeString()
169
165
 
170
166
  const artifact = result.artifacts[0]
171
167
 
172
- t
168
+ test
173
169
  .expect(artifact)
174
170
  .toHaveProperty("path")
175
- t
171
+ test
176
172
  .expect(artifact)
177
173
  .toHaveProperty("type")
178
- t
174
+ test
179
175
  .expect(artifact)
180
176
  .toHaveProperty("size")
181
177
  } finally {
@@ -186,7 +182,7 @@ export const greeting = "Hello World";`
186
182
  }
187
183
  })
188
184
 
189
- t.it("should resolve entrypoints to artifacts correctly", async () => {
185
+ test.it("should resolve entrypoints to artifacts correctly", async () => {
190
186
  const tmpDir = await NFS.mkdtemp(
191
187
  NPath.join(NOS.tmpdir(), "effect-start-test-"),
192
188
  )
@@ -210,18 +206,17 @@ export const greeting = "Hello World";`
210
206
  const expectedOutput = bundle.entrypoints[firstEntrypoint]
211
207
  const resolvedOutput = bundle.resolve(firstEntrypoint)
212
208
 
213
- t
209
+ test
214
210
  .expect(resolvedOutput)
215
211
  .toBe(expectedOutput)
216
212
 
217
213
  const artifact = bundle.getArtifact(resolvedOutput!)
218
214
 
219
- t
215
+ test
220
216
  .expect(artifact)
221
217
  .not
222
218
  .toBeNull()
223
-
224
- t
219
+ test
225
220
  .expect(artifact)
226
221
  .toBeTruthy()
227
222
  } finally {
@@ -232,7 +227,7 @@ export const greeting = "Hello World";`
232
227
  }
233
228
  })
234
229
 
235
- t.it("should include all artifact metadata", async () => {
230
+ test.it("should include all artifact metadata", async () => {
236
231
  const tmpDir = await NFS.mkdtemp(
237
232
  NPath.join(NOS.tmpdir(), "effect-start-test-"),
238
233
  )
@@ -251,17 +246,16 @@ export const greeting = "Hello World";`
251
246
 
252
247
  const artifact = bundle.artifacts[0]
253
248
 
254
- t
249
+ test
255
250
  .expect(artifact.path)
256
251
  .toBeString()
257
- t
252
+ test
258
253
  .expect(artifact.type)
259
254
  .toBeString()
260
- t
255
+ test
261
256
  .expect(artifact.size)
262
257
  .toBeNumber()
263
-
264
- t
258
+ test
265
259
  .expect(artifact.type)
266
260
  .toContain("javascript")
267
261
  } finally {
@@ -18,8 +18,8 @@ import * as NPath from "node:path"
18
18
  import type {
19
19
  BundleContext,
20
20
  BundleManifest,
21
- } from "../Bundle.ts"
22
- import * as Bundle from "../Bundle.ts"
21
+ } from "../bundler/Bundle.ts"
22
+ import * as Bundle from "../bundler/Bundle.ts"
23
23
  import * as FileSystemExtra from "../FileSystemExtra.ts"
24
24
  import { BunImportTrackerPlugin } from "./index.ts"
25
25
 
@@ -1,11 +1,11 @@
1
- import * as t from "bun:test"
1
+ import * as test from "bun:test"
2
2
  import { effectFn } from "../testing"
3
3
  import * as BunBundle from "./BunBundle.ts"
4
4
  import * as BunImportTrackerPlugin from "./BunImportTrackerPlugin.ts"
5
5
 
6
6
  const effect = effectFn()
7
7
 
8
- t.it("imports", () =>
8
+ test.it("imports", () =>
9
9
  effect(function*() {
10
10
  const importTracker = BunImportTrackerPlugin.make()
11
11
  yield* BunBundle.build({
@@ -22,10 +22,8 @@ t.it("imports", () =>
22
22
  e0,
23
23
  ] = importTracker.state.entries()
24
24
 
25
- t
26
- .expect(
27
- e0,
28
- )
25
+ test
26
+ .expect(e0)
29
27
  .toEqual([
30
28
  "src/bun/BunBundle_imports.test.ts",
31
29
  [
@@ -1,11 +1,13 @@
1
- import * as t from "bun:test"
1
+ import * as test from "bun:test"
2
2
  import * as Effect from "effect/Effect"
3
+ import * as Layer from "effect/Layer"
4
+ import * as Route from "../Route.ts"
3
5
  import * as BunHttpServer from "./BunHttpServer.ts"
4
6
 
5
- t.describe("BunHttpServer smart port selection", () => {
7
+ test.describe("smart port selection", () => {
6
8
  // Skip when running in TTY because the random port logic requires !isTTY && CLAUDECODE,
7
9
  // and process.stdout.isTTY cannot be mocked
8
- t.test.skipIf(process.stdout.isTTY)(
10
+ test.test.skipIf(process.stdout.isTTY)(
9
11
  "uses random port when PORT not set, isTTY=false, CLAUDECODE set",
10
12
  async () => {
11
13
  const originalPort = process.env.PORT
@@ -24,7 +26,10 @@ t.describe("BunHttpServer smart port selection", () => {
24
26
  ),
25
27
  )
26
28
 
27
- t.expect(port).not.toBe(3000)
29
+ test
30
+ .expect(port)
31
+ .not
32
+ .toBe(3000)
28
33
  } finally {
29
34
  if (originalPort !== undefined) {
30
35
  process.env.PORT = originalPort
@@ -40,7 +45,7 @@ t.describe("BunHttpServer smart port selection", () => {
40
45
  },
41
46
  )
42
47
 
43
- t.test("uses explicit PORT even when CLAUDECODE is set", async () => {
48
+ test.test("uses explicit PORT even when CLAUDECODE is set", async () => {
44
49
  const originalPort = process.env.PORT
45
50
  const originalClaudeCode = process.env.CLAUDECODE
46
51
 
@@ -57,7 +62,9 @@ t.describe("BunHttpServer smart port selection", () => {
57
62
  ),
58
63
  )
59
64
 
60
- t.expect(port).toBe(5678)
65
+ test
66
+ .expect(port)
67
+ .toBe(5678)
61
68
  } finally {
62
69
  if (originalPort !== undefined) {
63
70
  process.env.PORT = originalPort
@@ -72,3 +79,173 @@ t.describe("BunHttpServer smart port selection", () => {
72
79
  }
73
80
  })
74
81
  })
82
+
83
+ const BunHttpServerTest = Layer.scoped(
84
+ BunHttpServer.BunHttpServer,
85
+ BunHttpServer.make({ port: 0 }),
86
+ )
87
+
88
+ const testLayer = (routes: ReturnType<typeof Route.tree>) =>
89
+ Layer.provideMerge(
90
+ BunHttpServer.layerRoutes(routes),
91
+ BunHttpServerTest,
92
+ )
93
+
94
+ test.describe("routes", () => {
95
+ test.test("serves static text route", async () => {
96
+ const routes = Route.tree({
97
+ "/": Route.get(Route.text("Hello, World!")),
98
+ })
99
+
100
+ const response = await Effect.runPromise(
101
+ Effect.scoped(
102
+ Effect
103
+ .gen(function*() {
104
+ const bunServer = yield* BunHttpServer.BunHttpServer
105
+ return yield* Effect.promise(() =>
106
+ fetch(`http://localhost:${bunServer.server.port}/`)
107
+ )
108
+ })
109
+ .pipe(Effect.provide(testLayer(routes))),
110
+ ),
111
+ )
112
+
113
+ test.expect(response.status).toBe(200)
114
+ test.expect(await response.text()).toBe("Hello, World!")
115
+ })
116
+
117
+ test.test("serves JSON route", async () => {
118
+ const routes = Route.tree({
119
+ "/api/data": Route.get(Route.json({ message: "success", value: 42 })),
120
+ })
121
+
122
+ const response = await Effect.runPromise(
123
+ Effect.scoped(
124
+ Effect
125
+ .gen(function*() {
126
+ const bunServer = yield* BunHttpServer.BunHttpServer
127
+ return yield* Effect.promise(() =>
128
+ fetch(`http://localhost:${bunServer.server.port}/api/data`)
129
+ )
130
+ })
131
+ .pipe(Effect.provide(testLayer(routes))),
132
+ ),
133
+ )
134
+
135
+ test.expect(response.status).toBe(200)
136
+ test.expect(response.headers.get("Content-Type")).toBe("application/json")
137
+ test.expect(await response.json()).toEqual({
138
+ message: "success",
139
+ value: 42,
140
+ })
141
+ })
142
+
143
+ test.test("returns 404 for unknown routes", async () => {
144
+ const routes = Route.tree({
145
+ "/": Route.get(Route.text("Home")),
146
+ })
147
+
148
+ const response = await Effect.runPromise(
149
+ Effect.scoped(
150
+ Effect
151
+ .gen(function*() {
152
+ const bunServer = yield* BunHttpServer.BunHttpServer
153
+ return yield* Effect.promise(() =>
154
+ fetch(`http://localhost:${bunServer.server.port}/unknown`)
155
+ )
156
+ })
157
+ .pipe(Effect.provide(testLayer(routes))),
158
+ ),
159
+ )
160
+
161
+ test.expect(response.status).toBe(404)
162
+ })
163
+
164
+ test.test("handles content negotiation", async () => {
165
+ const routes = Route.tree({
166
+ "/data": Route
167
+ .get(Route.json({ type: "json" }))
168
+ .get(Route.html("<div>html</div>")),
169
+ })
170
+
171
+ const [jsonResponse, htmlResponse] = await Effect.runPromise(
172
+ Effect.scoped(
173
+ Effect
174
+ .gen(function*() {
175
+ const bunServer = yield* BunHttpServer.BunHttpServer
176
+ const baseUrl = `http://localhost:${bunServer.server.port}`
177
+
178
+ const json = yield* Effect.promise(() =>
179
+ fetch(`${baseUrl}/data`, {
180
+ headers: { Accept: "application/json" },
181
+ })
182
+ )
183
+
184
+ const html = yield* Effect.promise(() =>
185
+ fetch(`${baseUrl}/data`, {
186
+ headers: { Accept: "text/html" },
187
+ })
188
+ )
189
+
190
+ return [json, html] as const
191
+ })
192
+ .pipe(Effect.provide(testLayer(routes))),
193
+ ),
194
+ )
195
+
196
+ test.expect(jsonResponse.headers.get("Content-Type")).toBe(
197
+ "application/json",
198
+ )
199
+ test.expect(await jsonResponse.json()).toEqual({ type: "json" })
200
+
201
+ test.expect(htmlResponse.headers.get("Content-Type")).toBe(
202
+ "text/html; charset=utf-8",
203
+ )
204
+ test.expect(await htmlResponse.text()).toBe("<div>html</div>")
205
+ })
206
+
207
+ test.test("returns 406 for unacceptable content type", async () => {
208
+ const routes = Route.tree({
209
+ "/data": Route.get(Route.json({ type: "json" })),
210
+ })
211
+
212
+ const response = await Effect.runPromise(
213
+ Effect.scoped(
214
+ Effect
215
+ .gen(function*() {
216
+ const bunServer = yield* BunHttpServer.BunHttpServer
217
+ return yield* Effect.promise(() =>
218
+ fetch(`http://localhost:${bunServer.server.port}/data`, {
219
+ headers: { Accept: "image/png" },
220
+ })
221
+ )
222
+ })
223
+ .pipe(Effect.provide(testLayer(routes))),
224
+ ),
225
+ )
226
+
227
+ test.expect(response.status).toBe(406)
228
+ })
229
+
230
+ test.test("handles parameterized routes", async () => {
231
+ const routes = Route.tree({
232
+ "/users/:id": Route.get(Route.text("user")),
233
+ })
234
+
235
+ const response = await Effect.runPromise(
236
+ Effect.scoped(
237
+ Effect
238
+ .gen(function*() {
239
+ const bunServer = yield* BunHttpServer.BunHttpServer
240
+ return yield* Effect.promise(() =>
241
+ fetch(`http://localhost:${bunServer.server.port}/users/123`)
242
+ )
243
+ })
244
+ .pipe(Effect.provide(testLayer(routes))),
245
+ ),
246
+ )
247
+
248
+ test.expect(response.status).toBe(200)
249
+ test.expect(await response.text()).toBe("user")
250
+ })
251
+ })
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  import * as HttpApp from "@effect/platform/HttpApp"
2
3
  import * as HttpServer from "@effect/platform/HttpServer"
3
4
  import * as HttpServerError from "@effect/platform/HttpServerError"
@@ -14,7 +15,11 @@ import * as Layer from "effect/Layer"
14
15
  import * as Option from "effect/Option"
15
16
  import type * as Scope from "effect/Scope"
16
17
  import * as FileRouter from "../FileRouter.ts"
18
+ import * as PathPattern from "../PathPattern.ts"
17
19
  import * as Random from "../Random.ts"
20
+ import * as Route from "../Route.ts"
21
+ import * as RouteHttp from "../RouteHttp.ts"
22
+ import * as RouteTree from "../RouteTree.ts"
18
23
  import EmptyHTML from "./_empty.html"
19
24
  import {
20
25
  makeResponse,
@@ -158,12 +163,76 @@ export const make = (
158
163
  })
159
164
  })
160
165
 
166
+ /**
167
+ * Provides HttpServer using BunHttpServer under the hood.
168
+ */
161
169
  export const layer = (
162
170
  options?: ServeOptions,
163
- ): Layer.Layer<BunHttpServer> =>
164
- Layer.scoped(BunHttpServer, make(options ?? {}))
171
+ ): Layer.Layer<HttpServer.HttpServer | BunHttpServer> =>
172
+ Layer.provideMerge(
173
+ Layer.scoped(HttpServer.HttpServer, makeBunServer),
174
+ Layer.scoped(BunHttpServer, make(options ?? {})),
175
+ )
165
176
 
166
- export const makeHttpServer: Effect.Effect<
177
+ /**
178
+ * Registers routes provided via {@link Route.layer}
179
+ */
180
+ export function layerAuto() {
181
+ return Layer.unwrapEffect(
182
+ Effect.gen(function*() {
183
+ const bunServer = yield* BunHttpServer
184
+ const routes = yield* Effect.serviceOption(Route.Routes)
185
+
186
+ if (Option.isSome(routes)) {
187
+ return layerRoutes(routes.value)
188
+ } else {
189
+ return Layer.empty
190
+ }
191
+ }),
192
+ )
193
+ }
194
+
195
+ /**
196
+ * Register routes in Bun.serve.
197
+ */
198
+ export function layerRoutes(
199
+ tree: RouteTree.RouteTree,
200
+ ): Layer.Layer<never, never, BunHttpServer> {
201
+ return Layer.effectDiscard(
202
+ Effect.gen(function*() {
203
+ const bunServer = yield* BunHttpServer
204
+ const runtime = yield* Effect.runtime<BunHttpServer>()
205
+ const toWebHandler = RouteHttp.toWebHandlerRuntime(runtime)
206
+
207
+ const bunRoutes: BunRoute.BunRoutes = {}
208
+ const pathGroups = new Map<string, RouteMount.MountedRoute[]>()
209
+
210
+ for (const route of RouteTree.walk(tree)) {
211
+ const bunDescriptors = BunRoute.descriptors(route)
212
+ if (bunDescriptors) {
213
+ const htmlBundle = yield* Effect.promise(bunDescriptors.bunLoad)
214
+ bunRoutes[`${bunDescriptors.bunPrefix}/*`] = htmlBundle
215
+ }
216
+
217
+ const path = Route.descriptor(route).path
218
+ const group = pathGroups.get(path) ?? []
219
+ group.push(route)
220
+ pathGroups.set(path, group)
221
+ }
222
+
223
+ for (const [path, routes] of pathGroups) {
224
+ const handler = toWebHandler(routes)
225
+ for (const bunPath of PathPattern.toBun(path)) {
226
+ bunRoutes[bunPath] = handler
227
+ }
228
+ }
229
+
230
+ bunServer.addRoutes(bunRoutes)
231
+ }),
232
+ )
233
+ }
234
+
235
+ const makeBunServer: Effect.Effect<
167
236
  HttpServer.HttpServer,
168
237
  never,
169
238
  Scope.Scope | BunHttpServer
@@ -228,35 +297,6 @@ export const makeHttpServer: Effect.Effect<
228
297
  })
229
298
  })
230
299
 
231
- export const layerServer = (
232
- options?: ServeOptions,
233
- ): Layer.Layer<HttpServer.HttpServer | BunHttpServer> =>
234
- Layer.provideMerge(
235
- Layer.scoped(HttpServer.HttpServer, makeHttpServer),
236
- layer(options ?? {}),
237
- )
238
-
239
- /**
240
- * Adds routes from {@like FileRouter.FileRouter} to Bun Server.
241
- *
242
- * It ain't clean but it works until we figure out interfaces
243
- * for other servers.
244
- */
245
- export function layerFileRouter() {
246
- return Layer.effectDiscard(
247
- Effect.gen(function*() {
248
- const bunServer = yield* BunHttpServer
249
- const manifest = yield* Effect.serviceOption(FileRouter.FileRouter)
250
-
251
- if (Option.isSome(manifest)) {
252
- const router = yield* FileRouter.fromManifest(manifest.value)
253
- const bunRoutes = yield* BunRoute.routesFromRouter(router)
254
- bunServer.addRoutes(bunRoutes)
255
- }
256
- }),
257
- )
258
- }
259
-
260
300
  const removeHost = (url: string) => {
261
301
  if (url[0] === "/") {
262
302
  return url
@@ -37,18 +37,30 @@ export class ServerRequestImpl extends Inspectable.Class
37
37
  {
38
38
  readonly [HttpServerRequest.TypeId]: HttpServerRequest.TypeId
39
39
  readonly [HttpIncomingMessage.TypeId]: HttpIncomingMessage.TypeId
40
+ readonly source: Request
41
+ resolve: (response: Response) => void
42
+ readonly url: string
43
+ private bunServer: BunServerInstance<WebSocketContext>
44
+ headersOverride?: Headers.Headers
45
+ private remoteAddressOverride?: string
40
46
 
41
47
  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
+ source: Request,
49
+ resolve: (response: Response) => void,
50
+ url: string,
51
+ bunServer: BunServerInstance<WebSocketContext>,
52
+ headersOverride?: Headers.Headers,
53
+ remoteAddressOverride?: string,
48
54
  ) {
49
55
  super()
50
56
  this[HttpServerRequest.TypeId] = HttpServerRequest.TypeId
51
57
  this[HttpIncomingMessage.TypeId] = HttpIncomingMessage.TypeId
58
+ this.source = source
59
+ this.resolve = resolve
60
+ this.url = url
61
+ this.bunServer = bunServer
62
+ this.headersOverride = headersOverride
63
+ this.remoteAddressOverride = remoteAddressOverride
52
64
  }
53
65
 
54
66
  toJSON(): unknown {
@@ -1,4 +1,4 @@
1
- import * as t from "bun:test"
1
+ import * as test from "bun:test"
2
2
  import * as BunImportTrackerPlugin from "./BunImportTrackerPlugin.ts"
3
3
  import * as BunVirtualFilesPlugin from "./BunVirtualFilesPlugin.ts"
4
4
 
@@ -27,7 +27,7 @@ export const message = "Hello, World!"
27
27
  `,
28
28
  }
29
29
 
30
- t.it("virtual import", async () => {
30
+ test.it("virtual import", async () => {
31
31
  const trackerPlugin = BunImportTrackerPlugin.make({
32
32
  baseDir: Bun.fileURLToPath(import.meta.resolve("../..")),
33
33
  })
@@ -42,7 +42,7 @@ t.it("virtual import", async () => {
42
42
  ],
43
43
  })
44
44
 
45
- t
45
+ test
46
46
  .expect(
47
47
  [...trackerPlugin.state.entries()],
48
48
  )