effect-start 0.10.0 → 0.11.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 +5 -3
- package/src/FileHttpRouter.test.ts +4 -4
- package/src/FileHttpRouter.ts +6 -8
- package/src/FileRouter.ts +4 -6
- package/src/FileRouterCodegen.test.ts +2 -2
- package/src/HttpAppExtra.test.ts +84 -0
- package/src/HttpAppExtra.ts +399 -47
- package/src/Route.test.ts +59 -33
- package/src/Route.ts +59 -49
- package/src/RouteRender.ts +6 -4
- package/src/Router.test.ts +416 -0
- package/src/Router.ts +279 -0
- package/src/RouterPattern.test.ts +29 -3
- package/src/RouterPattern.ts +30 -5
- package/src/TestHttpClient.test.ts +29 -0
- package/src/TestHttpClient.ts +122 -73
- package/src/assets.d.ts +39 -0
- package/src/bun/BunHttpServer.test.ts +74 -0
- package/src/bun/BunHttpServer.ts +22 -9
- package/src/bun/BunRoute.test.ts +307 -134
- package/src/bun/BunRoute.ts +240 -139
- package/src/bun/BunRoute_bundles.test.ts +181 -181
- package/src/index.ts +14 -14
- package/src/middlewares/BasicAuthMiddleware.test.ts +74 -0
- package/src/middlewares/BasicAuthMiddleware.ts +36 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effect-start",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"exports": {
|
|
@@ -15,11 +15,13 @@
|
|
|
15
15
|
"./jsx-runtime": "./src/jsx-runtime.ts",
|
|
16
16
|
"./jsx-dev-runtime": "./src/jsx-runtime.ts",
|
|
17
17
|
"./hyper": "./src/hyper/index.ts",
|
|
18
|
-
"./x/*": "./src/x/*/index.ts"
|
|
18
|
+
"./x/*": "./src/x/*/index.ts",
|
|
19
|
+
"./middlewares/BasicAuthMiddleware": "./src/middlewares/BasicAuthMiddleware.ts",
|
|
20
|
+
"./assets.d.ts": "./src/assets.d.ts"
|
|
19
21
|
},
|
|
20
22
|
"scripts": {
|
|
21
23
|
"format": "bunx dprint fmt",
|
|
22
|
-
"test": "bun test src/",
|
|
24
|
+
"test": "bun test ./src/",
|
|
23
25
|
"deploy": "bunx npm publish --access public"
|
|
24
26
|
},
|
|
25
27
|
"dependencies": {
|
|
@@ -61,7 +61,7 @@ t.it("HTTP methods", () =>
|
|
|
61
61
|
.post(Route.html(Effect.succeed("POST")))
|
|
62
62
|
.put(Route.html(Effect.succeed("PUT")))
|
|
63
63
|
.patch(Route.html(Effect.succeed("PATCH")))
|
|
64
|
-
.
|
|
64
|
+
.delete(Route.html(Effect.succeed("DELETE")))
|
|
65
65
|
.options(Route.html(Effect.succeed("OPTIONS")))
|
|
66
66
|
.head(Route.html(Effect.succeed("HEAD"))),
|
|
67
67
|
}),
|
|
@@ -148,19 +148,19 @@ t.it(
|
|
|
148
148
|
{
|
|
149
149
|
path: "/api-v1",
|
|
150
150
|
load: async () => ({
|
|
151
|
-
default: Route.text(
|
|
151
|
+
default: Route.text("API v1"),
|
|
152
152
|
}),
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
path: "/files~backup",
|
|
156
156
|
load: async () => ({
|
|
157
|
-
default: Route.text(
|
|
157
|
+
default: Route.text("Backup files"),
|
|
158
158
|
}),
|
|
159
159
|
},
|
|
160
160
|
{
|
|
161
161
|
path: "/test-route~temp",
|
|
162
162
|
load: async () => ({
|
|
163
|
-
default: Route.post(Route.text(
|
|
163
|
+
default: Route.post(Route.text("Test route")),
|
|
164
164
|
}),
|
|
165
165
|
},
|
|
166
166
|
]
|
package/src/FileHttpRouter.ts
CHANGED
|
@@ -146,8 +146,6 @@ export function make<
|
|
|
146
146
|
let router: HttpRouter.HttpRouter<any, any> = HttpRouter.empty
|
|
147
147
|
|
|
148
148
|
for (const { path, routeSet, layers } of routesWithModules) {
|
|
149
|
-
const httpRouterPath = RouterPattern.toHttpPath(path)
|
|
150
|
-
|
|
151
149
|
for (const route of routeSet.set) {
|
|
152
150
|
const matchingLayerRoutes = findMatchingLayerRoutes(route, layers)
|
|
153
151
|
|
|
@@ -182,12 +180,12 @@ export function make<
|
|
|
182
180
|
finalHandler = middleware(finalHandler)
|
|
183
181
|
}
|
|
184
182
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
router
|
|
190
|
-
|
|
183
|
+
for (const pattern of RouterPattern.toEffect(path)) {
|
|
184
|
+
router = HttpRouter.route(route.method)(
|
|
185
|
+
pattern,
|
|
186
|
+
finalHandler as any,
|
|
187
|
+
)(router)
|
|
188
|
+
}
|
|
191
189
|
}
|
|
192
190
|
}
|
|
193
191
|
|
package/src/FileRouter.ts
CHANGED
|
@@ -63,7 +63,7 @@ export function parseRoute(
|
|
|
63
63
|
)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
//
|
|
66
|
+
// rest segments must be the last segment before the handle
|
|
67
67
|
const pathSegments = segs.slice(0, -1) // All segments except the handle
|
|
68
68
|
const restIndex = pathSegments.findIndex(seg => seg._tag === "RestSegment")
|
|
69
69
|
|
|
@@ -75,7 +75,7 @@ export function parseRoute(
|
|
|
75
75
|
)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
//
|
|
78
|
+
// all segments before the rest must be literal, param, or group
|
|
79
79
|
for (let i = 0; i < restIndex; i++) {
|
|
80
80
|
const seg = pathSegments[i]
|
|
81
81
|
if (
|
|
@@ -89,7 +89,7 @@ export function parseRoute(
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
} else {
|
|
92
|
-
// No rest:
|
|
92
|
+
// No rest: all path segments are literal, param, or group
|
|
93
93
|
for (const seg of pathSegments) {
|
|
94
94
|
if (
|
|
95
95
|
seg._tag !== "LiteralSegment"
|
|
@@ -103,8 +103,6 @@ export function parseRoute(
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
// Construct routePath from path segments (excluding groups)
|
|
107
|
-
// Groups like (admin) are stripped from the URL path
|
|
108
106
|
const routePathSegments = pathSegments.filter(
|
|
109
107
|
seg => seg._tag !== "GroupSegment",
|
|
110
108
|
)
|
|
@@ -166,7 +164,7 @@ export function layer(options: {
|
|
|
166
164
|
load: () => Promise<Router.RouterManifest>
|
|
167
165
|
path: string
|
|
168
166
|
}) {
|
|
169
|
-
return Layer.
|
|
167
|
+
return Layer.provide(
|
|
170
168
|
Layer.effect(
|
|
171
169
|
Router.Router,
|
|
172
170
|
Effect.promise(() => options.load()),
|
|
@@ -329,7 +329,7 @@ t.it("handles routes with hyphens and underscores in path segments", () => {
|
|
|
329
329
|
})
|
|
330
330
|
|
|
331
331
|
t.it("validateRouteModule returns true for valid modules", () => {
|
|
332
|
-
const validRoute = Route.text(
|
|
332
|
+
const validRoute = Route.text("Hello")
|
|
333
333
|
|
|
334
334
|
t
|
|
335
335
|
.expect(
|
|
@@ -348,7 +348,7 @@ t.it("validateRouteModule returns true for valid modules", () => {
|
|
|
348
348
|
t
|
|
349
349
|
.expect(
|
|
350
350
|
FileRouterCodegen.validateRouteModule({
|
|
351
|
-
default: Route.json(
|
|
351
|
+
default: Route.json({ message: "Hello" }),
|
|
352
352
|
}),
|
|
353
353
|
)
|
|
354
354
|
.toBe(true)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { HttpServerRequest } from "@effect/platform"
|
|
2
|
+
import { RouteNotFound } from "@effect/platform/HttpServerError"
|
|
3
|
+
import * as t from "bun:test"
|
|
4
|
+
import {
|
|
5
|
+
Effect,
|
|
6
|
+
Layer,
|
|
7
|
+
} from "effect"
|
|
8
|
+
import * as Cause from "effect/Cause"
|
|
9
|
+
import * as HttpAppExtra from "./HttpAppExtra.ts"
|
|
10
|
+
import { effectFn } from "./testing.ts"
|
|
11
|
+
|
|
12
|
+
const mockRequest = HttpServerRequest.HttpServerRequest.of({
|
|
13
|
+
url: "http://localhost:3000/test",
|
|
14
|
+
method: "GET",
|
|
15
|
+
headers: {
|
|
16
|
+
"accept": "application/json",
|
|
17
|
+
"user-agent": "test",
|
|
18
|
+
},
|
|
19
|
+
} as any)
|
|
20
|
+
|
|
21
|
+
const mockRequestLayer = Layer.succeed(
|
|
22
|
+
HttpServerRequest.HttpServerRequest,
|
|
23
|
+
mockRequest,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
const effect = effectFn(mockRequestLayer)
|
|
27
|
+
|
|
28
|
+
t.describe("renderError", () => {
|
|
29
|
+
const routeNotFoundCause = Cause.fail(
|
|
30
|
+
new RouteNotFound({ request: {} as any }),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
t.it("returns JSON for Accept: application/json", () =>
|
|
34
|
+
effect(function*() {
|
|
35
|
+
const response = yield* HttpAppExtra.renderError(
|
|
36
|
+
routeNotFoundCause,
|
|
37
|
+
"application/json",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
t.expect(response.status).toEqual(404)
|
|
41
|
+
t.expect(response.headers["content-type"]).toContain("application/json")
|
|
42
|
+
}))
|
|
43
|
+
|
|
44
|
+
t.it("returns HTML for Accept: text/html", () =>
|
|
45
|
+
effect(function*() {
|
|
46
|
+
const response = yield* HttpAppExtra.renderError(
|
|
47
|
+
routeNotFoundCause,
|
|
48
|
+
"text/html",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
t.expect(response.status).toEqual(404)
|
|
52
|
+
t.expect(response.headers["content-type"]).toContain("text/html")
|
|
53
|
+
}))
|
|
54
|
+
|
|
55
|
+
t.it("returns plain text for Accept: text/plain", () =>
|
|
56
|
+
effect(function*() {
|
|
57
|
+
const response = yield* HttpAppExtra.renderError(
|
|
58
|
+
routeNotFoundCause,
|
|
59
|
+
"text/plain",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
t.expect(response.status).toEqual(404)
|
|
63
|
+
t.expect(response.headers["content-type"]).toContain("text/plain")
|
|
64
|
+
}))
|
|
65
|
+
|
|
66
|
+
t.it("returns JSON by default (no Accept header)", () =>
|
|
67
|
+
effect(function*() {
|
|
68
|
+
const response = yield* HttpAppExtra.renderError(routeNotFoundCause, "")
|
|
69
|
+
|
|
70
|
+
t.expect(response.status).toEqual(404)
|
|
71
|
+
t.expect(response.headers["content-type"]).toContain("application/json")
|
|
72
|
+
}))
|
|
73
|
+
|
|
74
|
+
t.it("returns 500 for unexpected errors", () =>
|
|
75
|
+
effect(function*() {
|
|
76
|
+
const unexpectedCause = Cause.fail({ message: "Something went wrong" })
|
|
77
|
+
const response = yield* HttpAppExtra.renderError(
|
|
78
|
+
unexpectedCause,
|
|
79
|
+
"application/json",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
t.expect(response.status).toEqual(500)
|
|
83
|
+
}))
|
|
84
|
+
})
|