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.
- package/package.json +8 -9
- package/src/Commander.test.ts +507 -245
- package/src/ContentNegotiation.test.ts +603 -0
- package/src/ContentNegotiation.ts +542 -0
- package/src/Entity.test.ts +592 -0
- package/src/Entity.ts +362 -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 +319 -0
- package/src/Http.ts +167 -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 +485 -0
- package/src/Route.ts +266 -1069
- package/src/RouteBody.test.ts +234 -0
- package/src/RouteBody.ts +193 -0
- package/src/RouteHook.test.ts +40 -0
- package/src/RouteHook.ts +106 -0
- package/src/RouteHttp.test.ts +2906 -0
- package/src/RouteHttp.ts +427 -0
- package/src/RouteHttpTracer.ts +92 -0
- package/src/RouteMount.test.ts +481 -0
- package/src/RouteMount.ts +470 -0
- package/src/RouteSchema.test.ts +427 -0
- package/src/RouteSchema.ts +423 -0
- package/src/RouteTree.test.ts +494 -0
- package/src/RouteTree.ts +219 -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/StreamExtra.ts +21 -1
- package/src/TuplePathPattern.ts +64 -0
- package/src/Values.test.ts +263 -0
- package/src/Values.ts +76 -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 +72 -32
- package/src/bun/BunHttpServer_web.ts +18 -6
- package/src/bun/BunImportTrackerPlugin.test.ts +3 -3
- package/src/bun/BunRoute.test.ts +124 -442
- package/src/bun/BunRoute.ts +146 -286
- 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 +3 -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 -14
- package/src/testing/TestLogger.ts +15 -9
- 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_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
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import * as HttpRouter from "@effect/platform/HttpRouter"
|
|
2
|
-
import * as
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
44
|
+
test
|
|
45
45
|
.expect(bundle.entrypoints)
|
|
46
46
|
.toBeObject()
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
+
test
|
|
62
61
|
.expect(firstEntrypoint)
|
|
63
62
|
.toBeString()
|
|
64
|
-
|
|
63
|
+
test
|
|
65
64
|
.expect(bundle.entrypoints[firstEntrypoint])
|
|
66
65
|
.toBeString()
|
|
67
66
|
|
|
68
67
|
const firstArtifact = bundle.artifacts[0]
|
|
69
68
|
|
|
70
|
-
|
|
69
|
+
test
|
|
71
70
|
.expect(firstArtifact)
|
|
72
71
|
.toHaveProperty("path")
|
|
73
|
-
|
|
72
|
+
test
|
|
74
73
|
.expect(firstArtifact)
|
|
75
74
|
.toHaveProperty("type")
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
137
|
+
test
|
|
140
138
|
.expect(result)
|
|
141
139
|
.toHaveProperty("entrypoints")
|
|
142
|
-
|
|
140
|
+
test
|
|
143
141
|
.expect(result)
|
|
144
142
|
.toHaveProperty("artifacts")
|
|
145
|
-
|
|
146
|
-
t
|
|
143
|
+
test
|
|
147
144
|
.expect(result.entrypoints)
|
|
148
145
|
.toBeObject()
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
159
|
+
test
|
|
164
160
|
.expect(firstKey)
|
|
165
161
|
.toBeString()
|
|
166
|
-
|
|
162
|
+
test
|
|
167
163
|
.expect(result.entrypoints[firstKey])
|
|
168
164
|
.toBeString()
|
|
169
165
|
|
|
170
166
|
const artifact = result.artifacts[0]
|
|
171
167
|
|
|
172
|
-
|
|
168
|
+
test
|
|
173
169
|
.expect(artifact)
|
|
174
170
|
.toHaveProperty("path")
|
|
175
|
-
|
|
171
|
+
test
|
|
176
172
|
.expect(artifact)
|
|
177
173
|
.toHaveProperty("type")
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
209
|
+
test
|
|
214
210
|
.expect(resolvedOutput)
|
|
215
211
|
.toBe(expectedOutput)
|
|
216
212
|
|
|
217
213
|
const artifact = bundle.getArtifact(resolvedOutput!)
|
|
218
214
|
|
|
219
|
-
|
|
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
|
-
|
|
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
|
-
|
|
249
|
+
test
|
|
255
250
|
.expect(artifact.path)
|
|
256
251
|
.toBeString()
|
|
257
|
-
|
|
252
|
+
test
|
|
258
253
|
.expect(artifact.type)
|
|
259
254
|
.toBeString()
|
|
260
|
-
|
|
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 {
|
package/src/bun/BunBundle.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
})
|
package/src/bun/BunHttpServer.ts
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
45
|
+
test
|
|
46
46
|
.expect(
|
|
47
47
|
[...trackerPlugin.state.entries()],
|
|
48
48
|
)
|