effect-start 0.14.0 → 0.15.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 (81) hide show
  1. package/package.json +8 -9
  2. package/src/Commander.test.ts +507 -245
  3. package/src/ContentNegotiation.test.ts +500 -0
  4. package/src/ContentNegotiation.ts +535 -0
  5. package/src/FileRouter.ts +16 -12
  6. package/src/{FileRouterCodegen.test.ts → FileRouterCodegen.todo.ts} +384 -219
  7. package/src/FileRouterCodegen.ts +6 -6
  8. package/src/FileRouterPattern.test.ts +93 -62
  9. package/src/FileRouter_files.test.ts +5 -5
  10. package/src/FileRouter_path.test.ts +121 -69
  11. package/src/FileRouter_tree.test.ts +62 -56
  12. package/src/FileSystemExtra.test.ts +46 -30
  13. package/src/Http.test.ts +24 -0
  14. package/src/Http.ts +25 -0
  15. package/src/HttpAppExtra.test.ts +39 -20
  16. package/src/HttpAppExtra.ts +0 -1
  17. package/src/HttpUtils.test.ts +35 -18
  18. package/src/HttpUtils.ts +2 -0
  19. package/src/PathPattern.test.ts +648 -0
  20. package/src/PathPattern.ts +483 -0
  21. package/src/Route.ts +258 -1073
  22. package/src/RouteBody.test.ts +182 -0
  23. package/src/RouteBody.ts +106 -0
  24. package/src/RouteHook.test.ts +40 -0
  25. package/src/RouteHook.ts +105 -0
  26. package/src/RouteHttp.test.ts +443 -0
  27. package/src/RouteHttp.ts +219 -0
  28. package/src/RouteMount.test.ts +468 -0
  29. package/src/RouteMount.ts +313 -0
  30. package/src/RouteSchema.test.ts +81 -0
  31. package/src/RouteSchema.ts +44 -0
  32. package/src/RouteTree.test.ts +346 -0
  33. package/src/RouteTree.ts +165 -0
  34. package/src/RouteTrie.test.ts +322 -0
  35. package/src/RouteTrie.ts +224 -0
  36. package/src/RouterPattern.test.ts +569 -548
  37. package/src/RouterPattern.ts +7 -7
  38. package/src/Start.ts +3 -3
  39. package/src/TuplePathPattern.ts +64 -0
  40. package/src/Values.ts +16 -0
  41. package/src/bun/BunBundle.test.ts +36 -42
  42. package/src/bun/BunBundle.ts +2 -2
  43. package/src/bun/BunBundle_imports.test.ts +4 -6
  44. package/src/bun/BunHttpServer.test.ts +183 -6
  45. package/src/bun/BunHttpServer.ts +56 -32
  46. package/src/bun/BunHttpServer_web.ts +18 -6
  47. package/src/bun/BunImportTrackerPlugin.test.ts +3 -3
  48. package/src/bun/BunRoute.ts +29 -210
  49. package/src/{BundleHttp.test.ts → bundler/BundleHttp.test.ts} +34 -60
  50. package/src/{BundleHttp.ts → bundler/BundleHttp.ts} +1 -2
  51. package/src/client/index.ts +1 -1
  52. package/src/{Effect_HttpRouter.test.ts → effect/HttpRouter.test.ts} +69 -90
  53. package/src/experimental/EncryptedCookies.test.ts +125 -64
  54. package/src/experimental/SseHttpResponse.ts +0 -1
  55. package/src/hyper/Hyper.ts +89 -0
  56. package/src/{HyperHtml.test.ts → hyper/HyperHtml.test.ts} +13 -13
  57. package/src/{HyperHtml.ts → hyper/HyperHtml.ts} +2 -2
  58. package/src/{jsx.d.ts → hyper/jsx.d.ts} +1 -1
  59. package/src/index.ts +2 -4
  60. package/src/middlewares/BasicAuthMiddleware.test.ts +29 -19
  61. package/src/{NodeFileSystem.ts → node/FileSystem.ts} +6 -2
  62. package/src/testing/TestHttpClient.test.ts +26 -26
  63. package/src/testing/TestLogger.test.ts +27 -11
  64. package/src/x/datastar/Datastar.test.ts +47 -48
  65. package/src/x/datastar/Datastar.ts +1 -1
  66. package/src/x/tailwind/TailwindPlugin.test.ts +56 -58
  67. package/src/x/tailwind/plugin.ts +1 -1
  68. package/src/FileHttpRouter.test.ts +0 -239
  69. package/src/FileHttpRouter.ts +0 -194
  70. package/src/Hyper.ts +0 -194
  71. package/src/Route.test.ts +0 -1370
  72. package/src/RouteRender.ts +0 -40
  73. package/src/Router.test.ts +0 -375
  74. package/src/Router.ts +0 -255
  75. package/src/bun/BunRoute.test.ts +0 -480
  76. package/src/bun/BunRoute_bundles.test.ts +0 -219
  77. /package/src/{Bundle.ts → bundler/Bundle.ts} +0 -0
  78. /package/src/{BundleFiles.ts → bundler/BundleFiles.ts} +0 -0
  79. /package/src/{HyperNode.ts → hyper/HyperNode.ts} +0 -0
  80. /package/src/{jsx-runtime.ts → hyper/jsx-runtime.ts} +0 -0
  81. /package/src/{NodeUtils.ts → node/Utils.ts} +0 -0
@@ -1,480 +0,0 @@
1
- import * as HttpMiddleware from "@effect/platform/HttpMiddleware"
2
- import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
3
- import type { HTMLBundle } from "bun"
4
- import * as t from "bun:test"
5
- import * as Effect from "effect/Effect"
6
- import * as Layer from "effect/Layer"
7
- import * as assert from "node:assert"
8
- import * as Route from "../Route.ts"
9
- import * as Router from "../Router.ts"
10
- import * as BunHttpServer from "./BunHttpServer.ts"
11
- import * as BunRoute from "./BunRoute.ts"
12
-
13
- t.describe(`${BunRoute.validateBunPattern.name}`, () => {
14
- t.test("allows exact paths", () => {
15
- const result = BunRoute.validateBunPattern("/users")
16
- t.expect(result._tag).toBe("None")
17
- })
18
-
19
- t.test("allows full-segment params", () => {
20
- const result = BunRoute.validateBunPattern("/users/[id]")
21
- t.expect(result._tag).toBe("None")
22
- })
23
-
24
- t.test("allows rest params", () => {
25
- const result = BunRoute.validateBunPattern("/docs/[...path]")
26
- t.expect(result._tag).toBe("None")
27
- })
28
-
29
- t.test("rejects prefixed params", () => {
30
- const result = BunRoute.validateBunPattern("/users/pk_[id]")
31
- assert.strictEqual(result._tag, "Some")
32
- t.expect(result.value.reason).toBe("UnsupportedPattern")
33
- t.expect(result.value.pattern).toBe("/users/pk_[id]")
34
- })
35
-
36
- t.test("rejects suffixed params", () => {
37
- const result = BunRoute.validateBunPattern("/users/[id]_details")
38
- assert.strictEqual(result._tag, "Some")
39
- t.expect(result.value.reason).toBe("UnsupportedPattern")
40
- })
41
-
42
- t.test("rejects dot suffix on params", () => {
43
- const result = BunRoute.validateBunPattern("/api/[id].json")
44
- assert.strictEqual(result._tag, "Some")
45
- t.expect(result.value.reason).toBe("UnsupportedPattern")
46
- })
47
-
48
- t.test("rejects tilde suffix on params", () => {
49
- const result = BunRoute.validateBunPattern("/api/[id]~test")
50
- assert.strictEqual(result._tag, "Some")
51
- t.expect(result.value.reason).toBe("UnsupportedPattern")
52
- })
53
-
54
- t.test("allows optional params (implemented via two patterns)", () => {
55
- const result = BunRoute.validateBunPattern("/users/[[id]]")
56
- t.expect(result._tag).toBe("None")
57
- })
58
-
59
- t.test("allows optional rest params (implemented via two patterns)", () => {
60
- const result = BunRoute.validateBunPattern("/docs/[[...path]]")
61
- t.expect(result._tag).toBe("None")
62
- })
63
- })
64
-
65
- t.describe(`${BunRoute.routesFromRouter.name}`, () => {
66
- t.test("fails with RouterError for unsupported patterns", async () => {
67
- const result = await Effect.runPromise(
68
- BunRoute
69
- .routesFromRouter(
70
- Router.mount("/users/pk_[id]", Route.text("user")),
71
- )
72
- .pipe(
73
- Effect.either,
74
- Effect.provide(BunHttpServer.layer({ port: 0 })),
75
- ),
76
- )
77
-
78
- assert.strictEqual(result._tag, "Left")
79
- t.expect(result.left._tag).toBe("RouterError")
80
- t.expect(result.left.reason).toBe("UnsupportedPattern")
81
- })
82
-
83
- t.test("converts text route to fetch handler", async () => {
84
- const fetch = await makeFetch(
85
- Router.mount("/hello", Route.text("Hello World")),
86
- )
87
-
88
- const response = await fetch("/hello")
89
-
90
- t.expect(response.status).toBe(200)
91
- t.expect(await response.text()).toBe("Hello World")
92
- })
93
-
94
- t.test("converts json route to fetch handler", async () => {
95
- const fetch = await makeFetch(
96
- Router.mount("/api/data", Route.json({ message: "ok", count: 42 })),
97
- )
98
-
99
- const response = await fetch("/api/data")
100
-
101
- t.expect(response.status).toBe(200)
102
- t.expect(await response.json()).toEqual({ message: "ok", count: 42 })
103
- })
104
-
105
- t.test("converts html route to fetch handler", async () => {
106
- const fetch = await makeFetch(
107
- Router.mount("/page", Route.html(Effect.succeed("<h1>Title</h1>"))),
108
- )
109
-
110
- const response = await fetch("/page")
111
-
112
- t.expect(response.status).toBe(200)
113
- t.expect(await response.text()).toBe("<h1>Title</h1>")
114
- })
115
-
116
- t.test("handles method-specific routes", async () => {
117
- const fetch = await makeFetch(
118
- Router.mount(
119
- "/users",
120
- Route.get(Route.json({ users: [] })).post(
121
- Route.json({ created: true }),
122
- ),
123
- ),
124
- )
125
-
126
- const getResponse = await fetch("/users")
127
- t.expect(await getResponse.json()).toEqual({ users: [] })
128
-
129
- const postResponse = await fetch("/users", { method: "POST" })
130
- t.expect(await postResponse.json()).toEqual({ created: true })
131
- })
132
-
133
- t.test("converts path syntax to Bun format", async () => {
134
- const routes = await makeBunRoutes(
135
- Router
136
- .mount("/users/[id]", Route.text("user"))
137
- .mount("/docs/[...path]", Route.text("docs")),
138
- )
139
-
140
- t.expect(routes["/users/:id"]).toBeDefined()
141
- t.expect(routes["/docs/*"]).toBeDefined()
142
- t.expect(routes["/users/[id]"]).toBeUndefined()
143
- t.expect(routes["/docs/[...path]"]).toBeUndefined()
144
- })
145
-
146
- t.test("creates proxy and internal routes for BunRoute", async () => {
147
- const mockBundle = { index: "index.html" } as HTMLBundle
148
- const bunRoute = BunRoute.html(() => Promise.resolve(mockBundle))
149
-
150
- const routes = await makeBunRoutes(
151
- Router.mount("/app", bunRoute),
152
- )
153
-
154
- const internalPath = Object.keys(routes).find((k) =>
155
- k.includes(".BunRoute-")
156
- )
157
-
158
- t.expect(internalPath).toBeDefined()
159
- t.expect(routes[internalPath!]).toBe(mockBundle)
160
- t.expect(typeof routes["/app"]).toBe("function")
161
- })
162
-
163
- t.test("handles mixed BunRoute and regular routes", async () => {
164
- const mockBundle = { index: "index.html" } as HTMLBundle
165
- const bunRoute = BunRoute.html(() => Promise.resolve(mockBundle))
166
-
167
- const routes = await makeBunRoutes(
168
- Router
169
- .mount("/app", bunRoute)
170
- .mount("/api/health", Route.json({ ok: true })),
171
- )
172
-
173
- const internalPath = Object.keys(routes).find((k) =>
174
- k.includes(".BunRoute-")
175
- )
176
-
177
- t.expect(internalPath).toBeDefined()
178
- t.expect(routes[internalPath!]).toBe(mockBundle)
179
- t.expect(typeof routes["/app"]).toBe("function")
180
- t.expect(routes["/api/health"]).toBeDefined()
181
- t.expect(typeof routes["/api/health"]).toBe("object")
182
- })
183
-
184
- t.test("groups multiple methods under same path", async () => {
185
- const fetch = await makeFetch(
186
- Router.mount(
187
- "/resource",
188
- Route
189
- .get(Route.text("get"))
190
- .post(Route.text("post"))
191
- .delete(Route.text("delete")),
192
- ),
193
- )
194
-
195
- const getRes = await fetch("/resource")
196
- const postRes = await fetch("/resource", { method: "POST" })
197
- const delRes = await fetch("/resource", { method: "DELETE" })
198
-
199
- t.expect(await getRes.text()).toBe("get")
200
- t.expect(await postRes.text()).toBe("post")
201
- t.expect(await delRes.text()).toBe("delete")
202
- })
203
- })
204
-
205
- t.describe("fetch handler Response", () => {
206
- t.test("returns Response instance", async () => {
207
- const fetch = await makeFetch(
208
- Router.mount("/test", Route.text("test")),
209
- )
210
-
211
- const response = await fetch("/test")
212
-
213
- t.expect(response).toBeInstanceOf(Response)
214
- })
215
-
216
- t.test("text response has correct content-type", async () => {
217
- const fetch = await makeFetch(
218
- Router.mount("/text", Route.text("hello")),
219
- )
220
-
221
- const response = await fetch("/text")
222
-
223
- t.expect(response.headers.get("content-type")).toContain("text/plain")
224
- })
225
-
226
- t.test("json response has correct content-type", async () => {
227
- const fetch = await makeFetch(
228
- Router.mount("/json", Route.json({ data: 1 })),
229
- )
230
-
231
- const response = await fetch("/json")
232
-
233
- t.expect(response.headers.get("content-type")).toContain("application/json")
234
- })
235
-
236
- t.test("html response has correct content-type", async () => {
237
- const fetch = await makeFetch(
238
- Router.mount("/html", Route.html(Effect.succeed("<p>hi</p>"))),
239
- )
240
-
241
- const response = await fetch("/html")
242
-
243
- t.expect(response.headers.get("content-type")).toContain("text/html")
244
- })
245
-
246
- t.test("response body is readable", async () => {
247
- const fetch = await makeFetch(
248
- Router.mount("/body", Route.text("readable body")),
249
- )
250
-
251
- const response = await fetch("/body")
252
-
253
- t.expect(response.bodyUsed).toBe(false)
254
- const text = await response.text()
255
- t.expect(text).toBe("readable body")
256
- t.expect(response.bodyUsed).toBe(true)
257
- })
258
-
259
- t.test("response ok is true for 200 status", async () => {
260
- const fetch = await makeFetch(
261
- Router.mount("/ok", Route.text("ok")),
262
- )
263
-
264
- const response = await fetch("/ok")
265
-
266
- t.expect(response.ok).toBe(true)
267
- t.expect(response.status).toBe(200)
268
- })
269
- })
270
-
271
- t.describe("Route.layer httpMiddleware", () => {
272
- t.test("applies middleware headers to child routes", async () => {
273
- const addHeader = HttpMiddleware.make((app) =>
274
- Effect.gen(function*() {
275
- const response = yield* app
276
- return HttpServerResponse.setHeader(response, "X-Layer-Applied", "true")
277
- })
278
- ) as Route.HttpMiddlewareFunction
279
-
280
- const router = Router
281
- .use(Route.layer(Route.http(addHeader)))
282
- .mount("/child", Route.text("child content"))
283
-
284
- const fetch = await makeFetch(router)
285
- const response = await fetch("/child")
286
-
287
- t.expect(response.headers.get("X-Layer-Applied")).toBe("true")
288
- t.expect(await response.text()).toBe("child content")
289
- })
290
-
291
- t.test("middleware only applies to children, not siblings", async () => {
292
- const addHeader = HttpMiddleware.make((app) =>
293
- Effect.gen(function*() {
294
- const response = yield* app
295
- return HttpServerResponse.setHeader(response, "X-Layer-Applied", "true")
296
- })
297
- ) as Route.HttpMiddlewareFunction
298
-
299
- const router = Router
300
- .mount("/outside", Route.text("outside content"))
301
- .use(Route.layer(Route.http(addHeader)))
302
- .mount("/inside", Route.text("inside content"))
303
-
304
- const fetch = await makeFetch(router)
305
-
306
- const insideResponse = await fetch("/inside")
307
- t.expect(insideResponse.headers.get("X-Layer-Applied")).toBe("true")
308
-
309
- const outsideResponse = await fetch("/outside")
310
- t.expect(outsideResponse.headers.get("X-Layer-Applied")).toBeNull()
311
- })
312
-
313
- t.test("multiple middleware are applied in order", async () => {
314
- const addHeader1 = HttpMiddleware.make((app) =>
315
- Effect.gen(function*() {
316
- const response = yield* app
317
- return HttpServerResponse.setHeader(response, "X-First", "1")
318
- })
319
- ) as Route.HttpMiddlewareFunction
320
-
321
- const addHeader2 = HttpMiddleware.make((app) =>
322
- Effect.gen(function*() {
323
- const response = yield* app
324
- return HttpServerResponse.setHeader(response, "X-Second", "2")
325
- })
326
- ) as Route.HttpMiddlewareFunction
327
-
328
- const router = Router
329
- .use(Route.layer(Route.http(addHeader1), Route.http(addHeader2)))
330
- .mount("/test", Route.text("test"))
331
-
332
- const fetch = await makeFetch(router)
333
- const response = await fetch("/test")
334
-
335
- t.expect(response.headers.get("X-First")).toBe("1")
336
- t.expect(response.headers.get("X-Second")).toBe("2")
337
- })
338
-
339
- t.test("nested layers apply all middleware", async () => {
340
- const outerHeader = HttpMiddleware.make((app) =>
341
- Effect.gen(function*() {
342
- const response = yield* app
343
- return HttpServerResponse.setHeader(response, "X-Outer", "outer")
344
- })
345
- ) as Route.HttpMiddlewareFunction
346
-
347
- const innerHeader = HttpMiddleware.make((app) =>
348
- Effect.gen(function*() {
349
- const response = yield* app
350
- return HttpServerResponse.setHeader(response, "X-Inner", "inner")
351
- })
352
- ) as Route.HttpMiddlewareFunction
353
-
354
- const router = Router
355
- .use(Route.layer(Route.http(outerHeader)))
356
- .use(Route.layer(Route.http(innerHeader)))
357
- .mount("/nested", Route.text("nested"))
358
-
359
- const fetch = await makeFetch(router)
360
- const response = await fetch("/nested")
361
-
362
- t.expect(response.headers.get("X-Outer")).toBe("outer")
363
- t.expect(response.headers.get("X-Inner")).toBe("inner")
364
- })
365
- })
366
-
367
- t.describe("BunRoute placeholder replacement", () => {
368
- t.test("%yield% is replaced with nested route content via layer", async () => {
369
- const layoutBundle = BunRoute.html(() =>
370
- import("../../static/LayoutSlots.html")
371
- )
372
-
373
- const router = Router
374
- .use(Route.layer(layoutBundle))
375
- .mount("/page", Route.html(Effect.succeed("<p>Child Content</p>")))
376
-
377
- await Effect.runPromise(
378
- Effect
379
- .gen(function*() {
380
- const bunServer = yield* BunHttpServer.BunHttpServer
381
- const routes = yield* BunRoute.routesFromRouter(router)
382
- bunServer.addRoutes(routes)
383
-
384
- const baseUrl =
385
- `http://${bunServer.server.hostname}:${bunServer.server.port}`
386
- const response = yield* Effect.promise(() => fetch(`${baseUrl}/page`))
387
-
388
- const html = yield* Effect.promise(() => response.text())
389
-
390
- t.expect(html).toContain("<body>")
391
- t.expect(html).toContain("<p>Child Content</p>")
392
- t.expect(html).not.toContain("%yield%")
393
- t.expect(html).not.toContain("%slots.")
394
- })
395
- .pipe(
396
- Effect.scoped,
397
- Effect.provide(BunHttpServer.layer({ port: 0 })),
398
- ),
399
- )
400
- })
401
-
402
- t.test("%yield% and %slots% are replaced with empty when no nested content", async () => {
403
- const layoutBundle = BunRoute.html(() =>
404
- import("../../static/LayoutSlots.html")
405
- )
406
-
407
- const router = Router.mount("/layout", layoutBundle)
408
-
409
- await Effect.runPromise(
410
- Effect
411
- .gen(function*() {
412
- const bunServer = yield* BunHttpServer.BunHttpServer
413
- const routes = yield* BunRoute.routesFromRouter(router)
414
- bunServer.addRoutes(routes)
415
-
416
- const baseUrl =
417
- `http://${bunServer.server.hostname}:${bunServer.server.port}`
418
- const response = yield* Effect.promise(() =>
419
- fetch(`${baseUrl}/layout`)
420
- )
421
-
422
- const html = yield* Effect.promise(() => response.text())
423
-
424
- t.expect(html).toContain("<title></title>")
425
- t.expect(html).toContain("<body>")
426
- t.expect(html).not.toContain("%yield%")
427
- t.expect(html).not.toContain("%slots.")
428
- })
429
- .pipe(
430
- Effect.scoped,
431
- Effect.provide(BunHttpServer.layer({ port: 0 })),
432
- ),
433
- )
434
- })
435
- })
436
-
437
- type FetchFn = (path: string, init?: { method?: string }) => Promise<Response>
438
-
439
- type HandlerFn = (
440
- req: Request,
441
- server: unknown,
442
- ) => Response | Promise<Response>
443
-
444
- async function makeBunRoutes(
445
- router: Router.Router.Any,
446
- ): Promise<BunRoute.BunRoutes> {
447
- return Effect.runPromise(
448
- BunRoute.routesFromRouter(router).pipe(
449
- Effect.provide(BunHttpServer.layer({ port: 0 })),
450
- ),
451
- )
452
- }
453
-
454
- async function makeFetch(router: Router.Router.Any): Promise<FetchFn> {
455
- const routes = await makeBunRoutes(router)
456
- const mockServer = {} as import("bun").Server<unknown>
457
-
458
- return async (path, init) => {
459
- const method = init?.method ?? "GET"
460
- const handler = routes[path]
461
-
462
- if (!handler) {
463
- throw new Error(`No handler for path: ${path}`)
464
- }
465
-
466
- if (typeof handler === "function") {
467
- return handler(new Request(`http://localhost${path}`, init), mockServer)
468
- }
469
-
470
- const methodHandler = (handler as Record<string, HandlerFn>)[method]
471
- if (!methodHandler) {
472
- throw new Error(`No handler for ${method} ${path}`)
473
- }
474
-
475
- return methodHandler(
476
- new Request(`http://localhost${path}`, init),
477
- mockServer,
478
- )
479
- }
480
- }
@@ -1,219 +0,0 @@
1
- import * as t from "bun:test"
2
- import * as Effect from "effect/Effect"
3
- import * as Route from "../Route.ts"
4
- import * as Router from "../Router.ts"
5
- import * as TestHttpClient from "../testing/TestHttpClient.ts"
6
- import * as BunHttpServer from "./BunHttpServer.ts"
7
- import * as BunRoute from "./BunRoute.ts"
8
-
9
- t.describe("BunRoute proxy with Bun.serve", () => {
10
- t.test("BunRoute proxy returns same content as direct bundle access", async () => {
11
- const bunRoute = BunRoute.html(() => import("../../static/TestPage.html"))
12
-
13
- const router = Router.mount("/test", bunRoute)
14
-
15
- await Effect.runPromise(
16
- Effect
17
- .gen(function*() {
18
- const bunServer = yield* BunHttpServer.BunHttpServer
19
-
20
- const routes = yield* BunRoute.routesFromRouter(router)
21
- bunServer.addRoutes(routes)
22
-
23
- const internalPath = Object.keys(routes).find((k) =>
24
- k.includes(".BunRoute-")
25
- )
26
- t.expect(internalPath).toBeDefined()
27
-
28
- const proxyHandler = routes["/test"]
29
- t.expect(typeof proxyHandler).toBe("function")
30
-
31
- const internalBundle = routes[internalPath!]
32
- t.expect(internalBundle).toHaveProperty("index")
33
-
34
- const baseUrl =
35
- `http://${bunServer.server.hostname}:${bunServer.server.port}`
36
- const client = TestHttpClient.make<never, never>(
37
- (req) => fetch(req),
38
- {
39
- baseUrl,
40
- },
41
- )
42
-
43
- const directResponse = yield* client.get(internalPath!)
44
- const proxyResponse = yield* client.get("/test")
45
-
46
- t.expect(proxyResponse.status).toBe(directResponse.status)
47
-
48
- const directText = yield* directResponse.text
49
- const proxyText = yield* proxyResponse.text
50
-
51
- t.expect(proxyText).toBe(directText)
52
- t.expect(proxyText).toContain("Test Page Content")
53
- })
54
- .pipe(
55
- Effect.scoped,
56
- Effect.provide(BunHttpServer.layer({ port: 0 })),
57
- ),
58
- )
59
- })
60
-
61
- t.test("multiple BunRoutes each get unique internal paths", async () => {
62
- const bunRoute1 = BunRoute.html(() => import("../../static/TestPage.html"))
63
- const bunRoute2 = BunRoute.html(() =>
64
- import("../../static/AnotherPage.html")
65
- )
66
-
67
- const router = Router
68
- .mount("/page1", bunRoute1)
69
- .mount("/page2", bunRoute2)
70
-
71
- await Effect.runPromise(
72
- Effect
73
- .gen(function*() {
74
- const bunServer = yield* BunHttpServer.BunHttpServer
75
- const routes = yield* BunRoute.routesFromRouter(router)
76
- bunServer.addRoutes(routes)
77
-
78
- const internalPaths = Object.keys(routes).filter((k) =>
79
- k.includes(".BunRoute-")
80
- )
81
- t.expect(internalPaths).toHaveLength(2)
82
-
83
- const nonces = internalPaths.map((p) => {
84
- const match = p.match(/\.BunRoute-([a-z0-9]+)/)
85
- return match?.[1]
86
- })
87
- t.expect(nonces[0]).not.toBe(nonces[1])
88
-
89
- const baseUrl =
90
- `http://${bunServer.server.hostname}:${bunServer.server.port}`
91
- const client = TestHttpClient.make<never, never>(
92
- (req) => fetch(req),
93
- {
94
- baseUrl,
95
- },
96
- )
97
-
98
- const response1 = yield* client.get("/page1")
99
- const response2 = yield* client.get("/page2")
100
-
101
- const text1 = yield* response1.text
102
- const text2 = yield* response2.text
103
-
104
- t.expect(text1).toContain("Test Page Content")
105
- t.expect(text2).toContain("Another Page Content")
106
- })
107
- .pipe(
108
- Effect.scoped,
109
- Effect.provide(BunHttpServer.layer({ port: 0 })),
110
- ),
111
- )
112
- })
113
-
114
- t.test("proxy preserves request headers", async () => {
115
- const bunRoute = BunRoute.html(() => import("../../static/TestPage.html"))
116
-
117
- const router = Router.mount("/headers-test", bunRoute)
118
-
119
- await Effect.runPromise(
120
- Effect
121
- .gen(function*() {
122
- const bunServer = yield* BunHttpServer.BunHttpServer
123
-
124
- const routes = yield* BunRoute.routesFromRouter(router)
125
- bunServer.addRoutes(routes)
126
-
127
- const baseUrl =
128
- `http://${bunServer.server.hostname}:${bunServer.server.port}`
129
- const client = TestHttpClient.make<never, never>(
130
- (req) => fetch(req),
131
- {
132
- baseUrl,
133
- },
134
- )
135
-
136
- const response = yield* client.get("/headers-test", {
137
- headers: {
138
- "Accept": "text/html",
139
- "X-Custom-Header": "test-value",
140
- },
141
- })
142
-
143
- t.expect(response.status).toBe(200)
144
- const text = yield* response.text
145
- t.expect(text).toContain("Test Page Content")
146
- })
147
- .pipe(
148
- Effect.scoped,
149
- Effect.provide(BunHttpServer.layer({ port: 0 })),
150
- ),
151
- )
152
- })
153
-
154
- t.test("mixed BunRoute and regular routes work together", async () => {
155
- const bunRoute = BunRoute.html(() => import("../../static/TestPage.html"))
156
-
157
- const router = Router
158
- .mount("/html", bunRoute)
159
- .mount("/api", Route.text("Hello from text route"))
160
-
161
- await Effect.runPromise(
162
- Effect
163
- .gen(function*() {
164
- const bunServer = yield* BunHttpServer.BunHttpServer
165
- const routes = yield* BunRoute.routesFromRouter(router)
166
- bunServer.addRoutes(routes)
167
-
168
- const baseUrl =
169
- `http://${bunServer.server.hostname}:${bunServer.server.port}`
170
- const client = TestHttpClient.make<never, never>(
171
- (req) => fetch(req),
172
- {
173
- baseUrl,
174
- },
175
- )
176
-
177
- const htmlResponse = yield* client.get("/html")
178
- const apiResponse = yield* client.get("/api")
179
-
180
- const htmlText = yield* htmlResponse.text
181
- const apiText = yield* apiResponse.text
182
-
183
- t.expect(htmlText).toContain("Test Page Content")
184
- t.expect(apiText).toBe("Hello from text route")
185
- })
186
- .pipe(
187
- Effect.scoped,
188
- Effect.provide(BunHttpServer.layer({ port: 0 })),
189
- ),
190
- )
191
- })
192
-
193
- t.test("nonce is different across separate BunRoute instances", async () => {
194
- const bunRoute1 = BunRoute.html(() => import("../../static/TestPage.html"))
195
- const bunRoute2 = BunRoute.html(() => import("../../static/TestPage.html"))
196
-
197
- const router = Router
198
- .mount("/test1", bunRoute1)
199
- .mount("/test2", bunRoute2)
200
-
201
- await Effect.runPromise(
202
- Effect
203
- .gen(function*() {
204
- const routes = yield* BunRoute.routesFromRouter(router)
205
-
206
- const internalPaths = Object.keys(routes).filter((k) =>
207
- k.includes(".BunRoute-")
208
- )
209
-
210
- t.expect(internalPaths).toHaveLength(2)
211
- t.expect(internalPaths[0]).not.toBe(internalPaths[1])
212
- })
213
- .pipe(
214
- Effect.scoped,
215
- Effect.provide(BunHttpServer.layer({ port: 0 })),
216
- ),
217
- )
218
- })
219
- })
File without changes
File without changes
File without changes
File without changes