effect-start 0.15.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.
@@ -1,11 +1,9 @@
1
- // @ts-nocheck
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
5
  import * as Option from "effect/Option"
8
- import * as Predicate from "effect/Predicate"
6
+ import * as Entity from "../Entity.ts"
9
7
  import * as Hyper from "../hyper/Hyper.ts"
10
8
  import * as HyperHtml from "../hyper/HyperHtml.ts"
11
9
  import * as Random from "../Random.ts"
@@ -13,108 +11,159 @@ import * as Route from "../Route.ts"
13
11
  import * as RouterPattern from "../RouterPattern.ts"
14
12
  import * as BunHttpServer from "./BunHttpServer.ts"
15
13
 
16
- const BunHandlerTypeId: unique symbol = Symbol.for("effect-start/BunHandler")
17
-
18
14
  const INTERNAL_FETCH_HEADER = "x-effect-start-internal-fetch"
19
15
 
20
- export type BunHandler =
21
- & Route.RouteHandler<string, Router.RouterError, BunHttpServer.BunHttpServer>
22
- & {
23
- [BunHandlerTypeId]: typeof BunHandlerTypeId
24
- internalPathPrefix: string
25
- load: () => Promise<Bun.HTMLBundle>
26
- }
16
+ export class BunRouteError extends Data.TaggedError("BunRouteError")<{
17
+ reason: "ProxyError" | "UnsupportedPattern"
18
+ pattern: string
19
+ message: string
20
+ }> {}
27
21
 
28
- export function isBunHandler(input: unknown): input is BunHandler {
29
- return typeof input === "function"
30
- && Predicate.hasProperty(input, BunHandlerTypeId)
22
+ export type BunDescriptors = {
23
+ bunPrefix: string
24
+ bunLoad: () => Promise<Bun.HTMLBundle>
25
+ }
26
+
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
31
38
  }
32
39
 
33
- export function bundle(
40
+ export function htmlBundle(
34
41
  load: () => Promise<Bun.HTMLBundle | { default: Bun.HTMLBundle }>,
35
- ): BunHandler {
36
- const internalPathPrefix = `/.BunRoute-${Random.token(6)}`
37
-
38
- const handler = (context: Route.RouteContext, next: Route.RouteNext) =>
39
- Effect.gen(function*() {
40
- const request = yield* HttpServerRequest.HttpServerRequest
41
- const originalRequest = request.source as Request
42
-
43
- if (
44
- originalRequest.headers.get(INTERNAL_FETCH_HEADER) === "true"
45
- ) {
46
- return yield* Effect.fail(
47
- new Router.RouterError({
48
- reason: "ProxyError",
49
- pattern: context.url.pathname,
50
- message:
51
- "Request to internal Bun server was caught by BunRoute handler. This should not happen. Please report a bug.",
52
- }),
53
- )
54
- }
55
-
56
- const bunServer = yield* BunHttpServer.BunHttpServer
57
-
58
- const internalPath = `${internalPathPrefix}${context.url.pathname}`
59
- const internalUrl = new URL(internalPath, bunServer.server.url)
60
-
61
- const headers = new Headers(originalRequest.headers)
62
- headers.set(INTERNAL_FETCH_HEADER, "true")
63
-
64
- const proxyRequest = new Request(internalUrl, {
65
- method: originalRequest.method,
66
- headers,
67
- })
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
+ }
68
89
 
69
- const response = yield* Effect.tryPromise({
70
- try: () => fetch(proxyRequest),
71
- catch: (error) =>
72
- new Router.RouterError({
73
- reason: "ProxyError",
74
- pattern: internalPath,
75
- message: `Failed to fetch internal HTML bundle: ${String(error)}`,
76
- }),
77
- })
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
+ }
78
139
 
79
- let html = yield* Effect.tryPromise({
80
- try: () => response.text(),
81
- catch: (error) =>
82
- new Router.RouterError({
83
- reason: "ProxyError",
84
- pattern: internalPath,
85
- message: String(error),
86
- }),
140
+ html = html.replace(/%children%/g, childrenHtml)
141
+
142
+ return Entity.make(html, {
143
+ status: response.status,
144
+ headers: {
145
+ "content-type": response.headers.get("content-type"),
146
+ },
147
+ })
87
148
  })
88
149
 
89
- const children = yield* next()
90
- let childrenHtml = ""
91
- if (children != null) {
92
- if (HttpServerResponse.isServerResponse(children)) {
93
- const webResponse = HttpServerResponse.toWeb(children)
94
- childrenHtml = yield* Effect.promise(() => webResponse.text())
95
- } else if (Hyper.isGenericJsxObject(children)) {
96
- childrenHtml = HyperHtml.renderToString(children)
97
- } else {
98
- childrenHtml = String(children)
99
- }
100
- }
101
-
102
- html = html.replace(/%yield%/g, childrenHtml)
103
- html = html.replace(
104
- /%slots\.(\w+)%/g,
105
- (_, name) => context.slots[name] ?? "",
106
- )
107
-
108
- return html
109
- })
110
-
111
- return Object.assign(handler, {
112
- [BunHandlerTypeId]: BunHandlerTypeId,
113
- internalPathPrefix,
114
- load: () => load().then(mod => "default" in mod ? mod.default : mod),
115
- }) as BunHandler
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),
161
+ )
162
+ }
116
163
  }
117
164
 
165
+
166
+
118
167
  type BunServerFetchHandler = (
119
168
  request: Request,
120
169
  server: Bun.Server<unknown>,
@@ -127,14 +176,6 @@ type BunServerRouteHandler =
127
176
 
128
177
  export type BunRoutes = Record<string, BunServerRouteHandler>
129
178
 
130
- type MethodHandlers = Partial<
131
- Record<Bun.Serve.HTTPMethod, BunServerFetchHandler>
132
- >
133
-
134
- function isMethodHandlers(value: unknown): value is MethodHandlers {
135
- return typeof value === "object" && value !== null && !("index" in value)
136
- }
137
-
138
179
  /**
139
180
  * Validates that a route pattern can be implemented with Bun.serve routes.
140
181
  *
@@ -156,7 +197,7 @@ function isMethodHandlers(value: unknown): value is MethodHandlers {
156
197
 
157
198
  export function validateBunPattern(
158
199
  pattern: string,
159
- ): Option.Option<Router.RouterError> {
200
+ ): Option.Option<BunRouteError> {
160
201
  const segments = RouterPattern.parse(pattern)
161
202
 
162
203
  const unsupported = Array.findFirst(segments, (seg) => {
@@ -169,7 +210,7 @@ export function validateBunPattern(
169
210
 
170
211
  if (Option.isSome(unsupported)) {
171
212
  return Option.some(
172
- new Router.RouterError({
213
+ new BunRouteError({
173
214
  reason: "UnsupportedPattern",
174
215
  pattern,
175
216
  message:
@@ -182,7 +223,7 @@ export function validateBunPattern(
182
223
  return Option.none()
183
224
  }
184
225
 
185
- export const isHTMLBundle = (handle: any) => {
226
+ export const isHtmlBundle = (handle: any) => {
186
227
  return (
187
228
  typeof handle === "object"
188
229
  && handle !== null
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * as Bundle from "./bundler/Bundle.ts"
2
+ export * as Entity from "./Entity.ts"
2
3
  export * as FileRouter from "./FileRouter.ts"
3
4
  export * as Route from "./Route.ts"
4
5
  export * as Start from "./Start.ts"
@@ -1,6 +1,5 @@
1
1
  import * as test from "bun:test"
2
2
  import * as Effect from "effect/Effect"
3
- import * as Logger from "effect/Logger"
4
3
  import * as Ref from "effect/Ref"
5
4
  import * as TestLogger from "./TestLogger.ts"
6
5
 
@@ -9,12 +8,10 @@ test.it("TestLogger captures log messages", () =>
9
8
  .gen(function*() {
10
9
  const logger = yield* TestLogger.TestLogger
11
10
 
12
- // Log some messages
13
11
  yield* Effect.logError("This is an error")
14
12
  yield* Effect.logWarning("This is a warning")
15
13
  yield* Effect.logInfo("This is info")
16
14
 
17
- // Read captured messages
18
15
  const messages = yield* Ref.get(logger.messages)
19
16
 
20
17
  test
@@ -1,9 +1,11 @@
1
+ import * as Cause from "effect/Cause"
1
2
  import * as Context from "effect/Context"
2
3
  import * as Effect from "effect/Effect"
3
4
  import * as FiberRef from "effect/FiberRef"
4
5
  import * as HashSet from "effect/HashSet"
5
6
  import * as Layer from "effect/Layer"
6
7
  import * as Logger from "effect/Logger"
8
+ import * as MutableRef from "effect/MutableRef"
7
9
  import * as Ref from "effect/Ref"
8
10
 
9
11
  export type TestLoggerContext = {
@@ -20,13 +22,19 @@ export function layer(): Layer.Layer<TestLogger> {
20
22
  TestLogger,
21
23
  Effect.gen(function*() {
22
24
  const messages = yield* Ref.make<Array<string>>([])
25
+ const mutableRef = (messages as any).ref as MutableRef.MutableRef<
26
+ Array<string>
27
+ >
23
28
 
24
- const customLogger = Logger.make(({ message, logLevel }) => {
25
- Effect.runSync(
26
- Ref.update(
27
- messages,
28
- (msgs) => [...msgs, `[${logLevel._tag}] ${String(message)}`],
29
- ),
29
+ const customLogger = Logger.make(({ message, logLevel, cause }) => {
30
+ const causeStr = !Cause.isEmpty(cause)
31
+ ? ` ${Cause.pretty(cause)}`
32
+ : ""
33
+ MutableRef.update(
34
+ mutableRef,
35
+ (
36
+ msgs,
37
+ ) => [...msgs, `[${logLevel._tag}] ${String(message)}${causeStr}`],
30
38
  )
31
39
  })
32
40
 
@@ -39,9 +47,7 @@ export function layer(): Layer.Layer<TestLogger> {
39
47
  ),
40
48
  )
41
49
 
42
- return {
43
- messages,
44
- }
50
+ return { messages }
45
51
  }),
46
52
  )
47
53
  }