effect-start 0.11.1 → 0.13.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.
@@ -0,0 +1,38 @@
1
+ import * as t from "bun:test"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Logger from "effect/Logger"
4
+ import * as Ref from "effect/Ref"
5
+ import * as TestLogger from "./TestLogger.ts"
6
+
7
+ t.it("TestLogger captures log messages", () =>
8
+ Effect
9
+ .gen(function*() {
10
+ const logger = yield* TestLogger.TestLogger
11
+
12
+ // Log some messages
13
+ yield* Effect.logError("This is an error")
14
+ yield* Effect.logWarning("This is a warning")
15
+ yield* Effect.logInfo("This is info")
16
+
17
+ // Read captured messages
18
+ const messages = yield* Ref.get(logger.messages)
19
+
20
+ t.expect(messages).toHaveLength(3)
21
+ t.expect(messages[0]).toContain("[Error]")
22
+ t.expect(messages[0]).toContain("This is an error")
23
+ t.expect(messages[1]).toContain("[Warning]")
24
+ t.expect(messages[1]).toContain("This is a warning")
25
+ t.expect(messages[2]).toContain("[Info]")
26
+ t.expect(messages[2]).toContain("This is info")
27
+ })
28
+ .pipe(Effect.provide(TestLogger.layer()), Effect.runPromise))
29
+
30
+ t.it("TestLogger starts with empty messages", () =>
31
+ Effect
32
+ .gen(function*() {
33
+ const logger = yield* TestLogger.TestLogger
34
+ const messages = yield* Ref.get(logger.messages)
35
+
36
+ t.expect(messages).toHaveLength(0)
37
+ })
38
+ .pipe(Effect.provide(TestLogger.layer()), Effect.runPromise))
@@ -0,0 +1,56 @@
1
+ import * as Context from "effect/Context"
2
+ import * as Effect from "effect/Effect"
3
+ import * as FiberRef from "effect/FiberRef"
4
+ import * as HashSet from "effect/HashSet"
5
+ import * as Layer from "effect/Layer"
6
+ import * as Logger from "effect/Logger"
7
+ import * as Ref from "effect/Ref"
8
+
9
+ export type TestLoggerContext = {
10
+ messages: Ref.Ref<Array<string>>
11
+ }
12
+
13
+ export class TestLogger extends Context.Tag("effect-start/TestLogger")<
14
+ TestLogger,
15
+ TestLoggerContext
16
+ >() {}
17
+
18
+ export function layer(): Layer.Layer<TestLogger> {
19
+ return Layer.effect(
20
+ TestLogger,
21
+ Effect.gen(function*() {
22
+ const messages = yield* Ref.make<Array<string>>([])
23
+
24
+ const customLogger = Logger.make(({ message, logLevel }) => {
25
+ Effect.runSync(
26
+ Ref.update(
27
+ messages,
28
+ (msgs) => [...msgs, `[${logLevel._tag}] ${String(message)}`],
29
+ ),
30
+ )
31
+ })
32
+
33
+ yield* FiberRef.update(
34
+ FiberRef.currentLoggers,
35
+ (loggers) =>
36
+ HashSet.add(
37
+ HashSet.remove(loggers, Logger.defaultLogger),
38
+ customLogger,
39
+ ),
40
+ )
41
+
42
+ return {
43
+ messages,
44
+ }
45
+ }),
46
+ )
47
+ }
48
+
49
+ export const messages: Effect.Effect<
50
+ Array<string>,
51
+ never,
52
+ TestLogger
53
+ > = Effect.gen(function*() {
54
+ const logger = yield* TestLogger
55
+ return yield* Ref.get(logger.messages)
56
+ })
@@ -13,8 +13,8 @@ import * as FiberSet from "effect/FiberSet"
13
13
  import * as Layer from "effect/Layer"
14
14
  import * as Option from "effect/Option"
15
15
  import type * as Scope from "effect/Scope"
16
+ import * as FileRouter from "../FileRouter.ts"
16
17
  import * as Random from "../Random.ts"
17
- import * as Router from "../Router.ts"
18
18
  import EmptyHTML from "./_empty.html"
19
19
  import {
20
20
  makeResponse,
@@ -42,7 +42,7 @@ interface ServeOptions {
42
42
  readonly development?: boolean
43
43
  }
44
44
 
45
- export type BunServer = {
45
+ export type BunHttpServer = {
46
46
  readonly server: Bun.Server<WebSocketContext>
47
47
  readonly addRoutes: (routes: BunRoute.BunRoutes) => void
48
48
  // TODO: we probably don't want to expose these methods publicly
@@ -50,14 +50,14 @@ export type BunServer = {
50
50
  readonly popHandler: () => void
51
51
  }
52
52
 
53
- export const BunServer = Context.GenericTag<BunServer>(
53
+ export const BunHttpServer = Context.GenericTag<BunHttpServer>(
54
54
  "effect-start/BunServer",
55
55
  )
56
56
 
57
57
  export const make = (
58
58
  options: ServeOptions,
59
59
  ): Effect.Effect<
60
- BunServer,
60
+ BunHttpServer,
61
61
  never,
62
62
  Scope.Scope
63
63
  > =>
@@ -138,7 +138,7 @@ export const make = (
138
138
  })
139
139
  }
140
140
 
141
- return BunServer.of({
141
+ return BunHttpServer.of({
142
142
  server,
143
143
  pushHandler(fetch) {
144
144
  handlerStack.push(fetch)
@@ -160,14 +160,15 @@ export const make = (
160
160
 
161
161
  export const layer = (
162
162
  options?: ServeOptions,
163
- ): Layer.Layer<BunServer> => Layer.scoped(BunServer, make(options ?? {}))
163
+ ): Layer.Layer<BunHttpServer> =>
164
+ Layer.scoped(BunHttpServer, make(options ?? {}))
164
165
 
165
166
  export const makeHttpServer: Effect.Effect<
166
167
  HttpServer.HttpServer,
167
168
  never,
168
- Scope.Scope | BunServer
169
+ Scope.Scope | BunHttpServer
169
170
  > = Effect.gen(function*() {
170
- const bunServer = yield* BunServer
171
+ const bunServer = yield* BunHttpServer
171
172
 
172
173
  return HttpServer.make({
173
174
  address: {
@@ -229,20 +230,26 @@ export const makeHttpServer: Effect.Effect<
229
230
 
230
231
  export const layerServer = (
231
232
  options?: ServeOptions,
232
- ): Layer.Layer<HttpServer.HttpServer | BunServer> =>
233
+ ): Layer.Layer<HttpServer.HttpServer | BunHttpServer> =>
233
234
  Layer.provideMerge(
234
235
  Layer.scoped(HttpServer.HttpServer, makeHttpServer),
235
236
  layer(options ?? {}),
236
237
  )
237
238
 
238
- export function layerRoutes() {
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() {
239
246
  return Layer.effectDiscard(
240
247
  Effect.gen(function*() {
241
- const bunServer = yield* BunServer
242
- const routerContext = yield* Effect.serviceOption(Router.Router)
248
+ const bunServer = yield* BunHttpServer
249
+ const manifest = yield* Effect.serviceOption(FileRouter.FileRouter)
243
250
 
244
- if (Option.isSome(routerContext)) {
245
- const router = yield* Router.fromManifest(routerContext.value)
251
+ if (Option.isSome(manifest)) {
252
+ const router = yield* FileRouter.fromManifest(manifest.value)
246
253
  const bunRoutes = yield* BunRoute.routesFromRouter(router)
247
254
  bunServer.addRoutes(bunRoutes)
248
255
  }
@@ -4,12 +4,12 @@ import type { HTMLBundle } from "bun"
4
4
  import * as t from "bun:test"
5
5
  import * as Effect from "effect/Effect"
6
6
  import * as Layer from "effect/Layer"
7
+ import * as assert from "node:assert"
7
8
  import * as Route from "../Route.ts"
8
9
  import * as Router from "../Router.ts"
9
10
  import * as BunHttpServer from "./BunHttpServer.ts"
10
11
  import * as BunRoute from "./BunRoute.ts"
11
12
 
12
-
13
13
  t.describe(`${BunRoute.validateBunPattern.name}`, () => {
14
14
  t.test("allows exact paths", () => {
15
15
  const result = BunRoute.validateBunPattern("/users")
@@ -28,35 +28,27 @@ t.describe(`${BunRoute.validateBunPattern.name}`, () => {
28
28
 
29
29
  t.test("rejects prefixed params", () => {
30
30
  const result = BunRoute.validateBunPattern("/users/pk_[id]")
31
- t.expect(result._tag).toBe("Some")
32
- if (result._tag === "Some") {
33
- t.expect(result.value.reason).toBe("UnsupportedPattern")
34
- t.expect(result.value.pattern).toBe("/users/pk_[id]")
35
- }
31
+ assert.strictEqual(result._tag, "Some")
32
+ t.expect(result.value.reason).toBe("UnsupportedPattern")
33
+ t.expect(result.value.pattern).toBe("/users/pk_[id]")
36
34
  })
37
35
 
38
36
  t.test("rejects suffixed params", () => {
39
37
  const result = BunRoute.validateBunPattern("/users/[id]_details")
40
- t.expect(result._tag).toBe("Some")
41
- if (result._tag === "Some") {
42
- t.expect(result.value.reason).toBe("UnsupportedPattern")
43
- }
38
+ assert.strictEqual(result._tag, "Some")
39
+ t.expect(result.value.reason).toBe("UnsupportedPattern")
44
40
  })
45
41
 
46
42
  t.test("rejects dot suffix on params", () => {
47
43
  const result = BunRoute.validateBunPattern("/api/[id].json")
48
- t.expect(result._tag).toBe("Some")
49
- if (result._tag === "Some") {
50
- t.expect(result.value.reason).toBe("UnsupportedPattern")
51
- }
44
+ assert.strictEqual(result._tag, "Some")
45
+ t.expect(result.value.reason).toBe("UnsupportedPattern")
52
46
  })
53
47
 
54
48
  t.test("rejects tilde suffix on params", () => {
55
49
  const result = BunRoute.validateBunPattern("/api/[id]~test")
56
- t.expect(result._tag).toBe("Some")
57
- if (result._tag === "Some") {
58
- t.expect(result.value.reason).toBe("UnsupportedPattern")
59
- }
50
+ assert.strictEqual(result._tag, "Some")
51
+ t.expect(result.value.reason).toBe("UnsupportedPattern")
60
52
  })
61
53
 
62
54
  t.test("allows optional params (implemented via two patterns)", () => {
@@ -83,34 +75,11 @@ t.describe(`${BunRoute.routesFromRouter.name}`, () => {
83
75
  ),
84
76
  )
85
77
 
86
- t.expect(result._tag).toBe("Left")
87
- if (result._tag === "Left") {
88
- t.expect(result.left._tag).toBe("RouterError")
89
- t.expect(result.left.reason).toBe("UnsupportedPattern")
90
- }
78
+ assert.strictEqual(result._tag, "Left")
79
+ t.expect(result.left._tag).toBe("RouterError")
80
+ t.expect(result.left.reason).toBe("UnsupportedPattern")
91
81
  })
92
82
 
93
- t.it(
94
- "converts text route to fetch handler",
95
- () =>
96
- Effect.runPromise(
97
- Effect
98
- .gen(function*() {
99
- const bunServer = yield* BunHttpServer.BunServer
100
- return 23
101
- })
102
- .pipe(
103
- Effect.provide(
104
- Layer.mergeAll(
105
- BunHttpServer.layer({
106
- port: 0,
107
- }),
108
- ),
109
- ),
110
- ),
111
- ),
112
- )
113
-
114
83
  t.test("converts text route to fetch handler", async () => {
115
84
  const fetch = await makeFetch(
116
85
  Router.mount("/hello", Route.text("Hello World")),
@@ -408,7 +377,7 @@ t.describe("BunRoute placeholder replacement", () => {
408
377
  await Effect.runPromise(
409
378
  Effect
410
379
  .gen(function*() {
411
- const bunServer = yield* BunHttpServer.BunServer
380
+ const bunServer = yield* BunHttpServer.BunHttpServer
412
381
  const routes = yield* BunRoute.routesFromRouter(router)
413
382
  bunServer.addRoutes(routes)
414
383
 
@@ -440,7 +409,7 @@ t.describe("BunRoute placeholder replacement", () => {
440
409
  await Effect.runPromise(
441
410
  Effect
442
411
  .gen(function*() {
443
- const bunServer = yield* BunHttpServer.BunServer
412
+ const bunServer = yield* BunHttpServer.BunHttpServer
444
413
  const routes = yield* BunRoute.routesFromRouter(router)
445
414
  bunServer.addRoutes(routes)
446
415
 
@@ -465,8 +434,6 @@ t.describe("BunRoute placeholder replacement", () => {
465
434
  })
466
435
  })
467
436
 
468
-
469
-
470
437
  type FetchFn = (path: string, init?: { method?: string }) => Promise<Response>
471
438
 
472
439
  type HandlerFn = (
@@ -474,9 +441,8 @@ type HandlerFn = (
474
441
  server: unknown,
475
442
  ) => Response | Promise<Response>
476
443
 
477
-
478
444
  async function makeBunRoutes(
479
- router: Router.RouterBuilder.Any,
445
+ router: Router.Router.Any,
480
446
  ): Promise<BunRoute.BunRoutes> {
481
447
  return Effect.runPromise(
482
448
  BunRoute.routesFromRouter(router).pipe(
@@ -485,7 +451,7 @@ async function makeBunRoutes(
485
451
  )
486
452
  }
487
453
 
488
- async function makeFetch(router: Router.RouterBuilder.Any): Promise<FetchFn> {
454
+ async function makeFetch(router: Router.Router.Any): Promise<FetchFn> {
489
455
  const routes = await makeBunRoutes(router)
490
456
  const mockServer = {} as import("bun").Server<unknown>
491
457
 
@@ -40,7 +40,7 @@ export function html(
40
40
  const handler: Route.RouteHandler<
41
41
  HttpServerResponse.HttpServerResponse,
42
42
  Router.RouterError,
43
- BunHttpServer.BunServer
43
+ BunHttpServer.BunHttpServer
44
44
  > = (context) =>
45
45
  Effect.gen(function*() {
46
46
  const originalRequest = context.request.source as Request
@@ -58,7 +58,8 @@ export function html(
58
58
  )
59
59
  }
60
60
 
61
- const bunServer = yield* BunHttpServer.BunServer
61
+ const bunServer = yield* BunHttpServer.BunHttpServer
62
+
62
63
  const internalPath = `${internalPathPrefix}${context.url.pathname}`
63
64
  const internalUrl = new URL(internalPath, bunServer.server.url)
64
65
 
@@ -181,61 +182,6 @@ function makeHandler(routes: Route.Route.Default[]) {
181
182
  })
182
183
  }
183
184
 
184
- /**
185
- * Finds BunRoutes in the Router and returns
186
- * a mapping of paths to their bundles that can be passed
187
- * to Bun's `serve` function.
188
- */
189
- export function bundlesFromRouter(
190
- router: Router.RouterContext,
191
- ): Effect.Effect<Record<string, Bun.HTMLBundle>> {
192
- return Function.pipe(
193
- Effect.forEach(
194
- router.routes,
195
- (mod) =>
196
- Effect.promise(() =>
197
- mod.load().then((m) => ({ path: mod.path, exported: m.default }))
198
- ),
199
- ),
200
- Effect.map((modules) =>
201
- modules.flatMap(({ path, exported }) => {
202
- if (Route.isRouteSet(exported)) {
203
- return [...exported.set]
204
- .filter(isBunRoute)
205
- .map((route) =>
206
- [
207
- path,
208
- route,
209
- ] as const
210
- )
211
- }
212
-
213
- return []
214
- })
215
- ),
216
- Effect.flatMap((bunRoutes) =>
217
- Effect.forEach(
218
- bunRoutes,
219
- ([path, route]) =>
220
- Effect.promise(() =>
221
- route.load().then((bundle) => {
222
- const httpPath = RouterPattern.toBun(path)
223
-
224
- return [
225
- httpPath,
226
- bundle,
227
- ] as const
228
- })
229
- ),
230
- { concurrency: "unbounded" },
231
- )
232
- ),
233
- Effect.map((entries) =>
234
- Object.fromEntries(entries) as Record<string, Bun.HTMLBundle>
235
- ),
236
- )
237
- }
238
-
239
185
  type BunServerFetchHandler = (
240
186
  request: Request,
241
187
  server: Bun.Server<unknown>,
@@ -314,11 +260,11 @@ export function validateBunPattern(
314
260
  * the HtmlBundle natively on the internal route.
315
261
  */
316
262
  export function routesFromRouter(
317
- router: Router.RouterBuilder.Any,
318
- runtime?: Runtime.Runtime<BunHttpServer.BunServer>,
319
- ): Effect.Effect<BunRoutes, Router.RouterError, BunHttpServer.BunServer> {
263
+ router: Router.Router.Any,
264
+ runtime?: Runtime.Runtime<BunHttpServer.BunHttpServer>,
265
+ ): Effect.Effect<BunRoutes, Router.RouterError, BunHttpServer.BunHttpServer> {
320
266
  return Effect.gen(function*() {
321
- const rt = runtime ?? (yield* Effect.runtime<BunHttpServer.BunServer>())
267
+ const rt = runtime ?? (yield* Effect.runtime<BunHttpServer.BunHttpServer>())
322
268
  const result: BunRoutes = {}
323
269
 
324
270
  for (const entry of router.entries) {
@@ -15,13 +15,13 @@ t.describe("BunRoute proxy with Bun.serve", () => {
15
15
  await Effect.runPromise(
16
16
  Effect
17
17
  .gen(function*() {
18
- const bunServer = yield* BunHttpServer.BunServer
18
+ const bunServer = yield* BunHttpServer.BunHttpServer
19
+
19
20
  const routes = yield* BunRoute.routesFromRouter(router)
20
21
  bunServer.addRoutes(routes)
21
22
 
22
23
  const internalPath = Object.keys(routes).find((k) =>
23
24
  k.includes(".BunRoute-")
24
-
25
25
  )
26
26
  t.expect(internalPath).toBeDefined()
27
27
 
@@ -71,7 +71,7 @@ t.describe("BunRoute proxy with Bun.serve", () => {
71
71
  await Effect.runPromise(
72
72
  Effect
73
73
  .gen(function*() {
74
- const bunServer = yield* BunHttpServer.BunServer
74
+ const bunServer = yield* BunHttpServer.BunHttpServer
75
75
  const routes = yield* BunRoute.routesFromRouter(router)
76
76
  bunServer.addRoutes(routes)
77
77
 
@@ -119,7 +119,8 @@ t.describe("BunRoute proxy with Bun.serve", () => {
119
119
  await Effect.runPromise(
120
120
  Effect
121
121
  .gen(function*() {
122
- const bunServer = yield* BunHttpServer.BunServer
122
+ const bunServer = yield* BunHttpServer.BunHttpServer
123
+
123
124
  const routes = yield* BunRoute.routesFromRouter(router)
124
125
  bunServer.addRoutes(routes)
125
126
 
@@ -160,7 +161,7 @@ t.describe("BunRoute proxy with Bun.serve", () => {
160
161
  await Effect.runPromise(
161
162
  Effect
162
163
  .gen(function*() {
163
- const bunServer = yield* BunHttpServer.BunServer
164
+ const bunServer = yield* BunHttpServer.BunHttpServer
164
165
  const routes = yield* BunRoute.routesFromRouter(router)
165
166
  bunServer.addRoutes(routes)
166
167