@nickyzj2023/utils 1.0.55 → 1.0.57
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 +1 -3
- package/dist/index.d.ts +446 -9
- package/dist/index.js +2 -3
- package/package.json +15 -4
- package/AGENTS.md +0 -167
- package/biome.json +0 -37
- package/dist/dom/index.d.ts +0 -1
- package/dist/dom/log.d.ts +0 -28
- package/dist/dom/timeLog.d.ts +0 -8
- package/dist/function/index.d.ts +0 -1
- package/dist/function/loop-until.d.ts +0 -36
- package/dist/function/loopUntil.d.ts +0 -22
- package/dist/hoc/index.d.ts +0 -1
- package/dist/hoc/with-cache.d.ts +0 -42
- package/dist/hoc/withCache.d.ts +0 -42
- package/dist/is/index.d.ts +0 -4
- package/dist/is/is-falsy.d.ts +0 -9
- package/dist/is/is-nil.d.ts +0 -9
- package/dist/is/is-object.d.ts +0 -8
- package/dist/is/is-primitive.d.ts +0 -9
- package/dist/is/isFalsy.d.ts +0 -9
- package/dist/is/isNil.d.ts +0 -9
- package/dist/is/isObject.d.ts +0 -8
- package/dist/is/isPrimitive.d.ts +0 -9
- package/dist/is/isTruthy.d.ts +0 -8
- package/dist/lru-cache.d.ts +0 -18
- package/dist/network/fetcher.d.ts +0 -56
- package/dist/network/get-real-url.d.ts +0 -2
- package/dist/network/getRealURL.d.ts +0 -2
- package/dist/network/image.d.ts +0 -59
- package/dist/network/index.d.ts +0 -4
- package/dist/network/to.d.ts +0 -9
- package/dist/number/index.d.ts +0 -1
- package/dist/number/random-int.d.ts +0 -7
- package/dist/number/randomInt.d.ts +0 -7
- package/dist/object/index.d.ts +0 -3
- package/dist/object/map-keys.d.ts +0 -17
- package/dist/object/map-values.d.ts +0 -21
- package/dist/object/mapKeys.d.ts +0 -17
- package/dist/object/mapValues.d.ts +0 -21
- package/dist/object/merge-objects.d.ts +0 -12
- package/dist/object/mergeObjects.d.ts +0 -12
- package/dist/string/case.d.ts +0 -32
- package/dist/string/compact.d.ts +0 -22
- package/dist/string/index.d.ts +0 -2
- package/dist/time/debounce.d.ts +0 -20
- package/dist/time/index.d.ts +0 -3
- package/dist/time/sleep.d.ts +0 -7
- package/dist/time/throttle.d.ts +0 -20
- package/docs/.nojekyll +0 -1
- package/docs/assets/hierarchy.js +0 -1
- package/docs/assets/highlight.css +0 -92
- package/docs/assets/icons.js +0 -18
- package/docs/assets/icons.svg +0 -1
- package/docs/assets/main.js +0 -60
- package/docs/assets/material-style.css +0 -262
- package/docs/assets/navigation.js +0 -1
- package/docs/assets/search.js +0 -1
- package/docs/assets/style.css +0 -1633
- package/docs/functions/camelToSnake.html +0 -178
- package/docs/functions/capitalize.html +0 -178
- package/docs/functions/compactStr.html +0 -182
- package/docs/functions/debounce.html +0 -187
- package/docs/functions/decapitalize.html +0 -178
- package/docs/functions/fetcher.html +0 -189
- package/docs/functions/getRealURL.html +0 -175
- package/docs/functions/imageUrlToBase64.html +0 -191
- package/docs/functions/isFalsy.html +0 -178
- package/docs/functions/isNil.html +0 -178
- package/docs/functions/isObject.html +0 -178
- package/docs/functions/isPrimitive.html +0 -178
- package/docs/functions/log.html +0 -180
- package/docs/functions/loopUntil.html +0 -186
- package/docs/functions/mapKeys.html +0 -180
- package/docs/functions/mapValues.html +0 -182
- package/docs/functions/mergeObjects.html +0 -184
- package/docs/functions/randomInt.html +0 -178
- package/docs/functions/sleep.html +0 -179
- package/docs/functions/snakeToCamel.html +0 -178
- package/docs/functions/throttle.html +0 -187
- package/docs/functions/to.html +0 -180
- package/docs/functions/withCache.html +0 -185
- package/docs/hierarchy.html +0 -174
- package/docs/index.html +0 -184
- package/docs/interfaces/LogOptions.html +0 -185
- package/docs/modules.html +0 -174
- package/docs/types/BunFetchOptions.html +0 -176
- package/docs/types/CamelToSnake.html +0 -174
- package/docs/types/Capitalize.html +0 -174
- package/docs/types/Decapitalize.html +0 -174
- package/docs/types/DeepMapKeys.html +0 -174
- package/docs/types/DeepMapValues.html +0 -174
- package/docs/types/Falsy.html +0 -174
- package/docs/types/ImageCompressionOptions.html +0 -188
- package/docs/types/Primitive.html +0 -174
- package/docs/types/RequestInit.html +0 -174
- package/docs/types/SetTtl.html +0 -174
- package/docs/types/SnakeToCamel.html +0 -174
- package/src/dom/index.ts +0 -1
- package/src/dom/log.ts +0 -72
- package/src/function/index.ts +0 -1
- package/src/function/loop-until.ts +0 -58
- package/src/hoc/index.ts +0 -1
- package/src/hoc/with-cache.ts +0 -117
- package/src/index.ts +0 -9
- package/src/is/index.ts +0 -4
- package/src/is/is-falsy.ts +0 -12
- package/src/is/is-nil.ts +0 -11
- package/src/is/is-object.ts +0 -10
- package/src/is/is-primitive.ts +0 -23
- package/src/network/fetcher.ts +0 -124
- package/src/network/get-real-url.ts +0 -18
- package/src/network/image.ts +0 -202
- package/src/network/index.ts +0 -4
- package/src/network/to.ts +0 -17
- package/src/number/index.ts +0 -1
- package/src/number/random-int.ts +0 -9
- package/src/object/index.ts +0 -3
- package/src/object/map-keys.ts +0 -50
- package/src/object/map-values.ts +0 -77
- package/src/object/merge-objects.ts +0 -50
- package/src/string/case.ts +0 -71
- package/src/string/compact.ts +0 -56
- package/src/string/index.ts +0 -2
- package/src/time/debounce.ts +0 -34
- package/src/time/index.ts +0 -3
- package/src/time/sleep.ts +0 -11
- package/src/time/throttle.ts +0 -34
- package/tsconfig.json +0 -32
package/src/is/is-nil.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 检测传入的值是否为**空值**(null、undefined)
|
|
3
|
-
*
|
|
4
|
-
* @example
|
|
5
|
-
* isNil(null); // true
|
|
6
|
-
* isNil(undefined); // true
|
|
7
|
-
* isNil(1); // false
|
|
8
|
-
*/
|
|
9
|
-
export const isNil = (value: any): value is null | undefined => {
|
|
10
|
-
return value === null || value === undefined;
|
|
11
|
-
};
|
package/src/is/is-object.ts
DELETED
package/src/is/is-primitive.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export type Primitive =
|
|
2
|
-
| number
|
|
3
|
-
| string
|
|
4
|
-
| boolean
|
|
5
|
-
| symbol
|
|
6
|
-
| bigint
|
|
7
|
-
| undefined
|
|
8
|
-
| null;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 检测传入的值是否为**原始值**(number、string、boolean、symbol、bigint、undefined、null)
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* isPrimitive(1); // true
|
|
15
|
-
* isPrimitive([]); // false
|
|
16
|
-
*/
|
|
17
|
-
export const isPrimitive = (value: any): value is Primitive => {
|
|
18
|
-
return (
|
|
19
|
-
value === null ||
|
|
20
|
-
value === undefined ||
|
|
21
|
-
(typeof value !== "object" && typeof value !== "function")
|
|
22
|
-
);
|
|
23
|
-
};
|
package/src/network/fetcher.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { log } from "../dom";
|
|
2
|
-
import { isNil, isObject } from "../is";
|
|
3
|
-
import { mergeObjects } from "../object";
|
|
4
|
-
|
|
5
|
-
// Bun 特有的 fetch 选项
|
|
6
|
-
export type BunFetchOptions = {
|
|
7
|
-
/** 代理服务器配置(仅 Bun 支持) */
|
|
8
|
-
proxy?: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
// 合并标准 RequestInit 和 Bun 特有的选项
|
|
12
|
-
export type RequestInit = globalThis.RequestInit &
|
|
13
|
-
BunFetchOptions & {
|
|
14
|
-
params?: Record<string, any>;
|
|
15
|
-
parser?: (response: Response) => Promise<any>;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 基于 Fetch API 的请求客户端
|
|
20
|
-
* @param baseURL 接口前缀
|
|
21
|
-
* @param baseOptions 客户端级别的请求体,后续调用时传递相同参数会覆盖上去
|
|
22
|
-
*
|
|
23
|
-
* @remarks
|
|
24
|
-
* 特性:
|
|
25
|
-
* - 合并实例、调用时的相同请求体
|
|
26
|
-
* - 在 params 里传递对象,自动转换为 queryString
|
|
27
|
-
* - 在 body 里传递对象,自动 JSON.stringify
|
|
28
|
-
* - 可选择使用 to() 转换请求结果为 [Error, Response]
|
|
29
|
-
* - 可选择使用 withCache() 缓存请求结果
|
|
30
|
-
* - 支持 proxy 选项(仅在 Bun 环境有效)
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
*
|
|
34
|
-
* // 用法1:直接发送请求
|
|
35
|
-
* const res = await fetcher().get<Blog>("https://nickyzj.run:3030/blogs/hello-world");
|
|
36
|
-
*
|
|
37
|
-
* // 用法2:创建实例
|
|
38
|
-
* const api = fetcher("https://nickyzj.run:3030", { headers: { Authorization: "Bearer token" } });
|
|
39
|
-
* const res = await api.get<Blog>("/blogs/hello-world", { headers: {...}, params: { page: 1 } }); // 与实例相同的 headers 会覆盖上去,params 会转成 ?page=1 跟到 url 后面
|
|
40
|
-
*
|
|
41
|
-
* // 用法3:使用代理(仅 Bun 环境)
|
|
42
|
-
* const api = fetcher("https://api.example.com", {
|
|
43
|
-
* proxy: "http://127.0.0.1:7890"
|
|
44
|
-
* });
|
|
45
|
-
*
|
|
46
|
-
* // 安全处理请求结果
|
|
47
|
-
* const [error, data] = await to(api.get<Blog>("/blogs/hello-world"));
|
|
48
|
-
* if (error) {
|
|
49
|
-
* console.error(error);
|
|
50
|
-
* return;
|
|
51
|
-
* }
|
|
52
|
-
* console.log(data);
|
|
53
|
-
*
|
|
54
|
-
* // 缓存请求结果
|
|
55
|
-
* const getBlogs = withCache(api.get);
|
|
56
|
-
* await getBlogs("/blogs");
|
|
57
|
-
* await sleep();
|
|
58
|
-
* await getBlogs("/blogs"); // 不发请求,使用缓存
|
|
59
|
-
*/
|
|
60
|
-
export const fetcher = (baseURL = "", baseOptions: RequestInit = {}) => {
|
|
61
|
-
const myFetch = async <T>(path: string, requestOptions: RequestInit = {}) => {
|
|
62
|
-
// 构建完整 URL
|
|
63
|
-
const url = new URL(baseURL ? `${baseURL}${path}` : path);
|
|
64
|
-
|
|
65
|
-
// 合并 options
|
|
66
|
-
const { params, parser, ...options } = mergeObjects(
|
|
67
|
-
baseOptions,
|
|
68
|
-
requestOptions,
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
// 转换 params 为查询字符串
|
|
72
|
-
if (isObject(params)) {
|
|
73
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
74
|
-
if (isNil(value)) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
url.searchParams.append(key, value.toString());
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// 转换 body 为字符串
|
|
82
|
-
if (isObject(options.body) || Array.isArray(options.body)) {
|
|
83
|
-
options.body = JSON.stringify(options.body);
|
|
84
|
-
options.headers = {
|
|
85
|
-
...options.headers,
|
|
86
|
-
"Content-Type": "application/json",
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// 发送请求
|
|
91
|
-
const response = await fetch(url, options);
|
|
92
|
-
if (!response.ok) {
|
|
93
|
-
// 如果后端给了报错详情,则先解析再抛出
|
|
94
|
-
const contentType = response.headers.get("Content-Type");
|
|
95
|
-
if (contentType?.startsWith("application/json")) {
|
|
96
|
-
throw await response.json();
|
|
97
|
-
}
|
|
98
|
-
throw new Error(response.statusText);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const data = await (parser?.(response) ?? response.json());
|
|
102
|
-
return data as T;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
get: <T>(url: string, options?: Omit<RequestInit, "method">) =>
|
|
107
|
-
myFetch<T>(url, { ...options, method: "GET" }),
|
|
108
|
-
|
|
109
|
-
post: <T>(
|
|
110
|
-
url: string,
|
|
111
|
-
body: any,
|
|
112
|
-
options?: Omit<RequestInit, "method" | "body">,
|
|
113
|
-
) => myFetch<T>(url, { ...options, method: "POST", body }),
|
|
114
|
-
|
|
115
|
-
put: <T>(
|
|
116
|
-
url: string,
|
|
117
|
-
body: any,
|
|
118
|
-
options?: Omit<RequestInit, "method" | "body">,
|
|
119
|
-
) => myFetch<T>(url, { ...options, method: "PUT", body }),
|
|
120
|
-
|
|
121
|
-
delete: <T>(url: string, options?: Omit<RequestInit, "method" | "body">) =>
|
|
122
|
-
myFetch<T>(url, { ...options, method: "DELETE" }),
|
|
123
|
-
};
|
|
124
|
-
};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { to } from "./to";
|
|
2
|
-
|
|
3
|
-
/** 从 url 响应头获取真实链接 */
|
|
4
|
-
export const getRealURL = async (originURL: string) => {
|
|
5
|
-
const [error, response] = await to(
|
|
6
|
-
fetch(originURL, {
|
|
7
|
-
method: "HEAD", // 用 HEAD 减少数据传输
|
|
8
|
-
redirect: "manual", // 手动处理重定向
|
|
9
|
-
}),
|
|
10
|
-
);
|
|
11
|
-
|
|
12
|
-
if (error) {
|
|
13
|
-
return originURL;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const location = response.headers.get("location");
|
|
17
|
-
return location || originURL;
|
|
18
|
-
};
|
package/src/network/image.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 将 ArrayBuffer 转换为 base64 字符串
|
|
3
|
-
* 兼容浏览器、Bun 和 Node.js
|
|
4
|
-
*/
|
|
5
|
-
const arrayBufferToBase64 = (buffer: ArrayBuffer): string => {
|
|
6
|
-
const bytes = new Uint8Array(buffer);
|
|
7
|
-
let binary = "";
|
|
8
|
-
for (let i = 0; i < bytes.byteLength; i++) {
|
|
9
|
-
binary += String.fromCharCode(bytes[i]!);
|
|
10
|
-
}
|
|
11
|
-
return btoa(binary);
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* 尝试动态导入 sharp 模块
|
|
16
|
-
* 用于检测用户项目中是否安装了 sharp
|
|
17
|
-
*/
|
|
18
|
-
const tryImportSharp = async () => {
|
|
19
|
-
try {
|
|
20
|
-
// 动态导入 sharp,避免在浏览器环境中报错
|
|
21
|
-
// 使用 new Function 避免 TypeScript 编译时解析该模块(sharp 是可选依赖)
|
|
22
|
-
const dynamicImport = new Function(
|
|
23
|
-
"modulePath",
|
|
24
|
-
"return import(modulePath)",
|
|
25
|
-
);
|
|
26
|
-
const sharpModule = await dynamicImport("sharp");
|
|
27
|
-
return sharpModule.default || sharpModule;
|
|
28
|
-
} catch {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 使用 sharp 压缩图片
|
|
35
|
-
*/
|
|
36
|
-
const compressWithSharp = async (
|
|
37
|
-
sharp: any,
|
|
38
|
-
arrayBuffer: ArrayBuffer,
|
|
39
|
-
mime: string,
|
|
40
|
-
quality: number,
|
|
41
|
-
): Promise<string> => {
|
|
42
|
-
const buffer = Buffer.from(arrayBuffer);
|
|
43
|
-
let sharpInstance = sharp(buffer);
|
|
44
|
-
|
|
45
|
-
// 根据 MIME 类型设置压缩选项
|
|
46
|
-
if (mime === "image/jpeg") {
|
|
47
|
-
sharpInstance = sharpInstance.jpeg({ quality: Math.round(quality * 100) });
|
|
48
|
-
} else if (mime === "image/png") {
|
|
49
|
-
// PNG 使用 compressionLevel (0-9),将 quality (0-1) 映射到 compressionLevel
|
|
50
|
-
const compressionLevel = Math.round((1 - quality) * 9);
|
|
51
|
-
sharpInstance = sharpInstance.png({ compressionLevel });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const compressedBuffer = await sharpInstance.toBuffer();
|
|
55
|
-
return `data:${mime};base64,${compressedBuffer.toString("base64")}`;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* 图片压缩选项
|
|
60
|
-
*/
|
|
61
|
-
export type ImageCompressionOptions = {
|
|
62
|
-
/** 压缩比率,默认 0.92 */
|
|
63
|
-
quality?: number;
|
|
64
|
-
/**
|
|
65
|
-
* 自定义压缩函数,用于覆盖默认压缩行为
|
|
66
|
-
* @param arrayBuffer 图片的 ArrayBuffer 数据
|
|
67
|
-
* @param mime 图片的 MIME 类型
|
|
68
|
-
* @param quality 压缩质量
|
|
69
|
-
* @returns 压缩后的 base64 字符串
|
|
70
|
-
*/
|
|
71
|
-
compressor?: (
|
|
72
|
-
arrayBuffer: ArrayBuffer,
|
|
73
|
-
mime: string,
|
|
74
|
-
quality: number,
|
|
75
|
-
) => Promise<string> | string;
|
|
76
|
-
/**
|
|
77
|
-
* 自定义 fetch 函数,用于使用自己封装的请求库读取图片
|
|
78
|
-
* 必须返回符合 Web 标准的 Response 对象
|
|
79
|
-
* @param url 图片地址
|
|
80
|
-
* @returns Promise<Response>
|
|
81
|
-
*/
|
|
82
|
-
fetcher?: (url: string) => Promise<Response>;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* 图片地址转 base64 数据
|
|
87
|
-
*
|
|
88
|
-
* @param imageUrl 图片地址
|
|
89
|
-
* @param options 可选配置
|
|
90
|
-
* @param options.quality 压缩比率,默认 0.92
|
|
91
|
-
* @param options.compressor 自定义压缩函数,用于覆盖默认压缩行为
|
|
92
|
-
*
|
|
93
|
-
* @example
|
|
94
|
-
* // 基本用法(浏览器自动使用 Canvas 压缩,Node.js/Bun 自动检测并使用 sharp)
|
|
95
|
-
* imageUrlToBase64("https://example.com/image.jpg");
|
|
96
|
-
*
|
|
97
|
-
* @example
|
|
98
|
-
* // 使用自定义 fetch 函数(如 axios 封装)
|
|
99
|
-
* imageUrlToBase64("https://example.com/image.jpg", {
|
|
100
|
-
* fetcher: async (url) => {
|
|
101
|
-
* // 使用 axios 或其他请求库,但必须返回 Response 对象
|
|
102
|
-
* const response = await axios.get(url, { responseType: 'arraybuffer' });
|
|
103
|
-
* return new Response(response.data, {
|
|
104
|
-
* status: response.status,
|
|
105
|
-
* statusText: response.statusText,
|
|
106
|
-
* headers: response.headers
|
|
107
|
-
* });
|
|
108
|
-
* }
|
|
109
|
-
* });
|
|
110
|
-
*
|
|
111
|
-
* @example
|
|
112
|
-
* // 使用自定义压缩函数覆盖默认行为
|
|
113
|
-
* imageUrlToBase64("https://example.com/image.jpg", {
|
|
114
|
-
* quality: 0.8,
|
|
115
|
-
* compressor: async (buffer, mime, quality) => {
|
|
116
|
-
* // 自定义压缩逻辑
|
|
117
|
-
* return `data:${mime};base64,...`;
|
|
118
|
-
* }
|
|
119
|
-
* });
|
|
120
|
-
*/
|
|
121
|
-
export const imageUrlToBase64 = async (
|
|
122
|
-
imageUrl: string,
|
|
123
|
-
options: ImageCompressionOptions = {},
|
|
124
|
-
): Promise<string> => {
|
|
125
|
-
const { quality = 0.92, compressor, fetcher = fetch } = options;
|
|
126
|
-
|
|
127
|
-
if (!imageUrl.startsWith("http")) {
|
|
128
|
-
throw new Error("图片地址必须以http或https开头");
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// 使用自定义 fetch 获取图片数据
|
|
132
|
-
const response = await fetcher(imageUrl);
|
|
133
|
-
if (!response.ok) {
|
|
134
|
-
throw new Error(`获取图片失败: ${response.statusText}`);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const mime = response.headers.get("Content-Type") || "image/jpeg";
|
|
138
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
139
|
-
|
|
140
|
-
// 对于非 JPEG/PNG 图片,直接返回 base64,不做压缩
|
|
141
|
-
if (mime !== "image/jpeg" && mime !== "image/png") {
|
|
142
|
-
const base64 = arrayBufferToBase64(arrayBuffer);
|
|
143
|
-
return `data:${mime};base64,${base64}`;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// 如果提供了自定义压缩函数,优先使用它
|
|
147
|
-
if (compressor) {
|
|
148
|
-
return await compressor(arrayBuffer, mime, quality);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// 浏览器环境:使用 OffscreenCanvas 压缩
|
|
152
|
-
if (typeof OffscreenCanvas !== "undefined") {
|
|
153
|
-
let bitmap: ImageBitmap | null = null;
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
const blob = new Blob([arrayBuffer], { type: mime });
|
|
157
|
-
bitmap = await createImageBitmap(blob);
|
|
158
|
-
|
|
159
|
-
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
|
|
160
|
-
const ctx = canvas.getContext("2d");
|
|
161
|
-
if (!ctx) {
|
|
162
|
-
throw new Error("无法获取 OffscreenCanvas context");
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
ctx.drawImage(bitmap, 0, 0);
|
|
166
|
-
bitmap.close();
|
|
167
|
-
bitmap = null;
|
|
168
|
-
|
|
169
|
-
// OffscreenCanvas 使用 convertToBlob 获取压缩后的图片
|
|
170
|
-
const compressedBlob = await canvas.convertToBlob({
|
|
171
|
-
type: mime,
|
|
172
|
-
quality: quality,
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
// 将 Blob 转换为 base64
|
|
176
|
-
const compressedArrayBuffer = await compressedBlob.arrayBuffer();
|
|
177
|
-
const base64 = arrayBufferToBase64(compressedArrayBuffer);
|
|
178
|
-
return `data:${mime};base64,${base64}`;
|
|
179
|
-
} catch {
|
|
180
|
-
// OffscreenCanvas 压缩失败,返回原始 base64
|
|
181
|
-
bitmap?.close();
|
|
182
|
-
const base64 = arrayBufferToBase64(arrayBuffer);
|
|
183
|
-
return `data:${mime};base64,${base64}`;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Node.js/Bun 环境:尝试使用 sharp 进行压缩
|
|
188
|
-
const sharp = await tryImportSharp();
|
|
189
|
-
if (sharp) {
|
|
190
|
-
try {
|
|
191
|
-
return await compressWithSharp(sharp, arrayBuffer, mime, quality);
|
|
192
|
-
} catch {
|
|
193
|
-
// sharp 压缩失败,返回原始 base64
|
|
194
|
-
const base64 = arrayBufferToBase64(arrayBuffer);
|
|
195
|
-
return `data:${mime};base64,${base64}`;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// 没有可用的压缩方式,直接返回原始 base64
|
|
200
|
-
const base64 = arrayBufferToBase64(arrayBuffer);
|
|
201
|
-
return `data:${mime};base64,${base64}`;
|
|
202
|
-
};
|
package/src/network/index.ts
DELETED
package/src/network/to.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Go 语言风格的异步处理方式
|
|
3
|
-
* @param promise 一个能被 await 的异步函数
|
|
4
|
-
* @returns 如果成功,返回 [null, 异步函数结果],否则返回 [Error, undefined]
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* const [error, response] = await to(fetcher().get<Blog>("/blogs/hello-world"));
|
|
8
|
-
*/
|
|
9
|
-
export const to = async <T, E = Error>(
|
|
10
|
-
promise: Promise<T>,
|
|
11
|
-
): Promise<[null, T] | [E, undefined]> => {
|
|
12
|
-
try {
|
|
13
|
-
return [null, await promise];
|
|
14
|
-
} catch (e) {
|
|
15
|
-
return [e as E, undefined];
|
|
16
|
-
}
|
|
17
|
-
};
|
package/src/number/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./random-int";
|
package/src/number/random-int.ts
DELETED
package/src/object/index.ts
DELETED
package/src/object/map-keys.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { isObject } from "../is";
|
|
2
|
-
|
|
3
|
-
// 这里的 DeepMapKeys 只能承诺 key 是 string,无法推断出 key 的具体字面量变化
|
|
4
|
-
// 因为 TS 不支持根据任意函数反推 Key 的字面量类型
|
|
5
|
-
export type DeepMapKeys<T> =
|
|
6
|
-
T extends Array<infer U>
|
|
7
|
-
? Array<DeepMapKeys<U>>
|
|
8
|
-
: T extends object
|
|
9
|
-
? { [key: string]: DeepMapKeys<T[keyof T]> }
|
|
10
|
-
: T;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* 递归处理对象里的 key
|
|
14
|
-
*
|
|
15
|
-
* @remarks
|
|
16
|
-
* 无法完整推导出类型,只能做到有递归,key 全为 string,value 为同层级的所有类型的联合
|
|
17
|
-
*
|
|
18
|
-
* @template T 要转换的对象
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* const obj = { a: { b: 1 } };
|
|
22
|
-
* const result = mapKeys(obj, (key) => key.toUpperCase());
|
|
23
|
-
* console.log(result); // { A: { B: 1 } }
|
|
24
|
-
*/
|
|
25
|
-
export const mapKeys = <T>(
|
|
26
|
-
obj: T,
|
|
27
|
-
getNewKey: (key: string) => string,
|
|
28
|
-
): DeepMapKeys<T> => {
|
|
29
|
-
// 递归处理数组
|
|
30
|
-
if (Array.isArray(obj)) {
|
|
31
|
-
return obj.map((item) => mapKeys(item, getNewKey)) as any;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// 处理普通对象
|
|
35
|
-
if (isObject(obj)) {
|
|
36
|
-
const keys = Object.keys(obj);
|
|
37
|
-
return keys.reduce(
|
|
38
|
-
(result, key) => {
|
|
39
|
-
const newKey = getNewKey(key);
|
|
40
|
-
const value = (obj as any)[key];
|
|
41
|
-
result[newKey] = mapKeys(value, getNewKey);
|
|
42
|
-
return result;
|
|
43
|
-
},
|
|
44
|
-
{} as Record<string, any>,
|
|
45
|
-
) as DeepMapKeys<T>;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// 处理非数组/对象
|
|
49
|
-
return obj as any;
|
|
50
|
-
};
|
package/src/object/map-values.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { isObject } from "../is";
|
|
2
|
-
|
|
3
|
-
// 这里的 DeepMapValues 尝试保留 key,但将 value 类型替换为 R
|
|
4
|
-
// 注意:如果原 value 是对象,我们递归处理结构,而不是把整个对象变成 R
|
|
5
|
-
export type DeepMapValues<T, R> =
|
|
6
|
-
T extends Array<infer U>
|
|
7
|
-
? Array<DeepMapValues<U, R>>
|
|
8
|
-
: T extends object
|
|
9
|
-
? { [K in keyof T]: T[K] extends object ? DeepMapValues<T[K], R> : R }
|
|
10
|
-
: R;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* 递归处理对象里的 value
|
|
14
|
-
*
|
|
15
|
-
* @remarks
|
|
16
|
-
* 无法完整推导出类型,所有 value 最终都会变为 any
|
|
17
|
-
*
|
|
18
|
-
* @template T 要转换的对象
|
|
19
|
-
* @template R 转换后的值类型,为 any,无法进一步推导
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* const obj = { a: 1, b: { c: 2 } };
|
|
23
|
-
* const result = mapValues(obj, (value, key) => isPrimitive(value) ? value + 1 : value);
|
|
24
|
-
* console.log(result); // { a: 2, b: { c: 3 } }
|
|
25
|
-
*/
|
|
26
|
-
export const mapValues = <T, R = any>(
|
|
27
|
-
obj: T,
|
|
28
|
-
getNewValue: (value: any, key: string | number) => R,
|
|
29
|
-
options?: {
|
|
30
|
-
/** 过滤函数,返回 true 表示保留该字段 */
|
|
31
|
-
filter?: (value: any, key: string | number) => boolean;
|
|
32
|
-
},
|
|
33
|
-
): DeepMapValues<T, R> => {
|
|
34
|
-
const { filter } = options ?? {};
|
|
35
|
-
|
|
36
|
-
// 处理数组
|
|
37
|
-
if (Array.isArray(obj)) {
|
|
38
|
-
const mappedArray = obj.map((item, index) => {
|
|
39
|
-
// 如果元素是对象,则递归处理
|
|
40
|
-
if (isObject(item)) {
|
|
41
|
-
return mapValues(item, getNewValue, options);
|
|
42
|
-
}
|
|
43
|
-
// 如果元素是原始值,则直接应用 getNewValue(此时 key 为数组下标)
|
|
44
|
-
return getNewValue(item, index);
|
|
45
|
-
});
|
|
46
|
-
// 如果有过滤器,则过滤一遍元素
|
|
47
|
-
if (filter) {
|
|
48
|
-
return mappedArray.filter((item, index) => filter(item, index)) as any;
|
|
49
|
-
}
|
|
50
|
-
return mappedArray as any;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// 处理普通对象
|
|
54
|
-
if (isObject(obj)) {
|
|
55
|
-
const keys = Object.keys(obj);
|
|
56
|
-
return keys.reduce((result, key) => {
|
|
57
|
-
const value = (obj as any)[key];
|
|
58
|
-
let newValue: any;
|
|
59
|
-
// 如果值为对象或数组,则递归处理
|
|
60
|
-
if (isObject(value) || Array.isArray(value)) {
|
|
61
|
-
newValue = mapValues(value, getNewValue, options);
|
|
62
|
-
}
|
|
63
|
-
// 否则直接应用 getNewValue
|
|
64
|
-
else {
|
|
65
|
-
newValue = getNewValue(value, key);
|
|
66
|
-
}
|
|
67
|
-
// 如果存在过滤器,则看情况保留该字段
|
|
68
|
-
if (!filter || filter(newValue, key)) {
|
|
69
|
-
result[key] = newValue;
|
|
70
|
-
}
|
|
71
|
-
return result;
|
|
72
|
-
}, {} as any) as DeepMapValues<T, R>;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 处理非数组/对象
|
|
76
|
-
return obj as any;
|
|
77
|
-
};
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { isObject, isPrimitive } from "../is";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 深度合并两个对象,规则如下:
|
|
5
|
-
* 1. 原始值覆盖:如果两个值都是原始类型,则用后者覆盖;
|
|
6
|
-
* 2. 数组拼接:如果两个值都是数组,则拼接为大数组;
|
|
7
|
-
* 3. 对象递归合并:如果两个值都是对象,则进行递归深度合并;
|
|
8
|
-
*
|
|
9
|
-
* @template T 第一个对象
|
|
10
|
-
* @template U 第二个对象
|
|
11
|
-
* @param {T} obj1 要合并的第一个对象,相同字段会被 obj2 覆盖
|
|
12
|
-
* @param {U} obj2 要合并的第二个对象
|
|
13
|
-
*/
|
|
14
|
-
export const mergeObjects = <
|
|
15
|
-
T extends Record<string, any>,
|
|
16
|
-
U extends Record<string, any>,
|
|
17
|
-
>(
|
|
18
|
-
obj1: T,
|
|
19
|
-
obj2: U,
|
|
20
|
-
) => {
|
|
21
|
-
const result: Record<string, any> = { ...obj1 };
|
|
22
|
-
|
|
23
|
-
for (const key of Object.keys(obj2)) {
|
|
24
|
-
const val1 = result[key];
|
|
25
|
-
const val2 = obj2[key];
|
|
26
|
-
|
|
27
|
-
// 如果都是原始值,则用后者覆盖
|
|
28
|
-
if (isPrimitive(val1) && isPrimitive(val2)) {
|
|
29
|
-
result[key] = val2;
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// 如果都是数组,则拼接为大数组
|
|
34
|
-
if (Array.isArray(val1) && Array.isArray(val2)) {
|
|
35
|
-
result[key] = val1.concat(val2);
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// 如果都是对象,则深度递归合并
|
|
40
|
-
if (isObject(val1) && isObject(val2)) {
|
|
41
|
-
result[key] = mergeObjects(val1, val2);
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// 其他情况用后者覆盖
|
|
46
|
-
result[key] = val2;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return result as T & U;
|
|
50
|
-
};
|
package/src/string/case.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
export type SnakeToCamel<S extends string> =
|
|
2
|
-
S extends `${infer Before}_${infer After}`
|
|
3
|
-
? After extends `${infer First}${infer Rest}`
|
|
4
|
-
? `${Before}${Uppercase<First>}${SnakeToCamel<Rest>}`
|
|
5
|
-
: Before
|
|
6
|
-
: S;
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 下划线命名法转为驼峰命名法
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* snakeToCamel("user_name") // "userName"
|
|
13
|
-
*/
|
|
14
|
-
export const snakeToCamel = <S extends string>(str: S): SnakeToCamel<S> => {
|
|
15
|
-
return str.replace(/_([a-zA-Z])/g, (match, pattern) =>
|
|
16
|
-
pattern.toUpperCase(),
|
|
17
|
-
) as SnakeToCamel<S>;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export type CamelToSnake<S extends string> =
|
|
21
|
-
S extends `${infer First}${infer Rest}`
|
|
22
|
-
? Rest extends Uncapitalize<Rest>
|
|
23
|
-
? `${Lowercase<First>}${CamelToSnake<Rest>}`
|
|
24
|
-
: `${Lowercase<First>}_${CamelToSnake<Rest>}`
|
|
25
|
-
: Lowercase<S>;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* 驼峰命名法转为下划线命名法
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* camelToSnake("shouldComponentUpdate") // "should_component_update"
|
|
32
|
-
*/
|
|
33
|
-
export const camelToSnake = <S extends string>(str: S): CamelToSnake<S> => {
|
|
34
|
-
return str.replace(
|
|
35
|
-
/([A-Z])/g,
|
|
36
|
-
(match, pattern) => `_${pattern.toLowerCase()}`,
|
|
37
|
-
) as CamelToSnake<S>;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export type Capitalize<S extends string> = S extends `${infer P1}${infer Rest}`
|
|
41
|
-
? P1 extends Capitalize<P1>
|
|
42
|
-
? S
|
|
43
|
-
: `${Uppercase<P1>}${Rest}`
|
|
44
|
-
: S;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* 字符串首字母大写
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* capitalize("hello") // "Hello"
|
|
51
|
-
*/
|
|
52
|
-
export const capitalize = <S extends string>(s: S): Capitalize<S> => {
|
|
53
|
-
return (s.charAt(0).toUpperCase() + s.slice(1)) as Capitalize<S>;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export type Decapitalize<S extends string> =
|
|
57
|
-
S extends `${infer P1}${infer Rest}`
|
|
58
|
-
? P1 extends Lowercase<P1>
|
|
59
|
-
? P1
|
|
60
|
-
: `${Lowercase<P1>}${Rest}`
|
|
61
|
-
: S;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* 字符串首字母小写
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* decapitalize("Hello") // "hello"
|
|
68
|
-
*/
|
|
69
|
-
export const decapitalize = <S extends string>(s: S): Decapitalize<S> => {
|
|
70
|
-
return (s.charAt(0).toLowerCase() + s.slice(1)) as Decapitalize<S>;
|
|
71
|
-
};
|