@ctil/gql 1.0.20 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +269 -269
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/{fp.esm-VY6KF7TP.js → fp.esm-QIG5OYFT.js} +3 -1
- package/dist/{fp.esm-VY6KF7TP.js.map → fp.esm-QIG5OYFT.js.map} +1 -1
- package/dist/index.cjs +554 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +337 -56
- package/dist/index.d.ts +337 -56
- package/dist/index.js +488 -13
- package/dist/index.js.map +1 -1
- package/package.json +53 -53
package/README.md
CHANGED
|
@@ -1,269 +1,269 @@
|
|
|
1
|
-
# @ctil/gql
|
|
2
|
-
|
|
3
|
-
轻量级 GraphQL 请求客户端,开箱即用地支持 Token 管理、设备指纹注入、请求/响应拦截、内存缓存与限流封装,适用于浏览器与 Node.js 场景。
|
|
4
|
-
|
|
5
|
-
- **核心能力**:基于 `graphql-request` 的统一请求管道
|
|
6
|
-
- **安全与体验**:内置登录信息存取与无感刷新(refresh token)
|
|
7
|
-
- **稳健**:内置防抖/速率限制与可选查询结果缓存
|
|
8
|
-
- **易用 API**:`auth`、`query`、`mutation`、`sms`、`gql`、`oss` 六大模块开箱即用
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## 安装
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
npm i @ctil/gql
|
|
16
|
-
# 或者
|
|
17
|
-
pnpm add @ctil/gql
|
|
18
|
-
# 或者
|
|
19
|
-
yarn add @ctil/gql
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
可选依赖(仅浏览器端需要且可自动降级):
|
|
23
|
-
- `@fingerprintjs/fingerprintjs`:更稳定的设备指纹生成
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## 快速开始
|
|
28
|
-
|
|
29
|
-
### 1) 初始化客户端
|
|
30
|
-
```ts
|
|
31
|
-
import { initGraphQLClient, useInterceptor } from '@ctil/gql';
|
|
32
|
-
|
|
33
|
-
initGraphQLClient({
|
|
34
|
-
endpoint: 'https://your-graphql-endpoint/graphql',
|
|
35
|
-
// Authorization: 'your-initial-access-token',
|
|
36
|
-
// headers: { 'X-Custom-Header': 'value' },
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// 可选:注册拦截器(支持多个)
|
|
40
|
-
useInterceptor({
|
|
41
|
-
onRequest: async ({ query, variables, headers }) => {
|
|
42
|
-
// 在这里可以统一追加 header / 修改 query / 埋点
|
|
43
|
-
return { query, variables, headers };
|
|
44
|
-
},
|
|
45
|
-
onResponse: async (data) => {
|
|
46
|
-
// 在这里可以统一解包响应结构
|
|
47
|
-
return data;
|
|
48
|
-
},
|
|
49
|
-
onError: async (error) => {
|
|
50
|
-
// 在这里可以统一处理错误(如上报、提示)
|
|
51
|
-
throw error;
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### 2) Query 查询
|
|
57
|
-
```ts
|
|
58
|
-
import { query } from '@ctil/gql';
|
|
59
|
-
|
|
60
|
-
// 列表查询(带可选缓存)
|
|
61
|
-
const list = await query.list({
|
|
62
|
-
operationName: 'user',
|
|
63
|
-
fields: ['id', 'username', { roles: { fields: ['id', 'roleCode'] } }],
|
|
64
|
-
where: { username: { _ilike: '%cai%' } },
|
|
65
|
-
orderBy: { id: desc },
|
|
66
|
-
limit: 10,
|
|
67
|
-
offset: 0,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// 主键查询
|
|
71
|
-
const byId = await query.byId({
|
|
72
|
-
operationName: 'user',
|
|
73
|
-
pk: '1000000000',
|
|
74
|
-
fields: ['id', 'username', 'phone'],
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// 分页查询(getXxxPageList)
|
|
78
|
-
const page = await query.page({
|
|
79
|
-
operationName: 'user',
|
|
80
|
-
fields: ['id', 'username'],
|
|
81
|
-
page: 1,
|
|
82
|
-
size: 20,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// 聚合查询(count/sum/avg/max/min + nodes)
|
|
86
|
-
const agg = await query.aggregate({
|
|
87
|
-
operationName: 'user',
|
|
88
|
-
fields: ['id', 'username'],
|
|
89
|
-
aggregateFields: { count: true },
|
|
90
|
-
});
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### 3) Mutation 变更
|
|
94
|
-
```ts
|
|
95
|
-
import { mutation } from '@ctil/gql';
|
|
96
|
-
|
|
97
|
-
// 插入单条
|
|
98
|
-
await mutation.insertOne({
|
|
99
|
-
operationName: 'user',
|
|
100
|
-
fields: ['id', 'username'],
|
|
101
|
-
data: { username: 'caicai', phone: '18800000000' },
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// 条件更新
|
|
105
|
-
await mutation.update({
|
|
106
|
-
operationName: 'user',
|
|
107
|
-
fields: { affected_rows: true },
|
|
108
|
-
_set: { nickname: 'Cai' },
|
|
109
|
-
where: { id: { _eq: 1000000000 } },
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// 按主键更新
|
|
113
|
-
await mutation.updateByPk({
|
|
114
|
-
operationName: 'user',
|
|
115
|
-
fields: ['id', 'nickname'],
|
|
116
|
-
_set: { nickname: 'NewName' },
|
|
117
|
-
pk_columns: 1000000000,
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// 条件删除
|
|
121
|
-
await mutation.delete({
|
|
122
|
-
operationName: 'user',
|
|
123
|
-
fields: ['id'],
|
|
124
|
-
where: { id: { _eq: 1000000000 } },
|
|
125
|
-
});
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### 4) Auth 鉴权
|
|
129
|
-
```ts
|
|
130
|
-
import { auth, setToken, removeToken, getLoginInfo } from '@ctil/gql';
|
|
131
|
-
|
|
132
|
-
// 登录(会自动持久化登录信息,并覆盖 Bearer Token)
|
|
133
|
-
const loginRes = await auth.login({
|
|
134
|
-
account: 'caicai',
|
|
135
|
-
password: 'caicai',
|
|
136
|
-
remember: true, // 浏览器:localStorage;false 为 sessionStorage
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// 获取/移除登录态
|
|
140
|
-
const info = getLoginInfo();
|
|
141
|
-
removeToken(); // 或者 auth.logout() 同时清缓存
|
|
142
|
-
|
|
143
|
-
// 刷新 Token(请求前会自动无感刷新,无需手动调用)
|
|
144
|
-
await auth.refreshToken({ refreshToken: info!.refreshToken, remember: true });
|
|
145
|
-
|
|
146
|
-
// 登出(当前/全部设备/指定设备)
|
|
147
|
-
await auth.logout();
|
|
148
|
-
await auth.logoutAllDevices();
|
|
149
|
-
await auth.logoutDevice('device-id-xxx');
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### 5) 短信模块
|
|
153
|
-
```ts
|
|
154
|
-
import { sms } from '@ctil/gql';
|
|
155
|
-
|
|
156
|
-
await sms.send({ phone: '18800000000' });
|
|
157
|
-
await sms.verify({ phone: '18800000000', code: '123456' });
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### 6) 原生 GQL 执行
|
|
161
|
-
```ts
|
|
162
|
-
import { gql } from '@ctil/gql';
|
|
163
|
-
|
|
164
|
-
const data = await gql.execute(`
|
|
165
|
-
query user_by_pk($id: Long!) {
|
|
166
|
-
user_by_pk(id: $id) { id username }
|
|
167
|
-
}
|
|
168
|
-
`, { id: 1000000000 });
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### 7) 对象存储 执行
|
|
172
|
-
```ts
|
|
173
|
-
import { oss } from '@ctil/gql';
|
|
174
|
-
|
|
175
|
-
// 本地文件上传
|
|
176
|
-
const file = new File();
|
|
177
|
-
const rs= await oss.uploadFile({
|
|
178
|
-
file: file
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
// 网络资源上传
|
|
182
|
-
const rs= await oss.uploadFromUrl({
|
|
183
|
-
url: "网络url",
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
// 根据资源ID获取文件
|
|
187
|
-
const rs= await oss.getFilePreview("1000000043")
|
|
188
|
-
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## Token 与登录信息管理
|
|
194
|
-
|
|
195
|
-
- 浏览器端:支持 `localStorage`(remember=true)或 `sessionStorage` 自动持久化
|
|
196
|
-
- Node.js:默认写入当前工作目录的 `loginInfo.json`,且保存在进程内存
|
|
197
|
-
- 自动无感刷新:对非 `refreshToken` 的请求,会在请求前检查 access/refresh 过期并尝试刷新;刷新失败会清除登录态并抛错
|
|
198
|
-
- 快捷方法:
|
|
199
|
-
- `setToken(token)` / `removeToken()`
|
|
200
|
-
- `setLoginInfo(userToken, remember?)` / `removeLoginInfo()` / `getLoginInfo()`
|
|
201
|
-
|
|
202
|
-
UserToken 结构包含:`userId`、`loginAccount`、`token`、`refreshToken`、`expireAt`、`refreshExpireAt`、`roles`、`permissions`、可选 `deviceId/deviceName` 等。
|
|
203
|
-
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
## 设备信息注入
|
|
207
|
-
|
|
208
|
-
每个请求都会自动在 Header 注入:
|
|
209
|
-
- `X-Device-Id`
|
|
210
|
-
- `X-Device-Name`
|
|
211
|
-
|
|
212
|
-
来源:
|
|
213
|
-
- 浏览器端:优先使用 `@fingerprintjs/fingerprintjs`;失败则回落到 IndexedDB + UUID;`deviceName` 基于平台和分辨率拼装
|
|
214
|
-
- Node.js:使用 `node-machine-id` 获取机器 ID,`os.hostname()` 作为设备名
|
|
215
|
-
|
|
216
|
-
---
|
|
217
|
-
|
|
218
|
-
## 缓存与限流
|
|
219
|
-
|
|
220
|
-
- 查询缓存:`query.list/byId/page/aggregate` 支持内存缓存(默认开启,TTL=5min)。
|
|
221
|
-
- 通过第二、三个参数控制:`useCache?: boolean`、`ttl?: number`
|
|
222
|
-
- 变更类操作(`mutation.*`)会自动清空缓存,保证一致性
|
|
223
|
-
- 限流:`rateLimit` 对常见操作内置默认规则,可通过 `rateLimitConfig` 自定义:
|
|
224
|
-
- 默认:query 每秒最多 10 次;mutation 每秒最多 3 次(并带 200ms 防抖)
|
|
225
|
-
- 针对登录与刷新有更严格的默认规则
|
|
226
|
-
|
|
227
|
-
---
|
|
228
|
-
|
|
229
|
-
## API 速览
|
|
230
|
-
|
|
231
|
-
- 客户端与配置:
|
|
232
|
-
- `initGraphQLClient(config)`、`getClient()`、`useInterceptor(i)`
|
|
233
|
-
- `setEndpoint(url)`、`setHeader(k,v)`、`setHeaders(obj)`、`removeHeader(k)`、`clearHeaders()`
|
|
234
|
-
- 鉴权:`auth.login`、`auth.register`、`auth.logout`、`auth.logoutAllDevices`、`auth.logoutDevice`、`auth.refreshToken`
|
|
235
|
-
- 查询:`query.list`、`query.byId`、`query.page`、`query.aggregate`
|
|
236
|
-
- 变更:`mutation.insertOne`、`mutation.batchInsert`、`mutation.update`、`mutation.batchUpdate`、`mutation.updateByPk`、`mutation.delete`、`mutation.deleteById`
|
|
237
|
-
- 短信:`sms.send`、`sms.verify`
|
|
238
|
-
- 原生:`gql.execute(query, variables?)`
|
|
239
|
-
- 对象存储:`oss.upload`、`oss.uploadFromUrl`、`oss.getFilePreview`
|
|
240
|
-
|
|
241
|
-
所有模块均具备良好的 TypeScript 类型提示与默认返回范型:`<T = requestResult<any>>`。
|
|
242
|
-
|
|
243
|
-
---
|
|
244
|
-
|
|
245
|
-
## 运行与构建
|
|
246
|
-
|
|
247
|
-
- 开发构建:`npm run dev`(tsup --watch)
|
|
248
|
-
- 生产构建:`npm run build`
|
|
249
|
-
- 发布前置:`prepublishOnly` 会自动构建
|
|
250
|
-
|
|
251
|
-
输出:
|
|
252
|
-
- CJS:`dist/index.cjs`
|
|
253
|
-
- ESM:`dist/index.mjs`
|
|
254
|
-
- 类型:`dist/index.d.ts`
|
|
255
|
-
|
|
256
|
-
---
|
|
257
|
-
|
|
258
|
-
## 使用注意
|
|
259
|
-
|
|
260
|
-
- 在首次使用前务必调用 `initGraphQLClient` 初始化客户端
|
|
261
|
-
- 如果你手动设置/移除 Token,请使用导出的 `setToken`/`removeToken`,以确保内部客户端重建
|
|
262
|
-
- 使用 `useInterceptor` 可以实现统一日志、透传 Trace-Id、错误收敛等
|
|
263
|
-
- 若你不希望缓存,可在查询方法中传 `useCache=false`
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
## 许可证
|
|
268
|
-
|
|
269
|
-
MIT © CaiCai@3104591385@qq.com
|
|
1
|
+
# @ctil/gql
|
|
2
|
+
|
|
3
|
+
轻量级 GraphQL 请求客户端,开箱即用地支持 Token 管理、设备指纹注入、请求/响应拦截、内存缓存与限流封装,适用于浏览器与 Node.js 场景。
|
|
4
|
+
|
|
5
|
+
- **核心能力**:基于 `graphql-request` 的统一请求管道
|
|
6
|
+
- **安全与体验**:内置登录信息存取与无感刷新(refresh token)
|
|
7
|
+
- **稳健**:内置防抖/速率限制与可选查询结果缓存
|
|
8
|
+
- **易用 API**:`auth`、`query`、`mutation`、`sms`、`gql`、`oss` 六大模块开箱即用
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm i @ctil/gql
|
|
16
|
+
# 或者
|
|
17
|
+
pnpm add @ctil/gql
|
|
18
|
+
# 或者
|
|
19
|
+
yarn add @ctil/gql
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
可选依赖(仅浏览器端需要且可自动降级):
|
|
23
|
+
- `@fingerprintjs/fingerprintjs`:更稳定的设备指纹生成
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 快速开始
|
|
28
|
+
|
|
29
|
+
### 1) 初始化客户端
|
|
30
|
+
```ts
|
|
31
|
+
import { initGraphQLClient, useInterceptor } from '@ctil/gql';
|
|
32
|
+
|
|
33
|
+
initGraphQLClient({
|
|
34
|
+
endpoint: 'https://your-graphql-endpoint/graphql',
|
|
35
|
+
// Authorization: 'your-initial-access-token',
|
|
36
|
+
// headers: { 'X-Custom-Header': 'value' },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// 可选:注册拦截器(支持多个)
|
|
40
|
+
useInterceptor({
|
|
41
|
+
onRequest: async ({ query, variables, headers }) => {
|
|
42
|
+
// 在这里可以统一追加 header / 修改 query / 埋点
|
|
43
|
+
return { query, variables, headers };
|
|
44
|
+
},
|
|
45
|
+
onResponse: async (data) => {
|
|
46
|
+
// 在这里可以统一解包响应结构
|
|
47
|
+
return data;
|
|
48
|
+
},
|
|
49
|
+
onError: async (error) => {
|
|
50
|
+
// 在这里可以统一处理错误(如上报、提示)
|
|
51
|
+
throw error;
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2) Query 查询
|
|
57
|
+
```ts
|
|
58
|
+
import { query } from '@ctil/gql';
|
|
59
|
+
|
|
60
|
+
// 列表查询(带可选缓存)
|
|
61
|
+
const list = await query.list({
|
|
62
|
+
operationName: 'user',
|
|
63
|
+
fields: ['id', 'username', { roles: { fields: ['id', 'roleCode'] } }],
|
|
64
|
+
where: { username: { _ilike: '%cai%' } },
|
|
65
|
+
orderBy: { id: desc },
|
|
66
|
+
limit: 10,
|
|
67
|
+
offset: 0,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// 主键查询
|
|
71
|
+
const byId = await query.byId({
|
|
72
|
+
operationName: 'user',
|
|
73
|
+
pk: '1000000000',
|
|
74
|
+
fields: ['id', 'username', 'phone'],
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// 分页查询(getXxxPageList)
|
|
78
|
+
const page = await query.page({
|
|
79
|
+
operationName: 'user',
|
|
80
|
+
fields: ['id', 'username'],
|
|
81
|
+
page: 1,
|
|
82
|
+
size: 20,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// 聚合查询(count/sum/avg/max/min + nodes)
|
|
86
|
+
const agg = await query.aggregate({
|
|
87
|
+
operationName: 'user',
|
|
88
|
+
fields: ['id', 'username'],
|
|
89
|
+
aggregateFields: { count: true },
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3) Mutation 变更
|
|
94
|
+
```ts
|
|
95
|
+
import { mutation } from '@ctil/gql';
|
|
96
|
+
|
|
97
|
+
// 插入单条
|
|
98
|
+
await mutation.insertOne({
|
|
99
|
+
operationName: 'user',
|
|
100
|
+
fields: ['id', 'username'],
|
|
101
|
+
data: { username: 'caicai', phone: '18800000000' },
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// 条件更新
|
|
105
|
+
await mutation.update({
|
|
106
|
+
operationName: 'user',
|
|
107
|
+
fields: { affected_rows: true },
|
|
108
|
+
_set: { nickname: 'Cai' },
|
|
109
|
+
where: { id: { _eq: 1000000000 } },
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// 按主键更新
|
|
113
|
+
await mutation.updateByPk({
|
|
114
|
+
operationName: 'user',
|
|
115
|
+
fields: ['id', 'nickname'],
|
|
116
|
+
_set: { nickname: 'NewName' },
|
|
117
|
+
pk_columns: 1000000000,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// 条件删除
|
|
121
|
+
await mutation.delete({
|
|
122
|
+
operationName: 'user',
|
|
123
|
+
fields: ['id'],
|
|
124
|
+
where: { id: { _eq: 1000000000 } },
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 4) Auth 鉴权
|
|
129
|
+
```ts
|
|
130
|
+
import { auth, setToken, removeToken, getLoginInfo } from '@ctil/gql';
|
|
131
|
+
|
|
132
|
+
// 登录(会自动持久化登录信息,并覆盖 Bearer Token)
|
|
133
|
+
const loginRes = await auth.login({
|
|
134
|
+
account: 'caicai',
|
|
135
|
+
password: 'caicai',
|
|
136
|
+
remember: true, // 浏览器:localStorage;false 为 sessionStorage
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// 获取/移除登录态
|
|
140
|
+
const info = getLoginInfo();
|
|
141
|
+
removeToken(); // 或者 auth.logout() 同时清缓存
|
|
142
|
+
|
|
143
|
+
// 刷新 Token(请求前会自动无感刷新,无需手动调用)
|
|
144
|
+
await auth.refreshToken({ refreshToken: info!.refreshToken, remember: true });
|
|
145
|
+
|
|
146
|
+
// 登出(当前/全部设备/指定设备)
|
|
147
|
+
await auth.logout();
|
|
148
|
+
await auth.logoutAllDevices();
|
|
149
|
+
await auth.logoutDevice('device-id-xxx');
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 5) 短信模块
|
|
153
|
+
```ts
|
|
154
|
+
import { sms } from '@ctil/gql';
|
|
155
|
+
|
|
156
|
+
await sms.send({ phone: '18800000000' });
|
|
157
|
+
await sms.verify({ phone: '18800000000', code: '123456' });
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 6) 原生 GQL 执行
|
|
161
|
+
```ts
|
|
162
|
+
import { gql } from '@ctil/gql';
|
|
163
|
+
|
|
164
|
+
const data = await gql.execute(`
|
|
165
|
+
query user_by_pk($id: Long!) {
|
|
166
|
+
user_by_pk(id: $id) { id username }
|
|
167
|
+
}
|
|
168
|
+
`, { id: 1000000000 });
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 7) 对象存储 执行
|
|
172
|
+
```ts
|
|
173
|
+
import { oss } from '@ctil/gql';
|
|
174
|
+
|
|
175
|
+
// 本地文件上传
|
|
176
|
+
const file = new File();
|
|
177
|
+
const rs= await oss.uploadFile({
|
|
178
|
+
file: file
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
// 网络资源上传
|
|
182
|
+
const rs= await oss.uploadFromUrl({
|
|
183
|
+
url: "网络url",
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// 根据资源ID获取文件
|
|
187
|
+
const rs= await oss.getFilePreview("1000000043")
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Token 与登录信息管理
|
|
194
|
+
|
|
195
|
+
- 浏览器端:支持 `localStorage`(remember=true)或 `sessionStorage` 自动持久化
|
|
196
|
+
- Node.js:默认写入当前工作目录的 `loginInfo.json`,且保存在进程内存
|
|
197
|
+
- 自动无感刷新:对非 `refreshToken` 的请求,会在请求前检查 access/refresh 过期并尝试刷新;刷新失败会清除登录态并抛错
|
|
198
|
+
- 快捷方法:
|
|
199
|
+
- `setToken(token)` / `removeToken()`
|
|
200
|
+
- `setLoginInfo(userToken, remember?)` / `removeLoginInfo()` / `getLoginInfo()`
|
|
201
|
+
|
|
202
|
+
UserToken 结构包含:`userId`、`loginAccount`、`token`、`refreshToken`、`expireAt`、`refreshExpireAt`、`roles`、`permissions`、可选 `deviceId/deviceName` 等。
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 设备信息注入
|
|
207
|
+
|
|
208
|
+
每个请求都会自动在 Header 注入:
|
|
209
|
+
- `X-Device-Id`
|
|
210
|
+
- `X-Device-Name`
|
|
211
|
+
|
|
212
|
+
来源:
|
|
213
|
+
- 浏览器端:优先使用 `@fingerprintjs/fingerprintjs`;失败则回落到 IndexedDB + UUID;`deviceName` 基于平台和分辨率拼装
|
|
214
|
+
- Node.js:使用 `node-machine-id` 获取机器 ID,`os.hostname()` 作为设备名
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## 缓存与限流
|
|
219
|
+
|
|
220
|
+
- 查询缓存:`query.list/byId/page/aggregate` 支持内存缓存(默认开启,TTL=5min)。
|
|
221
|
+
- 通过第二、三个参数控制:`useCache?: boolean`、`ttl?: number`
|
|
222
|
+
- 变更类操作(`mutation.*`)会自动清空缓存,保证一致性
|
|
223
|
+
- 限流:`rateLimit` 对常见操作内置默认规则,可通过 `rateLimitConfig` 自定义:
|
|
224
|
+
- 默认:query 每秒最多 10 次;mutation 每秒最多 3 次(并带 200ms 防抖)
|
|
225
|
+
- 针对登录与刷新有更严格的默认规则
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## API 速览
|
|
230
|
+
|
|
231
|
+
- 客户端与配置:
|
|
232
|
+
- `initGraphQLClient(config)`、`getClient()`、`useInterceptor(i)`
|
|
233
|
+
- `setEndpoint(url)`、`setHeader(k,v)`、`setHeaders(obj)`、`removeHeader(k)`、`clearHeaders()`
|
|
234
|
+
- 鉴权:`auth.login`、`auth.register`、`auth.logout`、`auth.logoutAllDevices`、`auth.logoutDevice`、`auth.refreshToken`
|
|
235
|
+
- 查询:`query.list`、`query.byId`、`query.page`、`query.aggregate`
|
|
236
|
+
- 变更:`mutation.insertOne`、`mutation.batchInsert`、`mutation.update`、`mutation.batchUpdate`、`mutation.updateByPk`、`mutation.delete`、`mutation.deleteById`
|
|
237
|
+
- 短信:`sms.send`、`sms.verify`
|
|
238
|
+
- 原生:`gql.execute(query, variables?)`
|
|
239
|
+
- 对象存储:`oss.upload`、`oss.uploadFromUrl`、`oss.getFilePreview`
|
|
240
|
+
|
|
241
|
+
所有模块均具备良好的 TypeScript 类型提示与默认返回范型:`<T = requestResult<any>>`。
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## 运行与构建
|
|
246
|
+
|
|
247
|
+
- 开发构建:`npm run dev`(tsup --watch)
|
|
248
|
+
- 生产构建:`npm run build`
|
|
249
|
+
- 发布前置:`prepublishOnly` 会自动构建
|
|
250
|
+
|
|
251
|
+
输出:
|
|
252
|
+
- CJS:`dist/index.cjs`
|
|
253
|
+
- ESM:`dist/index.mjs`
|
|
254
|
+
- 类型:`dist/index.d.ts`
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## 使用注意
|
|
259
|
+
|
|
260
|
+
- 在首次使用前务必调用 `initGraphQLClient` 初始化客户端
|
|
261
|
+
- 如果你手动设置/移除 Token,请使用导出的 `setToken`/`removeToken`,以确保内部客户端重建
|
|
262
|
+
- 使用 `useInterceptor` 可以实现统一日志、透传 Trace-Id、错误收敛等
|
|
263
|
+
- 若你不希望缓存,可在查询方法中传 `useCache=false`
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## 许可证
|
|
268
|
+
|
|
269
|
+
MIT © CaiCai@3104591385@qq.com
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import "./chunk-PZ5AY32C.js";
|
|
2
|
+
|
|
1
3
|
// node_modules/tslib/tslib.es6.mjs
|
|
2
4
|
var __assign = function() {
|
|
3
5
|
__assign = Object.assign || function __assign2(t) {
|
|
@@ -2696,4 +2698,4 @@ export {
|
|
|
2696
2698
|
transformSource,
|
|
2697
2699
|
withIframe
|
|
2698
2700
|
};
|
|
2699
|
-
//# sourceMappingURL=fp.esm-
|
|
2701
|
+
//# sourceMappingURL=fp.esm-QIG5OYFT.js.map
|