@ctil/gql 1.0.3 → 1.0.5

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 CHANGED
@@ -5,7 +5,7 @@
5
5
  - **核心能力**:基于 `graphql-request` 的统一请求管道
6
6
  - **安全与体验**:内置登录信息存取与无感刷新(refresh token)
7
7
  - **稳健**:内置防抖/速率限制与可选查询结果缓存
8
- - **易用 API**:`auth`、`query`、`mutation`、`sms`、`gql` 五大模块开箱即用
8
+ - **易用 API**:`auth`、`query`、`mutation`、`sms`、`gql`、`oss` 六大模块开箱即用
9
9
 
10
10
  ---
11
11
 
@@ -28,7 +28,7 @@ yarn add @ctil/gql
28
28
 
29
29
  ### 1) 初始化客户端
30
30
  ```ts
31
- import { initGraphQLClient, useInterceptor } from '@cc/request';
31
+ import { initGraphQLClient, useInterceptor } from '@ctil/gql';
32
32
 
33
33
  initGraphQLClient({
34
34
  endpoint: 'https://your-graphql-endpoint/graphql',
@@ -55,7 +55,7 @@ useInterceptor({
55
55
 
56
56
  ### 2) Query 查询
57
57
  ```ts
58
- import { query } from '@cc/request';
58
+ import { query } from '@ctil/gql';
59
59
 
60
60
  // 列表查询(带可选缓存)
61
61
  const list = await query.list({
@@ -92,7 +92,7 @@ const agg = await query.aggregate({
92
92
 
93
93
  ### 3) Mutation 变更
94
94
  ```ts
95
- import { mutation } from '@cc/request';
95
+ import { mutation } from '@ctil/gql';
96
96
 
97
97
  // 插入单条
98
98
  await mutation.insertOne({
@@ -127,7 +127,7 @@ await mutation.delete({
127
127
 
128
128
  ### 4) Auth 鉴权
129
129
  ```ts
130
- import { auth, setToken, removeToken, getLoginInfo } from '@cc/request';
130
+ import { auth, setToken, removeToken, getLoginInfo } from '@ctil/gql';
131
131
 
132
132
  // 登录(会自动持久化登录信息,并覆盖 Bearer Token)
133
133
  const loginRes = await auth.login({
@@ -151,7 +151,7 @@ await auth.logoutDevice('device-id-xxx');
151
151
 
152
152
  ### 5) 短信模块
153
153
  ```ts
154
- import { sms } from '@cc/request';
154
+ import { sms } from '@ctil/gql';
155
155
 
156
156
  await sms.send({ phone: '18800000000' });
157
157
  await sms.verify({ phone: '18800000000', code: '123456' });
@@ -159,7 +159,7 @@ await sms.verify({ phone: '18800000000', code: '123456' });
159
159
 
160
160
  ### 6) 原生 GQL 执行
161
161
  ```ts
162
- import { gql } from '@cc/request';
162
+ import { gql } from '@ctil/gql';
163
163
 
164
164
  const data = await gql.execute(`
165
165
  query user_by_pk($id: Long!) {
@@ -168,6 +168,26 @@ const data = await gql.execute(`
168
168
  `, { id: 1000000000 });
169
169
  ```
170
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
+
171
191
  ---
172
192
 
173
193
  ## Token 与登录信息管理
@@ -216,6 +236,7 @@ UserToken 结构包含:`userId`、`loginAccount`、`token`、`refreshToken`、
216
236
  - 变更:`mutation.insertOne`、`mutation.batchInsert`、`mutation.update`、`mutation.batchUpdate`、`mutation.updateByPk`、`mutation.delete`、`mutation.deleteById`
217
237
  - 短信:`sms.send`、`sms.verify`
218
238
  - 原生:`gql.execute(query, variables?)`
239
+ - 对象存储:`oss.upload`、`oss.uploadFromUrl`、`oss.getFilePreview`
219
240
 
220
241
  所有模块均具备良好的 TypeScript 类型提示与默认返回范型:`<T = requestResult<any>>`。
221
242
 
@@ -245,4 +266,4 @@ UserToken 结构包含:`userId`、`loginAccount`、`token`、`refreshToken`、
245
266
 
246
267
  ## 许可证
247
268
 
248
- MIT © CaiCai
269
+ MIT © CaiCai@3104591385@qq.com
Binary file
package/dist/index.cjs CHANGED
@@ -2752,6 +2752,7 @@ __export(index_exports, {
2752
2752
  gql: () => gql,
2753
2753
  initGraphQLClient: () => initGraphQLClient,
2754
2754
  mutation: () => mutation,
2755
+ oss: () => oss,
2755
2756
  query: () => query,
2756
2757
  removeHeader: () => removeHeader,
2757
2758
  removeToken: () => removeToken,
@@ -3214,14 +3215,14 @@ function visit(root, visitor, visitorKeys = QueryDocumentKeys) {
3214
3215
  let node = root;
3215
3216
  let key = void 0;
3216
3217
  let parent = void 0;
3217
- const path2 = [];
3218
+ const path3 = [];
3218
3219
  const ancestors = [];
3219
3220
  do {
3220
3221
  index2++;
3221
3222
  const isLeaving = index2 === keys.length;
3222
3223
  const isEdited = isLeaving && edits.length !== 0;
3223
3224
  if (isLeaving) {
3224
- key = ancestors.length === 0 ? void 0 : path2[path2.length - 1];
3225
+ key = ancestors.length === 0 ? void 0 : path3[path3.length - 1];
3225
3226
  node = parent;
3226
3227
  parent = ancestors.pop();
3227
3228
  if (isEdited) {
@@ -3255,20 +3256,20 @@ function visit(root, visitor, visitorKeys = QueryDocumentKeys) {
3255
3256
  if (node === null || node === void 0) {
3256
3257
  continue;
3257
3258
  }
3258
- path2.push(key);
3259
+ path3.push(key);
3259
3260
  }
3260
3261
  let result;
3261
3262
  if (!Array.isArray(node)) {
3262
3263
  var _enterLeaveMap$get, _enterLeaveMap$get2;
3263
3264
  isNode(node) || devAssert(false, `Invalid AST Node: ${inspect(node)}.`);
3264
3265
  const visitFn = isLeaving ? (_enterLeaveMap$get = enterLeaveMap.get(node.kind)) === null || _enterLeaveMap$get === void 0 ? void 0 : _enterLeaveMap$get.leave : (_enterLeaveMap$get2 = enterLeaveMap.get(node.kind)) === null || _enterLeaveMap$get2 === void 0 ? void 0 : _enterLeaveMap$get2.enter;
3265
- result = visitFn === null || visitFn === void 0 ? void 0 : visitFn.call(visitor, node, key, parent, path2, ancestors);
3266
+ result = visitFn === null || visitFn === void 0 ? void 0 : visitFn.call(visitor, node, key, parent, path3, ancestors);
3266
3267
  if (result === BREAK) {
3267
3268
  break;
3268
3269
  }
3269
3270
  if (result === false) {
3270
3271
  if (!isLeaving) {
3271
- path2.pop();
3272
+ path3.pop();
3272
3273
  continue;
3273
3274
  }
3274
3275
  } else if (result !== void 0) {
@@ -3277,7 +3278,7 @@ function visit(root, visitor, visitorKeys = QueryDocumentKeys) {
3277
3278
  if (isNode(result)) {
3278
3279
  node = result;
3279
3280
  } else {
3280
- path2.pop();
3281
+ path3.pop();
3281
3282
  continue;
3282
3283
  }
3283
3284
  }
@@ -3287,7 +3288,7 @@ function visit(root, visitor, visitorKeys = QueryDocumentKeys) {
3287
3288
  edits.push([key, node]);
3288
3289
  }
3289
3290
  if (isLeaving) {
3290
- path2.pop();
3291
+ path3.pop();
3291
3292
  } else {
3292
3293
  var _node$kind;
3293
3294
  stack = {
@@ -4112,6 +4113,63 @@ function buildGraphQLMutationRefreshToken(input) {
4112
4113
  return { query: query2, variables: finalVariables };
4113
4114
  }
4114
4115
 
4116
+ // src/builders/oss.ts
4117
+ function buildGraphQLUploadPresignedUrl(input) {
4118
+ const fields = [
4119
+ "uploadUrl",
4120
+ "downloadUrl",
4121
+ "objectKey",
4122
+ "expireAt",
4123
+ "contentType",
4124
+ "originalFileName",
4125
+ "resourceId",
4126
+ `uploadHeaders {
4127
+ contentMD5
4128
+ contentType
4129
+ x_oss_object_acl
4130
+ date
4131
+ }`
4132
+ ];
4133
+ const finalVariables = { ...input };
4134
+ const varDefsArr = [];
4135
+ varDefsArr.push("$fileMd5Base64: String!");
4136
+ varDefsArr.push("$fileSuffix: String!");
4137
+ varDefsArr.push("$originalFileName: String!");
4138
+ varDefsArr.push("$fileSize: Long!");
4139
+ if (input.acl) varDefsArr.push("$acl: String");
4140
+ if (input.folder) varDefsArr.push("$folder: String!");
4141
+ const varDefs = varDefsArr.join(", ");
4142
+ const query2 = `mutation uploadPresignedUrl${varDefs ? `(${varDefs})` : ""} {
4143
+ uploadPresignedUrl(
4144
+ fileMd5Base64: $fileMd5Base64,
4145
+ fileSuffix: $fileSuffix,
4146
+ originalFileName: $originalFileName,
4147
+ ${input.folder ? ", folder: $folder" : ""}
4148
+ fileSize: $fileSize
4149
+ ${input.acl ? ", acl: $acl" : ""}
4150
+ ) {
4151
+ ${buildFields(fields)}
4152
+ }
4153
+ }`;
4154
+ return { query: query2, variables: finalVariables };
4155
+ }
4156
+ function buildGraphQLGetFilePreview(resourceId) {
4157
+ const fields = [
4158
+ "id",
4159
+ "originalFileName",
4160
+ "contentType",
4161
+ "fileSize",
4162
+ "url"
4163
+ ];
4164
+ const finalVariables = { resourceId: Number(resourceId) };
4165
+ const query2 = `query getFilePreview($resourceId: Long!) {
4166
+ getFilePreview(resourceId: $resourceId) {
4167
+ ${buildFields(fields)}
4168
+ }
4169
+ }`;
4170
+ return { query: query2, variables: finalVariables };
4171
+ }
4172
+
4115
4173
  // src/rateLimit/rateLimitConfig.ts
4116
4174
  var rateLimitConfig = {
4117
4175
  defaultRules: {
@@ -4618,6 +4676,153 @@ var gql = {
4618
4676
  return execute({ query: query2, variables });
4619
4677
  }
4620
4678
  };
4679
+
4680
+ // src/core/api/oss.ts
4681
+ var import_crypto = __toESM(require("crypto"), 1);
4682
+ var import_fs2 = __toESM(require("fs"), 1);
4683
+ var import_path2 = __toESM(require("path"), 1);
4684
+ async function computeFileMd5Base64(file) {
4685
+ if (typeof window === "undefined") {
4686
+ let buffer;
4687
+ if (typeof file === "string") {
4688
+ buffer = import_fs2.default.readFileSync(import_path2.default.resolve(file));
4689
+ } else {
4690
+ buffer = Buffer.from(await file.arrayBuffer());
4691
+ }
4692
+ return import_crypto.default.createHash("md5").update(buffer).digest("base64");
4693
+ } else {
4694
+ const arrayBuffer = await file.arrayBuffer();
4695
+ const SparkMD5 = (await import("spark-md5")).default;
4696
+ return SparkMD5.ArrayBuffer.hash(arrayBuffer, true);
4697
+ }
4698
+ }
4699
+ function parseFileNameAndSuffix(url) {
4700
+ try {
4701
+ const urlObj = new URL(url);
4702
+ const pathname = urlObj.pathname;
4703
+ const decodedName = decodeURIComponent(pathname.split("/").pop() || "file");
4704
+ const suffix = decodedName.includes(".") ? decodedName.split(".").pop() : "";
4705
+ return { fileName: decodedName, fileSuffix: suffix };
4706
+ } catch (e) {
4707
+ const decodedName = decodeURIComponent(url.split("/").pop() || "file");
4708
+ const suffix = decodedName.includes(".") ? decodedName.split(".").pop() : "";
4709
+ return { fileName: decodedName, fileSuffix: suffix };
4710
+ }
4711
+ }
4712
+ var oss = {
4713
+ /**
4714
+ * 上传文件到 OSS(封装预签名 + fetch 上传 + 返回 FilePreview)
4715
+ * @param params.file 必传
4716
+ * @param params.folder 可选,可为空
4717
+ * @param params.acl 可选,默认 'private'
4718
+ */
4719
+ async uploadFile(params) {
4720
+ const { file, folder, acl = "private" } = params;
4721
+ const md5Base64 = await computeFileMd5Base64(file);
4722
+ const presignedInput = {
4723
+ fileMd5Base64: md5Base64,
4724
+ fileSuffix: file.name.split(".").pop() || "",
4725
+ originalFileName: file.name,
4726
+ fileSize: file.size,
4727
+ acl
4728
+ };
4729
+ if (folder) presignedInput.folder = folder;
4730
+ const presignedResult = await rateLimit("uploadPresignedUrl", "mutation", async () => {
4731
+ const { query: query2, variables } = buildGraphQLUploadPresignedUrl(presignedInput);
4732
+ const result = await execute({ query: query2, variables });
4733
+ return result;
4734
+ }, rateLimitConfig);
4735
+ console.log("presignedResult====>", presignedResult);
4736
+ if (!presignedResult.uploadPresignedUrl) throw new Error("\u83B7\u53D6\u9884\u7B7E\u540D URL \u5931\u8D25");
4737
+ const { uploadUrl, uploadHeaders, downloadUrl, resourceId, contentType, originalFileName } = presignedResult.uploadPresignedUrl;
4738
+ const headers = {};
4739
+ if (uploadHeaders) {
4740
+ if (uploadHeaders.contentMD5) headers["Content-MD5"] = uploadHeaders.contentMD5;
4741
+ if (uploadHeaders.contentType) headers["Content-Type"] = uploadHeaders.contentType;
4742
+ if (uploadHeaders.date) headers["Date"] = uploadHeaders.date;
4743
+ }
4744
+ const res = await fetch(uploadUrl, {
4745
+ method: "PUT",
4746
+ body: file,
4747
+ headers
4748
+ });
4749
+ if (!res.ok) {
4750
+ throw new Error(`\u4E0A\u4F20\u5931\u8D25: ${res.status} ${res.statusText}`);
4751
+ }
4752
+ return {
4753
+ id: resourceId,
4754
+ // 非空断言,确保 TS 类型通过
4755
+ originalFileName: originalFileName ?? file.name,
4756
+ contentType: contentType ?? file.type,
4757
+ fileSize: file.size,
4758
+ url: downloadUrl ?? uploadUrl
4759
+ // downloadUrl 可能为 undefined,fallback 使用 uploadUrl
4760
+ };
4761
+ },
4762
+ /**
4763
+ * 从网络 URL 上传文件到 OSS
4764
+ * @param url 必传 网络文件 URL
4765
+ * @param params.folder 可选
4766
+ * @param params.acl 可选
4767
+ */
4768
+ async uploadFromUrl(params) {
4769
+ const { url, folder, acl = "private" } = params;
4770
+ const res = await fetch(url);
4771
+ if (!res.ok) throw new Error(`\u4E0B\u8F7D\u7F51\u7EDC\u6587\u4EF6\u5931\u8D25: ${res.status} ${res.statusText}`);
4772
+ const arrayBuffer = await res.arrayBuffer();
4773
+ const { fileName, fileSuffix } = parseFileNameAndSuffix(url);
4774
+ const fileSize = arrayBuffer.byteLength;
4775
+ const fileType = res.headers.get("Content-Type") || "application/octet-stream";
4776
+ let md5Base64;
4777
+ if (typeof window === "undefined") {
4778
+ md5Base64 = import_crypto.default.createHash("md5").update(Buffer.from(arrayBuffer)).digest("base64");
4779
+ } else {
4780
+ const SparkMD5 = (await import("spark-md5")).default;
4781
+ md5Base64 = SparkMD5.ArrayBuffer.hash(arrayBuffer, true);
4782
+ }
4783
+ const presignedInput = {
4784
+ fileMd5Base64: md5Base64,
4785
+ fileSuffix,
4786
+ originalFileName: fileName,
4787
+ fileSize,
4788
+ acl
4789
+ };
4790
+ if (folder) presignedInput.folder = folder;
4791
+ const presignedResult = await rateLimit("uploadPresignedUrl", "mutation", async () => {
4792
+ const { query: query2, variables } = buildGraphQLUploadPresignedUrl(presignedInput);
4793
+ const result = await execute({ query: query2, variables });
4794
+ return result;
4795
+ }, rateLimitConfig);
4796
+ if (!presignedResult.uploadPresignedUrl) throw new Error("\u83B7\u53D6\u9884\u7B7E\u540D URL \u5931\u8D25");
4797
+ const { uploadUrl, uploadHeaders, downloadUrl, resourceId, contentType, originalFileName } = presignedResult.uploadPresignedUrl;
4798
+ const headers = {};
4799
+ if (uploadHeaders) {
4800
+ if (uploadHeaders.contentMD5) headers["Content-MD5"] = uploadHeaders.contentMD5;
4801
+ if (uploadHeaders.contentType) headers["Content-Type"] = uploadHeaders.contentType;
4802
+ if (uploadHeaders.date) headers["Date"] = uploadHeaders.date;
4803
+ }
4804
+ const uploadBody = typeof window === "undefined" ? Buffer.from(arrayBuffer) : new Blob([arrayBuffer]);
4805
+ const uploadRes = await fetch(uploadUrl, { method: "PUT", body: uploadBody, headers });
4806
+ if (!uploadRes.ok) throw new Error(`\u4E0A\u4F20\u5931\u8D25: ${uploadRes.status} ${uploadRes.statusText}`);
4807
+ return {
4808
+ id: resourceId,
4809
+ originalFileName: originalFileName ?? fileName,
4810
+ contentType: contentType ?? fileType,
4811
+ fileSize,
4812
+ url: downloadUrl ?? uploadUrl
4813
+ };
4814
+ },
4815
+ /**
4816
+ * 获取文件预览信息
4817
+ */
4818
+ async getFilePreview(resourceId) {
4819
+ return rateLimit("getFilePreview", "query", async () => {
4820
+ const { query: query2, variables } = buildGraphQLGetFilePreview(resourceId);
4821
+ const result = await execute({ query: query2, variables });
4822
+ return result;
4823
+ }, rateLimitConfig);
4824
+ }
4825
+ };
4621
4826
  // Annotate the CommonJS export names for ESM import in node:
4622
4827
  0 && (module.exports = {
4623
4828
  auth,
@@ -4628,6 +4833,7 @@ var gql = {
4628
4833
  gql,
4629
4834
  initGraphQLClient,
4630
4835
  mutation,
4836
+ oss,
4631
4837
  query,
4632
4838
  removeHeader,
4633
4839
  removeToken,