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.
- package/package.json +8 -9
- package/src/Commander.test.ts +507 -245
- package/src/ContentNegotiation.test.ts +500 -0
- package/src/ContentNegotiation.ts +535 -0
- package/src/FileRouter.ts +16 -12
- package/src/{FileRouterCodegen.test.ts → FileRouterCodegen.todo.ts} +384 -219
- package/src/FileRouterCodegen.ts +6 -6
- package/src/FileRouterPattern.test.ts +93 -62
- package/src/FileRouter_files.test.ts +5 -5
- package/src/FileRouter_path.test.ts +121 -69
- package/src/FileRouter_tree.test.ts +62 -56
- package/src/FileSystemExtra.test.ts +46 -30
- package/src/Http.test.ts +24 -0
- package/src/Http.ts +25 -0
- package/src/HttpAppExtra.test.ts +39 -20
- package/src/HttpAppExtra.ts +0 -1
- package/src/HttpUtils.test.ts +35 -18
- package/src/HttpUtils.ts +2 -0
- package/src/PathPattern.test.ts +648 -0
- package/src/PathPattern.ts +483 -0
- package/src/Route.ts +258 -1073
- package/src/RouteBody.test.ts +182 -0
- package/src/RouteBody.ts +106 -0
- package/src/RouteHook.test.ts +40 -0
- package/src/RouteHook.ts +105 -0
- package/src/RouteHttp.test.ts +443 -0
- package/src/RouteHttp.ts +219 -0
- package/src/RouteMount.test.ts +468 -0
- package/src/RouteMount.ts +313 -0
- package/src/RouteSchema.test.ts +81 -0
- package/src/RouteSchema.ts +44 -0
- package/src/RouteTree.test.ts +346 -0
- package/src/RouteTree.ts +165 -0
- package/src/RouteTrie.test.ts +322 -0
- package/src/RouteTrie.ts +224 -0
- package/src/RouterPattern.test.ts +569 -548
- package/src/RouterPattern.ts +7 -7
- package/src/Start.ts +3 -3
- package/src/TuplePathPattern.ts +64 -0
- package/src/Values.ts +16 -0
- package/src/bun/BunBundle.test.ts +36 -42
- package/src/bun/BunBundle.ts +2 -2
- package/src/bun/BunBundle_imports.test.ts +4 -6
- package/src/bun/BunHttpServer.test.ts +183 -6
- package/src/bun/BunHttpServer.ts +56 -32
- package/src/bun/BunHttpServer_web.ts +18 -6
- package/src/bun/BunImportTrackerPlugin.test.ts +3 -3
- package/src/bun/BunRoute.ts +29 -210
- package/src/{BundleHttp.test.ts → bundler/BundleHttp.test.ts} +34 -60
- package/src/{BundleHttp.ts → bundler/BundleHttp.ts} +1 -2
- package/src/client/index.ts +1 -1
- package/src/{Effect_HttpRouter.test.ts → effect/HttpRouter.test.ts} +69 -90
- package/src/experimental/EncryptedCookies.test.ts +125 -64
- package/src/experimental/SseHttpResponse.ts +0 -1
- package/src/hyper/Hyper.ts +89 -0
- package/src/{HyperHtml.test.ts → hyper/HyperHtml.test.ts} +13 -13
- package/src/{HyperHtml.ts → hyper/HyperHtml.ts} +2 -2
- package/src/{jsx.d.ts → hyper/jsx.d.ts} +1 -1
- package/src/index.ts +2 -4
- package/src/middlewares/BasicAuthMiddleware.test.ts +29 -19
- package/src/{NodeFileSystem.ts → node/FileSystem.ts} +6 -2
- package/src/testing/TestHttpClient.test.ts +26 -26
- package/src/testing/TestLogger.test.ts +27 -11
- package/src/x/datastar/Datastar.test.ts +47 -48
- package/src/x/datastar/Datastar.ts +1 -1
- package/src/x/tailwind/TailwindPlugin.test.ts +56 -58
- package/src/x/tailwind/plugin.ts +1 -1
- package/src/FileHttpRouter.test.ts +0 -239
- package/src/FileHttpRouter.ts +0 -194
- package/src/Hyper.ts +0 -194
- package/src/Route.test.ts +0 -1370
- package/src/RouteRender.ts +0 -40
- package/src/Router.test.ts +0 -375
- package/src/Router.ts +0 -255
- package/src/bun/BunRoute.test.ts +0 -480
- package/src/bun/BunRoute_bundles.test.ts +0 -219
- /package/src/{Bundle.ts → bundler/Bundle.ts} +0 -0
- /package/src/{BundleFiles.ts → bundler/BundleFiles.ts} +0 -0
- /package/src/{HyperNode.ts → hyper/HyperNode.ts} +0 -0
- /package/src/{jsx-runtime.ts → hyper/jsx-runtime.ts} +0 -0
- /package/src/{NodeUtils.ts → node/Utils.ts} +0 -0
package/src/bun/BunRoute.test.ts
DELETED
|
@@ -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
|