@i.un/api-client 0.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/dist/client.js ADDED
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/client.ts
21
+ var client_exports = {};
22
+ __export(client_exports, {
23
+ createApiClient: () => createApiClient,
24
+ isApiError: () => isApiError
25
+ });
26
+ module.exports = __toCommonJS(client_exports);
27
+ var import_ofetch = require("ofetch");
28
+ var isApiError = (error) => {
29
+ return error instanceof Error && "code" in error;
30
+ };
31
+ var defaultUnwrapResponse = (result, returnFullResponse = false) => {
32
+ if (result && typeof result === "object" && "code" in result) {
33
+ const body = result;
34
+ if (body.code === 0) {
35
+ return returnFullResponse ? body : body.data;
36
+ }
37
+ }
38
+ return result;
39
+ };
40
+ var defaultCreateErrorFromResult = (res) => {
41
+ const error = new Error(res.message || "Request failed");
42
+ error.code = res.code;
43
+ error.data = res.data;
44
+ return error;
45
+ };
46
+ var extractAccessToken = (data) => {
47
+ if (typeof data === "string" && data) {
48
+ return data;
49
+ }
50
+ if (!data || typeof data !== "object") {
51
+ throw new Error("Invalid refresh token response: data is not an object or string");
52
+ }
53
+ const anyData = data;
54
+ const accessToken = anyData.access_token ?? anyData.token;
55
+ if (typeof accessToken === "string" && accessToken) {
56
+ return accessToken;
57
+ }
58
+ throw new Error(
59
+ "Invalid refresh token response: no access_token or token field found"
60
+ );
61
+ };
62
+ function createApiClient(options) {
63
+ const {
64
+ baseURL,
65
+ tokenStorage,
66
+ refreshToken = false,
67
+ isAuthError = (code) => code === 401,
68
+ unwrapResponse = defaultUnwrapResponse,
69
+ createErrorFromResult = defaultCreateErrorFromResult
70
+ } = options;
71
+ let refreshingPromise = null;
72
+ const refreshTokenFn = !refreshToken ? null : typeof refreshToken === "string" ? async () => {
73
+ const res = await (0, import_ofetch.ofetch)(refreshToken, {
74
+ baseURL,
75
+ method: "POST"
76
+ });
77
+ if (res.code !== 0) {
78
+ throw createErrorFromResult(res);
79
+ }
80
+ return extractAccessToken(res.data);
81
+ } : refreshToken;
82
+ const rawRequest = import_ofetch.ofetch.create({
83
+ baseURL,
84
+ async onRequest({ options: options2 }) {
85
+ const token = await tokenStorage.getAccessToken();
86
+ const headers = new Headers(options2.headers);
87
+ if (token) {
88
+ headers.set("Authorization", `Bearer ${token}`);
89
+ }
90
+ options2.headers = headers;
91
+ },
92
+ onResponse({ response }) {
93
+ if (response.status === 204) {
94
+ response._data = null;
95
+ }
96
+ },
97
+ async onResponseError(context) {
98
+ const { response } = context;
99
+ const message = response?._data?.message || `HTTP ${response?.status || "Network Error"}`;
100
+ const error = new Error(message);
101
+ error.status = response?.status;
102
+ error.data = response?._data;
103
+ throw error;
104
+ }
105
+ });
106
+ async function request(url, options2 = {}) {
107
+ const { returnFullResponse, _retry, ...fetchOptions } = options2;
108
+ const res = await rawRequest(url, fetchOptions);
109
+ if (res.code === 0) {
110
+ return unwrapResponse(res, !!returnFullResponse);
111
+ }
112
+ if (isAuthError(res.code) && !_retry && refreshTokenFn) {
113
+ try {
114
+ if (!refreshingPromise) {
115
+ refreshingPromise = refreshTokenFn().finally(() => {
116
+ refreshingPromise = null;
117
+ });
118
+ }
119
+ const newToken = await refreshingPromise;
120
+ if (newToken) {
121
+ await tokenStorage.setAccessToken(newToken);
122
+ }
123
+ return await request(url, {
124
+ ...options2 || {},
125
+ _retry: true
126
+ });
127
+ } catch (e) {
128
+ await tokenStorage.setAccessToken("");
129
+ throw createErrorFromResult(res);
130
+ }
131
+ }
132
+ throw createErrorFromResult(res);
133
+ }
134
+ async function get(url, params = {}, options2) {
135
+ return request(url, {
136
+ ...options2,
137
+ method: "GET",
138
+ query: params
139
+ });
140
+ }
141
+ async function post(url, body = {}, options2) {
142
+ return request(url, {
143
+ ...options2,
144
+ method: "POST",
145
+ body
146
+ });
147
+ }
148
+ async function put(url, body = {}, options2) {
149
+ return request(url, {
150
+ ...options2,
151
+ method: "PUT",
152
+ body
153
+ });
154
+ }
155
+ async function patch(url, body = {}, options2) {
156
+ return request(url, {
157
+ ...options2,
158
+ method: "PATCH",
159
+ body
160
+ });
161
+ }
162
+ async function del(url, params = {}, options2) {
163
+ return request(url, {
164
+ ...options2,
165
+ method: "DELETE",
166
+ query: params
167
+ });
168
+ }
169
+ return {
170
+ rawRequest,
171
+ request,
172
+ get,
173
+ post,
174
+ put,
175
+ patch,
176
+ del
177
+ };
178
+ }
179
+ // Annotate the CommonJS export names for ESM import in node:
180
+ 0 && (module.exports = {
181
+ createApiClient,
182
+ isApiError
183
+ });
184
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import {\n ofetch,\n type FetchOptions,\n type FetchContext,\n type $Fetch,\n} from \"ofetch\";\n\nexport interface ApiResult<T> {\n code: number;\n data: T;\n message: string;\n}\n\nexport interface ApiError extends Error {\n code: number; // 业务错误码\n data?: unknown; // 后端返回的 data 字段(如果有)\n status?: number; // HTTP 状态码(网络错误时)\n}\n\n// 类型守卫:判断是否是 API 业务错误\nexport const isApiError = (error: unknown): error is ApiError => {\n return error instanceof Error && \"code\" in error;\n};\n\nexport interface TokenStorage {\n getAccessToken: () => Promise<string> | string;\n setAccessToken: (token: string) => Promise<void> | void;\n}\n\nexport interface CreateApiClientOptions {\n baseURL: string;\n tokenStorage: TokenStorage;\n refreshToken?: (() => Promise<string>) | string | false;\n isAuthError?: (code: number) => boolean;\n unwrapResponse?<T>(result: unknown, returnFullResponse: boolean): T;\n createErrorFromResult?(res: unknown): Error;\n}\n\ntype RequestOptions = FetchOptions<\"json\"> & { returnFullResponse?: boolean };\n\n// 解包后端统一响应格式(默认实现)\nconst defaultUnwrapResponse = <T>(\n result: unknown,\n returnFullResponse = false\n): T => {\n if (result && typeof result === \"object\" && \"code\" in result) {\n const body = result as Record<string, unknown>;\n if (body.code === 0) {\n return returnFullResponse ? (body as T) : (body.data as T);\n }\n }\n return result as T;\n};\n\nconst defaultCreateErrorFromResult = (res: ApiResult<unknown>): ApiError => {\n const error = new Error(res.message || \"Request failed\") as ApiError;\n error.code = res.code;\n error.data = res.data;\n return error;\n};\n\nconst extractAccessToken = (data: unknown): string => {\n if (typeof data === \"string\" && data) {\n return data;\n }\n\n if (!data || typeof data !== \"object\") {\n throw new Error(\"Invalid refresh token response: data is not an object or string\");\n }\n\n const anyData = data as Record<string, unknown>;\n\n const accessToken = anyData.access_token ?? anyData.token;\n\n if (typeof accessToken === \"string\" && accessToken) {\n return accessToken;\n }\n\n throw new Error(\n \"Invalid refresh token response: no access_token or token field found\"\n );\n};\n\nexport function createApiClient(options: CreateApiClientOptions) {\n const {\n baseURL,\n tokenStorage,\n refreshToken = false,\n isAuthError = (code: number) => code === 401,\n unwrapResponse = defaultUnwrapResponse,\n createErrorFromResult = defaultCreateErrorFromResult,\n } = options;\n\n let refreshingPromise: Promise<string> | null = null;\n\n const refreshTokenFn: (() => Promise<string>) | null = !refreshToken\n ? null\n : typeof refreshToken === \"string\"\n ? async () => {\n const res = await ofetch<ApiResult<unknown>>(refreshToken, {\n baseURL,\n method: \"POST\",\n });\n\n if (res.code !== 0) {\n throw createErrorFromResult(res as ApiResult<unknown>);\n }\n\n return extractAccessToken(res.data);\n }\n : refreshToken;\n\n const rawRequest = ofetch.create({\n baseURL,\n\n async onRequest({ options }: FetchContext) {\n const token = await tokenStorage.getAccessToken();\n\n const headers = new Headers(options.headers as HeadersInit | undefined);\n\n if (token) {\n headers.set(\"Authorization\", `Bearer ${token}`);\n }\n\n options.headers = headers;\n },\n onResponse({ response }) {\n // 不在这里处理 code,统一交给 fetchApi 层处理\n if (response.status === 204) {\n response._data = null;\n }\n },\n\n async onResponseError(context: FetchContext) {\n // 后端统一返回 HTTP 200,此处只处理网络层错误(如超时、断网)\n const { response } = context;\n const message =\n (response?._data as Record<string, unknown>)?.message ||\n `HTTP ${response?.status || \"Network Error\"}`;\n\n const error = new Error(message as string) as ApiError;\n error.status = response?.status;\n error.data = response?._data;\n throw error;\n },\n }) as $Fetch;\n\n async function request<T = unknown>(\n url: string,\n options: RequestOptions & { _retry?: boolean } = {}\n ): Promise<T> {\n // 提取自定义选项,避免传递给 $fetch\n const { returnFullResponse, _retry, ...fetchOptions } = options;\n\n const res = await rawRequest<ApiResult<T>>(url, fetchOptions);\n // const res = await rawRequest(url, fetchOptions as RequestOptions);\n\n if (res.code === 0) {\n return unwrapResponse<T>(res, !!returnFullResponse);\n // if (returnFullResponse) {\n // return res as unknown as T;\n // }\n\n // return res.data;\n }\n\n if (isAuthError(res.code) && !_retry && refreshTokenFn) {\n try {\n if (!refreshingPromise) {\n refreshingPromise = refreshTokenFn().finally(() => {\n refreshingPromise = null;\n });\n }\n\n const newToken = await refreshingPromise;\n\n if (newToken) {\n await tokenStorage.setAccessToken(newToken);\n }\n\n return await request<T>(url, {\n ...(options || {}),\n _retry: true,\n } as any);\n } catch (e) {\n await tokenStorage.setAccessToken(\"\");\n throw createErrorFromResult(res as ApiResult<unknown>);\n }\n }\n\n throw createErrorFromResult(res as ApiResult<unknown>);\n }\n\n async function get<T = unknown>(\n url: string,\n params: FetchOptions[\"query\"] = {},\n options?: RequestOptions\n ) {\n return request<T>(url, {\n ...options,\n method: \"GET\",\n query: params,\n } as any);\n }\n\n async function post<T = unknown>(\n url: string,\n body: FetchOptions[\"body\"] = {},\n options?: RequestOptions\n ) {\n return request<T>(url, {\n ...options,\n method: \"POST\",\n body,\n } as any);\n }\n\n async function put<T = unknown>(\n url: string,\n body: FetchOptions[\"body\"] = {},\n options?: RequestOptions\n ) {\n return request<T>(url, {\n ...options,\n method: \"PUT\",\n body,\n } as any);\n }\n\n async function patch<T = unknown>(\n url: string,\n body: FetchOptions[\"body\"] = {},\n options?: RequestOptions\n ) {\n return request<T>(url, {\n ...options,\n method: \"PATCH\",\n body,\n } as any);\n }\n\n async function del<T = unknown>(\n url: string,\n params: FetchOptions[\"query\"] = {},\n options?: RequestOptions\n ) {\n return request<T>(url, {\n ...options,\n method: \"DELETE\",\n query: params,\n } as any);\n }\n\n return {\n rawRequest,\n request,\n get,\n post,\n put,\n patch,\n del,\n };\n}\n"],"mappings":"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","names":["options"]}
@@ -0,0 +1,160 @@
1
+ // src/client.ts
2
+ import {
3
+ ofetch
4
+ } from "ofetch";
5
+ var isApiError = (error) => {
6
+ return error instanceof Error && "code" in error;
7
+ };
8
+ var defaultUnwrapResponse = (result, returnFullResponse = false) => {
9
+ if (result && typeof result === "object" && "code" in result) {
10
+ const body = result;
11
+ if (body.code === 0) {
12
+ return returnFullResponse ? body : body.data;
13
+ }
14
+ }
15
+ return result;
16
+ };
17
+ var defaultCreateErrorFromResult = (res) => {
18
+ const error = new Error(res.message || "Request failed");
19
+ error.code = res.code;
20
+ error.data = res.data;
21
+ return error;
22
+ };
23
+ var extractAccessToken = (data) => {
24
+ if (typeof data === "string" && data) {
25
+ return data;
26
+ }
27
+ if (!data || typeof data !== "object") {
28
+ throw new Error("Invalid refresh token response: data is not an object or string");
29
+ }
30
+ const anyData = data;
31
+ const accessToken = anyData.access_token ?? anyData.token;
32
+ if (typeof accessToken === "string" && accessToken) {
33
+ return accessToken;
34
+ }
35
+ throw new Error(
36
+ "Invalid refresh token response: no access_token or token field found"
37
+ );
38
+ };
39
+ function createApiClient(options) {
40
+ const {
41
+ baseURL,
42
+ tokenStorage,
43
+ refreshToken = false,
44
+ isAuthError = (code) => code === 401,
45
+ unwrapResponse = defaultUnwrapResponse,
46
+ createErrorFromResult = defaultCreateErrorFromResult
47
+ } = options;
48
+ let refreshingPromise = null;
49
+ const refreshTokenFn = !refreshToken ? null : typeof refreshToken === "string" ? async () => {
50
+ const res = await ofetch(refreshToken, {
51
+ baseURL,
52
+ method: "POST"
53
+ });
54
+ if (res.code !== 0) {
55
+ throw createErrorFromResult(res);
56
+ }
57
+ return extractAccessToken(res.data);
58
+ } : refreshToken;
59
+ const rawRequest = ofetch.create({
60
+ baseURL,
61
+ async onRequest({ options: options2 }) {
62
+ const token = await tokenStorage.getAccessToken();
63
+ const headers = new Headers(options2.headers);
64
+ if (token) {
65
+ headers.set("Authorization", `Bearer ${token}`);
66
+ }
67
+ options2.headers = headers;
68
+ },
69
+ onResponse({ response }) {
70
+ if (response.status === 204) {
71
+ response._data = null;
72
+ }
73
+ },
74
+ async onResponseError(context) {
75
+ const { response } = context;
76
+ const message = response?._data?.message || `HTTP ${response?.status || "Network Error"}`;
77
+ const error = new Error(message);
78
+ error.status = response?.status;
79
+ error.data = response?._data;
80
+ throw error;
81
+ }
82
+ });
83
+ async function request(url, options2 = {}) {
84
+ const { returnFullResponse, _retry, ...fetchOptions } = options2;
85
+ const res = await rawRequest(url, fetchOptions);
86
+ if (res.code === 0) {
87
+ return unwrapResponse(res, !!returnFullResponse);
88
+ }
89
+ if (isAuthError(res.code) && !_retry && refreshTokenFn) {
90
+ try {
91
+ if (!refreshingPromise) {
92
+ refreshingPromise = refreshTokenFn().finally(() => {
93
+ refreshingPromise = null;
94
+ });
95
+ }
96
+ const newToken = await refreshingPromise;
97
+ if (newToken) {
98
+ await tokenStorage.setAccessToken(newToken);
99
+ }
100
+ return await request(url, {
101
+ ...options2 || {},
102
+ _retry: true
103
+ });
104
+ } catch (e) {
105
+ await tokenStorage.setAccessToken("");
106
+ throw createErrorFromResult(res);
107
+ }
108
+ }
109
+ throw createErrorFromResult(res);
110
+ }
111
+ async function get(url, params = {}, options2) {
112
+ return request(url, {
113
+ ...options2,
114
+ method: "GET",
115
+ query: params
116
+ });
117
+ }
118
+ async function post(url, body = {}, options2) {
119
+ return request(url, {
120
+ ...options2,
121
+ method: "POST",
122
+ body
123
+ });
124
+ }
125
+ async function put(url, body = {}, options2) {
126
+ return request(url, {
127
+ ...options2,
128
+ method: "PUT",
129
+ body
130
+ });
131
+ }
132
+ async function patch(url, body = {}, options2) {
133
+ return request(url, {
134
+ ...options2,
135
+ method: "PATCH",
136
+ body
137
+ });
138
+ }
139
+ async function del(url, params = {}, options2) {
140
+ return request(url, {
141
+ ...options2,
142
+ method: "DELETE",
143
+ query: params
144
+ });
145
+ }
146
+ return {
147
+ rawRequest,
148
+ request,
149
+ get,
150
+ post,
151
+ put,
152
+ patch,
153
+ del
154
+ };
155
+ }
156
+ export {
157
+ createApiClient,
158
+ isApiError
159
+ };
160
+ //# sourceMappingURL=client.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import {\n ofetch,\n type FetchOptions,\n type FetchContext,\n type $Fetch,\n} from \"ofetch\";\n\nexport interface ApiResult<T> {\n code: number;\n data: T;\n message: string;\n}\n\nexport interface ApiError extends Error {\n code: number; // 业务错误码\n data?: unknown; // 后端返回的 data 字段(如果有)\n status?: number; // HTTP 状态码(网络错误时)\n}\n\n// 类型守卫:判断是否是 API 业务错误\nexport const isApiError = (error: unknown): error is ApiError => {\n return error instanceof Error && \"code\" in error;\n};\n\nexport interface TokenStorage {\n getAccessToken: () => Promise<string> | string;\n setAccessToken: (token: string) => Promise<void> | void;\n}\n\nexport interface CreateApiClientOptions {\n baseURL: string;\n tokenStorage: TokenStorage;\n refreshToken?: (() => Promise<string>) | string | false;\n isAuthError?: (code: number) => boolean;\n unwrapResponse?<T>(result: unknown, returnFullResponse: boolean): T;\n createErrorFromResult?(res: unknown): Error;\n}\n\ntype RequestOptions = FetchOptions<\"json\"> & { returnFullResponse?: boolean };\n\n// 解包后端统一响应格式(默认实现)\nconst defaultUnwrapResponse = <T>(\n result: unknown,\n returnFullResponse = false\n): T => {\n if (result && typeof result === \"object\" && \"code\" in result) {\n const body = result as Record<string, unknown>;\n if (body.code === 0) {\n return returnFullResponse ? (body as T) : (body.data as T);\n }\n }\n return result as T;\n};\n\nconst defaultCreateErrorFromResult = (res: ApiResult<unknown>): ApiError => {\n const error = new Error(res.message || \"Request failed\") as ApiError;\n error.code = res.code;\n error.data = res.data;\n return error;\n};\n\nconst extractAccessToken = (data: unknown): string => {\n if (typeof data === \"string\" && data) {\n return data;\n }\n\n if (!data || typeof data !== \"object\") {\n throw new Error(\"Invalid refresh token response: data is not an object or string\");\n }\n\n const anyData = data as Record<string, unknown>;\n\n const accessToken = anyData.access_token ?? anyData.token;\n\n if (typeof accessToken === \"string\" && accessToken) {\n return accessToken;\n }\n\n throw new Error(\n \"Invalid refresh token response: no access_token or token field found\"\n );\n};\n\nexport function createApiClient(options: CreateApiClientOptions) {\n const {\n baseURL,\n tokenStorage,\n refreshToken = false,\n isAuthError = (code: number) => code === 401,\n unwrapResponse = defaultUnwrapResponse,\n createErrorFromResult = defaultCreateErrorFromResult,\n } = options;\n\n let refreshingPromise: Promise<string> | null = null;\n\n const refreshTokenFn: (() => Promise<string>) | null = !refreshToken\n ? null\n : typeof refreshToken === \"string\"\n ? async () => {\n const res = await ofetch<ApiResult<unknown>>(refreshToken, {\n baseURL,\n method: \"POST\",\n });\n\n if (res.code !== 0) {\n throw createErrorFromResult(res as ApiResult<unknown>);\n }\n\n return extractAccessToken(res.data);\n }\n : refreshToken;\n\n const rawRequest = ofetch.create({\n baseURL,\n\n async onRequest({ options }: FetchContext) {\n const token = await tokenStorage.getAccessToken();\n\n const headers = new Headers(options.headers as HeadersInit | undefined);\n\n if (token) {\n headers.set(\"Authorization\", `Bearer ${token}`);\n }\n\n options.headers = headers;\n },\n onResponse({ response }) {\n // 不在这里处理 code,统一交给 fetchApi 层处理\n if (response.status === 204) {\n response._data = null;\n }\n },\n\n async onResponseError(context: FetchContext) {\n // 后端统一返回 HTTP 200,此处只处理网络层错误(如超时、断网)\n const { response } = context;\n const message =\n (response?._data as Record<string, unknown>)?.message ||\n `HTTP ${response?.status || \"Network Error\"}`;\n\n const error = new Error(message as string) as ApiError;\n error.status = response?.status;\n error.data = response?._data;\n throw error;\n },\n }) as $Fetch;\n\n async function request<T = unknown>(\n url: string,\n options: RequestOptions & { _retry?: boolean } = {}\n ): Promise<T> {\n // 提取自定义选项,避免传递给 $fetch\n const { returnFullResponse, _retry, ...fetchOptions } = options;\n\n const res = await rawRequest<ApiResult<T>>(url, fetchOptions);\n // const res = await rawRequest(url, fetchOptions as RequestOptions);\n\n if (res.code === 0) {\n return unwrapResponse<T>(res, !!returnFullResponse);\n // if (returnFullResponse) {\n // return res as unknown as T;\n // }\n\n // return res.data;\n }\n\n if (isAuthError(res.code) && !_retry && refreshTokenFn) {\n try {\n if (!refreshingPromise) {\n refreshingPromise = refreshTokenFn().finally(() => {\n refreshingPromise = null;\n });\n }\n\n const newToken = await refreshingPromise;\n\n if (newToken) {\n await tokenStorage.setAccessToken(newToken);\n }\n\n return await request<T>(url, {\n ...(options || {}),\n _retry: true,\n } as any);\n } catch (e) {\n await tokenStorage.setAccessToken(\"\");\n throw createErrorFromResult(res as ApiResult<unknown>);\n }\n }\n\n throw createErrorFromResult(res as ApiResult<unknown>);\n }\n\n async function get<T = unknown>(\n url: string,\n params: FetchOptions[\"query\"] = {},\n options?: RequestOptions\n ) {\n return request<T>(url, {\n ...options,\n method: \"GET\",\n query: params,\n } as any);\n }\n\n async function post<T = unknown>(\n url: string,\n body: FetchOptions[\"body\"] = {},\n options?: RequestOptions\n ) {\n return request<T>(url, {\n ...options,\n method: \"POST\",\n body,\n } as any);\n }\n\n async function put<T = unknown>(\n url: string,\n body: FetchOptions[\"body\"] = {},\n options?: RequestOptions\n ) {\n return request<T>(url, {\n ...options,\n method: \"PUT\",\n body,\n } as any);\n }\n\n async function patch<T = unknown>(\n url: string,\n body: FetchOptions[\"body\"] = {},\n options?: RequestOptions\n ) {\n return request<T>(url, {\n ...options,\n method: \"PATCH\",\n body,\n } as any);\n }\n\n async function del<T = unknown>(\n url: string,\n params: FetchOptions[\"query\"] = {},\n options?: RequestOptions\n ) {\n return request<T>(url, {\n ...options,\n method: \"DELETE\",\n query: params,\n } as any);\n }\n\n return {\n rawRequest,\n request,\n get,\n post,\n put,\n patch,\n del,\n };\n}\n"],"mappings":"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","names":["options"]}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@i.un/api-client",
3
+ "version": "0.1.0",
4
+ "description": "Universal API client for i.un services",
5
+ "license": "MIT",
6
+ "author": "nova <www.nova@gmail.com>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": ""
10
+ },
11
+ "homepage": "",
12
+ "keywords": [
13
+ "api",
14
+ "client",
15
+ "fetch",
16
+ "ts",
17
+ "sdk",
18
+ "refresh-token",
19
+ "token",
20
+ "access-token"
21
+ ],
22
+ "main": "dist/client.cjs",
23
+ "module": "dist/client.mjs",
24
+ "types": "dist/client.d.ts",
25
+ "sideEffects": false,
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/client.d.ts",
29
+ "import": "./dist/client.mjs",
30
+ "require": "./dist/client.cjs"
31
+ }
32
+ },
33
+ "scripts": {
34
+ "build": "tsup",
35
+ "build:watch": "tsup --watch",
36
+ "clean": "rm -rf dist"
37
+ },
38
+ "dependencies": {
39
+ "ofetch": ">=1.4.0"
40
+ },
41
+ "devDependencies": {
42
+ "tsup": "^8.0.0",
43
+ "typescript": "^5.6.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=18"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }