跳到主要内容

TypeScript

Keq 提供了完善的 TypeScript 支持,让你在编写代码时获得完整的类型提示和类型安全保障。

指定响应类型

Keq 支持通过泛型参数指定响应类型:

import { request } from "keq"

interface Cat {
  id: string
  breed: string
  name: string
}

// 使用泛型参数指定返回类型
const cat = await request.get<Cat>("/api/cats/123")
// cat 的类型为 Cat

const cats = await request.get<Cat[]>("/api/cats")
// cats 的类型为 Cat[]

API 类型约束

使用 KeqApiSchema 接口

当你需要为特定的 API 定义类型时,可以扩展 KeqApiSchema 接口:

import { KeqRequest, KeqApiSchema, KeqQueryValue, KeqParamValue } from "keq"

// 定义 API 的类型结构
export interface CatApiSchema extends KeqApiSchema {
  "/cats": {
    get: {
      requestParams: {
        [key: string]: KeqParamValue // 必须包含索引签名
      }
      requestQuery: {
        breed: string
        [key: string]: KeqQueryValue // 必须包含索引签名
      }
      requestHeaders: {
        Authorization: string
        [key: string]: string | number // 必须包含索引签名
      }
      requestBody: {}
      responseBody: {
        id: string
        breed: string
        name: string
      }[]
    }
    post: {
      requestParams: {
        [key: string]: KeqParamValue
      }
      requestQuery: {
        [key: string]: KeqQueryValue
      }
      requestHeaders: {
        Authorization: string
        [key: string]: string | number
      }
      requestBody: {
        breed: string
        name: string
      }
      responseBody: {
        id: string
        breed: string
        name: string
      }
    }
  }
  "/cats/{id}": {
    get: {
      requestParams: {
        id: string
        [key: string]: KeqParamValue
      }
      requestQuery: {
        [key: string]: KeqQueryValue
      }
      requestHeaders: {
        [key: string]: string | number
      }
      requestBody: {}
      responseBody: {
        id: string
        breed: string
        name: string
      }
    }
  }
}

// 创建带类型的请求实例
const catAPI = new KeqRequest<CatApiSchema>()

// 现在你会获得完整的类型提示和检查
const cats = await catAPI
  .get("/cats")
  .query("breed", "Persian") // ✅ 正确:breed 是 string 类型
  .query("breed", 123)       // ❌ 错误:TypeScript 会提示类型不匹配
  .set("Authorization", "Bearer token")

const cat = await catAPI
  .get("/cats/{id}")
  .param("id", "123") // ✅ 正确:id 是 string 类型

扩展自定义选项

通过模块扩展,你可以为 Keq 添加自定义选项并获得类型支持:

import { Keq, KeqMiddleware } from "keq"

// 扩展 KeqMiddlewareOptions 接口
declare module "keq" {
  interface KeqMiddlewareOptions<OP> {
    silent(value: boolean): Keq<OP>
  }
}

// 实现对应的中间件
function errorHandlerMiddleware(): KeqMiddleware {
  return async (context, next) => {
    await next()

    if (context.response && context.response.status >= 400) {
      const isSilent = context.options.silent || false
      if (!isSilent) {
        console.error(`Request failed: ${context.response.status}`)
      }
    }
  }
}

// 现在可以使用自定义选项,并有类型提示
await request
  .get("/api/data")
  .option("silent", true)  // ✅ 有类型提示

扩展 KeqContextData

KeqContextData 是请求级别的共享数据接口,用于中间件之间传递数据。通过模块扩展,可以为其添加强类型字段:

import { KeqMiddleware } from "keq"

// 扩展 KeqContextData,添加自定义字段
declare module "keq" {
  interface KeqContextData {
    startTime?: number
    metrics?: {
      duration: number
      size: number
    }
  }
}

// 中间件中读写 context.data 时会有完整的类型提示
const metricsMiddleware: KeqMiddleware = async (context, next) => {
  context.data.startTime = Date.now()  // ✅ 类型安全

  await next()

  context.data.metrics = {             // ✅ 结构有类型约束
    duration: Date.now() - context.data.startTime,
    size: Number(context.response?.headers.get("content-length") || 0),
  }
}

扩展 KeqGlobal

KeqGlobal 是跨请求共享的全局数据接口。当你需要在请求之间共享状态(如 token 缓存、请求计数器)时,可以扩展它来获得类型约束:

declare module "keq" {
  interface KeqGlobal {
    tokenCache?: {
      accessToken: string
      expiresAt: number
    }
    requestCount?: number
  }
}

// 在中间件中类型安全地访问全局数据
const tokenMiddleware: KeqMiddleware = async (context, next) => {
  const cache = context.global.tokenCache  // ✅ 类型推导正确

  if (!cache || cache.expiresAt < Date.now()) {
    // 刷新 token...
  }

  context.request.headers.set("Authorization", `Bearer ${cache?.accessToken}`)
  await next()
}

扩展 KeqEvents

通过扩展 KeqEvents 接口定义自定义事件的类型签名,使事件的触发和监听都是类型安全的:

import { KeqContext } from "keq"

declare module "keq" {
  interface KeqEvents {
    "cache:hit": { context: KeqContext; key: string }
    "cache:miss": { context: KeqContext; key: string }
  }
}

// 触发事件时,参数结构受类型约束
const cacheMiddleware: KeqMiddleware = async (context, next) => {
  const key = context.request.url.toString()
  const cached = getCache(key)

  if (cached) {
    context.emitter.emit("cache:hit", { context, key })  // ✅ 参数类型正确
    context.output = cached
    return
  }

  context.emitter.emit("cache:miss", { context, key })
  await next()
}

// 监听事件时,回调参数有完整的类型提示
await request
  .get("/api/cats")
  .use(cacheMiddleware)
  .on("cache:hit", ({ key }) => {    // ✅ key 被推导为 string
    console.log("命中缓存:", key)
  })

KeqOperation 字段类型详解

KeqOperation 定义了单个 API 操作的完整类型结构。各字段支持的值类型如下:

字段类型说明
requestParams{ [key: string]: KeqPathParameterInit }路由参数,值支持 URI Template 变量类型(string、number、数组等)
requestQuery{ [key: string]: KeqQueryInit }查询参数,支持 string | number | bigint | null | undefined | 嵌套对象 | 数组
requestHeaders{ [key: string]: string | number }请求头
requestBodyKeqBodyInit请求体,支持 JSON 对象、FormData、URLSearchParams、Blob、ReadableStream、ArrayBuffer、string
responseBodyany响应体,可以是任意类型
索引签名是必须的

在定义 KeqApiSchema 时,requestParamsrequestQueryrequestHeaders 字段都必须包含索引签名。这是因为 Keq 的链式 API 同时支持类型约束的已知 key 和动态的任意 key。缺少索引签名会导致编译错误。

import { KeqApiSchema, KeqQueryValue, KeqPathParameterInit } from "keq"

interface MyApiSchema extends KeqApiSchema {
  "/users/{id}/posts": {
    get: {
      requestParams: {
        id: string
        [key: string]: KeqPathParameterInit  // 必须
      }
      requestQuery: {
        page: number
        limit: number
        tags: string[]                       // 支持数组类型
        filter: { status: string }           // 支持嵌套对象
        [key: string]: KeqQueryValue         // 必须
      }
      requestHeaders: {
        "X-Request-ID": string
        [key: string]: string | number       // 必须
      }
      requestBody: {}
      responseBody: {
        data: Array<{ id: string; title: string }>
        total: number
      }
    }
  }
}

与 keq-cli 配合使用

对于大型项目,手动编写 KeqApiSchema 并不现实。keq-cli 能够从 OpenAPI/Swagger 文档自动生成完整的类型定义,包括所有请求参数、请求体和响应体的类型约束。

npx keq-cli build -o ./src/api -m userService ./openapi.json

生成的代码会自动包含完整的 KeqApiSchema 类型,开箱即用:

import { request } from "./src/api/user-service"

// 自动拥有完整的类型提示和检查
const users = await request.get("/users")
  .query("page", 1)       // ✅ 类型检查
  .query("limit", 20)     // ✅ 类型检查