A lightweight GraphQL request client for cc ecosystem
npm install @ctil/gql轻量级 GraphQL 请求客户端,开箱即用地支持 Token 管理、设备指纹注入、请求/响应拦截、内存缓存与限流封装,适用于浏览器与 Node.js 场景。
- 核心能力:基于 graphql-request 的统一请求管道
- 安全与体验:内置登录信息存取与无感刷新(refresh token)
- 稳健:内置防抖/速率限制与可选查询结果缓存
- 易用 API:auth、query、mutation、sms、gql、oss 六大模块开箱即用
---
``bash`
npm i @ctil/gql或者
pnpm add @ctil/gql或者
yarn add @ctil/gql
可选依赖(仅浏览器端需要且可自动降级):
- @fingerprintjs/fingerprintjs:更稳定的设备指纹生成
---
ts
import { initGraphQLClient, useInterceptor } from '@ctil/gql';initGraphQLClient({
endpoint: 'https://your-graphql-endpoint/graphql',
// Authorization: 'your-initial-access-token',
// headers: { 'X-Custom-Header': 'value' },
});
// 可选:注册拦截器(支持多个)
useInterceptor({
onRequest: async ({ query, variables, headers }) => {
// 在这里可以统一追加 header / 修改 query / 埋点
return { query, variables, headers };
},
onResponse: async (data) => {
// 在这里可以统一解包响应结构
return data;
},
onError: async (error) => {
// 在这里可以统一处理错误(如上报、提示)
throw error;
},
});
`$3
`ts
import { query } from '@ctil/gql';// 列表查询(带可选缓存)
const list = await query.list({
operationName: 'user',
fields: ['id', 'username', { roles: { fields: ['id', 'roleCode'] } }],
where: { username: { _ilike: '%cai%' } },
orderBy: { id: desc },
limit: 10,
offset: 0,
});
// 主键查询
const byId = await query.byId({
operationName: 'user',
pk: '1000000000',
fields: ['id', 'username', 'phone'],
});
// 分页查询(getXxxPageList)
const page = await query.page({
operationName: 'user',
fields: ['id', 'username'],
page: 1,
size: 20,
});
// 聚合查询(count/sum/avg/max/min + nodes)
const agg = await query.aggregate({
operationName: 'user',
fields: ['id', 'username'],
aggregateFields: { count: true },
});
`$3
`ts
import { mutation } from '@ctil/gql';// 插入单条
await mutation.insertOne({
operationName: 'user',
fields: ['id', 'username'],
data: { username: 'caicai', phone: '18800000000' },
});
// 条件更新
await mutation.update({
operationName: 'user',
fields: { affected_rows: true },
_set: { nickname: 'Cai' },
where: { id: { _eq: 1000000000 } },
});
// 按主键更新
await mutation.updateByPk({
operationName: 'user',
fields: ['id', 'nickname'],
_set: { nickname: 'NewName' },
pk_columns: 1000000000,
});
// 条件删除
await mutation.delete({
operationName: 'user',
fields: ['id'],
where: { id: { _eq: 1000000000 } },
});
`$3
`ts
import { auth, setToken, removeToken, getLoginInfo } from '@ctil/gql';// 登录(会自动持久化登录信息,并覆盖 Bearer Token)
const loginRes = await auth.login({
account: 'caicai',
password: 'caicai',
remember: true, // 浏览器:localStorage;false 为 sessionStorage
});
// 获取/移除登录态
const info = getLoginInfo();
removeToken(); // 或者 auth.logout() 同时清缓存
// 刷新 Token(请求前会自动无感刷新,无需手动调用)
await auth.refreshToken({ refreshToken: info!.refreshToken, remember: true });
// 登出(当前/全部设备/指定设备)
await auth.logout();
await auth.logoutAllDevices();
await auth.logoutDevice('device-id-xxx');
`$3
`ts
import { sms } from '@ctil/gql';await sms.send({ phone: '18800000000' });
await sms.verify({ phone: '18800000000', code: '123456' });
`$3
`ts
import { gql } from '@ctil/gql';const data = await gql.execute(
, { id: 1000000000 });
`$3
`ts
import { oss } from '@ctil/gql';// 本地文件上传
const file = new File();
const rs= await oss.uploadFile({
file: file
})
// 网络资源上传
const rs= await oss.uploadFromUrl({
url: "网络url",
})
// 根据资源ID获取文件
const rs= await oss.getFilePreview("1000000043")
`---
Token 与登录信息管理
- 浏览器端:支持
localStorage(remember=true)或 sessionStorage 自动持久化
- Node.js:默认写入当前工作目录的 loginInfo.json,且保存在进程内存
- 自动无感刷新:对非 refreshToken 的请求,会在请求前检查 access/refresh 过期并尝试刷新;刷新失败会清除登录态并抛错
- 快捷方法:
- setToken(token) / removeToken()
- setLoginInfo(userToken, remember?) / removeLoginInfo() / getLoginInfo()UserToken 结构包含:
userId、loginAccount、token、refreshToken、expireAt、refreshExpireAt、roles、permissions、可选 deviceId/deviceName 等。---
设备信息注入
每个请求都会自动在 Header 注入:
-
X-Device-Id
- X-Device-Name来源:
- 浏览器端:优先使用
@fingerprintjs/fingerprintjs;失败则回落到 IndexedDB + UUID;deviceName 基于平台和分辨率拼装
- Node.js:使用 node-machine-id 获取机器 ID,os.hostname() 作为设备名---
缓存与限流
- 查询缓存:
query.list/byId/page/aggregate 支持内存缓存(默认开启,TTL=5min)。
- 通过第二、三个参数控制:useCache?: boolean、ttl?: number
- 变更类操作(mutation.*)会自动清空缓存,保证一致性
- 限流:rateLimit 对常见操作内置默认规则,可通过 rateLimitConfig 自定义:
- 默认:query 每秒最多 10 次;mutation 每秒最多 3 次(并带 200ms 防抖)
- 针对登录与刷新有更严格的默认规则---
API 速览
- 客户端与配置:
-
initGraphQLClient(config)、getClient()、useInterceptor(i)
- setEndpoint(url)、setHeader(k,v)、setHeaders(obj)、removeHeader(k)、clearHeaders()
- 鉴权:auth.login、auth.register、auth.logout、auth.logoutAllDevices、auth.logoutDevice、auth.refreshToken
- 查询:query.list、query.byId、query.page、query.aggregate
- 变更:mutation.insertOne、mutation.batchInsert、mutation.update、mutation.batchUpdate、mutation.updateByPk、mutation.delete、mutation.deleteById
- 短信:sms.send、sms.verify
- 原生:gql.execute(query, variables?)
- 对象存储:oss.upload、oss.uploadFromUrl、oss.getFilePreview所有模块均具备良好的 TypeScript 类型提示与默认返回范型:
。---
运行与构建
- 开发构建:
npm run dev(tsup --watch)
- 生产构建:npm run build
- 发布前置:prepublishOnly 会自动构建输出:
- CJS:
dist/index.cjs
- ESM:dist/index.mjs
- 类型:dist/index.d.ts---
使用注意
- 在首次使用前务必调用
initGraphQLClient 初始化客户端
- 如果你手动设置/移除 Token,请使用导出的 setToken/removeToken,以确保内部客户端重建
- 使用 useInterceptor 可以实现统一日志、透传 Trace-Id、错误收敛等
- 若你不希望缓存,可在查询方法中传 useCache=false`---
MIT © CaiCai@3104591385@qq.com