@ysgroup/core 0.1.2 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ysgroup/core",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export { useYsStore } from "./store";
5
5
  export {
6
6
  YsRest,
7
7
  YsRestError,
8
+ type YsAdminRpcCall,
8
9
  type DeleteBlocker,
9
10
  type DeleteImpact,
10
11
  type RevealedCredential,
package/src/rest.ts CHANGED
@@ -1,4 +1,13 @@
1
- /* @ysgroup/core —— REST 客户端(管理接口,带 JWT)。 */
1
+ /* @ysgroup/core —— 管理接口客户端(支持 HTTP 或统一 WS RPC 传输)。 */
2
+
3
+ export type YsAdminRpcCall = (data: {
4
+ method: string;
5
+ path: string;
6
+ access_token: string;
7
+ body?: unknown;
8
+ upload?: { name: string; content_type: string; base64: string };
9
+ user_agent: string;
10
+ }) => Promise<unknown>;
2
11
 
3
12
  export class YsRestError extends Error {
4
13
  constructor(
@@ -33,9 +42,23 @@ export class YsRest {
33
42
  constructor(
34
43
  private baseUrl: string,
35
44
  private getToken: () => string,
45
+ private rpcCall?: YsAdminRpcCall,
36
46
  ) {}
37
47
 
38
48
  private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
49
+ if (this.rpcCall) {
50
+ try {
51
+ return await this.rpcCall({
52
+ method,
53
+ path,
54
+ access_token: this.getToken(),
55
+ body,
56
+ user_agent: navigator.userAgent,
57
+ }) as T;
58
+ } catch (error) {
59
+ throw rpcError(error);
60
+ }
61
+ }
39
62
  const resp = await fetch(`${this.baseUrl}${path}`, {
40
63
  method,
41
64
  headers: {
@@ -59,6 +82,23 @@ export class YsRest {
59
82
  }
60
83
 
61
84
  private async upload<T>(path: string, file: File): Promise<T> {
85
+ if (this.rpcCall) {
86
+ try {
87
+ return await this.rpcCall({
88
+ method: "POST",
89
+ path,
90
+ access_token: this.getToken(),
91
+ upload: {
92
+ name: file.name,
93
+ content_type: file.type || "application/octet-stream",
94
+ base64: await fileToBase64(file),
95
+ },
96
+ user_agent: navigator.userAgent,
97
+ }) as T;
98
+ } catch (error) {
99
+ throw rpcError(error);
100
+ }
101
+ }
62
102
  const form = new FormData();
63
103
  form.append("upload", file);
64
104
  const resp = await fetch(`${this.baseUrl}${path}`, {
@@ -73,6 +113,10 @@ export class YsRest {
73
113
  return (await resp.json()) as T;
74
114
  }
75
115
 
116
+ raw<T>(method: string, path: string, body?: unknown) {
117
+ return this.request<T>(method, path, body);
118
+ }
119
+
76
120
  list<T = Record<string, unknown>>(table: string) {
77
121
  return this.request<T[]>("GET", `/api/tables/${table}`);
78
122
  }
@@ -199,7 +243,14 @@ export class YsRest {
199
243
  listOwnerFiles(ownerId: string) {
200
244
  return this.request<Record<string, unknown>[]>("GET", `/api/owners/${ownerId}/files`);
201
245
  }
202
- downloadFile(fileId: string) {
246
+ async downloadFile(fileId: string) {
247
+ if (this.rpcCall) {
248
+ const result = await this.request<{
249
+ base64: string;
250
+ content_type: string;
251
+ }>("GET", `/api/files/${fileId}/download`);
252
+ return base64ToBlob(result.base64, result.content_type);
253
+ }
203
254
  return fetch(`${this.baseUrl}/api/files/${fileId}/download`, {
204
255
  headers: { Authorization: `Bearer ${this.getToken()}` },
205
256
  }).then(async (resp) => {
@@ -212,6 +263,35 @@ export class YsRest {
212
263
  }
213
264
  }
214
265
 
266
+ function rpcError(error: unknown): YsRestError {
267
+ const message = String(error).replace(/^Error:\s*/, "");
268
+ const match = message.match(/^\[(\d+)\]\s*(.*)$/s);
269
+ if (!match) return new YsRestError(500, message);
270
+ let detail: unknown = match[2];
271
+ try {
272
+ detail = JSON.parse(match[2]);
273
+ } catch {
274
+ // 普通文本错误保持原文。
275
+ }
276
+ return new YsRestError(Number(match[1]), detail);
277
+ }
278
+
279
+ function fileToBase64(file: File): Promise<string> {
280
+ return new Promise((resolve, reject) => {
281
+ const reader = new FileReader();
282
+ reader.onerror = () => reject(reader.error ?? new Error("读取上传文件失败"));
283
+ reader.onload = () => resolve(String(reader.result).split(",", 2)[1] ?? "");
284
+ reader.readAsDataURL(file);
285
+ });
286
+ }
287
+
288
+ function base64ToBlob(value: string, contentType: string): Blob {
289
+ const binary = atob(value);
290
+ const bytes = new Uint8Array(binary.length);
291
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
292
+ return new Blob([bytes], { type: contentType });
293
+ }
294
+
215
295
  export interface TopologyNode {
216
296
  name: string;
217
297
  title: string;