create-secra 0.1.4 → 0.1.7

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.
Files changed (64) hide show
  1. package/README.md +6 -0
  2. package/README.zh-CN.md +6 -0
  3. package/antd-adapter-template/apps/core/index.html +13 -0
  4. package/antd-adapter-template/apps/core/package.json +18 -0
  5. package/antd-adapter-template/apps/core/public/favicon.ico +1 -0
  6. package/antd-adapter-template/apps/core/public/favicon.svg +1 -0
  7. package/antd-adapter-template/apps/core/public/logo.svg +1 -0
  8. package/antd-adapter-template/apps/core/src/api/auth.ts +49 -0
  9. package/antd-adapter-template/apps/core/src/assets/react.svg +1 -0
  10. package/antd-adapter-template/apps/core/src/components/AntdGlobalProvider.tsx +87 -0
  11. package/antd-adapter-template/apps/core/src/components/AntdRootLayout.tsx +10 -0
  12. package/antd-adapter-template/apps/core/src/components/layout.tsx +387 -0
  13. package/antd-adapter-template/apps/core/src/guards/auth-route-guard.ts +45 -0
  14. package/antd-adapter-template/apps/core/src/main.tsx +65 -0
  15. package/antd-adapter-template/apps/core/src/pages/auth/components/account-login-fields.tsx +60 -0
  16. package/antd-adapter-template/apps/core/src/pages/auth/components/phone-login-fields.tsx +60 -0
  17. package/antd-adapter-template/apps/core/src/pages/auth/login.tsx +169 -0
  18. package/antd-adapter-template/apps/core/src/pages/index.tsx +156 -0
  19. package/antd-adapter-template/apps/core/src/router.ts +42 -0
  20. package/antd-adapter-template/apps/core/src/shims/use-sync-external-store-shim.ts +3 -0
  21. package/antd-adapter-template/apps/core/src/theme/theme.css +48 -0
  22. package/antd-adapter-template/apps/core/src/types/crypto-js.d.ts +5 -0
  23. package/antd-adapter-template/apps/core/src/utils/index.ts +12 -0
  24. package/antd-adapter-template/apps/core/src/utils/md5.ts +6 -0
  25. package/antd-adapter-template/apps/core/tsconfig.app.json +11 -0
  26. package/antd-adapter-template/apps/core/tsconfig.json +13 -0
  27. package/antd-adapter-template/apps/core/tsconfig.node.json +7 -0
  28. package/antd-adapter-template/apps/core/vite.config.ts +118 -0
  29. package/antd-adapter-template/eslint.config.js +23 -0
  30. package/antd-adapter-template/package.json +63 -0
  31. package/antd-adapter-template/packages/sdk/.swcrc +18 -0
  32. package/antd-adapter-template/packages/sdk/package.json +52 -0
  33. package/antd-adapter-template/packages/sdk/src/build/index.ts +28 -0
  34. package/antd-adapter-template/packages/sdk/src/build/plugins/auto-import.ts +46 -0
  35. package/antd-adapter-template/packages/sdk/src/build/plugins/bundle-analyzer.ts +33 -0
  36. package/antd-adapter-template/packages/sdk/src/build/plugins/remove-console.ts +23 -0
  37. package/antd-adapter-template/packages/sdk/src/build/plugins/unocss.ts +202 -0
  38. package/antd-adapter-template/packages/sdk/src/build/plugins/unplugin-icon.ts +43 -0
  39. package/antd-adapter-template/packages/sdk/src/components/i18n-switch-dropdown.tsx +139 -0
  40. package/antd-adapter-template/packages/sdk/src/components/index.ts +2 -0
  41. package/antd-adapter-template/packages/sdk/src/components/theme-switch-dropdown.tsx +131 -0
  42. package/antd-adapter-template/packages/sdk/src/hooks/auth/core.ts +101 -0
  43. package/antd-adapter-template/packages/sdk/src/hooks/auth/index.ts +139 -0
  44. package/antd-adapter-template/packages/sdk/src/hooks/auth/with-auth.tsx +41 -0
  45. package/antd-adapter-template/packages/sdk/src/hooks/index.ts +1 -0
  46. package/antd-adapter-template/packages/sdk/src/i18n/index.ts +150 -0
  47. package/antd-adapter-template/packages/sdk/src/index.ts +11 -0
  48. package/antd-adapter-template/packages/sdk/src/request/index.ts +436 -0
  49. package/antd-adapter-template/packages/sdk/src/storage/README.md +30 -0
  50. package/antd-adapter-template/packages/sdk/src/storage/index.ts +57 -0
  51. package/antd-adapter-template/packages/sdk/src/styles/reset.css +111 -0
  52. package/antd-adapter-template/packages/sdk/src/theme/index.ts +466 -0
  53. package/antd-adapter-template/packages/sdk/tsconfig.json +16 -0
  54. package/antd-adapter-template/pnpm-workspace.yaml +3 -0
  55. package/antd-adapter-template/tsconfig.app.json +29 -0
  56. package/antd-adapter-template/tsconfig.json +7 -0
  57. package/antd-adapter-template/tsconfig.node.json +27 -0
  58. package/antd-adapter-template/turbo.json +17 -0
  59. package/bin/index.mjs +165 -33
  60. package/package.json +6 -2
  61. package/template/apps/core/src/main.tsx +11 -18
  62. package/template/apps/core/src/router.ts +5 -1
  63. package/template/package.json +1 -1
  64. package/template/packages/sdk/src/build/plugins/unocss.ts +3 -0
@@ -0,0 +1,150 @@
1
+ import { $t, setLang, setupI18n } from "@vlian/framework/library";
2
+
3
+ export type SdkLang = "en-US" | "zh-CN";
4
+
5
+ export const sdkLocales: Record<SdkLang, Record<string, unknown>> = {
6
+ "en-US": {
7
+ secraAdmin: {
8
+ login: {
9
+ subtitle: "Unified identity authentication and admin portal",
10
+ otherLoginMethods: "Other login methods",
11
+ accountTab: "Account Login",
12
+ phoneTab: "Phone Login",
13
+ autoLogin: "Remember me",
14
+ forgotPassword: "Forgot password",
15
+ usernamePlaceholder: "Username: admin or user",
16
+ usernameRequired: "Please enter your username!",
17
+ passwordPlaceholder: "Password: ant.design",
18
+ passwordRequired: "Please enter your password!",
19
+ passwordStrengthText:
20
+ "Password should contain numbers, letters and special characters, at least 8 characters long.",
21
+ passwordStrengthWeak: "Strength: Weak",
22
+ passwordStrengthMedium: "Strength: Medium",
23
+ passwordStrengthStrong: "Strength: Strong",
24
+ mobilePlaceholder: "Phone number",
25
+ mobileRequired: "Please enter your phone number!",
26
+ mobileInvalid: "Invalid phone number format!",
27
+ captchaPlaceholder: "Please enter verification code",
28
+ captchaRequired: "Please enter verification code!",
29
+ captchaButton: "Get code",
30
+ captchaButtonTiming: "{{count}} Get code",
31
+ captchaSuccess: "Code sent successfully! Verification code: 1234",
32
+ phoneLoginNotReady: "Phone login is not connected to backend yet",
33
+ loginSuccessTitle: "Login successful",
34
+ loginWelcomeUser: "Welcome, {{username}}",
35
+ loginFailedCheckCredentials: "Login failed, please check your username and password",
36
+ },
37
+ home: {
38
+ title: "Theme sync between antd and @vlian/framework",
39
+ currentMode: "Current mode",
40
+ resolvedMode: "Resolved",
41
+ modeSwitchTitle: "Mode switch",
42
+ unifiedThemeTitle: "Unified theme (single primary color)",
43
+ previewTitle: "Sync preview",
44
+ currentUnifiedThemeTitle: "Current unified theme state",
45
+ themePreset: "Theme preset",
46
+ mode: {
47
+ light: "Light",
48
+ dark: "Dark",
49
+ system: "System",
50
+ },
51
+ },
52
+ },
53
+ },
54
+ "zh-CN": {
55
+ secraAdmin: {
56
+ login: {
57
+ subtitle: "统一身份认证与后台管理入口",
58
+ otherLoginMethods: "其他登录方式",
59
+ accountTab: "账号密码登录",
60
+ phoneTab: "手机号登录",
61
+ autoLogin: "自动登录",
62
+ forgotPassword: "忘记密码",
63
+ usernamePlaceholder: "用户名: admin or user",
64
+ usernameRequired: "请输入用户名!",
65
+ passwordPlaceholder: "密码: ant.design",
66
+ passwordRequired: "请输入密码!",
67
+ passwordStrengthText:
68
+ "Password should contain numbers, letters and special characters, at least 8 characters long.",
69
+ passwordStrengthWeak: "强度:弱",
70
+ passwordStrengthMedium: "强度:中",
71
+ passwordStrengthStrong: "强度:强",
72
+ mobilePlaceholder: "手机号",
73
+ mobileRequired: "请输入手机号!",
74
+ mobileInvalid: "手机号格式错误!",
75
+ captchaPlaceholder: "请输入验证码",
76
+ captchaRequired: "请输入验证码!",
77
+ captchaButton: "获取验证码",
78
+ captchaButtonTiming: "{{count}} 获取验证码",
79
+ captchaSuccess: "获取验证码成功!验证码为:1234",
80
+ phoneLoginNotReady: "手机号登录暂未接入后端",
81
+ loginSuccessTitle: "登录成功",
82
+ loginWelcomeUser: "欢迎,{{username}}",
83
+ loginFailedCheckCredentials: "登录失败,请检查账号密码",
84
+ },
85
+ home: {
86
+ title: "antd + @vlian/framework 主题联动",
87
+ currentMode: "当前模式",
88
+ resolvedMode: "解析后",
89
+ modeSwitchTitle: "模式切换",
90
+ unifiedThemeTitle: "统一主题(单一主色驱动)",
91
+ previewTitle: "同步结果预览",
92
+ currentUnifiedThemeTitle: "当前统一主题状态",
93
+ themePreset: "主题预设",
94
+ mode: {
95
+ light: "浅色",
96
+ dark: "深色",
97
+ system: "跟随系统",
98
+ },
99
+ },
100
+ },
101
+ },
102
+ };
103
+
104
+ type SetupSdkI18nOptions = {
105
+ lang?: SdkLang;
106
+ locales?: Partial<Record<SdkLang, Record<string, unknown>>>;
107
+ };
108
+
109
+ const isPlainRecord = (value: unknown): value is Record<string, unknown> => {
110
+ if (!value || typeof value !== "object") {
111
+ return false;
112
+ }
113
+ return Object.prototype.toString.call(value) === "[object Object]";
114
+ };
115
+
116
+ const deepMergeLocale = (
117
+ base: Record<string, unknown>,
118
+ override: Record<string, unknown>,
119
+ ): Record<string, unknown> => {
120
+ const merged: Record<string, unknown> = { ...base };
121
+ for (const [key, value] of Object.entries(override)) {
122
+ const previous = merged[key];
123
+ if (isPlainRecord(previous) && isPlainRecord(value)) {
124
+ merged[key] = deepMergeLocale(previous, value);
125
+ continue;
126
+ }
127
+ merged[key] = value;
128
+ }
129
+ return merged;
130
+ };
131
+
132
+ export function getSdkI18nLocales(locales: SetupSdkI18nOptions["locales"] = {}) {
133
+ const enOverride = locales["en-US"] ?? {};
134
+ const zhOverride = locales["zh-CN"] ?? {};
135
+
136
+ return {
137
+ "en-US": deepMergeLocale(sdkLocales["en-US"], enOverride),
138
+ "zh-CN": deepMergeLocale(sdkLocales["zh-CN"], zhOverride),
139
+ } as Record<SdkLang, Record<string, unknown>>;
140
+ }
141
+
142
+ export function setupSdkI18n(options: SetupSdkI18nOptions = {}) {
143
+ const { lang = "zh-CN", locales = {} } = options;
144
+
145
+ setupI18n(getSdkI18nLocales(locales));
146
+
147
+ setLang(lang);
148
+ }
149
+
150
+ export { setLang as setSdkLang, $t as t };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 根入口只导出运行时通用能力(theme/components/storage/i18n/hooks)。
3
+ * request/build 通过子路径导出,避免将构建时依赖耦合进运行时入口。
4
+ * - @vlian/sdk/request
5
+ * - @vlian/sdk/build
6
+ */
7
+ export * from "./theme/index.js";
8
+ export * from "./components/index.js";
9
+ export * from "./storage/index.js";
10
+ export * from "./i18n/index.js";
11
+ export * from "./hooks/index.js";
@@ -0,0 +1,436 @@
1
+ import {
2
+ RequestClient,
3
+ RequestError,
4
+ RequestErrorCode,
5
+ RequestErrorType,
6
+ axiosAdapter,
7
+ kyAdapter,
8
+ type RequestBody,
9
+ type RequestHeaders,
10
+ type RequestOptions,
11
+ type RequestQuery,
12
+ type Response,
13
+ PluginPriority,
14
+ type RequestPlugin,
15
+ type RequestClientConfig,
16
+ } from "@vlian/framework/request";
17
+
18
+ export interface ApiResponse<T = unknown> {
19
+ data: T;
20
+ code: number;
21
+ message: string;
22
+ }
23
+
24
+ export interface SdkRequestMeta extends Record<string, unknown> {
25
+ /** Disable default error message prompt. */
26
+ silent?: boolean;
27
+ /** Explicitly control error message prompt. */
28
+ showErrorMessage?: boolean;
29
+ }
30
+
31
+ export interface SdkRequestOptions<TResponse = unknown>
32
+ extends Omit<RequestOptions<TResponse>, "body" | "query" | "_responseType"> {
33
+ data?: RequestBody;
34
+ params?: RequestQuery;
35
+ meta?: SdkRequestMeta;
36
+ body?: RequestBody;
37
+ query?: RequestQuery;
38
+ _responseType?: SdkResponse<TResponse>;
39
+ }
40
+
41
+ export interface SdkResponse<T = unknown> {
42
+ data: T | undefined;
43
+ code: number;
44
+ message: string;
45
+ headers: RequestHeaders;
46
+ raw: unknown;
47
+ status: number;
48
+ error: boolean;
49
+ version: string;
50
+ }
51
+
52
+ export interface SdkSuccessContext<T = unknown> {
53
+ status: number;
54
+ normalized: ApiResponse<T>;
55
+ response: Response<unknown>;
56
+ }
57
+
58
+ export type SdkSuccessPredicate<T = unknown> = (context: SdkSuccessContext<T>) => boolean;
59
+
60
+ const DEFAULT_MESSAGE = "消息提示";
61
+ const RESPONSE_VERSION = "1.0.0";
62
+ export const SDK_REQUEST_VERSION = RESPONSE_VERSION;
63
+ const DEFAULT_SUCCESS_PREDICATE: SdkSuccessPredicate = ({ status, normalized }) =>
64
+ status === 200 && normalized.code === 0;
65
+
66
+ const getMessageHandler = (): ((message: string) => void) | null => {
67
+ if (typeof window === "undefined") {
68
+ return null;
69
+ }
70
+ const message = (window as unknown as { $message?: { error?: (msg: string) => void } })
71
+ .$message;
72
+ if (message && typeof message.error === "function") {
73
+ return message.error.bind(message);
74
+ }
75
+ return null;
76
+ };
77
+
78
+ const extractMessage = (payload: unknown): string | undefined => {
79
+ if (!payload || typeof payload !== "object") {
80
+ return undefined;
81
+ }
82
+ if ("error" in payload) {
83
+ const errorPayload = (payload as { error?: unknown }).error;
84
+ const errorMessage = extractMessage(errorPayload);
85
+ if (errorMessage) {
86
+ return errorMessage;
87
+ }
88
+ }
89
+ if ("message" in payload && typeof (payload as { message?: unknown }).message === "string") {
90
+ return (payload as { message: string }).message;
91
+ }
92
+ return undefined;
93
+ };
94
+
95
+ const toSafeErrorMessage = (value: unknown, fallback: string): string => {
96
+ if (typeof value === "string") {
97
+ const trimmed = value.trim();
98
+ return trimmed || fallback;
99
+ }
100
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
101
+ return String(value);
102
+ }
103
+ if (typeof value === "symbol") {
104
+ return value.toString();
105
+ }
106
+ if (value && typeof value === "object") {
107
+ const nestedMessage = extractMessage(value);
108
+ if (nestedMessage) {
109
+ return nestedMessage;
110
+ }
111
+ try {
112
+ const serialized = JSON.stringify(value);
113
+ if (serialized && serialized !== "{}") {
114
+ return serialized;
115
+ }
116
+ } catch {
117
+ // Ignore serialization failures and continue fallback chain.
118
+ }
119
+ try {
120
+ return Object.prototype.toString.call(value);
121
+ } catch {
122
+ return fallback;
123
+ }
124
+ }
125
+ try {
126
+ const stringValue = String(value);
127
+ return stringValue && stringValue !== "undefined" && stringValue !== "null"
128
+ ? stringValue
129
+ : fallback;
130
+ } catch {
131
+ return fallback;
132
+ }
133
+ };
134
+
135
+ const extractErrorPayload = (error: RequestError): unknown => {
136
+ if (error.response?.data) {
137
+ return error.response.data;
138
+ }
139
+ const cause = error.cause as { response?: { data?: unknown } } | undefined;
140
+ if (cause?.response?.data) {
141
+ return cause.response.data;
142
+ }
143
+ return undefined;
144
+ };
145
+
146
+ const normalizeResponse = <T>(payload: unknown, status: number): ApiResponse<T> => {
147
+ const objectPayload = payload && typeof payload === "object" ? (payload as Record<string, unknown>) : undefined;
148
+ const hasEnvelope =
149
+ !!objectPayload && ("code" in objectPayload || "message" in objectPayload || "data" in objectPayload);
150
+ const data =
151
+ objectPayload && "data" in objectPayload
152
+ ? (objectPayload.data as T)
153
+ : (hasEnvelope ? (undefined as T) : (payload as T));
154
+ const codeValue =
155
+ objectPayload && "code" in objectPayload ? objectPayload.code : status === 200 ? 0 : status;
156
+ const code =
157
+ typeof codeValue === "number"
158
+ ? (Number.isFinite(codeValue) ? codeValue : 0)
159
+ : (() => {
160
+ const parsed = Number(codeValue ?? 0);
161
+ return Number.isFinite(parsed) ? parsed : 0;
162
+ })();
163
+ const message =
164
+ objectPayload && "message" in objectPayload
165
+ ? toSafeErrorMessage(objectPayload.message, DEFAULT_MESSAGE)
166
+ : DEFAULT_MESSAGE;
167
+ return {
168
+ data,
169
+ code,
170
+ message,
171
+ };
172
+ };
173
+
174
+ const createDefaultResponsePlugin = (options?: {
175
+ successPredicate?: SdkSuccessPredicate;
176
+ }): RequestPlugin => ({
177
+ name: "sdk-default-response",
178
+ priority: PluginPriority.HIGH,
179
+ onAfterRequest(context) {
180
+ const successPredicate = options?.successPredicate ?? DEFAULT_SUCCESS_PREDICATE;
181
+ const response = context.response as Response<unknown> | undefined;
182
+ if (!response) {
183
+ return;
184
+ }
185
+ const normalized = normalizeResponse(response.data, response.status);
186
+ const isSuccess = successPredicate({
187
+ status: response.status,
188
+ normalized,
189
+ response,
190
+ });
191
+ const transformed: SdkResponse<unknown> = {
192
+ data: normalized.data as unknown,
193
+ code: normalized.code,
194
+ message: normalized.message,
195
+ raw: response.raw,
196
+ headers: response.headers || {},
197
+ status: response.status,
198
+ error: !isSuccess,
199
+ version: RESPONSE_VERSION,
200
+ };
201
+ const mergedResponse = {
202
+ ...response,
203
+ data: transformed,
204
+ } as Response<SdkResponse<unknown>>;
205
+
206
+ if (isSuccess) {
207
+ return mergedResponse;
208
+ }
209
+
210
+ throw new RequestError(
211
+ normalized.message || `HTTP ${response.status} 错误`,
212
+ response.status === 200 ? RequestErrorType.UNKNOWN_ERROR : RequestErrorType.HTTP_ERROR,
213
+ response.status >= 500 ? RequestErrorCode.SERVER_ERROR : RequestErrorCode.CLIENT_ERROR,
214
+ {
215
+ status: response.status,
216
+ response: mergedResponse,
217
+ },
218
+ );
219
+ },
220
+ onError(context) {
221
+ const { error, request } = context;
222
+ if (!error || !request) {
223
+ return;
224
+ }
225
+ const meta = (request.meta || {}) as SdkRequestMeta;
226
+ if (meta.silent === true || meta.showErrorMessage === false) {
227
+ return;
228
+ }
229
+ if (error.type === RequestErrorType.ABORT_ERROR) {
230
+ return;
231
+ }
232
+ let message = toSafeErrorMessage(error.message, "请求失败");
233
+ if (error.type === RequestErrorType.NETWORK_ERROR) {
234
+ message = "网络连接失败,请检查网络";
235
+ } else if (error.type === RequestErrorType.TIMEOUT_ERROR) {
236
+ message = "请求超时,请稍后重试";
237
+ } else {
238
+ const payload = extractErrorPayload(error);
239
+ if (payload) {
240
+ message = extractMessage(payload) || message;
241
+ }
242
+ }
243
+
244
+ const handler = getMessageHandler();
245
+ if (handler) {
246
+ handler(message);
247
+ } else if (typeof console !== "undefined") {
248
+ console.error(message);
249
+ }
250
+ },
251
+ });
252
+
253
+ const mapRequestOptions = <T>(options: SdkRequestOptions<T>): RequestOptions<SdkResponse<T>> => ({
254
+ ...options,
255
+ body: options.body ?? options.data,
256
+ query: options.query ?? options.params,
257
+ meta: options.meta as Record<string, unknown> | undefined,
258
+ });
259
+
260
+ class SdkRequestClient {
261
+ private client: RequestClient;
262
+ private initPromise?: Promise<void>;
263
+ private successPredicate: SdkSuccessPredicate;
264
+
265
+ constructor(config: RequestClientConfig & { successPredicate?: SdkSuccessPredicate }) {
266
+ const { successPredicate, ...requestClientConfig } = config;
267
+ this.successPredicate = successPredicate ?? DEFAULT_SUCCESS_PREDICATE;
268
+ this.client = new RequestClient(requestClientConfig);
269
+ }
270
+
271
+ private async ensureInitialized() {
272
+ if (!this.initPromise) {
273
+ this.initPromise = this.client.initialize();
274
+ }
275
+ await this.initPromise;
276
+ }
277
+
278
+ async request<TResponse = unknown>(
279
+ options: SdkRequestOptions<TResponse>,
280
+ ): Promise<SdkResponse<TResponse>> {
281
+ await this.ensureInitialized();
282
+ let response: Response<unknown>;
283
+ try {
284
+ response = await this.client.request<unknown>(mapRequestOptions(options));
285
+ } catch (error) {
286
+ if (error instanceof RequestError) {
287
+ throw error;
288
+ }
289
+ throw new RequestError(
290
+ toSafeErrorMessage(error, "请求失败"),
291
+ RequestErrorType.UNKNOWN_ERROR,
292
+ RequestErrorCode.CLIENT_ERROR,
293
+ {
294
+ cause: error,
295
+ request: mapRequestOptions(options),
296
+ context: {
297
+ source: "sdk-request-client.request",
298
+ },
299
+ },
300
+ );
301
+ }
302
+ const payload = response.data as SdkResponse<TResponse> | Record<string, unknown> | null | undefined;
303
+ if (payload && typeof payload === "object" && "version" in payload) {
304
+ return payload as SdkResponse<TResponse>;
305
+ }
306
+ const normalized = normalizeResponse<TResponse>(response.data, response.status);
307
+ const isSuccess = this.successPredicate({
308
+ status: response.status,
309
+ normalized,
310
+ response: response as Response<unknown>,
311
+ });
312
+ return {
313
+ data: normalized.data as TResponse,
314
+ code: normalized.code,
315
+ message: normalized.message,
316
+ headers: response.headers || {},
317
+ raw: response.raw,
318
+ status: response.status,
319
+ error: !isSuccess,
320
+ version: RESPONSE_VERSION,
321
+ };
322
+ }
323
+
324
+ async get<TResponse = unknown>(
325
+ urlOrOptions: string | SdkRequestOptions<TResponse>,
326
+ options?: SdkRequestOptions<TResponse>,
327
+ ): Promise<SdkResponse<TResponse>> {
328
+ if (typeof urlOrOptions === "string") {
329
+ return this.request({
330
+ ...(options || {}),
331
+ url: urlOrOptions,
332
+ method: "GET",
333
+ });
334
+ }
335
+ return this.request({
336
+ ...urlOrOptions,
337
+ method: "GET",
338
+ });
339
+ }
340
+
341
+ async post<TResponse = unknown>(
342
+ urlOrOptions: string | SdkRequestOptions<TResponse>,
343
+ data?: RequestBody,
344
+ options?: SdkRequestOptions<TResponse>,
345
+ ): Promise<SdkResponse<TResponse>> {
346
+ if (typeof urlOrOptions === "string") {
347
+ return this.request({
348
+ ...(options || {}),
349
+ url: urlOrOptions,
350
+ method: "POST",
351
+ data,
352
+ });
353
+ }
354
+ return this.request({
355
+ ...urlOrOptions,
356
+ method: "POST",
357
+ });
358
+ }
359
+
360
+ async put<TResponse = unknown>(
361
+ urlOrOptions: string | SdkRequestOptions<TResponse>,
362
+ data?: RequestBody,
363
+ options?: SdkRequestOptions<TResponse>,
364
+ ): Promise<SdkResponse<TResponse>> {
365
+ if (typeof urlOrOptions === "string") {
366
+ return this.request({
367
+ ...(options || {}),
368
+ url: urlOrOptions,
369
+ method: "PUT",
370
+ data,
371
+ });
372
+ }
373
+ return this.request({
374
+ ...urlOrOptions,
375
+ method: "PUT",
376
+ });
377
+ }
378
+
379
+ async patch<TResponse = unknown>(
380
+ urlOrOptions: string | SdkRequestOptions<TResponse>,
381
+ data?: RequestBody,
382
+ options?: SdkRequestOptions<TResponse>,
383
+ ): Promise<SdkResponse<TResponse>> {
384
+ if (typeof urlOrOptions === "string") {
385
+ return this.request({
386
+ ...(options || {}),
387
+ url: urlOrOptions,
388
+ method: "PATCH",
389
+ data,
390
+ });
391
+ }
392
+ return this.request({
393
+ ...urlOrOptions,
394
+ method: "PATCH",
395
+ });
396
+ }
397
+
398
+ async delete<TResponse = unknown>(
399
+ urlOrOptions: string | SdkRequestOptions<TResponse>,
400
+ options?: SdkRequestOptions<TResponse>,
401
+ ): Promise<SdkResponse<TResponse>> {
402
+ if (typeof urlOrOptions === "string") {
403
+ return this.request({
404
+ ...(options || {}),
405
+ url: urlOrOptions,
406
+ method: "DELETE",
407
+ });
408
+ }
409
+ return this.request({
410
+ ...urlOrOptions,
411
+ method: "DELETE",
412
+ });
413
+ }
414
+
415
+ async setAdapter(adapter: string | (() => Promise<unknown>)) {
416
+ await this.ensureInitialized();
417
+ // @ts-expect-error - adapter type is ensured by request client
418
+ await this.client.setAdapter(adapter);
419
+ }
420
+ }
421
+
422
+ export const platformRequestClient = new SdkRequestClient({
423
+ defaultAdapter: kyAdapter,
424
+ plugins: [createDefaultResponsePlugin()],
425
+ successPredicate: DEFAULT_SUCCESS_PREDICATE,
426
+ });
427
+
428
+ // export const uploadRequestClient = new SdkRequestClient({
429
+ // defaultAdapter: axiosAdapter,
430
+ // plugins: [createDefaultResponsePlugin()],
431
+ // successPredicate: DEFAULT_SUCCESS_PREDICATE,
432
+ // });
433
+
434
+ // export const createSdkRequestPlugin = (options?: {
435
+ // successPredicate?: SdkSuccessPredicate;
436
+ // }) => createDefaultResponsePlugin(options);
@@ -0,0 +1,30 @@
1
+ # Storage 使用示例
2
+
3
+ 以下示例基于 `cache` 对象,演示 `localStorage`、`sessionStorage`、`indexedDB` 三种缓存方式。
4
+
5
+ ## localStorage
6
+
7
+ ```ts
8
+ import { cache } from "./index.js";
9
+
10
+ await cache.localstorage.set("user:token", "token-demo-value", 60 * 60 * 1000);
11
+ const token = await cache.localstorage.get<string>("user:token");
12
+ ```
13
+
14
+ ## sessionStorage
15
+
16
+ ```ts
17
+ import { cache } from "./index.js";
18
+
19
+ await cache.sessionStorage.set("page:draft", { title: "草稿标题" });
20
+ const draft = await cache.sessionStorage.get<{ title: string }>("page:draft");
21
+ ```
22
+
23
+ ## indexedDB
24
+
25
+ ```ts
26
+ import { cache } from "./index.js";
27
+
28
+ await cache.indexeddb.set("table:list", [{ id: 1, name: "demo" }]);
29
+ const list = await cache.indexeddb.get<Array<{ id: number; name: string }>>("table:list");
30
+ ```
@@ -0,0 +1,57 @@
1
+ import { storage } from "@vlian/framework/library";
2
+
3
+ export type StorageStrategy = "localstorage" | "sessionStorage" | "indexeddb";
4
+
5
+ export interface CacheMethods {
6
+ get<T>(key: string): Promise<T | null>;
7
+ set<T>(key: string, value: T, expire?: number): Promise<void>;
8
+ remove(key: string): Promise<void>;
9
+ clean(): Promise<void>;
10
+ }
11
+
12
+ let initialized = false;
13
+
14
+ const ensureStorageInitialized = () => {
15
+ if (initialized) {
16
+ return;
17
+ }
18
+
19
+ storage.initialize({
20
+ prefix: "secra-admin-sdk",
21
+ tableName: "secra-admin-sdk",
22
+ defaultExpire: 24 * 60 * 60 * 1000,
23
+ });
24
+ initialized = true;
25
+ };
26
+
27
+ const createCacheMethods = (
28
+ getter: () => {
29
+ get<T>(key: string): Promise<T | null>;
30
+ set<T>(key: string, value: T, options?: { expire?: number }): Promise<void>;
31
+ remove(key: string): Promise<void>;
32
+ clean(): Promise<void>;
33
+ },
34
+ ): CacheMethods => ({
35
+ async get<T>(key: string): Promise<T | null> {
36
+ ensureStorageInitialized();
37
+ return getter().get<T>(key);
38
+ },
39
+ async set<T>(key: string, value: T, expire?: number): Promise<void> {
40
+ ensureStorageInitialized();
41
+ await getter().set(key, value, expire !== undefined ? { expire } : undefined);
42
+ },
43
+ async remove(key: string): Promise<void> {
44
+ ensureStorageInitialized();
45
+ await getter().remove(key);
46
+ },
47
+ async clean(): Promise<void> {
48
+ ensureStorageInitialized();
49
+ await getter().clean();
50
+ },
51
+ });
52
+
53
+ export const cache: Record<StorageStrategy, CacheMethods> = {
54
+ localstorage: createCacheMethods(() => storage.local),
55
+ sessionStorage: createCacheMethods(() => storage.session),
56
+ indexeddb: createCacheMethods(() => storage.indexedDB),
57
+ };