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
package/src/bun/BunRoute.ts
CHANGED
|
@@ -1,187 +1,169 @@
|
|
|
1
|
-
import * as HttpApp from "@effect/platform/HttpApp"
|
|
2
|
-
import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
|
|
3
|
-
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
4
1
|
import type * as Bun from "bun"
|
|
5
2
|
import * as Array from "effect/Array"
|
|
3
|
+
import * as Data from "effect/Data"
|
|
6
4
|
import * as Effect from "effect/Effect"
|
|
7
|
-
import * as Function from "effect/Function"
|
|
8
5
|
import * as Option from "effect/Option"
|
|
9
|
-
import * as
|
|
10
|
-
import
|
|
11
|
-
import * as
|
|
12
|
-
import * as HttpUtils from "../HttpUtils.ts"
|
|
13
|
-
import * as HyperHtml from "../HyperHtml.ts"
|
|
6
|
+
import * as Entity from "../Entity.ts"
|
|
7
|
+
import * as Hyper from "../hyper/Hyper.ts"
|
|
8
|
+
import * as HyperHtml from "../hyper/HyperHtml.ts"
|
|
14
9
|
import * as Random from "../Random.ts"
|
|
15
10
|
import * as Route from "../Route.ts"
|
|
16
|
-
import * as Router from "../Router.ts"
|
|
17
|
-
import * as RouteRender from "../RouteRender.ts"
|
|
18
11
|
import * as RouterPattern from "../RouterPattern.ts"
|
|
19
12
|
import * as BunHttpServer from "./BunHttpServer.ts"
|
|
20
13
|
|
|
21
|
-
const TypeId: unique symbol = Symbol.for("effect-start/BunRoute")
|
|
22
|
-
|
|
23
14
|
const INTERNAL_FETCH_HEADER = "x-effect-start-internal-fetch"
|
|
24
15
|
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// A suffix like `/*~internal` would match the same as `/*`, shadowing the internal route.
|
|
31
|
-
internalPathPrefix: string
|
|
32
|
-
load: () => Promise<Bun.HTMLBundle>
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function html(
|
|
36
|
-
load: () => Promise<Bun.HTMLBundle | { default: Bun.HTMLBundle }>,
|
|
37
|
-
): BunRoute {
|
|
38
|
-
const internalPathPrefix = `/.BunRoute-${Random.token(6)}`
|
|
39
|
-
|
|
40
|
-
const handler: Route.RouteHandler<
|
|
41
|
-
HttpServerResponse.HttpServerResponse,
|
|
42
|
-
Router.RouterError,
|
|
43
|
-
BunHttpServer.BunHttpServer
|
|
44
|
-
> = (context) =>
|
|
45
|
-
Effect.gen(function*() {
|
|
46
|
-
const originalRequest = context.request.source as Request
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
originalRequest.headers.get(INTERNAL_FETCH_HEADER) === "true"
|
|
50
|
-
) {
|
|
51
|
-
return yield* Effect.fail(
|
|
52
|
-
new Router.RouterError({
|
|
53
|
-
reason: "ProxyError",
|
|
54
|
-
pattern: context.url.pathname,
|
|
55
|
-
message:
|
|
56
|
-
"Request to internal Bun server was caught by BunRoute handler. This should not happen. Please report a bug.",
|
|
57
|
-
}),
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const bunServer = yield* BunHttpServer.BunHttpServer
|
|
62
|
-
|
|
63
|
-
const internalPath = `${internalPathPrefix}${context.url.pathname}`
|
|
64
|
-
const internalUrl = new URL(internalPath, bunServer.server.url)
|
|
65
|
-
|
|
66
|
-
const headers = new Headers(originalRequest.headers)
|
|
67
|
-
headers.set(INTERNAL_FETCH_HEADER, "true")
|
|
68
|
-
|
|
69
|
-
const proxyRequest = new Request(internalUrl, {
|
|
70
|
-
method: originalRequest.method,
|
|
71
|
-
headers,
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
const response = yield* Effect.tryPromise({
|
|
75
|
-
try: () => fetch(proxyRequest),
|
|
76
|
-
catch: (error) =>
|
|
77
|
-
new Router.RouterError({
|
|
78
|
-
reason: "ProxyError",
|
|
79
|
-
pattern: internalPath,
|
|
80
|
-
message: `Failed to fetch internal HTML bundle: ${String(error)}`,
|
|
81
|
-
}),
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
let html = yield* Effect.tryPromise({
|
|
85
|
-
try: () => response.text(),
|
|
86
|
-
catch: (error) =>
|
|
87
|
-
new Router.RouterError({
|
|
88
|
-
reason: "ProxyError",
|
|
89
|
-
pattern: internalPath,
|
|
90
|
-
message: String(error),
|
|
91
|
-
}),
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
const children = yield* context.next<Router.RouterError, never>()
|
|
95
|
-
let childrenHtml = ""
|
|
96
|
-
if (children != null) {
|
|
97
|
-
if (HttpServerResponse.isServerResponse(children)) {
|
|
98
|
-
const webResponse = HttpServerResponse.toWeb(children)
|
|
99
|
-
childrenHtml = yield* Effect.promise(() => webResponse.text())
|
|
100
|
-
} else if (Route.isGenericJsxObject(children)) {
|
|
101
|
-
childrenHtml = HyperHtml.renderToString(children)
|
|
102
|
-
} else {
|
|
103
|
-
childrenHtml = String(children)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
html = html.replace(/%yield%/g, childrenHtml)
|
|
108
|
-
html = html.replace(/%slots\.(\w+)%/g, (_, name) =>
|
|
109
|
-
context.slots[name] ?? "")
|
|
110
|
-
|
|
111
|
-
return HttpServerResponse
|
|
112
|
-
.html(html)
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
const route = Route.make({
|
|
116
|
-
method: "*",
|
|
117
|
-
media: "text/html",
|
|
118
|
-
handler,
|
|
119
|
-
schemas: {},
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
const bunRoute: BunRoute = Object.assign(
|
|
123
|
-
Object.create(route),
|
|
124
|
-
{
|
|
125
|
-
[TypeId]: TypeId,
|
|
126
|
-
internalPathPrefix,
|
|
127
|
-
load: () => load().then(mod => "default" in mod ? mod.default : mod),
|
|
128
|
-
set: [],
|
|
129
|
-
},
|
|
130
|
-
)
|
|
16
|
+
export class BunRouteError extends Data.TaggedError("BunRouteError")<{
|
|
17
|
+
reason: "ProxyError" | "UnsupportedPattern"
|
|
18
|
+
pattern: string
|
|
19
|
+
message: string
|
|
20
|
+
}> {}
|
|
131
21
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
22
|
+
export type BunDescriptors = {
|
|
23
|
+
bunPrefix: string
|
|
24
|
+
bunLoad: () => Promise<Bun.HTMLBundle>
|
|
135
25
|
}
|
|
136
26
|
|
|
137
|
-
export function
|
|
138
|
-
|
|
27
|
+
export function descriptors(
|
|
28
|
+
route: Route.Route.Route,
|
|
29
|
+
): BunDescriptors | undefined {
|
|
30
|
+
const descriptor = Route.descriptor(route) as Partial<BunDescriptors>
|
|
31
|
+
if (
|
|
32
|
+
typeof descriptor.bunPrefix === "string"
|
|
33
|
+
&& typeof descriptor.bunLoad === "function"
|
|
34
|
+
) {
|
|
35
|
+
return descriptor as BunDescriptors
|
|
36
|
+
}
|
|
37
|
+
return undefined
|
|
139
38
|
}
|
|
140
39
|
|
|
141
|
-
function
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
40
|
+
export function htmlBundle(
|
|
41
|
+
load: () => Promise<Bun.HTMLBundle | { default: Bun.HTMLBundle }>,
|
|
42
|
+
) {
|
|
43
|
+
const bunPrefix = `/.BunRoute-${Random.token(6)}`
|
|
44
|
+
const bunLoad = () => load().then(mod => "default" in mod ? mod.default : mod)
|
|
45
|
+
const descriptors = { bunPrefix, bunLoad, format: "html" as const }
|
|
46
|
+
|
|
47
|
+
return function<
|
|
48
|
+
D extends Route.RouteDescriptor.Any,
|
|
49
|
+
B extends {},
|
|
50
|
+
I extends Route.Route.Tuple,
|
|
51
|
+
>(
|
|
52
|
+
self: Route.RouteSet.RouteSet<D, B, I>,
|
|
53
|
+
): Route.RouteSet.RouteSet<
|
|
54
|
+
D,
|
|
55
|
+
B,
|
|
56
|
+
[
|
|
57
|
+
...I,
|
|
58
|
+
Route.Route.Route<
|
|
59
|
+
BunDescriptors & { format: "html" },
|
|
60
|
+
{ request: Request },
|
|
61
|
+
string,
|
|
62
|
+
BunRouteError,
|
|
63
|
+
BunHttpServer.BunHttpServer
|
|
64
|
+
>,
|
|
65
|
+
]
|
|
66
|
+
> {
|
|
67
|
+
const handler: Route.Route.Handler<
|
|
68
|
+
BunDescriptors & { format: "html" } & { request: Request },
|
|
69
|
+
string,
|
|
70
|
+
BunRouteError,
|
|
71
|
+
BunHttpServer.BunHttpServer
|
|
72
|
+
> = (context, next) =>
|
|
73
|
+
Effect.gen(function*() {
|
|
74
|
+
const originalRequest = context.request
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
originalRequest.headers.get(INTERNAL_FETCH_HEADER) === "true"
|
|
78
|
+
) {
|
|
79
|
+
const url = new URL(originalRequest.url)
|
|
80
|
+
return yield* Effect.fail(
|
|
81
|
+
new BunRouteError({
|
|
82
|
+
reason: "ProxyError",
|
|
83
|
+
pattern: url.pathname,
|
|
84
|
+
message:
|
|
85
|
+
"Request to internal Bun server was caught by BunRoute handler. This should not happen. Please report a bug.",
|
|
86
|
+
}),
|
|
87
|
+
)
|
|
88
|
+
}
|
|
147
89
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
90
|
+
const bunServer = yield* BunHttpServer.BunHttpServer
|
|
91
|
+
const url = new URL(originalRequest.url)
|
|
92
|
+
|
|
93
|
+
const internalPath = `${bunPrefix}${url.pathname}`
|
|
94
|
+
const internalUrl = new URL(internalPath, bunServer.server.url)
|
|
95
|
+
|
|
96
|
+
const headers = new Headers(originalRequest.headers)
|
|
97
|
+
headers.set(INTERNAL_FETCH_HEADER, "true")
|
|
98
|
+
|
|
99
|
+
const proxyRequest = new Request(internalUrl, {
|
|
100
|
+
method: originalRequest.method,
|
|
101
|
+
headers,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const response = yield* Effect.tryPromise({
|
|
105
|
+
try: () => fetch(proxyRequest),
|
|
106
|
+
catch: (error) =>
|
|
107
|
+
new BunRouteError({
|
|
108
|
+
reason: "ProxyError",
|
|
109
|
+
pattern: internalPath,
|
|
110
|
+
message: `Failed to fetch internal HTML bundle: ${String(error)}`,
|
|
111
|
+
}),
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
let html = yield* Effect.tryPromise({
|
|
115
|
+
try: () => response.text(),
|
|
116
|
+
catch: (error) =>
|
|
117
|
+
new BunRouteError({
|
|
118
|
+
reason: "ProxyError",
|
|
119
|
+
pattern: internalPath,
|
|
120
|
+
message: String(error),
|
|
121
|
+
}),
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const childEntity = yield* Entity.resolve(next(context))
|
|
125
|
+
const children = childEntity?.body ?? childEntity
|
|
126
|
+
|
|
127
|
+
let childrenHtml = ""
|
|
128
|
+
if (children != null) {
|
|
129
|
+
if ((children as unknown) instanceof Response) {
|
|
130
|
+
childrenHtml = yield* Effect.promise(() =>
|
|
131
|
+
(children as unknown as Response).text()
|
|
132
|
+
)
|
|
133
|
+
} else if (Hyper.isGenericJsxObject(children)) {
|
|
134
|
+
childrenHtml = HyperHtml.renderToString(children)
|
|
135
|
+
} else {
|
|
136
|
+
childrenHtml = String(children)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
165
139
|
|
|
166
|
-
|
|
167
|
-
return HttpServerResponse.empty({ status: 406 })
|
|
168
|
-
}
|
|
140
|
+
html = html.replace(/%children%/g, childrenHtml)
|
|
169
141
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
142
|
+
return Entity.make(html, {
|
|
143
|
+
status: response.status,
|
|
144
|
+
headers: {
|
|
145
|
+
"content-type": response.headers.get("content-type"),
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
})
|
|
178
149
|
|
|
179
|
-
|
|
180
|
-
|
|
150
|
+
const route = Route.make<
|
|
151
|
+
BunDescriptors & { format: "html" },
|
|
152
|
+
{ request: Request },
|
|
153
|
+
string,
|
|
154
|
+
BunRouteError,
|
|
155
|
+
BunHttpServer.BunHttpServer
|
|
156
|
+
>(handler, descriptors)
|
|
157
|
+
|
|
158
|
+
return Route.set(
|
|
159
|
+
[...Route.items(self), route] as any,
|
|
160
|
+
Route.descriptor(self),
|
|
181
161
|
)
|
|
182
|
-
}
|
|
162
|
+
}
|
|
183
163
|
}
|
|
184
164
|
|
|
165
|
+
|
|
166
|
+
|
|
185
167
|
type BunServerFetchHandler = (
|
|
186
168
|
request: Request,
|
|
187
169
|
server: Bun.Server<unknown>,
|
|
@@ -194,14 +176,6 @@ type BunServerRouteHandler =
|
|
|
194
176
|
|
|
195
177
|
export type BunRoutes = Record<string, BunServerRouteHandler>
|
|
196
178
|
|
|
197
|
-
type MethodHandlers = Partial<
|
|
198
|
-
Record<Bun.Serve.HTTPMethod, BunServerFetchHandler>
|
|
199
|
-
>
|
|
200
|
-
|
|
201
|
-
function isMethodHandlers(value: unknown): value is MethodHandlers {
|
|
202
|
-
return typeof value === "object" && value !== null && !("index" in value)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
179
|
/**
|
|
206
180
|
* Validates that a route pattern can be implemented with Bun.serve routes.
|
|
207
181
|
*
|
|
@@ -223,7 +197,7 @@ function isMethodHandlers(value: unknown): value is MethodHandlers {
|
|
|
223
197
|
|
|
224
198
|
export function validateBunPattern(
|
|
225
199
|
pattern: string,
|
|
226
|
-
): Option.Option<
|
|
200
|
+
): Option.Option<BunRouteError> {
|
|
227
201
|
const segments = RouterPattern.parse(pattern)
|
|
228
202
|
|
|
229
203
|
const unsupported = Array.findFirst(segments, (seg) => {
|
|
@@ -236,7 +210,7 @@ export function validateBunPattern(
|
|
|
236
210
|
|
|
237
211
|
if (Option.isSome(unsupported)) {
|
|
238
212
|
return Option.some(
|
|
239
|
-
new
|
|
213
|
+
new BunRouteError({
|
|
240
214
|
reason: "UnsupportedPattern",
|
|
241
215
|
pattern,
|
|
242
216
|
message:
|
|
@@ -249,121 +223,7 @@ export function validateBunPattern(
|
|
|
249
223
|
return Option.none()
|
|
250
224
|
}
|
|
251
225
|
|
|
252
|
-
|
|
253
|
-
* Converts a RouterBuilder into Bun-compatible routes passed to {@link Bun.serve}.
|
|
254
|
-
*
|
|
255
|
-
* For BunRoutes (HtmlBundle), creates two routes:
|
|
256
|
-
* - An internal route at `${path}~BunRoute-${nonce}:${path}` holding the actual HtmlBundle
|
|
257
|
-
* - A proxy route at the original path that forwards requests to the internal route
|
|
258
|
-
*
|
|
259
|
-
* This allows middleware to be attached to the proxy route while Bun handles
|
|
260
|
-
* the HtmlBundle natively on the internal route.
|
|
261
|
-
*/
|
|
262
|
-
export function routesFromRouter(
|
|
263
|
-
router: Router.Router.Any,
|
|
264
|
-
runtime?: Runtime.Runtime<BunHttpServer.BunHttpServer>,
|
|
265
|
-
): Effect.Effect<BunRoutes, Router.RouterError, BunHttpServer.BunHttpServer> {
|
|
266
|
-
return Effect.gen(function*() {
|
|
267
|
-
const rt = runtime ?? (yield* Effect.runtime<BunHttpServer.BunHttpServer>())
|
|
268
|
-
const result: BunRoutes = {}
|
|
269
|
-
|
|
270
|
-
for (const entry of router.entries) {
|
|
271
|
-
const { path, route: routeSet, layers } = entry
|
|
272
|
-
|
|
273
|
-
const validationError = validateBunPattern(path)
|
|
274
|
-
if (Option.isSome(validationError)) {
|
|
275
|
-
return yield* Effect.fail(validationError.value)
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
for (const route of routeSet.set) {
|
|
279
|
-
if (isBunRoute(route)) {
|
|
280
|
-
const bundle = yield* Effect.promise(() => route.load())
|
|
281
|
-
const bunPaths = RouterPattern.toBun(path)
|
|
282
|
-
for (const bunPath of bunPaths) {
|
|
283
|
-
const internalPath = `${route.internalPathPrefix}${bunPath}`
|
|
284
|
-
result[internalPath] = bundle
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
for (const layer of layers) {
|
|
290
|
-
for (const route of layer.set) {
|
|
291
|
-
if (isBunRoute(route)) {
|
|
292
|
-
const bundle = yield* Effect.promise(() => route.load())
|
|
293
|
-
const bunPaths = RouterPattern.toBun(path)
|
|
294
|
-
for (const bunPath of bunPaths) {
|
|
295
|
-
const internalPath = `${route.internalPathPrefix}${bunPath}`
|
|
296
|
-
result[internalPath] = bundle
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
for (const path of Object.keys(router.mounts)) {
|
|
304
|
-
const routeSet = router.mounts[path]
|
|
305
|
-
|
|
306
|
-
const validationError = validateBunPattern(path)
|
|
307
|
-
if (Option.isSome(validationError)) {
|
|
308
|
-
continue
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const httpPaths = RouterPattern.toBun(path as Route.RoutePattern)
|
|
312
|
-
|
|
313
|
-
const byMethod = new Map<Route.RouteMethod, Route.Route.Default[]>()
|
|
314
|
-
for (const route of routeSet.set) {
|
|
315
|
-
const existing = byMethod.get(route.method) ?? []
|
|
316
|
-
existing.push(route)
|
|
317
|
-
byMethod.set(route.method, existing)
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const entry = router.entries.find((e) => e.path === path)
|
|
321
|
-
const allMiddleware = (entry?.layers ?? [])
|
|
322
|
-
.map((layer) => layer.httpMiddleware)
|
|
323
|
-
.filter((m): m is Route.HttpMiddlewareFunction => m !== undefined)
|
|
324
|
-
|
|
325
|
-
for (const [method, routes] of byMethod) {
|
|
326
|
-
let httpApp: HttpApp.Default<any, any> = makeHandler(routes)
|
|
327
|
-
|
|
328
|
-
for (const middleware of allMiddleware) {
|
|
329
|
-
httpApp = middleware(httpApp)
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
const webHandler = HttpApp.toWebHandlerRuntime(rt)(httpApp)
|
|
333
|
-
const handler: BunServerFetchHandler = (request) => {
|
|
334
|
-
const url = new URL(request.url)
|
|
335
|
-
if (url.pathname.startsWith("/.BunRoute-")) {
|
|
336
|
-
return new Response(
|
|
337
|
-
"Internal routing error: BunRoute internal path was not matched. "
|
|
338
|
-
+ "This indicates the HTMLBundle route was not registered. Please report a bug.",
|
|
339
|
-
{ status: 500 },
|
|
340
|
-
)
|
|
341
|
-
}
|
|
342
|
-
return webHandler(request)
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
for (const httpPath of httpPaths) {
|
|
346
|
-
if (method === "*") {
|
|
347
|
-
if (!(httpPath in result)) {
|
|
348
|
-
result[httpPath] = handler
|
|
349
|
-
}
|
|
350
|
-
} else {
|
|
351
|
-
const existing = result[httpPath]
|
|
352
|
-
if (isMethodHandlers(existing)) {
|
|
353
|
-
existing[method] = handler
|
|
354
|
-
} else if (!(httpPath in result)) {
|
|
355
|
-
result[httpPath] = { [method]: handler }
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return result
|
|
363
|
-
})
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
export const isHTMLBundle = (handle: any) => {
|
|
226
|
+
export const isHtmlBundle = (handle: any) => {
|
|
367
227
|
return (
|
|
368
228
|
typeof handle === "object"
|
|
369
229
|
&& handle !== null
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import * as HttpRouter from "@effect/platform/HttpRouter"
|
|
2
2
|
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
3
|
-
import * as
|
|
3
|
+
import * as test from "bun:test"
|
|
4
4
|
import * as Effect from "effect/Effect"
|
|
5
5
|
import * as Layer from "effect/Layer"
|
|
6
|
-
import IndexHtml from "
|
|
7
|
-
import * as BunBundle from "
|
|
6
|
+
import IndexHtml from "../../static/react-dashboard.html" with { type: "file" }
|
|
7
|
+
import * as BunBundle from "../bun/BunBundle.ts"
|
|
8
|
+
import { effectFn } from "../testing"
|
|
9
|
+
import * as TestHttpClient from "../testing/TestHttpClient.ts"
|
|
8
10
|
import * as Bundle from "./Bundle.ts"
|
|
9
11
|
import * as BundleHttp from "./BundleHttp.ts"
|
|
10
|
-
import { effectFn } from "./testing"
|
|
11
|
-
import * as TestHttpClient from "./testing/TestHttpClient.ts"
|
|
12
12
|
|
|
13
13
|
const effect = effectFn(
|
|
14
14
|
Layer.effect(
|
|
@@ -17,7 +17,7 @@ const effect = effectFn(
|
|
|
17
17
|
),
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
test.it("entrypoint with specific uri", () =>
|
|
21
21
|
effect(function*() {
|
|
22
22
|
const App = HttpRouter.empty.pipe(
|
|
23
23
|
HttpRouter.get(
|
|
@@ -28,19 +28,15 @@ t.it("entrypoint with specific uri", () =>
|
|
|
28
28
|
const Client = TestHttpClient.make(App)
|
|
29
29
|
|
|
30
30
|
const dashboardRes = yield* Client.get("/react-dashboard")
|
|
31
|
-
|
|
32
|
-
.expect(
|
|
33
|
-
dashboardRes.status,
|
|
34
|
-
)
|
|
31
|
+
test
|
|
32
|
+
.expect(dashboardRes.status)
|
|
35
33
|
.toBe(200)
|
|
36
|
-
|
|
37
|
-
.expect(
|
|
38
|
-
yield* dashboardRes.text,
|
|
39
|
-
)
|
|
34
|
+
test
|
|
35
|
+
.expect(yield* dashboardRes.text)
|
|
40
36
|
.toStartWith("<!DOCTYPE html>")
|
|
41
37
|
}))
|
|
42
38
|
|
|
43
|
-
|
|
39
|
+
test.it("entrypoint without uri parameter", () =>
|
|
44
40
|
effect(function*() {
|
|
45
41
|
const App = HttpRouter.empty.pipe(
|
|
46
42
|
HttpRouter.get(
|
|
@@ -68,10 +64,8 @@ t.it("entrypoint without uri parameter", () =>
|
|
|
68
64
|
() => HttpServerResponse.empty({ status: 404 }),
|
|
69
65
|
),
|
|
70
66
|
)
|
|
71
|
-
|
|
72
|
-
.expect(
|
|
73
|
-
indexRes.status,
|
|
74
|
-
)
|
|
67
|
+
test
|
|
68
|
+
.expect(indexRes.status)
|
|
75
69
|
.toBe(404)
|
|
76
70
|
|
|
77
71
|
const indexPathRes = yield* Client.get("/index").pipe(
|
|
@@ -80,22 +74,16 @@ t.it("entrypoint without uri parameter", () =>
|
|
|
80
74
|
() => HttpServerResponse.empty({ status: 404 }),
|
|
81
75
|
),
|
|
82
76
|
)
|
|
83
|
-
|
|
84
|
-
.expect(
|
|
85
|
-
indexPathRes.status,
|
|
86
|
-
)
|
|
77
|
+
test
|
|
78
|
+
.expect(indexPathRes.status)
|
|
87
79
|
.toBe(404)
|
|
88
80
|
|
|
89
81
|
const dashboardRes = yield* Client.get("/react-dashboard")
|
|
90
|
-
|
|
91
|
-
.expect(
|
|
92
|
-
dashboardRes.status,
|
|
93
|
-
)
|
|
82
|
+
test
|
|
83
|
+
.expect(dashboardRes.status)
|
|
94
84
|
.toBe(200)
|
|
95
|
-
|
|
96
|
-
.expect(
|
|
97
|
-
yield* dashboardRes.text,
|
|
98
|
-
)
|
|
85
|
+
test
|
|
86
|
+
.expect(yield* dashboardRes.text)
|
|
99
87
|
.toStartWith("<!DOCTYPE html>")
|
|
100
88
|
|
|
101
89
|
const nonexistentRes = yield* Client.get("/nonexistent").pipe(
|
|
@@ -104,14 +92,12 @@ t.it("entrypoint without uri parameter", () =>
|
|
|
104
92
|
() => HttpServerResponse.empty({ status: 404 }),
|
|
105
93
|
),
|
|
106
94
|
)
|
|
107
|
-
|
|
108
|
-
.expect(
|
|
109
|
-
nonexistentRes.status,
|
|
110
|
-
)
|
|
95
|
+
test
|
|
96
|
+
.expect(nonexistentRes.status)
|
|
111
97
|
.toBe(404)
|
|
112
98
|
}))
|
|
113
99
|
|
|
114
|
-
|
|
100
|
+
test.it("withEntrypoints middleware", () =>
|
|
115
101
|
effect(function*() {
|
|
116
102
|
const fallbackApp = Effect.succeed(
|
|
117
103
|
HttpServerResponse.text("Fallback", { status: 404 }),
|
|
@@ -121,38 +107,26 @@ t.it("withEntrypoints middleware", () =>
|
|
|
121
107
|
const Client = TestHttpClient.make(App)
|
|
122
108
|
|
|
123
109
|
const rootRes = yield* Client.get("/")
|
|
124
|
-
|
|
125
|
-
.expect(
|
|
126
|
-
rootRes.status,
|
|
127
|
-
)
|
|
110
|
+
test
|
|
111
|
+
.expect(rootRes.status)
|
|
128
112
|
.toBe(404)
|
|
129
|
-
|
|
130
|
-
.expect(
|
|
131
|
-
yield* rootRes.text,
|
|
132
|
-
)
|
|
113
|
+
test
|
|
114
|
+
.expect(yield* rootRes.text)
|
|
133
115
|
.toBe("Fallback")
|
|
134
116
|
|
|
135
117
|
const dashboardRes = yield* Client.get("/react-dashboard")
|
|
136
|
-
|
|
137
|
-
.expect(
|
|
138
|
-
dashboardRes.status,
|
|
139
|
-
)
|
|
118
|
+
test
|
|
119
|
+
.expect(dashboardRes.status)
|
|
140
120
|
.toBe(200)
|
|
141
|
-
|
|
142
|
-
.expect(
|
|
143
|
-
yield* dashboardRes.text,
|
|
144
|
-
)
|
|
121
|
+
test
|
|
122
|
+
.expect(yield* dashboardRes.text)
|
|
145
123
|
.toStartWith("<!DOCTYPE html>")
|
|
146
124
|
|
|
147
125
|
const nonexistentRes = yield* Client.get("/nonexistent")
|
|
148
|
-
|
|
149
|
-
.expect(
|
|
150
|
-
nonexistentRes.status,
|
|
151
|
-
)
|
|
126
|
+
test
|
|
127
|
+
.expect(nonexistentRes.status)
|
|
152
128
|
.toBe(404)
|
|
153
|
-
|
|
154
|
-
.expect(
|
|
155
|
-
yield* nonexistentRes.text,
|
|
156
|
-
)
|
|
129
|
+
test
|
|
130
|
+
.expect(yield* nonexistentRes.text)
|
|
157
131
|
.toBe("Fallback")
|
|
158
132
|
}))
|
|
@@ -12,9 +12,8 @@ import * as Scope from "effect/Scope"
|
|
|
12
12
|
import * as Stream from "effect/Stream"
|
|
13
13
|
import * as NPath from "node:path"
|
|
14
14
|
import * as NUrl from "node:url"
|
|
15
|
+
import * as SseHttpResponse from "../experimental/SseHttpResponse.ts"
|
|
15
16
|
import * as Bundle from "./Bundle.ts"
|
|
16
|
-
import * as SseHttpResponse from "./experimental/SseHttpResponse.ts"
|
|
17
|
-
|
|
18
17
|
|
|
19
18
|
const DefaultBundleEndpoint = "/_bundle"
|
|
20
19
|
|