npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ctil/gql

v1.1.6

Published

A lightweight GraphQL request client for cc ecosystem

Readme

@ctil/gql

轻量级 GraphQL 请求客户端,开箱即用地支持 Token 管理、设备指纹注入、请求/响应拦截、内存缓存与限流封装,适用于浏览器与 Node.js 场景。

  • 核心能力:基于 graphql-request 的统一请求管道
  • 安全与体验:内置登录信息存取与无感刷新(refresh token)
  • 稳健:内置防抖/速率限制与可选查询结果缓存
  • 易用 APIauthquerymutationsmsgqloss 六大模块开箱即用

安装

npm i @ctil/gql
# 或者
pnpm add @ctil/gql
# 或者
yarn add @ctil/gql

可选依赖(仅浏览器端需要且可自动降级):

  • @fingerprintjs/fingerprintjs:更稳定的设备指纹生成

快速开始

1) 初始化客户端

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;
  },
});

2) Query 查询

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) Mutation 变更

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 } },
});

4) Auth 鉴权

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');

5) 短信模块

import { sms } from '@ctil/gql';

await sms.send({ phone: '18800000000' });
await sms.verify({ phone: '18800000000', code: '123456' });

6) 原生 GQL 执行

import { gql } from '@ctil/gql';

const data = await gql.execute(`
  query user_by_pk($id: Long!) {
    user_by_pk(id: $id) { id username }
  }
`, { id: 1000000000 });

7) 对象存储 执行

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 结构包含:userIdloginAccounttokenrefreshTokenexpireAtrefreshExpireAtrolespermissions、可选 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?: booleanttl?: 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.loginauth.registerauth.logoutauth.logoutAllDevicesauth.logoutDeviceauth.refreshToken
  • 查询:query.listquery.byIdquery.pagequery.aggregate
  • 变更:mutation.insertOnemutation.batchInsertmutation.updatemutation.batchUpdatemutation.updateByPkmutation.deletemutation.deleteById
  • 短信:sms.sendsms.verify
  • 原生:gql.execute(query, variables?)
  • 对象存储:oss.uploadoss.uploadFromUrloss.getFilePreview

所有模块均具备良好的 TypeScript 类型提示与默认返回范型:<T = requestResult<any>>


运行与构建

  • 开发构建: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@[email protected]