error.ts 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243
  1. import { NamedError } from "@kirincode-ai/core/util/error"
  2. import { ConfigErrorV1 } from "@kirincode-ai/core/v1/config/error"
  3. import { Cause, Effect } from "effect"
  4. import { HttpRouter, HttpServerError, HttpServerRespondable, HttpServerResponse } from "effect/unstable/http"
  5. // Keep typed HttpApi failures on their declared error path; this boundary only replaces defect-only empty 500s.
  6. export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) =>
  7. effect.pipe(
  8. Effect.catchCause((cause) => {
  9. const defect = cause.reasons.filter(Cause.isDieReason).find((reason) => {
  10. if (HttpServerResponse.isHttpServerResponse(reason.defect)) return false
  11. if (HttpServerError.isHttpServerError(reason.defect)) return false
  12. if (HttpServerRespondable.isRespondable(reason.defect)) return false
  13. return true
  14. })
  15. if (!defect) return Effect.failCause(cause)
  16. const error = defect.defect
  17. if (
  18. ConfigErrorV1.JsonError.isInstance(error) ||
  19. ConfigErrorV1.InvalidError.isInstance(error) ||
  20. ConfigErrorV1.FrontmatterError.isInstance(error) ||
  21. ConfigErrorV1.DirectoryTypoError.isInstance(error)
  22. ) {
  23. return Effect.succeed(HttpServerResponse.jsonUnsafe(error.toObject(), { status: 400 }))
  24. }
  25. const ref = `err_${crypto.randomUUID().slice(0, 8)}`
  26. return Effect.logError("failed", { ref, error, cause: Cause.pretty(cause) }).pipe(
  27. Effect.as(
  28. HttpServerResponse.jsonUnsafe(
  29. new NamedError.Unknown({
  30. message: "Unexpected server error. Check server logs for details.",
  31. ref,
  32. }).toObject(),
  33. { status: 500 },
  34. ),
  35. ),
  36. )
  37. }),
  38. ),
  39. ).layer