This commit is contained in:
@@ -81,10 +81,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@effect/schema": "^0.72.2",
|
||||
"@prisma/studio-server": "^0.502.0",
|
||||
"@tanstack/form-core": "^0.30.0",
|
||||
"@trpc/server": "^10.45.2",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jsonwebtoken": "^9.0.6",
|
||||
"@types/ws": "^8.5.12",
|
||||
"bun-types": "^1.1.26",
|
||||
"effect": "^3.7.2",
|
||||
"express": "^4.19.2",
|
||||
@@ -95,6 +96,7 @@
|
||||
"openai": "^4.57.3",
|
||||
"tsup": "^8.2.4",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.5.4"
|
||||
"typescript": "^5.5.4",
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./express"
|
||||
export * as JSONWebToken from "./JSONWebToken"
|
||||
export * as OpenAIClient from "./OpenAIClient"
|
||||
export * from "./trpc"
|
||||
|
||||
38
src/Layers/trpc/TRPCContext.ts
Normal file
38
src/Layers/trpc/TRPCContext.ts
Normal 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>()
|
||||
55
src/Layers/trpc/TRPCContextCreator.ts
Normal file
55
src/Layers/trpc/TRPCContextCreator.ts
Normal 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 }
|
||||
}
|
||||
67
src/Layers/trpc/createTRCPErrorMapper.ts
Normal file
67
src/Layers/trpc/createTRCPErrorMapper.ts
Normal 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
1
src/Layers/trpc/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * as TRPCContextCreator from "./TRPCContextCreator"
|
||||
Reference in New Issue
Block a user