sukovanej / effect-http

Declarative HTTP API library for effect-ts
https://sukovanej.github.io/effect-http
MIT License
251 stars 20 forks source link

Running with Bun not working #659

Closed SamuelLHuber closed 2 months ago

SamuelLHuber commented 2 months ago

Hello, I can't get it to run with Bun.

I tried running the bun-server.ts from the samples, but got

188 | /** @internal */
189 | export const layerServer = (evaluate, options) => Layer.scoped(Server.HttpServer, make(evaluate, options));
190 | /** @internal */
191 | export const layer = (evaluate, options) => Layer.mergeAll(Layer.scoped(Server.HttpServer, make(evaluate, options)), internalPlatform.layer, Etag.layerWeak, NodeContext.layer);
192 | /** @internal */
193 | export const layerTest = /*#__PURE__*/Server.layerTestClient.pipe( /*#__PURE__*/Layer.provide(NodeHttpClient.layerWithoutAgent), /*#__PURE__*/Layer.provide( /*#__PURE__*/NodeHttpClient.makeAgentLayer({
                                                   ^
TypeError: undefined is not an object (evaluating 'Server.layerTestClient.pipe')

I'd love to run effect-http fully in Bun with the BunRuntime, but couldn't get that to work any way I tried.

@effect/platform-bun provides a Bun HTTPServer sample.

When I try to run it using

const HttpLive = HttpServer.serve(app).pipe(
  Layer.provide(BunHttpServer.layer({ port: 3000 }))
)

BunRuntime.runMain(Layer.launch(HttpLive))

where app is built using RouterBuilder.make

I run into a TypeError with SwaggerFiles

  Type 'SwaggerFiles' is not assignable to type 'never'.

once expanded to handle SwaggerFiles like the bun sample from effect-http-node

const HttpLive = HttpServer.serve(app).pipe(
  Layer.provide(BunHttpServer.layer({ port: 3000 })),
  Layer.provide(NodeSwaggerFiles.SwaggerFilesLive),
  Layer.provide(BunContext.layer),
  Layer.launch,
  Effect.scoped
)

BunRuntime.runMain(HttpLive)

I get the same error

188 | /** @internal */
189 | export const layerServer = (evaluate, options) => Layer.scoped(Server.HttpServer, make(evaluate, options));
190 | /** @internal */
191 | export const layer = (evaluate, options) => Layer.mergeAll(Layer.scoped(Server.HttpServer, make(evaluate, options)), internalPlatform.layer, Etag.layerWeak, NodeContext.layer);
192 | /** @internal */
193 | export const layerTest = /*#__PURE__*/Server.layerTestClient.pipe( /*#__PURE__*/Layer.provide(NodeHttpClient.layerWithoutAgent), /*#__PURE__*/Layer.provide( /*#__PURE__*/NodeHttpClient.makeAgentLayer({
                                                   ^
TypeError: undefined is not an object (evaluating 'Server.layerTestClient.pipe')
/node_modules/@effect/platform-node/dist/esm/internal/httpServer.js:193:46

this may be due to node and bun being mixed when using NodeSwaggerFiles?

SamuelLHuber commented 2 months ago

After fighting with Claude I got it to write a BunSwaggerFiles.ts that works to provide a SwaggerFilesLive layer to let me run the API

bun add swagger-ui-dist

and add BunSwaggerFiles.ts to provide the Layer

// your imports
import { SwaggerFilesLive } from "./BunSwaggerFiles"

// your app with RouterBuilder

const HttpLive = HttpServer.serve(app).pipe(
  Layer.provide(BunHttpServer.layer({ port: 3000 })),
  Layer.provide(SwaggerFilesLive),
  Layer.provide(BunContext.layer),
  Layer.launch,
  Effect.scoped
)

BunRuntime.runMain(HttpLive)

BunSwaggerFiles.ts code

import * as FileSystem from "@effect/platform/FileSystem"
import * as Path from "@effect/platform/Path"
import * as SwaggerRouter from "effect-http/SwaggerRouter"
import * as Effect from "effect/Effect"
import * as Layer from "effect/Layer"
import * as Record from "effect/Record"

/** @internal */
const readFile = (path: string) => Effect.flatMap(FileSystem.FileSystem, (fs) => fs.readFile(path))

/** @internal */
const SWAGGER_FILE_NAMES = [
  "index.css",
  "swagger-ui.css",
  "swagger-ui-bundle.js",
  "swagger-ui-standalone-preset.js",
  "favicon-32x32.png",
  "favicon-16x16.png"
]

/** @internal */
const findSwaggerDistPath = Effect.gen(function*(_) {
  const fs = yield* _(FileSystem.FileSystem)
  const path = yield* _(Path.Path)

  const swaggerPath = path.join(process.cwd(), "node_modules", "swagger-ui-dist")

  if (yield* _(fs.exists(swaggerPath))) {
    return swaggerPath
  }

  throw new Error("Could not find swagger-ui-dist directory")
})

/** @internal */
const readSwaggerFile = (swaggerBasePath: string, file: string) =>
  Effect.flatMap(Path.Path, (path) => readFile(path.join(swaggerBasePath, file)).pipe(Effect.orDie))

/** @internal */
export const SwaggerFilesLive = Effect.gen(function*(_) {
  const swaggerBasePath = yield* _(findSwaggerDistPath)

  const files = yield* _(
    SWAGGER_FILE_NAMES,
    Effect.forEach((path) => Effect.zip(Effect.succeed(path), readSwaggerFile(swaggerBasePath, path))),
    Effect.map(Record.fromEntries)
  )

  const size = Object.entries(files).reduce(
    (acc, [_, content]) => acc + content.byteLength,
    0
  )
  const sizeMb = (size / 1024 / 1024).toFixed(1)

  yield* _(Effect.logDebug(`Static swagger UI files loaded (${sizeMb}MB)`))

  return { files }
}).pipe(Layer.effect(SwaggerRouter.SwaggerFiles))
sukovanej commented 2 months ago

Hey, I updated the bun-server.ts example. The NodeSwaggerFiles should work for both bun and node. The new version of the example works for me locally as is, could you please try it? Tried with bun v1.1.24.

SamuelLHuber commented 2 months ago

yes that works! also needed to run bun update to make sure I have the newest effect-http-node version