TRPCContextCreator
All checks were successful
Lint / lint (push) Successful in 12s

This commit is contained in:
Julien Valverdé
2024-09-05 06:07:29 +02:00
parent ca10286e1f
commit 85b6340b97
7 changed files with 166 additions and 2 deletions

View File

@@ -1,3 +1,4 @@
export * from "./express"
export * as JSONWebToken from "./JSONWebToken"
export * as OpenAIClient from "./OpenAIClient"
export * from "./trpc"

View File

@@ -0,0 +1,38 @@
import type { TRPCError } from "@trpc/server"
import { Data, type Effect, type Runtime } from "effect"
import type { RuntimeFiber } from "effect/Fiber"
import type express from "express"
import type { IncomingMessage } from "node:http"
import type { WebSocket } from "ws"
export interface TRPCContext<R> {
readonly runtime: Runtime.Runtime<R>
readonly run: <A, E>(
effect: Effect.Effect<A, E, R>,
options?: { readonly signal?: AbortSignal },
) => Promise<A>
readonly fork: <A, E>(
effect: Effect.Effect<A, E, R>,
options?: Runtime.RunForkOptions,
) => RuntimeFiber<A, TRPCError>
readonly transaction: TRPCContextTransaction
}
export type TRPCContextTransaction = Data.TaggedEnum<{
readonly Express: {
readonly req: express.Request
readonly res: express.Response
}
readonly WebSocket: {
readonly req: IncomingMessage
readonly res: WebSocket
}
}>
export const TRPCContextTransactionEnum = Data.taggedEnum<TRPCContextTransaction>()

View File

@@ -0,0 +1,55 @@
import type { CreateExpressContextOptions } from "@trpc/server/adapters/express"
import type { CreateWSSContextFnOptions } from "@trpc/server/adapters/ws"
import { Context, Effect, Layer, Runtime } from "effect"
import { createTRCPErrorMapper } from "./createTRCPErrorMapper"
import { TRPCContextTransactionEnum, type TRPCContext, type TRPCContextTransaction } from "./TRPCContext"
export { TRPCErrorCause } from "./createTRCPErrorMapper"
export { TRPCContextTransactionEnum } from "./TRPCContext"
export type { TRPCContext, TRPCContextTransaction } from "./TRPCContext"
export const makeService = <R>() => {
class TRPCContextCreator extends Context.Tag("TRPCContextCreator")<TRPCContextCreator, {
readonly createContext: (transaction: TRPCContextTransaction) => TRPCContext<R>
readonly createExpressContext: (context: CreateExpressContextOptions) => TRPCContext<R>
readonly createWebSocketContext: (context: CreateWSSContextFnOptions) => TRPCContext<R>
}>() {}
const TRPCContextCreatorLive = Layer.effect(TRPCContextCreator, Effect.gen(function*() {
const runtime = yield* Effect.runtime<R>()
const mapErrors = yield* createTRCPErrorMapper
const run = <A, E>(
effect: Effect.Effect<A, E, R>,
options?: { readonly signal?: AbortSignal },
) => Runtime.runPromise(runtime)(
effect.pipe(mapErrors),
options,
)
const fork = <A, E>(
effect: Effect.Effect<A, E, R>,
options?: Runtime.RunForkOptions,
) => Runtime.runFork(runtime)(
effect.pipe(mapErrors),
options,
)
const createContext = (transaction: TRPCContextTransaction) => ({
runtime,
run,
fork,
transaction,
})
const createExpressContext = (context: CreateExpressContextOptions) => createContext(TRPCContextTransactionEnum.Express(context))
const createWebSocketContext = (context: CreateWSSContextFnOptions) => createContext(TRPCContextTransactionEnum.WebSocket(context))
return { createContext, createExpressContext, createWebSocketContext }
}))
return { TRPCContextCreator, TRPCContextCreatorLive }
}

View File

@@ -0,0 +1,67 @@
import { Effect, type Cause } from "effect"
const importTRPCServer = Effect.tryPromise({
try: () => import("@trpc/server"),
catch: cause => new Error("Could not import '@trpc/server'. Make sure it is installed.", { cause }),
})
export const createTRCPErrorMapper = importTRPCServer.pipe(Effect.map(({ TRPCError }) =>
<A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.sandbox(effect).pipe(
Effect.catchTags({
Empty: cause => Effect.fail(
new TRPCError({
code: "INTERNAL_SERVER_ERROR",
cause: new TRPCErrorCause(cause),
})
),
Fail: cause => Effect.fail(
cause.error instanceof TRPCError
? cause.error
: new TRPCError({
code: "INTERNAL_SERVER_ERROR",
cause: new TRPCErrorCause(cause),
})
),
Die: cause => Effect.fail(
cause.defect instanceof TRPCError
? cause.defect
: new TRPCError({
code: "INTERNAL_SERVER_ERROR",
cause: new TRPCErrorCause(cause),
})
),
Interrupt: cause => Effect.fail(
new TRPCError({
code: "INTERNAL_SERVER_ERROR",
cause: new TRPCErrorCause(cause),
})
),
Sequential: cause => Effect.fail(
new TRPCError({
code: "INTERNAL_SERVER_ERROR",
cause: new TRPCErrorCause(cause),
})
),
Parallel: cause => Effect.fail(
new TRPCError({
code: "INTERNAL_SERVER_ERROR",
cause: new TRPCErrorCause(cause),
})
),
}),
Effect.tapError(Effect.logError),
)
))
export class TRPCErrorCause<E> extends Error {
constructor(readonly cause: Cause.Cause<E>) {
super()
}
}

1
src/Layers/trpc/index.ts Normal file
View File

@@ -0,0 +1 @@
export * as TRPCContextCreator from "./TRPCContextCreator"