@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.
Files changed (129) hide show
  1. package/README.md +1 -3
  2. package/dist/index.d.ts +446 -9
  3. package/dist/index.js +2 -3
  4. package/package.json +15 -4
  5. package/AGENTS.md +0 -167
  6. package/biome.json +0 -37
  7. package/dist/dom/index.d.ts +0 -1
  8. package/dist/dom/log.d.ts +0 -28
  9. package/dist/dom/timeLog.d.ts +0 -8
  10. package/dist/function/index.d.ts +0 -1
  11. package/dist/function/loop-until.d.ts +0 -36
  12. package/dist/function/loopUntil.d.ts +0 -22
  13. package/dist/hoc/index.d.ts +0 -1
  14. package/dist/hoc/with-cache.d.ts +0 -42
  15. package/dist/hoc/withCache.d.ts +0 -42
  16. package/dist/is/index.d.ts +0 -4
  17. package/dist/is/is-falsy.d.ts +0 -9
  18. package/dist/is/is-nil.d.ts +0 -9
  19. package/dist/is/is-object.d.ts +0 -8
  20. package/dist/is/is-primitive.d.ts +0 -9
  21. package/dist/is/isFalsy.d.ts +0 -9
  22. package/dist/is/isNil.d.ts +0 -9
  23. package/dist/is/isObject.d.ts +0 -8
  24. package/dist/is/isPrimitive.d.ts +0 -9
  25. package/dist/is/isTruthy.d.ts +0 -8
  26. package/dist/lru-cache.d.ts +0 -18
  27. package/dist/network/fetcher.d.ts +0 -56
  28. package/dist/network/get-real-url.d.ts +0 -2
  29. package/dist/network/getRealURL.d.ts +0 -2
  30. package/dist/network/image.d.ts +0 -59
  31. package/dist/network/index.d.ts +0 -4
  32. package/dist/network/to.d.ts +0 -9
  33. package/dist/number/index.d.ts +0 -1
  34. package/dist/number/random-int.d.ts +0 -7
  35. package/dist/number/randomInt.d.ts +0 -7
  36. package/dist/object/index.d.ts +0 -3
  37. package/dist/object/map-keys.d.ts +0 -17
  38. package/dist/object/map-values.d.ts +0 -21
  39. package/dist/object/mapKeys.d.ts +0 -17
  40. package/dist/object/mapValues.d.ts +0 -21
  41. package/dist/object/merge-objects.d.ts +0 -12
  42. package/dist/object/mergeObjects.d.ts +0 -12
  43. package/dist/string/case.d.ts +0 -32
  44. package/dist/string/compact.d.ts +0 -22
  45. package/dist/string/index.d.ts +0 -2
  46. package/dist/time/debounce.d.ts +0 -20
  47. package/dist/time/index.d.ts +0 -3
  48. package/dist/time/sleep.d.ts +0 -7
  49. package/dist/time/throttle.d.ts +0 -20
  50. package/docs/.nojekyll +0 -1
  51. package/docs/assets/hierarchy.js +0 -1
  52. package/docs/assets/highlight.css +0 -92
  53. package/docs/assets/icons.js +0 -18
  54. package/docs/assets/icons.svg +0 -1
  55. package/docs/assets/main.js +0 -60
  56. package/docs/assets/material-style.css +0 -262
  57. package/docs/assets/navigation.js +0 -1
  58. package/docs/assets/search.js +0 -1
  59. package/docs/assets/style.css +0 -1633
  60. package/docs/functions/camelToSnake.html +0 -178
  61. package/docs/functions/capitalize.html +0 -178
  62. package/docs/functions/compactStr.html +0 -182
  63. package/docs/functions/debounce.html +0 -187
  64. package/docs/functions/decapitalize.html +0 -178
  65. package/docs/functions/fetcher.html +0 -189
  66. package/docs/functions/getRealURL.html +0 -175
  67. package/docs/functions/imageUrlToBase64.html +0 -191
  68. package/docs/functions/isFalsy.html +0 -178
  69. package/docs/functions/isNil.html +0 -178
  70. package/docs/functions/isObject.html +0 -178
  71. package/docs/functions/isPrimitive.html +0 -178
  72. package/docs/functions/log.html +0 -180
  73. package/docs/functions/loopUntil.html +0 -186
  74. package/docs/functions/mapKeys.html +0 -180
  75. package/docs/functions/mapValues.html +0 -182
  76. package/docs/functions/mergeObjects.html +0 -184
  77. package/docs/functions/randomInt.html +0 -178
  78. package/docs/functions/sleep.html +0 -179
  79. package/docs/functions/snakeToCamel.html +0 -178
  80. package/docs/functions/throttle.html +0 -187
  81. package/docs/functions/to.html +0 -180
  82. package/docs/functions/withCache.html +0 -185
  83. package/docs/hierarchy.html +0 -174
  84. package/docs/index.html +0 -184
  85. package/docs/interfaces/LogOptions.html +0 -185
  86. package/docs/modules.html +0 -174
  87. package/docs/types/BunFetchOptions.html +0 -176
  88. package/docs/types/CamelToSnake.html +0 -174
  89. package/docs/types/Capitalize.html +0 -174
  90. package/docs/types/Decapitalize.html +0 -174
  91. package/docs/types/DeepMapKeys.html +0 -174
  92. package/docs/types/DeepMapValues.html +0 -174
  93. package/docs/types/Falsy.html +0 -174
  94. package/docs/types/ImageCompressionOptions.html +0 -188
  95. package/docs/types/Primitive.html +0 -174
  96. package/docs/types/RequestInit.html +0 -174
  97. package/docs/types/SetTtl.html +0 -174
  98. package/docs/types/SnakeToCamel.html +0 -174
  99. package/src/dom/index.ts +0 -1
  100. package/src/dom/log.ts +0 -72
  101. package/src/function/index.ts +0 -1
  102. package/src/function/loop-until.ts +0 -58
  103. package/src/hoc/index.ts +0 -1
  104. package/src/hoc/with-cache.ts +0 -117
  105. package/src/index.ts +0 -9
  106. package/src/is/index.ts +0 -4
  107. package/src/is/is-falsy.ts +0 -12
  108. package/src/is/is-nil.ts +0 -11
  109. package/src/is/is-object.ts +0 -10
  110. package/src/is/is-primitive.ts +0 -23
  111. package/src/network/fetcher.ts +0 -124
  112. package/src/network/get-real-url.ts +0 -18
  113. package/src/network/image.ts +0 -202
  114. package/src/network/index.ts +0 -4
  115. package/src/network/to.ts +0 -17
  116. package/src/number/index.ts +0 -1
  117. package/src/number/random-int.ts +0 -9
  118. package/src/object/index.ts +0 -3
  119. package/src/object/map-keys.ts +0 -50
  120. package/src/object/map-values.ts +0 -77
  121. package/src/object/merge-objects.ts +0 -50
  122. package/src/string/case.ts +0 -71
  123. package/src/string/compact.ts +0 -56
  124. package/src/string/index.ts +0 -2
  125. package/src/time/debounce.ts +0 -34
  126. package/src/time/index.ts +0 -3
  127. package/src/time/sleep.ts +0 -11
  128. package/src/time/throttle.ts +0 -34
  129. 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
- };
@@ -1,10 +0,0 @@
1
- /**
2
- * 检测传入的值是否为**普通对象**
3
- *
4
- * @example
5
- * const obj = { a: 1 };
6
- * isObject(obj); // true
7
- */
8
- export const isObject = (value: any): value is Record<string, any> => {
9
- return value?.constructor === Object;
10
- };
@@ -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
- };
@@ -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
- };
@@ -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
- };
@@ -1,4 +0,0 @@
1
- export * from "./fetcher";
2
- export * from "./get-real-url";
3
- export * from "./image";
4
- export * from "./to";
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
- };
@@ -1 +0,0 @@
1
- export * from "./random-int";
@@ -1,9 +0,0 @@
1
- /**
2
- * 在指定闭区间内生成随机整数
3
- *
4
- * @example
5
- * randomInt(1, 10); // 1 <= x <= 10
6
- */
7
- export const randomInt = (min: number, max: number) => {
8
- return Math.floor(Math.random() * (max - min + 1)) + min;
9
- };
@@ -1,3 +0,0 @@
1
- export * from "./map-keys";
2
- export * from "./map-values";
3
- export * from "./merge-objects";
@@ -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
- };
@@ -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
- };
@@ -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
- };