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/RouterPattern.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
export type RouterPattern = `/${string}`
|
|
2
2
|
|
|
3
3
|
export type ParamDelimiter = "_" | "-" | "." | "," | ";" | "!" | "@" | "~"
|
|
4
4
|
export type ParamPrefix = `${string}${ParamDelimiter}` | ""
|
|
@@ -206,7 +206,7 @@ function colonParamSegment(segment: Segment): string {
|
|
|
206
206
|
* - `[[...param]]` → `/`, `/*`
|
|
207
207
|
* - `pk_[id]` → `pk_:id`
|
|
208
208
|
*/
|
|
209
|
-
export function toColon(path:
|
|
209
|
+
export function toColon(path: RouterPattern): string[] {
|
|
210
210
|
return buildPaths(parse(path), colonParamSegment, "/*")
|
|
211
211
|
}
|
|
212
212
|
|
|
@@ -221,7 +221,7 @@ export const toHono = toColon
|
|
|
221
221
|
* - `[[...param]]` → `/`, `/*param`
|
|
222
222
|
* - `pk_[id]` → `pk_:id`
|
|
223
223
|
*/
|
|
224
|
-
export function toExpress(path:
|
|
224
|
+
export function toExpress(path: RouterPattern): string[] {
|
|
225
225
|
const segments = parse(path)
|
|
226
226
|
const optionalRestIndex = segments.findIndex(
|
|
227
227
|
(s) => s._tag === "RestSegment" && s.optional,
|
|
@@ -291,7 +291,7 @@ export function toExpress(path: Route.RoutePattern): string[] {
|
|
|
291
291
|
* - `[[...param]]` → `/`, `/*`
|
|
292
292
|
* - `pk_[id]` → `pk_:id`
|
|
293
293
|
*/
|
|
294
|
-
export function toEffect(path:
|
|
294
|
+
export function toEffect(path: RouterPattern): string[] {
|
|
295
295
|
return buildPaths(parse(path), colonParamSegment, "/*")
|
|
296
296
|
}
|
|
297
297
|
|
|
@@ -304,7 +304,7 @@ export function toEffect(path: Route.RoutePattern): string[] {
|
|
|
304
304
|
* - `[[...param]]` → `:param*`
|
|
305
305
|
* - `pk_[id]` → `pk_:id`
|
|
306
306
|
*/
|
|
307
|
-
export function toURLPattern(path:
|
|
307
|
+
export function toURLPattern(path: RouterPattern): string[] {
|
|
308
308
|
const segments = parse(path)
|
|
309
309
|
const joined = segments
|
|
310
310
|
.map((segment) => {
|
|
@@ -332,7 +332,7 @@ export function toURLPattern(path: Route.RoutePattern): string[] {
|
|
|
332
332
|
* - `[[...param]]` → `/`, `$`
|
|
333
333
|
* - `pk_[id]` → (not supported, emits `pk_$id`)
|
|
334
334
|
*/
|
|
335
|
-
export function toRemix(path:
|
|
335
|
+
export function toRemix(path: RouterPattern): string[] {
|
|
336
336
|
const segments = parse(path)
|
|
337
337
|
const optionalRestIndex = segments.findIndex(
|
|
338
338
|
(s) => s._tag === "RestSegment" && s.optional,
|
|
@@ -377,7 +377,7 @@ export function toRemix(path: Route.RoutePattern): string[] {
|
|
|
377
377
|
* - `[[...param]]` → `/`, `/*` (two routes)
|
|
378
378
|
* - `pk_[id]` → `pk_:id`
|
|
379
379
|
*/
|
|
380
|
-
export function toBun(path:
|
|
380
|
+
export function toBun(path: RouterPattern): string[] {
|
|
381
381
|
const segments = parse(path)
|
|
382
382
|
|
|
383
383
|
const optionalIndex = segments.findIndex(
|
package/src/Start.ts
CHANGED
|
@@ -8,7 +8,7 @@ import * as Function from "effect/Function"
|
|
|
8
8
|
import * as Layer from "effect/Layer"
|
|
9
9
|
import * as BunHttpServer from "./bun/BunHttpServer.ts"
|
|
10
10
|
import * as BunRuntime from "./bun/BunRuntime.ts"
|
|
11
|
-
import * as NodeFileSystem from "./
|
|
11
|
+
import * as NodeFileSystem from "./node/FileSystem.ts"
|
|
12
12
|
import * as StartApp from "./StartApp.ts"
|
|
13
13
|
|
|
14
14
|
export function layer<
|
|
@@ -45,13 +45,13 @@ export function serve<ROut, E>(
|
|
|
45
45
|
)
|
|
46
46
|
|
|
47
47
|
return Function.pipe(
|
|
48
|
-
BunHttpServer.
|
|
48
|
+
BunHttpServer.layerAuto(),
|
|
49
49
|
HttpServer.withLogAddress,
|
|
50
50
|
Layer.provide(appLayer),
|
|
51
51
|
Layer.provide([
|
|
52
52
|
FetchHttpClient.layer,
|
|
53
53
|
HttpRouter.Default.Live,
|
|
54
|
-
BunHttpServer.
|
|
54
|
+
BunHttpServer.layer(),
|
|
55
55
|
NodeFileSystem.layer,
|
|
56
56
|
StartApp.layer(),
|
|
57
57
|
]),
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export type PathTuple = ReadonlyArray<
|
|
2
|
+
string | [string, string?, string?] | [[string]]
|
|
3
|
+
>
|
|
4
|
+
|
|
5
|
+
export function format(tuple: PathTuple): `/${string}` {
|
|
6
|
+
return "/" + tuple
|
|
7
|
+
.map((el) => {
|
|
8
|
+
if (typeof el === "string") return el
|
|
9
|
+
if (Array.isArray(el[0])) return "[[" + el[0][0] + "]]"
|
|
10
|
+
const [name, suffix, prefix] = el
|
|
11
|
+
return (prefix ?? "") + "[" + name + "]" + (suffix ?? "")
|
|
12
|
+
})
|
|
13
|
+
.join("/") as `/${string}`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function toColon(tuple: PathTuple): string {
|
|
17
|
+
return "/" + tuple
|
|
18
|
+
.map((el) => {
|
|
19
|
+
if (typeof el === "string") return el
|
|
20
|
+
if (Array.isArray(el[0])) return "*"
|
|
21
|
+
const [name, suffix, prefix] = el
|
|
22
|
+
return (prefix ?? "") + ":" + name + (suffix ?? "")
|
|
23
|
+
})
|
|
24
|
+
.join("/")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const toHono = toColon
|
|
28
|
+
|
|
29
|
+
export function toExpress(tuple: PathTuple): string {
|
|
30
|
+
return "/" + tuple
|
|
31
|
+
.map((el) => {
|
|
32
|
+
if (typeof el === "string") return el
|
|
33
|
+
if (Array.isArray(el[0])) return "*" + el[0][0]
|
|
34
|
+
const [name, suffix, prefix] = el
|
|
35
|
+
return (prefix ?? "") + ":" + name + (suffix ?? "")
|
|
36
|
+
})
|
|
37
|
+
.join("/")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const toEffect = toColon
|
|
41
|
+
|
|
42
|
+
export function toURLPattern(tuple: PathTuple): string {
|
|
43
|
+
return "/" + tuple
|
|
44
|
+
.map((el) => {
|
|
45
|
+
if (typeof el === "string") return el
|
|
46
|
+
if (Array.isArray(el[0])) return ":" + el[0][0] + "+"
|
|
47
|
+
const [name, suffix, prefix] = el
|
|
48
|
+
return (prefix ?? "") + ":" + name + (suffix ?? "")
|
|
49
|
+
})
|
|
50
|
+
.join("/")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function toRemix(tuple: PathTuple): string {
|
|
54
|
+
return "/" + tuple
|
|
55
|
+
.map((el) => {
|
|
56
|
+
if (typeof el === "string") return el
|
|
57
|
+
if (Array.isArray(el[0])) return "$"
|
|
58
|
+
const [name, suffix, prefix] = el
|
|
59
|
+
return (prefix ?? "") + "$" + name + (suffix ?? "")
|
|
60
|
+
})
|
|
61
|
+
.join("/")
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const toBun = toColon
|
package/src/Values.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type JsonPrimitives =
|
|
2
|
+
| string
|
|
3
|
+
| number
|
|
4
|
+
| boolean
|
|
5
|
+
| null
|
|
6
|
+
|
|
7
|
+
export type Json =
|
|
8
|
+
| JsonPrimitives
|
|
9
|
+
| Json[]
|
|
10
|
+
| {
|
|
11
|
+
[key: string]:
|
|
12
|
+
| Json
|
|
13
|
+
// undefined won't be included in JSON objects but this will allow
|
|
14
|
+
// to use Json type in functions that return object of multiple shapes
|
|
15
|
+
| undefined
|
|
16
|
+
}
|
|
@@ -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
|
+
})
|