@anjianshi/utils 2.9.1 → 3.0.1
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/env-node/index.d.ts +0 -1
- package/env-node/index.js +0 -1
- package/env-react/hooks.d.ts +19 -0
- package/env-react/hooks.js +31 -0
- package/env-service/prisma/extensions/with-transaction.d.ts +2 -2
- package/env-service/prisma/extensions/with-transaction.js +3 -3
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/lang/async.d.ts +1 -1
- package/lang/index.d.ts +1 -1
- package/lang/index.js +1 -1
- package/lang/{may-success.d.ts → result.d.ts} +14 -7
- package/lang/{may-success.js → result.js} +20 -2
- package/lang/string.d.ts +1 -1
- package/lang/string.js +3 -3
- package/package.json +4 -4
- package/safe-request.d.ts +53 -0
- package/safe-request.js +140 -0
- package/validators/base.d.ts +5 -5
- package/env-node/safe-request.d.ts +0 -26
- package/env-node/safe-request.js +0 -40
package/env-node/index.d.ts
CHANGED
package/env-node/index.js
CHANGED
package/env-react/hooks.d.ts
CHANGED
|
@@ -2,3 +2,22 @@
|
|
|
2
2
|
* 生成一个 state 以及与其值同步的 ref
|
|
3
3
|
*/
|
|
4
4
|
export declare function useStateWithRef<T>(initialValue: T | (() => T)): readonly [T, import("react").Dispatch<import("react").SetStateAction<T>>, import("react").RefObject<T>];
|
|
5
|
+
/**
|
|
6
|
+
* 在 useEffect() 中执行异步操作。
|
|
7
|
+
* 因是异步运行,对清理机制的处理有所变化。
|
|
8
|
+
*
|
|
9
|
+
* 原本 useEffect() 中,通过返回一个函数来设置清理条件,在异步操作中不适用。
|
|
10
|
+
* 此外,同步内容是一定会在执行完只后才有可能触发清理的,但异步内容有可能在运行完之前,依赖就已经变化,触发了清理。
|
|
11
|
+
*
|
|
12
|
+
* 这里通过一个 context 对象来应对异步的情况。
|
|
13
|
+
* context.cancelled 值代表此次运行是否已被取消(清理),异步操作执行过程中如果发现此值变为 true,则可停止执行了。
|
|
14
|
+
* context.onCancelled(() => {}) 可注册一个回调,此次执行被清理时触发。多次调用仅保留最后一次的回调。
|
|
15
|
+
*
|
|
16
|
+
* 注意:需要配置 ESLint react-hooks/exhaustive-deps 规则以保证 deps 参与依赖检查
|
|
17
|
+
* 详见 https://react.dev/reference/eslint-plugin-react-hooks/lints/exhaustive-deps
|
|
18
|
+
*/
|
|
19
|
+
export declare function useAsyncEffect(callback: (context: AsyncEffectContext) => Promise<void>, deps?: unknown[]): void;
|
|
20
|
+
export interface AsyncEffectContext {
|
|
21
|
+
cancelled: boolean;
|
|
22
|
+
onCancel: (callback: () => void) => void;
|
|
23
|
+
}
|
package/env-react/hooks.js
CHANGED
|
@@ -14,3 +14,34 @@ export function useStateWithRef(initialValue) {
|
|
|
14
14
|
}, []);
|
|
15
15
|
return [state, setStateWithRef, ref];
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* 在 useEffect() 中执行异步操作。
|
|
19
|
+
* 因是异步运行,对清理机制的处理有所变化。
|
|
20
|
+
*
|
|
21
|
+
* 原本 useEffect() 中,通过返回一个函数来设置清理条件,在异步操作中不适用。
|
|
22
|
+
* 此外,同步内容是一定会在执行完只后才有可能触发清理的,但异步内容有可能在运行完之前,依赖就已经变化,触发了清理。
|
|
23
|
+
*
|
|
24
|
+
* 这里通过一个 context 对象来应对异步的情况。
|
|
25
|
+
* context.cancelled 值代表此次运行是否已被取消(清理),异步操作执行过程中如果发现此值变为 true,则可停止执行了。
|
|
26
|
+
* context.onCancelled(() => {}) 可注册一个回调,此次执行被清理时触发。多次调用仅保留最后一次的回调。
|
|
27
|
+
*
|
|
28
|
+
* 注意:需要配置 ESLint react-hooks/exhaustive-deps 规则以保证 deps 参与依赖检查
|
|
29
|
+
* 详见 https://react.dev/reference/eslint-plugin-react-hooks/lints/exhaustive-deps
|
|
30
|
+
*/
|
|
31
|
+
export function useAsyncEffect(callback, deps = []) {
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
let onCancel = null;
|
|
34
|
+
const context = {
|
|
35
|
+
cancelled: false,
|
|
36
|
+
onCancel(callback) {
|
|
37
|
+
onCancel = callback;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
void callback(context);
|
|
41
|
+
return () => {
|
|
42
|
+
context.cancelled = true;
|
|
43
|
+
if (onCancel)
|
|
44
|
+
onCancel();
|
|
45
|
+
};
|
|
46
|
+
}, deps); // eslint-disable-line ts-react-hooks/exhaustive-deps
|
|
47
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type ITXClientDenyList } from '@prisma/client/runtime/library.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Result, Failed } from '../../../index.js';
|
|
3
3
|
export declare const withTransaction: (client: any) => import("@prisma/client/extension").PrismaClientExtends<import("@prisma/client/runtime/library").InternalArgs<{}, {}, {}, {
|
|
4
4
|
$withTransaction: typeof $withTransaction;
|
|
5
5
|
}>>;
|
|
6
6
|
export type GetPrismaClientInTransaction<PrismaClient> = Omit<PrismaClient, ITXClientDenyList>;
|
|
7
7
|
export type WithTransactionMethod = typeof $withTransaction;
|
|
8
|
-
declare function $withTransaction<That extends object, R extends
|
|
8
|
+
declare function $withTransaction<That extends object, R extends Result<unknown, unknown>>(this: That, callback: (dbInTransaction: GetPrismaClientInTransaction<That>) => Promise<R>): Promise<Failed<any> | R>;
|
|
9
9
|
export {};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 在事务中执行回调。与 $transaction 有几点不同:
|
|
3
|
-
* 1. 回调必须返回
|
|
3
|
+
* 1. 回调必须返回 Result 值
|
|
4
4
|
* 2. 回调返回 Failed 值或抛出异常都会触发回滚。
|
|
5
5
|
* 如果是返回 Failed,会作为此方法的返回值;如果是抛出异常,则异常会继续向上传递,直到被捕获或触发请求失败。
|
|
6
6
|
* 3. 如果已经处于事务中,会沿用上层事务,且回调返回 Failed 或抛出异常会触发上层事务的回滚。
|
|
7
7
|
*
|
|
8
|
-
* const result:
|
|
8
|
+
* const result: Result = await db.$withTransaction(
|
|
9
9
|
* async (dbInTransaction) => {
|
|
10
10
|
* // do something
|
|
11
11
|
* return success()
|
|
@@ -26,7 +26,7 @@ class FailedInTransaction extends Error {
|
|
|
26
26
|
this.failed = failed;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
// 注意:此函数的返回值为 `R | Failed<any>`,例如实际可能为 `
|
|
29
|
+
// 注意:此函数的返回值为 `R | Failed<any>`,例如实际可能为 `Result<xxx, xxx> | Failed<any>`,这是有意为之的,`Failed<any>` 并不多余。
|
|
30
30
|
// 因为有时 callback() 只会返回 success 结果,此时 R=Success<xxx>,但是 $withTransaction 整体的返回值仍有可能有 Failed<any>,所以不能用 R 作为整体返回值。
|
|
31
31
|
async function $withTransaction(callback) {
|
|
32
32
|
const executeCallback = async (dbInTransaction) => {
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
package/lang/async.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 返回一个指定毫秒后 resolve 的 Promise
|
|
6
6
|
* 若指定了 resolveValue,则 Promise 最终会解析出此值
|
|
7
7
|
*/
|
|
8
|
-
declare function sleep(ms: number): Promise<
|
|
8
|
+
declare function sleep(ms: number): Promise<undefined>;
|
|
9
9
|
declare function sleep<T>(ms: number, resolveValue: T): Promise<T>;
|
|
10
10
|
export { sleep };
|
|
11
11
|
/**
|
package/lang/index.d.ts
CHANGED
package/lang/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Result:代表一种“可能失败”的操作结果,可以作为函数返回值,也可以作为接口响应值。
|
|
3
3
|
* 它的灵感来自 Scala 的 Option 类型。
|
|
4
4
|
*
|
|
5
5
|
* 原本,在 JavaScript 里一个可能失败的操作有两种表示失败的方式:
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 2. 抛出异常
|
|
8
8
|
* 返回空值的方式无法附带失败信息;而抛出异常会导致层层嵌套的 try catch 语句,且其实性能不好。
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* Result 就是为了解决这两个痛点:
|
|
11
11
|
* 1. 它的 Failed 类型可以携带失败信息。
|
|
12
12
|
* 2. 无需 try catch,只需简单的 result.success 判断
|
|
13
13
|
*/
|
|
@@ -16,13 +16,13 @@ export interface Success<T = void> {
|
|
|
16
16
|
success: true;
|
|
17
17
|
data: T;
|
|
18
18
|
}
|
|
19
|
-
export interface Failed<T =
|
|
19
|
+
export interface Failed<T = unknown> {
|
|
20
20
|
success: false;
|
|
21
21
|
message: string;
|
|
22
22
|
code?: string | number;
|
|
23
23
|
data: T;
|
|
24
24
|
}
|
|
25
|
-
export type
|
|
25
|
+
export type Result<T = void, FailedT = unknown> = Success<T> | Failed<FailedT>;
|
|
26
26
|
/** 生成 Success 数据 */
|
|
27
27
|
declare function success(): Success;
|
|
28
28
|
declare function success<T>(data: T): Success<T>;
|
|
@@ -33,8 +33,15 @@ declare function failed<T>(message: string, code: string | number | undefined, d
|
|
|
33
33
|
export { failed };
|
|
34
34
|
/**
|
|
35
35
|
* 若传入值为 success,格式化其 data;否则原样返回错误
|
|
36
|
-
* 支持传入会返回
|
|
36
|
+
* 支持传入会返回 Result 的 Promise
|
|
37
37
|
*/
|
|
38
|
-
declare function formatSuccess<T1, T2, FT = void>(value:
|
|
39
|
-
declare function formatSuccess<T1, T2, FT = void>(value: Promise<
|
|
38
|
+
declare function formatSuccess<T1, T2, FT = void>(value: Result<T1, FT>, formatter: (value: T1) => T2): Result<T2, FT>;
|
|
39
|
+
declare function formatSuccess<T1, T2, FT = void>(value: Promise<Result<T1, FT>>, formatter: (value: T1) => T2): Promise<Result<T2, FT>>;
|
|
40
40
|
export { formatSuccess };
|
|
41
|
+
/**
|
|
42
|
+
* 把可能抛出异常的 Promise 转换为返回 Result 的 Promise。
|
|
43
|
+
* 其中返回的 Failed 对象的 data 是 catch 捕获到的错误对象。
|
|
44
|
+
*
|
|
45
|
+
* 通过此函数可避免写一长串嵌套的 try catch 语句。
|
|
46
|
+
*/
|
|
47
|
+
export declare function handleException<T>(promise: Promise<T>): Promise<Result<T>>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Result:代表一种“可能失败”的操作结果,可以作为函数返回值,也可以作为接口响应值。
|
|
3
3
|
* 它的灵感来自 Scala 的 Option 类型。
|
|
4
4
|
*
|
|
5
5
|
* 原本,在 JavaScript 里一个可能失败的操作有两种表示失败的方式:
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 2. 抛出异常
|
|
8
8
|
* 返回空值的方式无法附带失败信息;而抛出异常会导致层层嵌套的 try catch 语句,且其实性能不好。
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* Result 就是为了解决这两个痛点:
|
|
11
11
|
* 1. 它的 Failed 类型可以携带失败信息。
|
|
12
12
|
* 2. 无需 try catch,只需简单的 result.success 判断
|
|
13
13
|
*/
|
|
@@ -25,3 +25,21 @@ function formatSuccess(value, formatter) {
|
|
|
25
25
|
return value.success ? success(formatter(value.data)) : value;
|
|
26
26
|
}
|
|
27
27
|
export { formatSuccess };
|
|
28
|
+
/**
|
|
29
|
+
* 把可能抛出异常的 Promise 转换为返回 Result 的 Promise。
|
|
30
|
+
* 其中返回的 Failed 对象的 data 是 catch 捕获到的错误对象。
|
|
31
|
+
*
|
|
32
|
+
* 通过此函数可避免写一长串嵌套的 try catch 语句。
|
|
33
|
+
*/
|
|
34
|
+
export async function handleException(promise) {
|
|
35
|
+
return promise.then(data => success(data), (error) => {
|
|
36
|
+
let message;
|
|
37
|
+
try {
|
|
38
|
+
message = (error?.message ?? '') || String(error);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
message = 'Got Exception';
|
|
42
|
+
}
|
|
43
|
+
return failed(message, undefined, error);
|
|
44
|
+
});
|
|
45
|
+
}
|
package/lang/string.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export declare function numericCompare(a: string, b: string): number;
|
|
|
10
10
|
* 1. 默认设置 radix 为 10,无需再手动指定
|
|
11
11
|
* 2. 支持指定 fallback,当解析出来的数字是 NaN 时返回这个值
|
|
12
12
|
*/
|
|
13
|
-
export declare function safeParseInt(value: string | number, fallback?: number,
|
|
13
|
+
export declare function safeParseInt(value: string | number, fallback?: number, radix?: number): number;
|
|
14
14
|
/**
|
|
15
15
|
* 字符串解析成浮点数;若解析结果是 NaN,返回 fallback
|
|
16
16
|
*/
|
package/lang/string.js
CHANGED
|
@@ -45,9 +45,9 @@ export function numericCompare(a, b) {
|
|
|
45
45
|
* 1. 默认设置 radix 为 10,无需再手动指定
|
|
46
46
|
* 2. 支持指定 fallback,当解析出来的数字是 NaN 时返回这个值
|
|
47
47
|
*/
|
|
48
|
-
export function safeParseInt(value, fallback,
|
|
49
|
-
const raw = parseInt(String(value),
|
|
50
|
-
return isFinite(raw) ? raw :
|
|
48
|
+
export function safeParseInt(value, fallback, radix = 10) {
|
|
49
|
+
const raw = parseInt(String(value), radix);
|
|
50
|
+
return isFinite(raw) ? raw : fallback ?? raw;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
53
|
* 字符串解析成浮点数;若解析结果是 NaN,返回 fallback
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anjianshi/utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Common JavaScript Utils",
|
|
5
5
|
"homepage": "https://github.com/anjianshi/js-packages/utils",
|
|
6
6
|
"bugs": {
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
"typescript": "^5.8.3",
|
|
33
33
|
"vconsole": "^3.15.1",
|
|
34
34
|
"@anjianshi/presets-eslint-base": "6.0.0",
|
|
35
|
-
"@anjianshi/presets-eslint-react": "6.0.0",
|
|
36
35
|
"@anjianshi/presets-eslint-node": "6.0.0",
|
|
36
|
+
"@anjianshi/presets-eslint-react": "6.0.0",
|
|
37
37
|
"@anjianshi/presets-eslint-typescript": "6.0.0",
|
|
38
|
-
"@anjianshi/presets-prettier": "3.0.
|
|
39
|
-
"@anjianshi/presets-typescript": "3.2.
|
|
38
|
+
"@anjianshi/presets-prettier": "3.0.5",
|
|
39
|
+
"@anjianshi/presets-typescript": "3.2.5"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@emotion/react": "^11.14.0",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type Result } from './lang/result.js';
|
|
2
|
+
import { type Logger } from './logging/index.js';
|
|
3
|
+
export type { Options as RequestOptions, FormattedOptions as RequestFormattedOptions };
|
|
4
|
+
interface Options {
|
|
5
|
+
urlPrefix?: string;
|
|
6
|
+
url?: string;
|
|
7
|
+
query?: Record<string, string | number | undefined>;
|
|
8
|
+
method?: string;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
body?: string | FormData | null;
|
|
11
|
+
/**
|
|
12
|
+
* 向后端传递的数据。对于 GET 请求,会合并到 query 中;对于 POST 请求,会作为 POST body,代替 body 参数
|
|
13
|
+
* 注意:为了支持传入 interface 类型的值,Record 只能定义成 Record<string, any>
|
|
14
|
+
*/
|
|
15
|
+
data?: FormData | Record<string, any>;
|
|
16
|
+
/** 超时时间,不指定或设为 0 代表不限 */
|
|
17
|
+
timeout?: number;
|
|
18
|
+
}
|
|
19
|
+
type FormattedOptions = Required<Pick<Options, 'url' | 'method' | 'headers' | 'body' | 'timeout'>>;
|
|
20
|
+
type PredefinedOptions = Pick<Options, 'urlPrefix' | 'method' | 'headers' | 'timeout'>;
|
|
21
|
+
/**
|
|
22
|
+
* 建立一个请求发起器,并可预设部分选项。
|
|
23
|
+
* 可以继承此类来自定义默认的错误处理逻辑。
|
|
24
|
+
*
|
|
25
|
+
* 请求失败时的 Failed 对象,其 code 为 HTTP status,没有 status 时为 0
|
|
26
|
+
* data 为解析出的响应内容,没有或解析失败则为 undefined
|
|
27
|
+
*/
|
|
28
|
+
export declare class SafeRequestClient {
|
|
29
|
+
readonly logger: Logger;
|
|
30
|
+
readonly prefefinedOptions: PredefinedOptions;
|
|
31
|
+
constructor(options?: PredefinedOptions & {
|
|
32
|
+
loggerName?: string;
|
|
33
|
+
});
|
|
34
|
+
/** 生成一个快捷方式函数,调用它相当于调用 client.request() */
|
|
35
|
+
asFunction(): <T>(inputUrl: string, inputOptions?: Options) => Promise<Result<T>>;
|
|
36
|
+
request<T>(inputUrl: string, inputOptions?: Options): Promise<Result<T>>;
|
|
37
|
+
formatOptions(input: Options): Promise<FormattedOptions>;
|
|
38
|
+
/** 请求发起前调用此方法补充 Headers 内容 */
|
|
39
|
+
protected getHeaders(options: FormattedOptions, inputOptions: Options): Record<string, string> | undefined | Promise<Record<string, string> | undefined>;
|
|
40
|
+
protected parseResponse<T>(options: FormattedOptions, response: Response): Promise<Result<T>>;
|
|
41
|
+
/** 若请求未成功发起,会触发此回调来生成失败信息 */
|
|
42
|
+
protected onRequestError(error: Error, url: string): import("./lang/result.js").Failed<undefined>;
|
|
43
|
+
/** 请求成功发起,但服务端返回失败状态(如 500),会触发此回调来生成失败信息 */
|
|
44
|
+
protected onResponseError(url: string, response: Response, responseData: unknown): Promise<import("./lang/result.js").Failed<unknown>>;
|
|
45
|
+
/** 服务端返回内容解析失败时,会触发此回调来生成失败信息 */
|
|
46
|
+
protected onParseFailed(error: Error, response: Response, url: string): import("./lang/result.js").Failed<undefined>;
|
|
47
|
+
/** 处理超时 */
|
|
48
|
+
protected onTimeout(url: string): import("./lang/result.js").Failed<undefined>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 模块自带一个可直接调用发起请求的函数,跳过初始化实例
|
|
52
|
+
*/
|
|
53
|
+
export declare const safeRequest: <T>(inputUrl: string, inputOptions?: Options) => Promise<Result<T>>;
|
package/safe-request.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { sleep } from './lang/async.js';
|
|
2
|
+
import { failed, handleException } from './lang/result.js';
|
|
3
|
+
import { getLogger } from './logging/index.js';
|
|
4
|
+
import { combineUrl } from './url.js';
|
|
5
|
+
/**
|
|
6
|
+
* 建立一个请求发起器,并可预设部分选项。
|
|
7
|
+
* 可以继承此类来自定义默认的错误处理逻辑。
|
|
8
|
+
*
|
|
9
|
+
* 请求失败时的 Failed 对象,其 code 为 HTTP status,没有 status 时为 0
|
|
10
|
+
* data 为解析出的响应内容,没有或解析失败则为 undefined
|
|
11
|
+
*/
|
|
12
|
+
export class SafeRequestClient {
|
|
13
|
+
logger;
|
|
14
|
+
prefefinedOptions;
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.logger = getLogger(options.loggerName ?? 'request');
|
|
17
|
+
this.prefefinedOptions = options;
|
|
18
|
+
}
|
|
19
|
+
/** 生成一个快捷方式函数,调用它相当于调用 client.request() */
|
|
20
|
+
asFunction() {
|
|
21
|
+
return async (inputUrl, inputOptions) => this.request(inputUrl, inputOptions);
|
|
22
|
+
}
|
|
23
|
+
async request(inputUrl, inputOptions) {
|
|
24
|
+
const options = await this.formatOptions({
|
|
25
|
+
url: inputUrl,
|
|
26
|
+
...(inputOptions ?? {}),
|
|
27
|
+
});
|
|
28
|
+
const { url, method, headers, body, timeout } = options;
|
|
29
|
+
try {
|
|
30
|
+
// 发起请求
|
|
31
|
+
const request = fetch(url, { method, headers, body });
|
|
32
|
+
let response;
|
|
33
|
+
try {
|
|
34
|
+
response = await (typeof timeout === 'number'
|
|
35
|
+
? Promise.race([request, sleep(timeout)])
|
|
36
|
+
: request);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
// 处理“请求发起失败”
|
|
40
|
+
return this.onRequestError(error, url);
|
|
41
|
+
}
|
|
42
|
+
// 处理超时
|
|
43
|
+
if (response === undefined) {
|
|
44
|
+
return this.onTimeout(url);
|
|
45
|
+
}
|
|
46
|
+
// 处理“服务端返回失败状态”
|
|
47
|
+
if (!response.status.toString().startsWith('2')) {
|
|
48
|
+
// 此时服务端仍可能输出一些内容,试着解析出来
|
|
49
|
+
const responseDataRes = await this.parseResponse(options, response);
|
|
50
|
+
const responseData = responseDataRes.success ? responseDataRes.data : undefined;
|
|
51
|
+
return await this.onResponseError(url, response, responseData);
|
|
52
|
+
}
|
|
53
|
+
// 解析响应内容
|
|
54
|
+
return await this.parseResponse(options, response);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
this.logger.error('Unexpected error', error);
|
|
58
|
+
return failed('Request handle failed.');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async formatOptions(input) {
|
|
62
|
+
const predefined = this.prefefinedOptions;
|
|
63
|
+
const { urlPrefix = predefined.urlPrefix ?? '', url: rawUrl, query = {}, method = predefined.method ?? 'GET', headers: rawHeaders = {}, body: rawBody = null, data, timeout = predefined.timeout ?? 0, } = input;
|
|
64
|
+
const headers = {
|
|
65
|
+
...(predefined.headers ?? {}),
|
|
66
|
+
...rawHeaders,
|
|
67
|
+
};
|
|
68
|
+
let body = rawBody;
|
|
69
|
+
if (data !== undefined) {
|
|
70
|
+
if (method === 'GET') {
|
|
71
|
+
Object.assign(query, data);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
body = data instanceof FormData ? data : JSON.stringify(data);
|
|
75
|
+
headers['Content-Type'] = 'application/json; charset=utf-8';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const url = combineUrl(urlPrefix + (rawUrl ?? ''), query);
|
|
79
|
+
const options = {
|
|
80
|
+
method,
|
|
81
|
+
url,
|
|
82
|
+
headers,
|
|
83
|
+
body,
|
|
84
|
+
timeout,
|
|
85
|
+
};
|
|
86
|
+
Object.assign(options.headers, await this.getHeaders(options, input));
|
|
87
|
+
return options;
|
|
88
|
+
}
|
|
89
|
+
/** 请求发起前调用此方法补充 Headers 内容 */
|
|
90
|
+
getHeaders(
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
92
|
+
options,
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
94
|
+
inputOptions) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
async parseResponse(options, response) {
|
|
98
|
+
let result;
|
|
99
|
+
result = await handleException(response.json());
|
|
100
|
+
if (result.success)
|
|
101
|
+
return result;
|
|
102
|
+
const contentType = (response.headers.get('Content-Type') ?? '').toLowerCase().trim();
|
|
103
|
+
if (contentType.startsWith('text/') || contentType === '') {
|
|
104
|
+
result = (await handleException(response.text()));
|
|
105
|
+
if (result.success)
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
result = (await handleException(response.arrayBuffer()));
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
/** 若请求未成功发起,会触发此回调来生成失败信息 */
|
|
112
|
+
onRequestError(error, url) {
|
|
113
|
+
this.logger.error('Request Failed', { url, error });
|
|
114
|
+
return failed('Request Failed', 0, undefined);
|
|
115
|
+
}
|
|
116
|
+
/** 请求成功发起,但服务端返回失败状态(如 500),会触发此回调来生成失败信息 */
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
118
|
+
async onResponseError(url, response, responseData) {
|
|
119
|
+
this.logger.error('Response Error Status', {
|
|
120
|
+
url,
|
|
121
|
+
status: response.status,
|
|
122
|
+
data: responseData,
|
|
123
|
+
});
|
|
124
|
+
return failed(`Response Error Status - ${response.status}`, response.status, responseData);
|
|
125
|
+
}
|
|
126
|
+
/** 服务端返回内容解析失败时,会触发此回调来生成失败信息 */
|
|
127
|
+
onParseFailed(error, response, url) {
|
|
128
|
+
this.logger.error('Response Parse Failed', { url, response, error });
|
|
129
|
+
return failed('Response Parse Failed', response.status, undefined);
|
|
130
|
+
}
|
|
131
|
+
/** 处理超时 */
|
|
132
|
+
onTimeout(url) {
|
|
133
|
+
this.logger.warn('Request Timeout', url);
|
|
134
|
+
return failed('Request Timeout', 0, undefined);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 模块自带一个可直接调用发起请求的函数,跳过初始化实例
|
|
139
|
+
*/
|
|
140
|
+
export const safeRequest = new SafeRequestClient().asFunction();
|
package/validators/base.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Result } from '../lang/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* 支持传入进行验证的值类型
|
|
4
4
|
*/
|
|
@@ -22,7 +22,7 @@ export interface CommonOptions<Value = any> {
|
|
|
22
22
|
* 指定后 required 选项将失去作用。
|
|
23
23
|
*/
|
|
24
24
|
defaults?: Value;
|
|
25
|
-
custom?: (input: Value) =>
|
|
25
|
+
custom?: (input: Value) => Result<Value>;
|
|
26
26
|
[key: string]: unknown;
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
@@ -66,14 +66,14 @@ export type Validated<Value, InputOptions extends CommonOptions> = FullfiledOpti
|
|
|
66
66
|
* 最终生成的 validator 函数类型
|
|
67
67
|
*/
|
|
68
68
|
export interface Validator<Value, InputOptions extends CommonOptions> {
|
|
69
|
-
(input: AllowedInputValue):
|
|
70
|
-
(field: string, input: AllowedInputValue):
|
|
69
|
+
(input: AllowedInputValue): Result<Validated<Value, InputOptions>>;
|
|
70
|
+
(field: string, input: AllowedInputValue): Result<Validated<Value, InputOptions>>;
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
73
|
* 返回支持指定格式的 options、并按照传入的逻辑进行验证的 validator 的生成器。
|
|
74
74
|
* 对 CommonOptions 相关内容的验证以自动包含在里面,只需要传入额外的验证逻辑即可。
|
|
75
75
|
*/
|
|
76
|
-
export declare function getValidatorGenerator<Value, Options extends CommonOptions>(validate: (field: string, input: PrimitiveType | Validated<Value, Options>, options: Options) =>
|
|
76
|
+
export declare function getValidatorGenerator<Value, Options extends CommonOptions>(validate: (field: string, input: PrimitiveType | Validated<Value, Options>, options: Options) => Result<Value>): <const InputOptions extends Options>(inputOptions: InputOptions) => Validator<Value, InputOptions>;
|
|
77
77
|
/**
|
|
78
78
|
* 返回只进行基本检查,不带定制的验证逻辑的 validator。
|
|
79
79
|
* 同时也是定制 validator 最小化实现的例子。
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { type MaySuccess, type Failed } from '../lang/may-success.js';
|
|
2
|
-
interface BaseOptions {
|
|
3
|
-
url: string;
|
|
4
|
-
method?: 'GET' | 'POST';
|
|
5
|
-
headers?: Record<string, string>;
|
|
6
|
-
body?: string | null;
|
|
7
|
-
/** 若请求未成功发起,会触发此函数来生成失败信息 */
|
|
8
|
-
handleRequestError?: (error: Error) => Failed<unknown>;
|
|
9
|
-
/** 请求成功发起,但服务端返回失败结果(如 500),会触发此函数来生成失败信息 */
|
|
10
|
-
handleResponseError?: (response: Response) => Failed<unknown> | Promise<Failed<unknown>>;
|
|
11
|
-
/** 服务端返回内容解析失败时,会触发此函数来生成失败信息 */
|
|
12
|
-
handleParseFailed?: (error: Error, response: Response) => Failed<unknown>;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* 发起请求并妥善处理返回值,可用于与外部服务对接
|
|
16
|
-
*/
|
|
17
|
-
declare function safeRequest<T>(options: BaseOptions & {
|
|
18
|
-
responseType?: 'json';
|
|
19
|
-
}): Promise<MaySuccess<T, Error | string>>;
|
|
20
|
-
declare function safeRequest(options: BaseOptions & {
|
|
21
|
-
responseType: 'binary';
|
|
22
|
-
}): Promise<MaySuccess<Buffer, Error | string>>;
|
|
23
|
-
declare function safeRequest(options: BaseOptions & {
|
|
24
|
-
responseType: 'text';
|
|
25
|
-
}): Promise<MaySuccess<string, Error | string>>;
|
|
26
|
-
export { safeRequest };
|
package/env-node/safe-request.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { success, failed } from '../lang/may-success.js';
|
|
2
|
-
function defaultHandleRequestError(error) {
|
|
3
|
-
return failed('Request Error', undefined, error);
|
|
4
|
-
}
|
|
5
|
-
async function defaultHandleResponseError(response) {
|
|
6
|
-
return failed(`Response Error:${response.status}`, undefined, await response.text());
|
|
7
|
-
}
|
|
8
|
-
function defaultHandleParseFailed(error) {
|
|
9
|
-
return failed('Response Parse Failed', undefined, error);
|
|
10
|
-
}
|
|
11
|
-
async function safeRequest(options) {
|
|
12
|
-
const { url, method = 'GET', headers = {}, body = null, responseType = 'json' } = options;
|
|
13
|
-
try {
|
|
14
|
-
const response = await fetch(url, {
|
|
15
|
-
method,
|
|
16
|
-
headers,
|
|
17
|
-
body,
|
|
18
|
-
});
|
|
19
|
-
try {
|
|
20
|
-
if (response.status !== 200 && (response.status !== 204 || responseType !== 'text')) {
|
|
21
|
-
return await (options.handleResponseError ?? defaultHandleResponseError)(response);
|
|
22
|
-
}
|
|
23
|
-
let responseBody;
|
|
24
|
-
if (responseType === 'json')
|
|
25
|
-
responseBody = await response.json();
|
|
26
|
-
else if (responseType === 'binary')
|
|
27
|
-
responseBody = Buffer.from(await response.arrayBuffer());
|
|
28
|
-
else
|
|
29
|
-
responseBody = await response.text();
|
|
30
|
-
return success(responseBody);
|
|
31
|
-
}
|
|
32
|
-
catch (e) {
|
|
33
|
-
return (options.handleParseFailed ?? defaultHandleParseFailed)(e, response);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
catch (e) {
|
|
37
|
-
return (options.handleRequestError ?? defaultHandleRequestError)(e);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
export { safeRequest };
|