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/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,60 @@ 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
|
+
)
|
|
176
|
+
|
|
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
|
+
}
|
|
165
194
|
|
|
166
|
-
|
|
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 routes: BunRoute.BunRoutes = {}
|
|
205
|
+
for (const [path, handler] of RouteHttp.walkHandles(tree)) {
|
|
206
|
+
for (const bunPath of PathPattern.toBun(path)) {
|
|
207
|
+
routes[bunPath] = handler
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// TODO: think how can we define routes upfront rather
|
|
212
|
+
// than add them after startup?
|
|
213
|
+
// now that we have Rooutes.Route thats should be possible
|
|
214
|
+
bunServer.addRoutes(routes)
|
|
215
|
+
}),
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const makeBunServer: Effect.Effect<
|
|
167
220
|
HttpServer.HttpServer,
|
|
168
221
|
never,
|
|
169
222
|
Scope.Scope | BunHttpServer
|
|
@@ -228,35 +281,6 @@ export const makeHttpServer: Effect.Effect<
|
|
|
228
281
|
})
|
|
229
282
|
})
|
|
230
283
|
|
|
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
284
|
const removeHost = (url: string) => {
|
|
261
285
|
if (url[0] === "/") {
|
|
262
286
|
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
|
)
|
package/src/bun/BunRoute.ts
CHANGED
|
@@ -1,49 +1,44 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-nocheck
|
|
2
2
|
import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
|
|
3
3
|
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
4
4
|
import type * as Bun from "bun"
|
|
5
5
|
import * as Array from "effect/Array"
|
|
6
6
|
import * as Effect from "effect/Effect"
|
|
7
|
-
import * as Function from "effect/Function"
|
|
8
7
|
import * as Option from "effect/Option"
|
|
9
8
|
import * as Predicate from "effect/Predicate"
|
|
10
|
-
import
|
|
11
|
-
import * as
|
|
12
|
-
import * as HttpUtils from "../HttpUtils.ts"
|
|
13
|
-
import * as HyperHtml from "../HyperHtml.ts"
|
|
9
|
+
import * as Hyper from "../hyper/Hyper.ts"
|
|
10
|
+
import * as HyperHtml from "../hyper/HyperHtml.ts"
|
|
14
11
|
import * as Random from "../Random.ts"
|
|
15
12
|
import * as Route from "../Route.ts"
|
|
16
|
-
import * as Router from "../Router.ts"
|
|
17
|
-
import * as RouteRender from "../RouteRender.ts"
|
|
18
13
|
import * as RouterPattern from "../RouterPattern.ts"
|
|
19
14
|
import * as BunHttpServer from "./BunHttpServer.ts"
|
|
20
15
|
|
|
21
|
-
const
|
|
16
|
+
const BunHandlerTypeId: unique symbol = Symbol.for("effect-start/BunHandler")
|
|
22
17
|
|
|
23
18
|
const INTERNAL_FETCH_HEADER = "x-effect-start-internal-fetch"
|
|
24
19
|
|
|
25
|
-
export type
|
|
26
|
-
& Route.
|
|
20
|
+
export type BunHandler =
|
|
21
|
+
& Route.RouteHandler<string, Router.RouterError, BunHttpServer.BunHttpServer>
|
|
27
22
|
& {
|
|
28
|
-
[
|
|
29
|
-
// Prefix because Bun.serve routes ignore everything after `*` in wildcard patterns.
|
|
30
|
-
// A suffix like `/*~internal` would match the same as `/*`, shadowing the internal route.
|
|
23
|
+
[BunHandlerTypeId]: typeof BunHandlerTypeId
|
|
31
24
|
internalPathPrefix: string
|
|
32
25
|
load: () => Promise<Bun.HTMLBundle>
|
|
33
26
|
}
|
|
34
27
|
|
|
35
|
-
export function
|
|
28
|
+
export function isBunHandler(input: unknown): input is BunHandler {
|
|
29
|
+
return typeof input === "function"
|
|
30
|
+
&& Predicate.hasProperty(input, BunHandlerTypeId)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function bundle(
|
|
36
34
|
load: () => Promise<Bun.HTMLBundle | { default: Bun.HTMLBundle }>,
|
|
37
|
-
):
|
|
35
|
+
): BunHandler {
|
|
38
36
|
const internalPathPrefix = `/.BunRoute-${Random.token(6)}`
|
|
39
37
|
|
|
40
|
-
const handler: Route.
|
|
41
|
-
HttpServerResponse.HttpServerResponse,
|
|
42
|
-
Router.RouterError,
|
|
43
|
-
BunHttpServer.BunHttpServer
|
|
44
|
-
> = (context) =>
|
|
38
|
+
const handler = (context: Route.RouteContext, next: Route.RouteNext) =>
|
|
45
39
|
Effect.gen(function*() {
|
|
46
|
-
const
|
|
40
|
+
const request = yield* HttpServerRequest.HttpServerRequest
|
|
41
|
+
const originalRequest = request.source as Request
|
|
47
42
|
|
|
48
43
|
if (
|
|
49
44
|
originalRequest.headers.get(INTERNAL_FETCH_HEADER) === "true"
|
|
@@ -91,13 +86,13 @@ export function html(
|
|
|
91
86
|
}),
|
|
92
87
|
})
|
|
93
88
|
|
|
94
|
-
const children = yield*
|
|
89
|
+
const children = yield* next()
|
|
95
90
|
let childrenHtml = ""
|
|
96
91
|
if (children != null) {
|
|
97
92
|
if (HttpServerResponse.isServerResponse(children)) {
|
|
98
93
|
const webResponse = HttpServerResponse.toWeb(children)
|
|
99
94
|
childrenHtml = yield* Effect.promise(() => webResponse.text())
|
|
100
|
-
} else if (
|
|
95
|
+
} else if (Hyper.isGenericJsxObject(children)) {
|
|
101
96
|
childrenHtml = HyperHtml.renderToString(children)
|
|
102
97
|
} else {
|
|
103
98
|
childrenHtml = String(children)
|
|
@@ -105,81 +100,19 @@ export function html(
|
|
|
105
100
|
}
|
|
106
101
|
|
|
107
102
|
html = html.replace(/%yield%/g, childrenHtml)
|
|
108
|
-
html = html.replace(
|
|
109
|
-
|
|
103
|
+
html = html.replace(
|
|
104
|
+
/%slots\.(\w+)%/g,
|
|
105
|
+
(_, name) => context.slots[name] ?? "",
|
|
106
|
+
)
|
|
110
107
|
|
|
111
|
-
return
|
|
112
|
-
.html(html)
|
|
108
|
+
return html
|
|
113
109
|
})
|
|
114
110
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
)
|
|
131
|
-
|
|
132
|
-
bunRoute.set.push(bunRoute)
|
|
133
|
-
|
|
134
|
-
return bunRoute
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function isBunRoute(input: unknown): input is BunRoute {
|
|
138
|
-
return Predicate.hasProperty(input, TypeId)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function makeHandler(routes: Route.Route.Default[]) {
|
|
142
|
-
return Effect.gen(function*() {
|
|
143
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
144
|
-
const accept = request.headers.accept ?? ""
|
|
145
|
-
|
|
146
|
-
let selectedRoute: Route.Route.Default | undefined
|
|
147
|
-
|
|
148
|
-
if (accept.includes("application/json")) {
|
|
149
|
-
selectedRoute = routes.find((r) => r.media === "application/json")
|
|
150
|
-
}
|
|
151
|
-
if (!selectedRoute && accept.includes("text/plain")) {
|
|
152
|
-
selectedRoute = routes.find((r) => r.media === "text/plain")
|
|
153
|
-
}
|
|
154
|
-
if (
|
|
155
|
-
!selectedRoute
|
|
156
|
-
&& (accept.includes("text/html")
|
|
157
|
-
|| accept.includes("*/*")
|
|
158
|
-
|| !accept)
|
|
159
|
-
) {
|
|
160
|
-
selectedRoute = routes.find((r) => r.media === "text/html")
|
|
161
|
-
}
|
|
162
|
-
if (!selectedRoute) {
|
|
163
|
-
selectedRoute = routes[0]
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (!selectedRoute) {
|
|
167
|
-
return HttpServerResponse.empty({ status: 406 })
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const context: Route.RouteContext = {
|
|
171
|
-
request,
|
|
172
|
-
get url() {
|
|
173
|
-
return HttpUtils.makeUrlFromRequest(request)
|
|
174
|
-
},
|
|
175
|
-
slots: {},
|
|
176
|
-
next: () => Effect.void,
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return yield* RouteRender.render(selectedRoute, context).pipe(
|
|
180
|
-
Effect.catchAllCause((cause) => HttpAppExtra.renderError(cause, accept)),
|
|
181
|
-
)
|
|
182
|
-
})
|
|
111
|
+
return Object.assign(handler, {
|
|
112
|
+
[BunHandlerTypeId]: BunHandlerTypeId,
|
|
113
|
+
internalPathPrefix,
|
|
114
|
+
load: () => load().then(mod => "default" in mod ? mod.default : mod),
|
|
115
|
+
}) as BunHandler
|
|
183
116
|
}
|
|
184
117
|
|
|
185
118
|
type BunServerFetchHandler = (
|
|
@@ -249,120 +182,6 @@ export function validateBunPattern(
|
|
|
249
182
|
return Option.none()
|
|
250
183
|
}
|
|
251
184
|
|
|
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
185
|
export const isHTMLBundle = (handle: any) => {
|
|
367
186
|
return (
|
|
368
187
|
typeof handle === "object"
|
|
@@ -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
|
|