[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-d96b0600-f9fe-4dc3-bc0c-21db59abd400":3,"$fxNJt53Dxq_EEN4LWF8Z2LScZvmOuB94cRoy6bRlLxb8":42},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":33},"d96b0600-f9fe-4dc3-bc0c-21db59abd400","fp-backend","Node.js\u002FDeno后端开发中fp-ts、ReaderTaskEither和函数式依赖注入的函数式编程模式","cat_coding_backend","mod_coding","sickn33,coding","---\nname: fp-backend\ndescription: Functional programming patterns for Node.js\u002FDeno backend development using fp-ts, ReaderTaskEither, and functional dependency injection\nrisk: unknown\nsource: community\nversion: 1.0.0\nauthor: kadu\ntags:\n  - fp-ts\n  - typescript\n  - backend\n  - functional-programming\n  - node\n  - deno\n  - dependency-injection\n  - reader-task-either\n---\n\n# fp-ts Backend Patterns\n\nFunctional programming patterns for building type-safe, testable backend services using fp-ts.\n\n## When to Use\n- You are building or refactoring a Node.js or Deno backend with fp-ts.\n- The task involves dependency injection, service composition, or typed backend errors with `ReaderTaskEither`.\n- You need functional backend architecture patterns rather than isolated utility snippets.\n\n## Core Concepts\n\n### ReaderTaskEither (RTE)\n\nThe `ReaderTaskEither\u003CR, E, A>` type is the backbone of functional backend development:\n- **R** (Reader): Dependencies\u002Fenvironment (database, config, logger)\n- **E** (Either left): Error type\n- **A** (Either right): Success value\n\n```typescript\nimport * as RTE from 'fp-ts\u002FReaderTaskEither'\nimport * as TE from 'fp-ts\u002FTaskEither'\nimport { pipe } from 'fp-ts\u002Ffunction'\n\n\u002F\u002F Define your dependencies\ntype Deps = {\n  db: DatabaseClient\n  logger: Logger\n  config: Config\n}\n\n\u002F\u002F Define domain errors\ntype AppError =\n  | { _tag: 'NotFound'; resource: string; id: string }\n  | { _tag: 'ValidationError'; message: string }\n  | { _tag: 'DatabaseError'; cause: unknown }\n  | { _tag: 'Unauthorized'; reason: string }\n\n\u002F\u002F A service function\nconst getUser = (id: string): RTE.ReaderTaskEither\u003CDeps, AppError, User> =>\n  pipe(\n    RTE.ask\u003CDeps>(),\n    RTE.flatMap(({ db, logger }) =>\n      pipe(\n        RTE.fromTaskEither(db.users.findById(id)),\n        RTE.mapLeft((e): AppError => ({ _tag: 'DatabaseError', cause: e })),\n        RTE.flatMap(user =>\n          user\n            ? RTE.right(user)\n            : RTE.left({ _tag: 'NotFound', resource: 'User', id })\n        ),\n        RTE.tap(user => RTE.fromIO(() => logger.info(`Found user: ${user.id}`)))\n      )\n    )\n  )\n```\n\n## Service Layer Patterns\n\n### Defining Service Modules\n\nStructure services as modules exporting RTE functions:\n\n```typescript\n\u002F\u002F src\u002Fservices\u002Fuser.service.ts\nimport * as RTE from 'fp-ts\u002FReaderTaskEither'\nimport * as TE from 'fp-ts\u002FTaskEither'\nimport * as A from 'fp-ts\u002FArray'\nimport { pipe } from 'fp-ts\u002Ffunction'\n\ntype UserDeps = {\n  db: DatabaseClient\n  hasher: PasswordHasher\n  mailer: EmailService\n}\n\ntype UserError =\n  | { _tag: 'UserNotFound'; id: string }\n  | { _tag: 'EmailExists'; email: string }\n  | { _tag: 'InvalidPassword' }\n\n\u002F\u002F Create user\nexport const create = (\n  input: CreateUserInput\n): RTE.ReaderTaskEither\u003CUserDeps, UserError, User> =>\n  pipe(\n    RTE.ask\u003CUserDeps>(),\n    RTE.flatMap(({ db, hasher }) =>\n      pipe(\n        \u002F\u002F Check email uniqueness\n        checkEmailUnique(input.email),\n        RTE.flatMap(() =>\n          RTE.fromTaskEither(hasher.hash(input.password))\n        ),\n        RTE.flatMap(hashedPassword =>\n          RTE.fromTaskEither(\n            db.users.create({\n              ...input,\n              password: hashedPassword,\n            })\n          )\n        )\n      )\n    )\n  )\n\n\u002F\u002F Find by ID\nexport const findById = (\n  id: string\n): RTE.ReaderTaskEither\u003CUserDeps, UserError, User> =>\n  pipe(\n    RTE.ask\u003CUserDeps>(),\n    RTE.flatMap(({ db }) =>\n      pipe(\n        RTE.fromTaskEither(db.users.findUnique({ where: { id } })),\n        RTE.flatMap(user =>\n          user\n            ? RTE.right(user)\n            : RTE.left({ _tag: 'UserNotFound' as const, id })\n        )\n      )\n    )\n  )\n\n\u002F\u002F Find many with pagination\nexport const findMany = (\n  params: PaginationParams\n): RTE.ReaderTaskEither\u003CUserDeps, UserError, PaginatedResult\u003CUser>> =>\n  pipe(\n    RTE.ask\u003CUserDeps>(),\n    RTE.flatMap(({ db }) =>\n      RTE.fromTaskEither(\n        pipe(\n          TE.Do,\n          TE.bind('users', () => db.users.findMany({\n            skip: params.offset,\n            take: params.limit,\n          })),\n          TE.bind('total', () => db.users.count()),\n          TE.map(({ users, total }) => ({\n            data: users,\n            total,\n            ...params,\n          }))\n        )\n      )\n    )\n  )\n\nconst checkEmailUnique = (\n  email: string\n): RTE.ReaderTaskEither\u003CUserDeps, UserError, void> =>\n  pipe(\n    RTE.ask\u003CUserDeps>(),\n    RTE.flatMap(({ db }) =>\n      pipe(\n        RTE.fromTaskEither(db.users.findUnique({ where: { email } })),\n        RTE.flatMap(existing =>\n          existing\n            ? RTE.left({ _tag: 'EmailExists' as const, email })\n            : RTE.right(undefined)\n        )\n      )\n    )\n  )\n```\n\n### Composing Services\n\n```typescript\n\u002F\u002F src\u002Fservices\u002Forder.service.ts\nimport * as UserService from '.\u002Fuser.service'\nimport * as ProductService from '.\u002Fproduct.service'\nimport * as PaymentService from '.\u002Fpayment.service'\n\ntype OrderDeps = UserService.UserDeps &\n  ProductService.ProductDeps &\n  PaymentService.PaymentDeps & {\n    db: DatabaseClient\n  }\n\nexport const createOrder = (\n  userId: string,\n  items: OrderItem[]\n): RTE.ReaderTaskEither\u003COrderDeps, OrderError, Order> =>\n  pipe(\n    RTE.Do,\n    \u002F\u002F Validate user exists\n    RTE.bind('user', () =>\n      pipe(\n        UserService.findById(userId),\n        RTE.mapLeft(toOrderError)\n      )\n    ),\n    \u002F\u002F Validate and get products\n    RTE.bind('products', () =>\n      pipe(\n        items,\n        A.traverse(RTE.ApplicativePar)(item =>\n          ProductService.findById(item.productId)\n        ),\n        RTE.mapLeft(toOrderError)\n      )\n    ),\n    \u002F\u002F Calculate total\n    RTE.bind('total', ({ products }) =>\n      RTE.right(calculateTotal(products, items))\n    ),\n    \u002F\u002F Process payment\n    RTE.bind('payment', ({ user, total }) =>\n      pipe(\n        PaymentService.charge(user, total),\n        RTE.mapLeft(toOrderError)\n      )\n    ),\n    \u002F\u002F Create order\n    RTE.flatMap(({ user, products, total, payment }) =>\n      createOrderRecord(user, products, items, total, payment)\n    )\n  )\n```\n\n## Functional Dependency Injection\n\n### Building the Dependency Container\n\n```typescript\n\u002F\u002F src\u002Fdeps.ts\nimport { pipe } from 'fp-ts\u002Ffunction'\nimport * as TE from 'fp-ts\u002FTaskEither'\nimport * as RTE from 'fp-ts\u002FReaderTaskEither'\n\n\u002F\u002F Layer 0: Config (no dependencies)\ntype Config = {\n  database: { url: string; poolSize: number }\n  redis: { url: string }\n  jwt: { secret: string; expiresIn: string }\n}\n\nconst loadConfig = (): TE.TaskEither\u003CError, Config> =>\n  TE.tryCatch(\n    async () => ({\n      database: {\n        url: process.env.DATABASE_URL!,\n        poolSize: parseInt(process.env.DB_POOL_SIZE || '10'),\n      },\n      redis: { url: process.env.REDIS_URL! },\n      jwt: {\n        secret: process.env.JWT_SECRET!,\n        expiresIn: process.env.JWT_EXPIRES || '1d',\n      },\n    }),\n    (e) => new Error(`Config error: ${e}`)\n  )\n\n\u002F\u002F Layer 1: Infrastructure (depends on config)\ntype Infrastructure = {\n  config: Config\n  db: PrismaClient\n  redis: RedisClient\n  logger: Logger\n}\n\nconst buildInfrastructure = (\n  config: Config\n): TE.TaskEither\u003CError, Infrastructure> =>\n  pipe(\n    TE.Do,\n    TE.bind('db', () =>\n      TE.tryCatch(\n        async () => {\n          const prisma = new PrismaClient({\n            datasources: { db: { url: config.database.url } },\n          })\n          await prisma.$connect()\n          return prisma\n        },\n        (e) => new Error(`Database error: ${e}`)\n      )\n    ),\n    TE.bind('redis', () =>\n      TE.tryCatch(\n        async () => createRedisClient(config.redis.url),\n        (e) => new Error(`Redis error: ${e}`)\n      )\n    ),\n    TE.bind('logger', () => TE.right(createLogger())),\n    TE.map(({ db, redis, logger }) => ({\n      config,\n      db,\n      redis,\n      logger,\n    }))\n  )\n\n\u002F\u002F Layer 2: Services (depends on infrastructure)\ntype Services = {\n  hasher: PasswordHasher\n  jwt: JwtService\n  mailer: EmailService\n}\n\nconst buildServices = (infra: Infrastructure): Services => ({\n  hasher: createBcryptHasher(),\n  jwt: createJwtService(infra.config.jwt),\n  mailer: createEmailService(infra.config),\n})\n\n\u002F\u002F Full application dependencies\nexport type AppDeps = Infrastructure & Services\n\nexport const buildDeps = (): TE.TaskEither\u003CError, AppDeps> =>\n  pipe(\n    loadConfig(),\n    TE.flatMap(buildInfrastructure),\n    TE.map(infra => ({\n      ...infra,\n      ...buildServices(infra),\n    }))\n  )\n\n\u002F\u002F Cleanup\nexport const destroyDeps = (deps: AppDeps): TE.TaskEither\u003CError, void> =>\n  pipe(\n    TE.tryCatch(\n      async () => {\n        await deps.db.$disconnect()\n        await deps.redis.quit()\n      },\n      (e) => new Error(`Cleanup error: ${e}`)\n    )\n  )\n```\n\n### Running Programs with Dependencies\n\n```typescript\n\u002F\u002F src\u002Fmain.ts\nimport { pipe } from 'fp-ts\u002Ffunction'\nimport * as TE from 'fp-ts\u002FTaskEither'\nimport * as RTE from 'fp-ts\u002FReaderTaskEither'\n\nconst program: RTE.ReaderTaskEither\u003CAppDeps, AppError, void> = pipe(\n  RTE.ask\u003CAppDeps>(),\n  RTE.flatMap(deps =>\n    pipe(\n      startServer(deps),\n      RTE.fromTaskEither\n    )\n  )\n)\n\nconst main = async () => {\n  const result = await pipe(\n    buildDeps(),\n    TE.mapLeft((e): AppError => ({ _tag: 'StartupError', cause: e })),\n    TE.flatMap(deps =>\n      pipe(\n        program(deps),\n        TE.tap(() => TE.fromIO(() => console.log('Server running'))),\n        \u002F\u002F Cleanup on exit\n        TE.tapError(() => destroyDeps(deps))\n      )\n    )\n  )()\n\n  if (result._tag === 'Left') {\n    console.error('Failed to start:', result.left)\n    process.exit(1)\n  }\n}\n\nmain()\n```\n\n## Database Operations\n\n### Prisma Wrappers\n\n```typescript\n\u002F\u002F src\u002Flib\u002Fdb.ts\nimport * as TE from 'fp-ts\u002FTaskEither'\nimport * as O from 'fp-ts\u002FOption'\nimport { PrismaClient, Prisma } from '@prisma\u002Fclient'\n\ntype DbError =\n  | { _tag: 'RecordNotFound'; model: string; id: string }\n  | { _tag: 'UniqueViolation'; field: string }\n  | { _tag: 'ForeignKeyViolation'; field: string }\n  | { _tag: 'UnknownDbError'; cause: unknown }\n\n\u002F\u002F Wrap Prisma operations\nconst wrapPrisma = \u003CA>(\n  operation: () => Promise\u003CA>\n): TE.TaskEither\u003CDbError, A> =>\n  TE.tryCatch(operation, (error): DbError => {\n    if (error instanceof Prisma.PrismaClientKnownRequestError) {\n      switch (error.code) {\n        case 'P2002':\n          return {\n            _tag: 'UniqueViolation',\n            field: (error.meta?.target as string[])?.join(', ') || 'unknown',\n          }\n        case 'P2003':\n          return {\n            _tag: 'ForeignKeyViolation',\n            field: error.meta?.field_name as string || 'unknown',\n          }\n        case 'P2025':\n          return {\n            _tag: 'RecordNotFound',\n            model: error.meta?.modelName as string || 'unknown',\n            id: 'unknown',\n          }\n      }\n    }\n    return { _tag: 'UnknownDbError', cause: error }\n  })\n\n\u002F\u002F Repository factory\nexport const createRepository = \u003C\n  Model,\n  CreateInput,\n  UpdateInput,\n  WhereUnique,\n  WhereMany\n>(\n  db: PrismaClient,\n  delegate: {\n    findUnique: (args: { where: WhereUnique }) => Promise\u003CModel | null>\n    findMany: (args: { where?: WhereMany; skip?: number; take?: number }) => Promise\u003CModel[]>\n    create: (args: { data: CreateInput }) => Promise\u003CModel>\n    update: (args: { where: WhereUnique; data: UpdateInput }) => Promise\u003CModel>\n    delete: (args: { where: WhereUnique }) => Promise\u003CModel>\n    count: (args?: { where?: WhereMany }) => Promise\u003Cnumber>\n  }\n) => ({\n  findUnique: (where: WhereUnique): TE.TaskEither\u003CDbError, O.Option\u003CModel>> =>\n    pipe(\n      wrapPrisma(() => delegate.findUnique({ where })),\n      TE.map(O.fromNullable)\n    ),\n\n  findMany: (\n    where?: WhereMany,\n    pagination?: { skip: number; take: number }\n  ): TE.TaskEither\u003CDbError, Model[]> =>\n    wrapPrisma(() => delegate.findMany({ where, ...pagination })),\n\n  create: (data: CreateInput): TE.TaskEither\u003CDbError, Model> =>\n    wrapPrisma(() => delegate.create({ data })),\n\n  update: (\n    where: WhereUnique,\n    data: UpdateInput\n  ): TE.TaskEither\u003CDbError, Model> =>\n    wrapPrisma(() => delegate.update({ where, data })),\n\n  delete: (where: WhereUnique): TE.TaskEither\u003CDbError, Model> =>\n    wrapPrisma(() => delegate.delete({ where })),\n\n  count: (where?: WhereMany): TE.TaskEither\u003CDbError, number> =>\n    wrapPrisma(() => delegate.count({ where })),\n})\n\n\u002F\u002F Usage\nconst userRepo = createRepository(prisma, prisma.user)\n```\n\n### Transaction Handling\n\n```typescript\n\u002F\u002F src\u002Flib\u002Ftransaction.ts\nimport * as TE from 'fp-ts\u002FTaskEither'\nimport * as RTE from 'fp-ts\u002FReaderTaskEither'\nimport { PrismaClient } from '@prisma\u002Fclient'\nimport { pipe } from 'fp-ts\u002Ffunction'\n\ntype TxClient = Omit\u003C\n  PrismaClient,\n  '$connect' | '$disconnect' | '$on' | '$transaction' | '$use'\n>\n\ntype TxDeps = { tx: TxClient }\n\n\u002F\u002F Transaction wrapper\nexport const withTransaction = \u003CR extends { db: PrismaClient }, E, A>(\n  program: RTE.ReaderTaskEither\u003CR & TxDeps, E, A>\n): RTE.ReaderTaskEither\u003CR, E | DbError, A> =>\n  pipe(\n    RTE.ask\u003CR>(),\n    RTE.flatMap(deps =>\n      RTE.fromTaskEither(\n        TE.tryCatch(\n          () =>\n            deps.db.$transaction(async tx => {\n              const result = await program({ ...deps, tx })()\n              if (result._tag === 'Left') {\n                throw result.left \u002F\u002F Rollback\n              }\n              return result.right\n            }),\n          (error): E | DbError => {\n            \u002F\u002F Re-throw domain errors\n            if (typeof error === 'object' && error !== null && '_tag' in error) {\n              return error as E\n            }\n            return { _tag: 'UnknownDbError', cause: error }\n          }\n        )\n      )\n    )\n  )\n\n\u002F\u002F Usage in service\nexport const transferFunds = (\n  fromId: string,\n  toId: string,\n  amount: number\n): RTE.ReaderTaskEither\u003CAppDeps, TransferError, Transfer> =>\n  withTransaction(\n    pipe(\n      RTE.Do,\n      RTE.bind('from', () => debitAccount(fromId, amount)),\n      RTE.bind('to', () => creditAccount(toId, amount)),\n      RTE.bind('transfer', ({ from, to }) =>\n        createTransferRecord(from, to, amount)\n      ),\n      RTE.map(({ transfer }) => transfer)\n    )\n  )\n\n\u002F\u002F Inside transaction, use tx instead of db\nconst debitAccount = (\n  accountId: string,\n  amount: number\n): RTE.ReaderTaskEither\u003CTxDeps, TransferError, Account> =>\n  pipe(\n    RTE.ask\u003CTxDeps>(),\n    RTE.flatMap(({ tx }) =>\n      RTE.fromTaskEither(\n        pipe(\n          TE.tryCatch(\n            () =>\n              tx.account.update({\n                where: { id: accountId },\n                data: { balance: { decrement: amount } },\n              }),\n            toDbError\n          ),\n          TE.flatMap(account =>\n            account.balance \u003C 0\n              ? TE.left({ _tag: 'InsufficientFunds' as const, accountId })\n              : TE.right(account)\n          )\n        )\n      )\n    )\n  )\n```\n\n## Middleware Patterns\n\n### Express Middleware\n\n```typescript\n\u002F\u002F src\u002Fmiddleware\u002Ffp-express.ts\nimport { Request, Response, NextFunction, RequestHandler } from 'express'\nimport * as TE from 'fp-ts\u002FTaskEither'\nimport * as RTE from 'fp-ts\u002FReaderTaskEither'\nimport * as E from 'fp-ts\u002FEither'\nimport { pipe } from 'fp-ts\u002Ffunction'\n\n\u002F\u002F Convert RTE handler to Express middleware\nexport const toHandler =\n  \u003CR, E, A>(\n    getDeps: (req: Request) => R,\n    handler: (req: Request) => RTE.ReaderTaskEither\u003CR, E, A>,\n    onError: (error: E, res: Response) => void\n  ): RequestHandler =>\n  async (req, res, next) => {\n    const deps = getDeps(req)\n    const result = await handler(req)(deps)()\n\n    pipe(\n      result,\n      E.fold(\n        error => onError(error, res),\n        data => res.json(data)\n      )\n    )\n  }\n\n\u002F\u002F Error handler\nconst handleError = (error: AppError, res: Response): void => {\n  switch (error._tag) {\n    case 'NotFound':\n      res.status(404).json({ error: error.resource + ' not found' })\n      break\n    case 'ValidationError':\n      res.status(400).json({ error: error.message })\n      break\n    case 'Unauthorized':\n      res.status(401).json({ error: error.reason })\n      break\n    default:\n      res.status(500).json({ error: 'Internal server error' })\n  }\n}\n\n\u002F\u002F Usage\nconst getUserHandler = toHandler(\n  req => req.app.locals.deps as AppDeps,\n  req => UserService.findById(req.params.id),\n  handleError\n)\n\napp.get('\u002Fusers\u002F:id', getUserHandler)\n```\n\n### Hono Middleware\n\n```typescript\n\u002F\u002F src\u002Fmiddleware\u002Ffp-hono.ts\nimport { Hono, Context, MiddlewareHandler } from 'hono'\nimport * as RTE from 'fp-ts\u002FReaderTaskEither'\nimport * as E from 'fp-ts\u002FEither'\nimport { pipe } from 'fp-ts\u002Ffunction'\n\n\u002F\u002F Store deps in context\ndeclare module 'hono' {\n  interface ContextVariableMap {\n    deps: AppDeps\n  }\n}\n\n\u002F\u002F Dependency injection middleware\nexport const withDeps = (deps: AppDeps): MiddlewareHandler =>\n  async (c, next) => {\n    c.set('deps', deps)\n    await next()\n  }\n\n\u002F\u002F Convert RTE to Hono handler\nexport const toHonoHandler =\n  \u003CE, A>(\n    handler: (c: Context) => RTE.ReaderTaskEither\u003CAppDeps, E, A>,\n    onError: (error: E, c: Context) => Response\n  ) =>\n  async (c: Context): Promise\u003CResponse> => {\n    const deps = c.get('deps')\n    const result = await handler(c)(deps)()\n\n    return pipe(\n      result,\n      E.fold(\n        error => onError(error, c),\n        data => c.json(data)\n      )\n    )\n  }\n\n\u002F\u002F Validation middleware\nexport const validate =\n  \u003CT>(schema: z.ZodSchema\u003CT>): MiddlewareHandler =>\n  async (c, next) => {\n    const body = await c.req.json()\n    const result = schema.safeParse(body)\n\n    if (!result.success) {\n      return c.json(\n        { error: 'Validation failed', details: result.error.flatten() },\n        400\n      )\n    }\n\n    c.set('validatedBody', result.data)\n    await next()\n  }\n\n\u002F\u002F Auth middleware using RTE\nexport const requireAuth: MiddlewareHandler = async (c, next) => {\n  const deps = c.get('deps')\n  const token = c.req.header('Authorization')?.replace('Bearer ', '')\n\n  if (!token) {\n    return c.json({ error: 'No token provided' }, 401)\n  }\n\n  const result = await pipe(\n    deps.jwt.verify(token),\n    TE.mapLeft(() => ({ _tag: 'Unauthorized' as const, reason: 'Invalid token' }))\n  )()\n\n  if (E.isLeft(result)) {\n    return c.json({ error: result.left.reason }, 401)\n  }\n\n  c.set('user', result.right)\n  await next()\n}\n\n\u002F\u002F Usage\nconst app = new Hono()\n\napp.use('*', withDeps(deps))\napp.use('\u002Fapi\u002F*', requireAuth)\n\napp.get(\n  '\u002Fapi\u002Fusers\u002F:id',\n  toHonoHandler(\n    c => UserService.findById(c.req.param('id')),\n    (error, c) => {\n      if (error._tag === 'UserNotFound') {\n        return c.json({ error: 'User not found' }, 404)\n      }\n      return c.json({ error: 'Internal error' }, 500)\n    }\n  )\n)\n```\n\n### Request Context Pattern\n\n```typescript\n\u002F\u002F src\u002Fcontext.ts\nimport * as RTE from 'fp-ts\u002FReaderTaskEither'\nimport { pipe } from 'fp-ts\u002Ffunction'\n\n\u002F\u002F Request-scoped context\ntype RequestContext = {\n  requestId: string\n  userId: O.Option\u003Cstring>\n  startTime: number\n}\n\ntype ContextDeps = AppDeps & { ctx: RequestContext }\n\n\u002F\u002F Logging with context\nconst logWithContext =\n  (level: 'info' | 'warn' | 'error') =>\n  (message: string, meta?: object): RTE.ReaderTaskEither\u003CContextDeps, never, void> =>\n    pipe(\n      RTE.ask\u003CContextDeps>(),\n      RTE.flatMap(({ logger, ctx }) =>\n        RTE.fromIO(() =>\n          loggerlevel,\n            elapsed: Date.now() - ctx.startTime,\n          })\n        )\n      )\n    )\n\nexport const log = {\n  info: logWithContext('info'),\n  warn: logWithContext('warn'),\n  error: logWithContext('error'),\n}\n\n\u002F\u002F Middleware to create context\nexport const withContext: MiddlewareHandler = async (c, next) => {\n  const deps = c.get('deps')\n  const ctx: RequestContext = {\n    requestId: crypto.randomUUID(),\n    userId: O.fromNullable(c.get('user')?.id),\n    startTime: Date.now(),\n  }\n\n  c.set('deps', { ...deps, ctx })\n\n  \u002F\u002F Log request start\n  deps.logger.info('Request started', {\n    requestId: ctx.requestId,\n    method: c.req.method,\n    path: c.req.path,\n  })\n\n  await next()\n\n  \u002F\u002F Log request end\n  deps.logger.info('Request completed', {\n    requestId: ctx.requestId,\n    status: c.res.status,\n    elapsed: Date.now() - ctx.startTime,\n  })\n}\n```\n\n## Error Handling Patterns\n\n### Typed Error Hierarchy\n\n```typescript\n\u002F\u002F src\u002Ferrors.ts\nimport * as E from 'fp-ts\u002FEither'\nimport * as O from 'fp-ts\u002FOption'\n\n\u002F\u002F Base error types\ntype DomainError =\n  | NotFoundError\n  | ValidationError\n  | ConflictError\n  | AuthError\n  | InfrastructureError\n\ntype NotFoundError = {\n  _tag: 'NotFoundError'\n  resource: string\n  id: string\n}\n\ntype ValidationError = {\n  _tag: 'ValidationError'\n  field: string\n  message: string\n  value?: unknown\n}\n\ntype ConflictError = {\n  _tag: 'ConflictError'\n  resource: string\n  field: string\n  value: string\n}\n\ntype AuthError =\n  | { _tag: 'Unauthenticated' }\n  | { _tag: 'Unauthorized'; required: string }\n  | { _tag: 'TokenExpired' }\n\ntype InfrastructureError = {\n  _tag: 'InfrastructureError'\n  service: string\n  cause: unknown\n}\n\n\u002F\u002F Smart constructors\nexport const notFound = (resource: string, id: string): NotFoundError => ({\n  _tag: 'NotFoundError',\n  resource,\n  id,\n})\n\nexport const validation = (\n  field: string,\n  message: string,\n  value?: unknown\n): ValidationError => ({\n  _tag: 'ValidationError',\n  field,\n  message,\n  value,\n})\n\nexport const conflict = (\n  resource: string,\n  field: string,\n  value: string\n): ConflictError => ({\n  _tag: 'ConflictError',\n  resource,\n  field,\n  value,\n})\n\n\u002F\u002F Error to HTTP status mapping\nexport const toHttpStatus = (error: DomainError): number => {\n  switch (error._tag) {\n    case 'NotFoundError':\n      return 404\n    case 'ValidationError':\n      return 400\n    case 'ConflictError':\n      return 409\n    case 'Unauthenticated':\n      return 401\n    case 'Unauthorized':\n      return 403\n    case 'TokenExpired':\n      return 401\n    case 'InfrastructureError':\n      return 503\n    default:\n      return 500\n  }\n}\n\n\u002F\u002F Error to response body\nexport const toResponseBody = (\n  error: DomainError\n): { error: string; details?: unknown } => {\n  switch (error._tag) {\n    case 'NotFoundError':\n      return { error: `${error.resource} not found` }\n    case 'ValidationError':\n      return {\n        error: 'Validation failed',\n        details: { field: error.field, message: error.message },\n      }\n    case 'ConflictError':\n      return {\n        error: `${error.resource} with ${error.field} already exists`,\n      }\n    case 'Unauthenticated':\n      return { error: 'Authentication required' }\n    case 'Unauthorized':\n      return { error: `Permission denied: ${error.required}` }\n    case 'TokenExpired':\n      return { error: 'Token expired' }\n    case 'InfrastructureError':\n      return { error: 'Service temporarily unavailable' }\n  }\n}\n```\n\n### Error Recovery\n\n```typescript\n\u002F\u002F src\u002Flib\u002Frecovery.ts\nimport * as RTE from 'fp-ts\u002FReaderTaskEither'\nimport * as TE from 'fp-ts\u002FTaskEither'\nimport { pipe } from 'fp-ts\u002Ffunction'\n\n\u002F\u002F Retry with exponential backoff\nexport const withRetry =\n  \u003CR, E, A>(\n    maxAttempts: number,\n    baseDelayMs: number,\n    shouldRetry: (error: E) => boolean\n  ) =>\n  (\n    operation: RTE.ReaderTaskEither\u003CR, E, A>\n  ): RTE.ReaderTaskEither\u003CR, E, A> =>\n    pipe(\n      RTE.ask\u003CR>(),\n      RTE.flatMap(deps => {\n        const attempt = (\n          remaining: number,\n          delay: number\n        ): TE.TaskEither\u003CE, A> =>\n          pipe(\n            operation(deps),\n            TE.orElse(error => {\n              if (remaining \u003C= 0 || !shouldRetry(error)) {\n                return TE.left(error)\n              }\n              return pipe(\n                TE.fromTask(() => new Promise(r => setTimeout(r, delay))),\n                TE.flatMap(() => attempt(remaining - 1, delay * 2))\n              )\n            })\n          )\n\n        return RTE.fromTaskEither(attempt(maxAttempts - 1, baseDelayMs))\n      })\n    )\n\n\u002F\u002F Fallback to cached value\nexport const withFallback =\n  \u003CR extends { cache: CacheClient }, E, A>(\n    cacheKey: string,\n    ttlSeconds: number\n  ) =>\n  (\n    operation: RTE.ReaderTaskEither\u003CR, E, A>\n  ): RTE.ReaderTaskEither\u003CR, E, A> =>\n    pipe(\n      RTE.ask\u003CR>(),\n      RTE.flatMap(({ cache, ...rest }) =>\n        pipe(\n          operation,\n          \u002F\u002F On success, cache the result\n          RTE.tap(result =>\n            RTE.fromTaskEither(cache.set(cacheKey, result, ttlSeconds))\n          ),\n          \u002F\u002F On failure, try to get cached value\n          RTE.orElse(error =>\n            pipe(\n              RTE.fromTaskEither(cache.get\u003CA>(cacheKey)),\n              RTE.flatMap(cached =>\n                cached ? RTE.right(cached) : RTE.left(error)\n              )\n            )\n          )\n        )\n      )\n    )\n\n\u002F\u002F Circuit breaker\ntype CircuitState = 'closed' | 'open' | 'half-open'\n\nexport const createCircuitBreaker = \u003CE>(\n  failureThreshold: number,\n  resetTimeoutMs: number,\n  isFailure: (error: E) => boolean\n) => {\n  let state: CircuitState = 'closed'\n  let failures = 0\n  let lastFailure = 0\n\n  return \u003CR, A>(\n    operation: RTE.ReaderTaskEither\u003CR, E, A>\n  ): RTE.ReaderTaskEither\u003CR, E | { _tag: 'CircuitOpen' }, A> =>\n    pipe(\n      RTE.ask\u003CR>(),\n      RTE.flatMap(deps => {\n        \u002F\u002F Check if circuit should reset\n        if (\n          state === 'open' &&\n          Date.now() - lastFailure > resetTimeoutMs\n        ) {\n          state = 'half-open'\n        }\n\n        if (state === 'open') {\n          return RTE.left({ _tag: 'CircuitOpen' as const })\n        }\n\n        return pipe(\n          operation,\n          RTE.tap(() => {\n            if (state === 'half-open') {\n              state = 'closed'\n              failures = 0\n            }\n            return RTE.right(undefined)\n          }),\n          RTE.tapError(error => {\n            if (isFailure(error)) {\n              failures++\n              lastFailure = Date.now()\n              if (failures >= failureThreshold) {\n                state = 'open'\n              }\n            }\n            return RTE.right(undefined)\n          })\n        )\n      })\n    )\n}\n```\n\n## Testing Strategies\n\n### Mocking Dependencies\n\n```typescript\n\u002F\u002F src\u002Fservices\u002F__tests__\u002Fuser.service.test.ts\nimport * as TE from 'fp-ts\u002FTaskEither'\nimport * as E from 'fp-ts\u002FEither'\nimport * as O from 'fp-ts\u002FOption'\nimport { describe, it, expect, vi } from 'vitest'\nimport * as UserService from '..\u002Fuser.service'\n\n\u002F\u002F Create mock dependencies\nconst createMockDeps = (overrides: Partial\u003CUserDeps> = {}): UserDeps => ({\n  db: {\n    users: {\n      findUnique: vi.fn(() => Promise.resolve(null)),\n      create: vi.fn(data => Promise.resolve({ id: '1', ...data })),\n      update: vi.fn((where, data) => Promise.resolve({ id: where.id, ...data })),\n    },\n  },\n  hasher: {\n    hash: vi.fn(password => TE.right(`hashed_${password}`)),\n    verify: vi.fn(() => TE.right(true)),\n  },\n  mailer: {\n    send: vi.fn(() => TE.right(undefined)),\n  },\n  ...overrides,\n})\n\ndescribe('UserService', () => {\n  describe('create', () => {\n    it('should create a user with hashed password', async () => {\n      const deps = createMockDeps()\n      const input = {\n        email: 'test@example.com',\n        password: 'secret123',\n        name: 'Test User',\n      }\n\n      const result = await UserService.create(input)(deps)()\n\n      expect(E.isRight(result)).toBe(true)\n      if (E.isRight(result)) {\n        expect(result.right.email).toBe(input.email)\n      }\n      expect(deps.hasher.hash).toHaveBeenCalledWith('secret123')\n    })\n\n    it('should fail when email already exists', async () => {\n      const existingUser = { id: '1', email: 'test@example.com' }\n      const deps = createMockDeps({\n        db: {\n          users: {\n            findUnique: vi.fn(() => Promise.resolve(existingUser)),\n            create: vi.fn(),\n          },\n        },\n      })\n\n      const result = await UserService.create({\n        email: 'test@example.com',\n        password: 'secret',\n        name: 'Test',\n      })(deps)()\n\n      expect(E.isLeft(result)).toBe(true)\n      if (E.isLeft(result)) {\n        expect(result.left._tag).toBe('EmailExists')\n      }\n    })\n  })\n\n  describe('findById', () => {\n    it('should return user when found', async () => {\n      const user = { id: '1', email: 'test@example.com', name: 'Test' }\n      const deps = createMockDeps({\n        db: {\n          users: {\n            findUnique: vi.fn(() => Promise.resolve(user)),\n          },\n        },\n      })\n\n      const result = await UserService.findById('1')(deps)()\n\n      expect(E.isRight(result)).toBe(true)\n      if (E.isRight(result)) {\n        expect(result.right).toEqual(user)\n      }\n    })\n\n    it('should return NotFound when user does not exist', async () => {\n      const deps = createMockDeps()\n\n      const result = await UserService.findById('nonexistent')(deps)()\n\n      expect(E.isLeft(result)).toBe(true)\n      if (E.isLeft(result)) {\n        expect(result.left._tag).toBe('UserNotFound')\n        expect(result.left.id).toBe('nonexistent')\n      }\n    })\n  })\n})\n```\n\n### Integration Testing with Test Containers\n\n```typescript\n\u002F\u002F src\u002F__tests__\u002Fintegration\u002Fuser.integration.test.ts\nimport { PostgreSqlContainer } from '@testcontainers\u002Fpostgresql'\nimport { PrismaClient } from '@prisma\u002Fclient'\nimport * as TE from 'fp-ts\u002FTaskEither'\nimport * as E from 'fp-ts\u002FEither'\nimport { pipe } from 'fp-ts\u002Ffunction'\nimport { describe, it, expect, beforeAll, afterAll } from 'vitest'\nimport { buildDeps, destroyDeps, AppDeps } from '..\u002F..\u002Fdeps'\nimport * as UserService from '..\u002F..\u002Fservices\u002Fuser.service'\n\ndescribe('UserService Integration', () => {\n  let container: PostgreSqlContainer\n  let deps: AppDeps\n\n  beforeAll(async () => {\n    \u002F\u002F Start PostgreSQL container\n    container = await new PostgreSqlContainer().start()\n\n    \u002F\u002F Build real dependencies with test database\n    process.env.DATABASE_URL = container.getConnectionUri()\n\n    const depsResult = await buildDeps()()\n    if (E.isLeft(depsResult)) {\n      throw new Error(`Failed to build deps: ${depsResult.left}`)\n    }\n    deps = depsResult.right\n\n    \u002F\u002F Run migrations\n    await deps.db.$executeRaw`CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"`\n    \u002F\u002F ... run Prisma migrations\n  }, 60000)\n\n  afterAll(async () => {\n    await destroyDeps(deps)()\n    await container.stop()\n  })\n\n  it('should create and retrieve a user', async () => {\n    \u002F\u002F Create user\n    const createResult = await UserService.create({\n      email: 'integration@test.com',\n      password: 'password123',\n      name: 'Integration Test',\n    })(deps)()\n\n    expect(E.isRight(createResult)).toBe(true)\n    if (E.isLeft(createResult)) return\n\n    const user = createResult.right\n\n    \u002F\u002F Retrieve user\n    const findResult = await UserService.findById(user.id)(deps)()\n\n    expect(E.isRight(findResult)).toBe(true)\n    if (E.isRight(findResult)) {\n      expect(findResult.right.email).toBe('integration@test.com')\n    }\n  })\n})\n```\n\n### Property-Based Testing\n\n```typescript\n\u002F\u002F src\u002F__tests__\u002Fproperty\u002Fuser.property.test.ts\nimport * as fc from 'fast-check'\nimport * as E from 'fp-ts\u002FEither'\nimport { describe, it, expect } from 'vitest'\nimport { validateEmail, validatePassword } from '..\u002F..\u002Fvalidation'\n\ndescribe('Validation Properties', () => {\n  it('valid emails should pass validation', () => {\n    fc.assert(\n      fc.property(fc.emailAddress(), email => {\n        const result = validateEmail(email)\n        return E.isRight(result)\n      })\n    )\n  })\n\n  it('passwords meeting requirements should pass', () => {\n    const validPassword = fc\n      .tuple(\n        fc.stringOf(fc.constantFrom(...'abcdefghijklmnopqrstuvwxyz'), {\n          minLength: 4,\n        }),\n        fc.stringOf(fc.constantFrom(...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'), {\n          minLength: 1,\n        }),\n        fc.stringOf(fc.constantFrom(...'0123456789'), { minLength: 1 }),\n        fc.stringOf(fc.constantFrom(...'!@#$%^&*'), { minLength: 1 })\n      )\n      .map(parts => parts.join(''))\n\n    fc.assert(\n      fc.property(validPassword, password => {\n        const result = validatePassword(password)\n        return E.isRight(result)\n      })\n    )\n  })\n\n  it('empty strings should fail email validation', () => {\n    const result = validateEmail('')\n    expect(E.isLeft(result)).toBe(true)\n  })\n})\n```\n\n## Quick Reference\n\n### Common Imports\n\n```typescript\nimport * as RTE from 'fp-ts\u002FReaderTaskEither'\nimport * as TE from 'fp-ts\u002FTaskEither'\nimport * as E from 'fp-ts\u002FEither'\nimport * as O from 'fp-ts\u002FOption'\nimport * as A from 'fp-ts\u002FArray'\nimport * as T from 'fp-ts\u002FTask'\nimport { pipe, flow } from 'fp-ts\u002Ffunction'\n```\n\n### RTE Cheat Sheet\n\n| Operation | Description |\n|-----------|-------------|\n| `RTE.right(a)` | Lift value into success |\n| `RTE.left(e)` | Create error |\n| `RTE.ask\u003CR>()` | Get dependencies |\n| `RTE.fromTaskEither(te)` | Lift TaskEither |\n| `RTE.fromEither(e)` | Lift Either |\n| `RTE.fromOption(onNone)(o)` | Lift Option |\n| `RTE.flatMap(f)` | Chain operations |\n| `RTE.map(f)` | Transform success |\n| `RTE.mapLeft(f)` | Transform error |\n| `RTE.tap(f)` | Side effect on success |\n| `RTE.tapError(f)` | Side effect on error |\n| `RTE.orElse(f)` | Recover from error |\n| `RTE.getOrElse(f)` | Extract with fallback |\n\n### Service Template\n\n```typescript\n\u002F\u002F Template for a new service\nimport * as RTE from 'fp-ts\u002FReaderTaskEither'\nimport { pipe } from 'fp-ts\u002Ffunction'\n\ntype MyServiceDeps = {\n  db: DatabaseClient\n  \u002F\u002F ... other dependencies\n}\n\ntype MyServiceError =\n  | { _tag: 'NotFound'; id: string }\n  | { _tag: 'ValidationFailed'; reason: string }\n\nexport const myOperation = (\n  input: Input\n): RTE.ReaderTaskEither\u003CMyServiceDeps, MyServiceError, Output> =>\n  pipe(\n    RTE.ask\u003CMyServiceDeps>(),\n    RTE.flatMap(deps =>\n      \u002F\u002F Your implementation here\n      RTE.right(output)\n    )\n  )\n```\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,227,926,"2026-05-16 13:18:31",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"编程开发","coding","mdi-code-braces","代码生成、调试、审查，提升开发效率",2,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":25,"skillCount":32,"createdAt":26},"后端开发","backend","mdi-server","API、数据库、服务端架构",296,[34],{"id":35,"skillId":4,"version":36,"fileName":37,"fileSize":38,"filePath":39,"fileHash":40,"manifest":41,"createdAt":19},"f4d3d27a-fbcd-4014-99ca-204163def534","1.0.0","fp-backend.zip",8913,"uploads\u002Fskills\u002Fd96b0600-f9fe-4dc3-bc0c-21db59abd400\u002Ffp-backend.zip","619b9722b90d5cf3ea5cf2f754088e7a3960f9f136b84b1a136e9c51ce2fc203","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":33785}]",{"code":43,"message":44,"data":45},200,"success",{"items":46,"stats":47,"page":50},[],{"averageRating":48,"totalRatings":48,"ratingCounts":49},0,[48,48,48,48,48],{"limit":51,"offset":48,"hasMore":52,"nextOffset":51,"ratedOnly":16},15,false]