@nickyzj2023/utils 1.0.55 → 1.0.56

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 +8 -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/README.md CHANGED
@@ -12,8 +12,6 @@ yarn add @nickyzj2023/utils
12
12
  # pnpm
13
13
  pnpm add @nickyzj2023/utils
14
14
 
15
- # bun
16
- bun add @nickyzj2023/utils
17
15
  ```
18
16
 
19
17
  使用方式:
@@ -32,4 +30,4 @@ if (error) {
32
30
  console.log(data);
33
31
  ```
34
32
 
35
- This project was created using `bun init` in bun v1.3.2. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
33
+
package/dist/index.d.ts CHANGED
@@ -1,9 +1,446 @@
1
- export * from "./dom";
2
- export * from "./function";
3
- export * from "./hoc";
4
- export * from "./is";
5
- export * from "./network";
6
- export * from "./number";
7
- export * from "./object";
8
- export * from "./string";
9
- export * from "./time";
1
+ /**
2
+ * log 配置选项
3
+ */
4
+ interface LogOptions {
5
+ /**
6
+ * 是否显示时间
7
+ * @default true
8
+ */
9
+ time?: boolean;
10
+ /**
11
+ * 是否显示调用者文件名
12
+ * @default true
13
+ */
14
+ fileName?: boolean;
15
+ }
16
+ /**
17
+ * 带额外信息的 console.log
18
+ * @param message - 日志消息,支持单条消息或消息数组
19
+ * @param options - 配置选项
20
+ *
21
+ * @example
22
+ * log("调试信息"); // "[14:30:00] [index.ts:15] 调试信息"
23
+ * log("调试信息", { time: false }); // "[index.ts:15] 调试信息"
24
+ * log("调试信息", { fileName: false }); // "[14:30:00] 调试信息"
25
+ * log("调试信息", { time: false, fileName: false }); // "调试信息"
26
+ * log(["消息1", "消息2"]); // "[14:30:00] [index.ts:15] 消息1 消息2"
27
+ */
28
+ declare const log: (message: any | any[], options?: LogOptions) => void;
29
+
30
+ /**
31
+ * 循环执行函数,直到符合停止条件
32
+ *
33
+ * @example
34
+ * // 循环请求大语言模型,直到其不再调用工具
35
+ * loopUntil(
36
+ * async () => {
37
+ * const completion = await chatCompletions();
38
+ * completion.tool_calls?.forEach(chooseAndHandleTool)
39
+ * return completion;
40
+ * },
41
+ * {
42
+ * shouldStop: (completion) => !completion.tool_calls,
43
+ * },
44
+ * ),
45
+ *
46
+ * @example
47
+ * // 不传递 shouldStop,执行 3 次后正常返回最后结果
48
+ * loopUntil(
49
+ * () => {
50
+ * return doSomething();
51
+ * },
52
+ * {
53
+ * maxRetries: 3,
54
+ * },
55
+ * ),
56
+ */
57
+ declare const loopUntil: <T>(fn: (count: number) => T | Promise<T>, options?: {
58
+ /**
59
+ * 最大循环次数
60
+ * @default 5
61
+ */
62
+ maxRetries?: number;
63
+ /** 停止循环条件。如果未传递,则执行 maxRetries 次后退出并返回最后结果 */
64
+ shouldStop?: (result: T) => boolean;
65
+ }) => Promise<T>;
66
+
67
+ type SetTtl = (seconds: number) => void;
68
+ /**
69
+ * 创建一个带缓存的高阶函数
70
+ *
71
+ * @template Args 被包装函数的参数类型数组
72
+ * @template Result 被包装函数的返回类型
73
+ *
74
+ * @param fn 需要被缓存的函数,参数里附带的 setTtl 方法用于根据具体情况改写过期时间
75
+ * @param ttlSeconds 以秒为单位的过期时间,-1 表示永不过期,默认 -1,会被回调函数里的 setTtl() 覆盖
76
+ *
77
+ * @returns 返回包装后的函数,以及缓存相关的额外方法
78
+ *
79
+ * @example
80
+ * // 异步函数示例
81
+ * const fetchData = withCache(async function (url: string) {
82
+ * const data = await fetch(url).then((res) => res.json());
83
+ * this.setTtl(data.expiresIn); // 根据实际情况调整过期时间
84
+ * return data;
85
+ * });
86
+ *
87
+ * await fetchData(urlA);
88
+ * await fetchData(urlA); // 使用缓存结果
89
+ * await fetchData(urlB);
90
+ * await fetchData(urlB); // 使用缓存结果
91
+ *
92
+ * fetchData.clear(); // 清除缓存
93
+ * await fetchData(urlA); // 重新请求
94
+ * await fetchData(urlB); // 重新请求
95
+ *
96
+ * // 缓存过期前
97
+ * await sleep();
98
+ * fetchData.updateTtl(180); // 更新 ttl 并为所有未过期的缓存续期
99
+ * await fetchData(urlA); // 使用缓存结果
100
+ * await fetchData(urlB); // 使用缓存结果
101
+ */
102
+ declare const withCache: <Args extends any[], Result>(fn: (this: {
103
+ setTtl: SetTtl;
104
+ }, ...args: Args) => Result, ttlSeconds?: number) => {
105
+ (...args: Args): Result;
106
+ clear(): void;
107
+ updateTtl(seconds: number): void;
108
+ };
109
+
110
+ type Falsy = false | 0 | -0 | 0n | "" | null | undefined;
111
+ /**
112
+ * 检测传入的值是否为**假值**(false、0、''、null、undefined、NaN等)
113
+ *
114
+ * @example
115
+ * isFalsy(""); // true
116
+ * isFalsy(1); // false
117
+ */
118
+ declare const isFalsy: (value: any) => value is Falsy;
119
+
120
+ /**
121
+ * 检测传入的值是否为**空值**(null、undefined)
122
+ *
123
+ * @example
124
+ * isNil(null); // true
125
+ * isNil(undefined); // true
126
+ * isNil(1); // false
127
+ */
128
+ declare const isNil: (value: any) => value is null | undefined;
129
+
130
+ /**
131
+ * 检测传入的值是否为**普通对象**
132
+ *
133
+ * @example
134
+ * const obj = { a: 1 };
135
+ * isObject(obj); // true
136
+ */
137
+ declare const isObject: (value: any) => value is Record<string, any>;
138
+
139
+ type Primitive = number | string | boolean | symbol | bigint | undefined | null;
140
+ /**
141
+ * 检测传入的值是否为**原始值**(number、string、boolean、symbol、bigint、undefined、null)
142
+ *
143
+ * @example
144
+ * isPrimitive(1); // true
145
+ * isPrimitive([]); // false
146
+ */
147
+ declare const isPrimitive: (value: any) => value is Primitive;
148
+
149
+ type FetchOptions = {
150
+ /** 代理服务器配置 */
151
+ proxy?: string;
152
+ };
153
+ type RequestInit = globalThis.RequestInit & FetchOptions & {
154
+ params?: Record<string, any>;
155
+ parser?: (response: Response) => Promise<any>;
156
+ };
157
+ /**
158
+ * 基于 Fetch API 的请求客户端
159
+ * @param baseURL 接口前缀
160
+ * @param baseOptions 客户端级别的请求体,后续调用时传递相同参数会覆盖上去
161
+ *
162
+ * @remarks
163
+ * 特性:
164
+ * - 合并实例、调用时的相同请求体
165
+ * - 在 params 里传递对象,自动转换为 queryString
166
+ * - 在 body 里传递对象,自动 JSON.stringify
167
+ * - 可选择使用 to() 转换请求结果为 [Error, Response]
168
+ * - 可选择使用 withCache() 缓存请求结果
169
+ * - 支持 proxy 选项
170
+ *
171
+ * @example
172
+ *
173
+ * // 用法1:直接发送请求
174
+ * const res = await fetcher().get<Blog>("https://nickyzj.run:3030/blogs/hello-world");
175
+ *
176
+ * // 用法2:创建实例
177
+ * const api = fetcher("https://nickyzj.run:3030", { headers: { Authorization: "Bearer token" } });
178
+ * const res = await api.get<Blog>("/blogs/hello-world", { headers: {...}, params: { page: 1 } }); // 与实例相同的 headers 会覆盖上去,params 会转成 ?page=1 跟到 url 后面
179
+ *
180
+ * // 用法3:使用代理
181
+ * const api = fetcher("https://api.example.com", {
182
+ * proxy: "http://127.0.0.1:7890"
183
+ * });
184
+ *
185
+ * // 安全处理请求结果
186
+ * const [error, data] = await to(api.get<Blog>("/blogs/hello-world"));
187
+ * if (error) {
188
+ * console.error(error);
189
+ * return;
190
+ * }
191
+ * console.log(data);
192
+ *
193
+ * // 缓存请求结果
194
+ * const getBlogs = withCache(api.get);
195
+ * await getBlogs("/blogs");
196
+ * await sleep();
197
+ * await getBlogs("/blogs"); // 不发请求,使用缓存
198
+ */
199
+ declare const fetcher: (baseURL?: string, baseOptions?: RequestInit) => {
200
+ get: <T>(url: string, options?: Omit<RequestInit, "method">) => Promise<T>;
201
+ post: <T>(url: string, body: any, options?: Omit<RequestInit, "method" | "body">) => Promise<T>;
202
+ put: <T>(url: string, body: any, options?: Omit<RequestInit, "method" | "body">) => Promise<T>;
203
+ delete: <T>(url: string, options?: Omit<RequestInit, "method" | "body">) => Promise<T>;
204
+ };
205
+
206
+ /** 从 url 响应头获取真实链接 */
207
+ declare const getRealURL: (originURL: string) => Promise<string>;
208
+
209
+ /**
210
+ * 图片压缩选项
211
+ */
212
+ type ImageCompressionOptions = {
213
+ /** 压缩比率,默认 0.92 */
214
+ quality?: number;
215
+ /**
216
+ * 自定义压缩函数,用于覆盖默认压缩行为
217
+ * @param arrayBuffer 图片的 ArrayBuffer 数据
218
+ * @param mime 图片的 MIME 类型
219
+ * @param quality 压缩质量
220
+ * @returns 压缩后的 base64 字符串
221
+ */
222
+ compressor?: (arrayBuffer: ArrayBuffer, mime: string, quality: number) => Promise<string> | string;
223
+ /**
224
+ * 自定义 fetch 函数,用于使用自己封装的请求库读取图片
225
+ * 必须返回符合 Web 标准的 Response 对象
226
+ * @param url 图片地址
227
+ * @returns Promise<Response>
228
+ */
229
+ fetcher?: (url: string) => Promise<Response>;
230
+ };
231
+ /**
232
+ * 图片地址转 base64 数据
233
+ *
234
+ * @param imageUrl 图片地址
235
+ * @param options 可选配置
236
+ * @param options.quality 压缩比率,默认 0.92
237
+ * @param options.compressor 自定义压缩函数,用于覆盖默认压缩行为
238
+ *
239
+ * @example
240
+ * // 基本用法(浏览器自动使用 Canvas 压缩,Node.js 自动检测并使用 sharp)
241
+ * imageUrlToBase64("https://example.com/image.jpg");
242
+ *
243
+ * @example
244
+ * // 使用自定义 fetch 函数(如 axios 封装)
245
+ * imageUrlToBase64("https://example.com/image.jpg", {
246
+ * fetcher: async (url) => {
247
+ * // 使用 axios 或其他请求库,但必须返回 Response 对象
248
+ * const response = await axios.get(url, { responseType: 'arraybuffer' });
249
+ * return new Response(response.data, {
250
+ * status: response.status,
251
+ * statusText: response.statusText,
252
+ * headers: response.headers
253
+ * });
254
+ * }
255
+ * });
256
+ *
257
+ * @example
258
+ * // 使用自定义压缩函数覆盖默认行为
259
+ * imageUrlToBase64("https://example.com/image.jpg", {
260
+ * quality: 0.8,
261
+ * compressor: async (buffer, mime, quality) => {
262
+ * // 自定义压缩逻辑
263
+ * return `data:${mime};base64,...`;
264
+ * }
265
+ * });
266
+ */
267
+ declare const imageUrlToBase64: (imageUrl: string, options?: ImageCompressionOptions) => Promise<string>;
268
+
269
+ /**
270
+ * Go 语言风格的异步处理方式
271
+ * @param promise 一个能被 await 的异步函数
272
+ * @returns 如果成功,返回 [null, 异步函数结果],否则返回 [Error, undefined]
273
+ *
274
+ * @example
275
+ * const [error, response] = await to(fetcher().get<Blog>("/blogs/hello-world"));
276
+ */
277
+ declare const to: <T, E = Error>(promise: Promise<T>) => Promise<[null, T] | [E, undefined]>;
278
+
279
+ /**
280
+ * 在指定闭区间内生成随机整数
281
+ *
282
+ * @example
283
+ * randomInt(1, 10); // 1 <= x <= 10
284
+ */
285
+ declare const randomInt: (min: number, max: number) => number;
286
+
287
+ type DeepMapKeys<T> = T extends Array<infer U> ? Array<DeepMapKeys<U>> : T extends object ? {
288
+ [key: string]: DeepMapKeys<T[keyof T]>;
289
+ } : T;
290
+ /**
291
+ * 递归处理对象里的 key
292
+ *
293
+ * @remarks
294
+ * 无法完整推导出类型,只能做到有递归,key 全为 string,value 为同层级的所有类型的联合
295
+ *
296
+ * @template T 要转换的对象
297
+ *
298
+ * @example
299
+ * const obj = { a: { b: 1 } };
300
+ * const result = mapKeys(obj, (key) => key.toUpperCase());
301
+ * console.log(result); // { A: { B: 1 } }
302
+ */
303
+ declare const mapKeys: <T>(obj: T, getNewKey: (key: string) => string) => DeepMapKeys<T>;
304
+
305
+ type DeepMapValues<T, R> = T extends Array<infer U> ? Array<DeepMapValues<U, R>> : T extends object ? {
306
+ [K in keyof T]: T[K] extends object ? DeepMapValues<T[K], R> : R;
307
+ } : R;
308
+ /**
309
+ * 递归处理对象里的 value
310
+ *
311
+ * @remarks
312
+ * 无法完整推导出类型,所有 value 最终都会变为 any
313
+ *
314
+ * @template T 要转换的对象
315
+ * @template R 转换后的值类型,为 any,无法进一步推导
316
+ *
317
+ * @example
318
+ * const obj = { a: 1, b: { c: 2 } };
319
+ * const result = mapValues(obj, (value, key) => isPrimitive(value) ? value + 1 : value);
320
+ * console.log(result); // { a: 2, b: { c: 3 } }
321
+ */
322
+ declare const mapValues: <T, R = any>(obj: T, getNewValue: (value: any, key: string | number) => R, options?: {
323
+ /** 过滤函数,返回 true 表示保留该字段 */
324
+ filter?: (value: any, key: string | number) => boolean;
325
+ }) => DeepMapValues<T, R>;
326
+
327
+ /**
328
+ * 深度合并两个对象,规则如下:
329
+ * 1. 原始值覆盖:如果两个值都是原始类型,则用后者覆盖;
330
+ * 2. 数组拼接:如果两个值都是数组,则拼接为大数组;
331
+ * 3. 对象递归合并:如果两个值都是对象,则进行递归深度合并;
332
+ *
333
+ * @template T 第一个对象
334
+ * @template U 第二个对象
335
+ * @param {T} obj1 要合并的第一个对象,相同字段会被 obj2 覆盖
336
+ * @param {U} obj2 要合并的第二个对象
337
+ */
338
+ declare const mergeObjects: <T extends Record<string, any>, U extends Record<string, any>>(obj1: T, obj2: U) => T & U;
339
+
340
+ type SnakeToCamel<S extends string> = S extends `${infer Before}_${infer After}` ? After extends `${infer First}${infer Rest}` ? `${Before}${Uppercase<First>}${SnakeToCamel<Rest>}` : Before : S;
341
+ /**
342
+ * 下划线命名法转为驼峰命名法
343
+ *
344
+ * @example
345
+ * snakeToCamel("user_name") // "userName"
346
+ */
347
+ declare const snakeToCamel: <S extends string>(str: S) => SnakeToCamel<S>;
348
+ type CamelToSnake<S extends string> = S extends `${infer First}${infer Rest}` ? Rest extends Uncapitalize<Rest> ? `${Lowercase<First>}${CamelToSnake<Rest>}` : `${Lowercase<First>}_${CamelToSnake<Rest>}` : Lowercase<S>;
349
+ /**
350
+ * 驼峰命名法转为下划线命名法
351
+ *
352
+ * @example
353
+ * camelToSnake("shouldComponentUpdate") // "should_component_update"
354
+ */
355
+ declare const camelToSnake: <S extends string>(str: S) => CamelToSnake<S>;
356
+ type Capitalize<S extends string> = S extends `${infer P1}${infer Rest}` ? P1 extends Capitalize<P1> ? S : `${Uppercase<P1>}${Rest}` : S;
357
+ /**
358
+ * 字符串首字母大写
359
+ *
360
+ * @example
361
+ * capitalize("hello") // "Hello"
362
+ */
363
+ declare const capitalize: <S extends string>(s: S) => Capitalize<S>;
364
+ type Decapitalize<S extends string> = S extends `${infer P1}${infer Rest}` ? P1 extends Lowercase<P1> ? P1 : `${Lowercase<P1>}${Rest}` : S;
365
+ /**
366
+ * 字符串首字母小写
367
+ *
368
+ * @example
369
+ * decapitalize("Hello") // "hello"
370
+ */
371
+ declare const decapitalize: <S extends string>(s: S) => Decapitalize<S>;
372
+
373
+ /**
374
+ * 将字符串压缩为单行精简格式
375
+ *
376
+ * @example
377
+ * // "Hello, world."
378
+ * compactStr(`
379
+ * Hello,
380
+ * world!
381
+ * `, {
382
+ * disableNewLineReplace: false,
383
+ * });
384
+ */
385
+ declare const compactStr: (text?: string, options?: {
386
+ /** 最大保留长度,设为 0 或 Infinity 则不截断,默认 Infinity */
387
+ maxLength?: number;
388
+ /** 是否将换行符替换为字面量 \n,默认开启 */
389
+ disableNewLineReplace?: boolean;
390
+ /** 是否合并连续的空格/制表符为一个空格,默认开启 */
391
+ disableWhitespaceCollapse?: boolean;
392
+ /** 截断后的后缀,默认为 "..." */
393
+ omission?: string;
394
+ }) => string;
395
+
396
+ /**
397
+ * 防抖:在指定时间内只执行最后一次调用
398
+ * @param fn 要防抖的函数
399
+ * @param delay 延迟时间,默认 300ms
400
+ *
401
+ * @remarks
402
+ * 连续触发时,只有最后一次会执行。适合用于搜索框输入、窗口大小调整等场景。
403
+ * 例如:用户输入"hello"过程中,不会触发搜索,只有停下来时才执行。
404
+ *
405
+ * 防抖 vs 节流:
406
+ * - 防抖:等待触发停止后才执行(最后一次)
407
+ * - 节流:按固定节奏执行(每隔多久执行一次)
408
+ *
409
+ * @example
410
+ * const search = debounce((keyword: string) => {
411
+ * console.log('搜索:', keyword);
412
+ * });
413
+ * search('hello'); // 300ms 后执行
414
+ */
415
+ declare const debounce: <T extends (...args: any[]) => any>(fn: T, delay?: number) => (...args: Parameters<T>) => void;
416
+
417
+ /**
418
+ * 延迟一段时间再执行后续代码
419
+ * @param time 延迟时间,默认 150ms
420
+ * @example
421
+ * await sleep(1000); // 等待 1 秒执行后续代码
422
+ */
423
+ declare const sleep: (time?: number) => Promise<unknown>;
424
+
425
+ /**
426
+ * 节流函数 - 在指定时间间隔内最多执行一次调用
427
+ * @param fn 要节流的函数
428
+ * @param delay 间隔时间,默认 300ms
429
+ *
430
+ * @remarks
431
+ * 节流:连续触发时,按照固定间隔执行。适合用于滚动、拖拽等高频触发场景。
432
+ * 例如:滚动页面时,每300ms最多执行一次回调,而不是每次滚动都执行。
433
+ *
434
+ * 防抖 vs 节流:
435
+ * - 防抖:等待触发停止后才执行(最后一次)
436
+ * - 节流:按固定节奏执行(每隔多久执行一次)
437
+ *
438
+ * @example
439
+ * const handleScroll = throttle(() => {
440
+ * console.log('滚动位置:', window.scrollY);
441
+ * }, 200);
442
+ * window.addEventListener('scroll', handleScroll);
443
+ */
444
+ declare const throttle: <T extends (...args: any[]) => any>(fn: T, delay?: number) => (this: any, ...args: Parameters<T>) => void;
445
+
446
+ export { type CamelToSnake, type Capitalize, type Decapitalize, type DeepMapKeys, type DeepMapValues, type Falsy, type FetchOptions, type ImageCompressionOptions, type LogOptions, type Primitive, type RequestInit, type SetTtl, type SnakeToCamel, camelToSnake, capitalize, compactStr, debounce, decapitalize, fetcher, getRealURL, imageUrlToBase64, isFalsy, isNil, isObject, isPrimitive, log, loopUntil, mapKeys, mapValues, mergeObjects, randomInt, sleep, snakeToCamel, throttle, to, withCache };
package/dist/index.js CHANGED
@@ -1,3 +1,2 @@
1
- // @bun
2
- var R=(x,Y)=>{let{time:T=!0,fileName:G=!0}=Y??{},M=[];if(T)M.push(`[${new Date().toLocaleTimeString()}]`);if(G){let{stack:E}=Error(),P=E?.split(`
3
- `)[2]?.trim()?.match(/at\s+(.*):(\d+)/);if(P?.[1]){let Q=P[1].split(/[/\\]/).pop();M.push(`[${Q}:${P[2]}]`)}}if(Array.isArray(x))M.push(...x);else M.push(x);console.log(...M)};var N=async(x,Y)=>{let{maxRetries:T=5,shouldStop:G}=Y??{},M;for(let E=0;E<T;E++)if(M=await x(E),G?.(M)===!0)return M;if(!G)return M;throw Error(`\u8D85\u8FC7\u4E86\u6700\u5927\u5FAA\u73AF\u6B21\u6570\uFF08${T}\uFF09\u4E14\u672A\u6EE1\u8DB3\u505C\u6B62\u6267\u884C\u6761\u4EF6`)};var h=(x,Y=-1)=>{let T=new Map,G=(...M)=>{let E=JSON.stringify(M),J=Date.now(),P=T.get(E);if(P&&J<P.expiresAt)return P.value;let Q=Y===-1?1/0:J+Y*1000,$={setTtl:(_)=>{Q=J+_*1000}},X=x.apply($,M);if(X instanceof Promise){let _=X.then((z)=>{return T.set(E,{value:z,expiresAt:Q}),z});return T.set(E,{value:_,expiresAt:Q}),_}return T.set(E,{value:X,expiresAt:Q}),X};return G.clear=()=>T.clear(),G.updateTtl=(M)=>{Y=M;let E=Date.now(),J=E+M*1000;for(let[P,Q]of T.entries())if(Q.expiresAt>E)Q.expiresAt=J,T.set(P,Q)},G};var n=(x)=>{return!x};var I=(x)=>{return x===null||x===void 0};var Z=(x)=>{return x?.constructor===Object};var C=(x)=>{return x===null||x===void 0||typeof x!=="object"&&typeof x!=="function"};var F=(x,Y)=>{if(Array.isArray(x))return x.map((T)=>F(T,Y));if(Z(x))return Object.keys(x).reduce((G,M)=>{let E=Y(M),J=x[M];return G[E]=F(J,Y),G},{});return x};var d=(x,Y,T)=>{let{filter:G}=T??{};if(Array.isArray(x)){let M=x.map((E,J)=>{if(Z(E))return d(E,Y,T);return Y(E,J)});if(G)return M.filter((E,J)=>G(E,J));return M}if(Z(x))return Object.keys(x).reduce((E,J)=>{let P=x[J],Q;if(Z(P)||Array.isArray(P))Q=d(P,Y,T);else Q=Y(P,J);if(!G||G(Q,J))E[J]=Q;return E},{});return x};var H=(x,Y)=>{let T={...x};for(let G of Object.keys(Y)){let M=T[G],E=Y[G];if(C(M)&&C(E)){T[G]=E;continue}if(Array.isArray(M)&&Array.isArray(E)){T[G]=M.concat(E);continue}if(Z(M)&&Z(E)){T[G]=H(M,E);continue}T[G]=E}return T};var Px=(x="",Y={})=>{let T=async(G,M={})=>{let E=new URL(x?`${x}${G}`:G),{params:J,parser:P,...Q}=H(Y,M);if(Z(J))Object.entries(J).forEach(([_,z])=>{if(I(z))return;E.searchParams.append(_,z.toString())});if(Z(Q.body)||Array.isArray(Q.body))Q.body=JSON.stringify(Q.body),Q.headers={...Q.headers,"Content-Type":"application/json"};let $=await fetch(E,Q);if(!$.ok){if($.headers.get("Content-Type")?.startsWith("application/json"))throw await $.json();throw Error($.statusText)}return await(P?.($)??$.json())};return{get:(G,M)=>T(G,{...M,method:"GET"}),post:(G,M,E)=>T(G,{...E,method:"POST",body:M}),put:(G,M,E)=>T(G,{...E,method:"PUT",body:M}),delete:(G,M)=>T(G,{...M,method:"DELETE"})}};var q=async(x)=>{try{return[null,await x]}catch(Y){return[Y,void 0]}};var _x=async(x)=>{let[Y,T]=await q(fetch(x,{method:"HEAD",redirect:"manual"}));if(Y)return x;return T.headers.get("location")||x};var D=(x)=>{let Y=new Uint8Array(x),T="";for(let G=0;G<Y.byteLength;G++)T+=String.fromCharCode(Y[G]);return btoa(T)},f=async()=>{try{let Y=await Function("modulePath","return import(modulePath)")("sharp");return Y.default||Y}catch{return null}},A=async(x,Y,T,G)=>{let M=Buffer.from(Y),E=x(M);if(T==="image/jpeg")E=E.jpeg({quality:Math.round(G*100)});else if(T==="image/png"){let P=Math.round((1-G)*9);E=E.png({compressionLevel:P})}let J=await E.toBuffer();return`data:${T};base64,${J.toString("base64")}`},zx=async(x,Y={})=>{let{quality:T=0.92,compressor:G,fetcher:M=fetch}=Y;if(!x.startsWith("http"))throw Error("\u56FE\u7247\u5730\u5740\u5FC5\u987B\u4EE5http\u6216https\u5F00\u5934");let E=await M(x);if(!E.ok)throw Error(`\u83B7\u53D6\u56FE\u7247\u5931\u8D25: ${E.statusText}`);let J=E.headers.get("Content-Type")||"image/jpeg",P=await E.arrayBuffer();if(J!=="image/jpeg"&&J!=="image/png"){let X=D(P);return`data:${J};base64,${X}`}if(G)return await G(P,J,T);if(typeof OffscreenCanvas<"u"){let X=null;try{let _=new Blob([P],{type:J});X=await createImageBitmap(_);let z=new OffscreenCanvas(X.width,X.height),W=z.getContext("2d");if(!W)throw Error("\u65E0\u6CD5\u83B7\u53D6 OffscreenCanvas context");W.drawImage(X,0,0),X.close(),X=null;let S=await(await z.convertToBlob({type:J,quality:T})).arrayBuffer(),K=D(S);return`data:${J};base64,${K}`}catch{X?.close();let _=D(P);return`data:${J};base64,${_}`}}let Q=await f();if(Q)try{return await A(Q,P,J,T)}catch{let X=D(P);return`data:${J};base64,${X}`}let $=D(P);return`data:${J};base64,${$}`};var dx=(x,Y)=>{return Math.floor(Math.random()*(Y-x+1))+x};var fx=(x)=>{return x.replace(/_([a-zA-Z])/g,(Y,T)=>T.toUpperCase())},Ax=(x)=>{return x.replace(/([A-Z])/g,(Y,T)=>`_${T.toLowerCase()}`)},Ox=(x)=>{return x.charAt(0).toUpperCase()+x.slice(1)},Rx=(x)=>{return x.charAt(0).toLowerCase()+x.slice(1)};var Ux=(x="",Y)=>{if(!x)return"";let{maxLength:T=1/0,disableNewLineReplace:G=!1,disableWhitespaceCollapse:M=!1,omission:E="..."}=Y??{},J=x;if(!G)J=J.replace(/\r?\n/g,"\\n");else J=J.replace(/\r?\n/g," ");if(!M)J=J.replace(/\s+/g," ");if(J=J.trim(),T>0&&J.length>T)return J.slice(0,T)+E;return J};var kx=(x,Y=300)=>{let T=null;return(...G)=>{if(T)clearTimeout(T);T=setTimeout(()=>{x(...G)},Y)}};var gx=async(x=150)=>{return new Promise((Y)=>{setTimeout(Y,x)})};var jx=(x,Y=300)=>{let T=null;return function(...G){if(!T)T=setTimeout(()=>{T=null,x.apply(this,G)},Y)}};export{h as withCache,q as to,jx as throttle,fx as snakeToCamel,gx as sleep,dx as randomInt,H as mergeObjects,d as mapValues,F as mapKeys,N as loopUntil,R as log,C as isPrimitive,Z as isObject,I as isNil,n as isFalsy,zx as imageUrlToBase64,_x as getRealURL,Px as fetcher,Rx as decapitalize,kx as debounce,Ux as compactStr,Ox as capitalize,Ax as camelToSnake};
1
+ var P=(e,s)=>{let{time:t=!0,fileName:n=!0}=s??{},a=[];if(t&&a.push(`[${new Date().toLocaleTimeString()}]`),n){let{stack:r}=new Error,i=r?.split(`
2
+ `)[2]?.trim()?.match(/at\s+(.*):(\d+)/);if(i?.[1]){let p=i[1].split(/[/\\]/).pop();a.push(`[${p}:${i[2]}]`)}}Array.isArray(e)?a.push(...e):a.push(e),console.log(...a)};var v=async(e,s)=>{let{maxRetries:t=5,shouldStop:n}=s??{},a;for(let r=0;r<t;r++)if(a=await e(r),n?.(a)===!0)return a;if(!n)return a;throw new Error(`\u8D85\u8FC7\u4E86\u6700\u5927\u5FAA\u73AF\u6B21\u6570\uFF08${t}\uFF09\u4E14\u672A\u6EE1\u8DB3\u505C\u6B62\u6267\u884C\u6761\u4EF6`)};var L=(e,s=-1)=>{let t=new Map,n=(...a)=>{let r=JSON.stringify(a),o=Date.now(),i=t.get(r);if(i&&o<i.expiresAt)return i.value;let p=s===-1?1/0:o+s*1e3,f={setTtl:u=>{p=o+u*1e3}},c=e.apply(f,a);if(c instanceof Promise){let u=c.then(m=>(t.set(r,{value:m,expiresAt:p}),m));return t.set(r,{value:u,expiresAt:p}),u}return t.set(r,{value:c,expiresAt:p}),c};return n.clear=()=>t.clear(),n.updateTtl=a=>{s=a;let r=Date.now(),o=r+a*1e3;for(let[i,p]of t.entries())p.expiresAt>r&&(p.expiresAt=o,t.set(i,p))},n};var q=e=>!e;var g=e=>e==null;var l=e=>e?.constructor===Object;var d=e=>e==null||typeof e!="object"&&typeof e!="function";var h=(e,s)=>Array.isArray(e)?e.map(t=>h(t,s)):l(e)?Object.keys(e).reduce((n,a)=>{let r=s(a),o=e[a];return n[r]=h(o,s),n},{}):e;var b=(e,s,t)=>{let{filter:n}=t??{};if(Array.isArray(e)){let a=e.map((r,o)=>l(r)?b(r,s,t):s(r,o));return n?a.filter((r,o)=>n(r,o)):a}return l(e)?Object.keys(e).reduce((r,o)=>{let i=e[o],p;return l(i)||Array.isArray(i)?p=b(i,s,t):p=s(i,o),(!n||n(p,o))&&(r[o]=p),r},{}):e};var x=(e,s)=>{let t={...e};for(let n of Object.keys(s)){let a=t[n],r=s[n];if(d(a)&&d(r)){t[n]=r;continue}if(Array.isArray(a)&&Array.isArray(r)){t[n]=a.concat(r);continue}if(l(a)&&l(r)){t[n]=x(a,r);continue}t[n]=r}return t};var ce=(e="",s={})=>{let t=async(n,a={})=>{let r=new URL(e?`${e}${n}`:n),{params:o,parser:i,...p}=x(s,a);l(o)&&Object.entries(o).forEach(([u,m])=>{g(m)||r.searchParams.append(u,m.toString())}),(l(p.body)||Array.isArray(p.body))&&(p.body=JSON.stringify(p.body),p.headers={...p.headers,"Content-Type":"application/json"});let f=await fetch(r,p);if(!f.ok)throw f.headers.get("Content-Type")?.startsWith("application/json")?await f.json():new Error(f.statusText);return await(i?.(f)??f.json())};return{get:(n,a)=>t(n,{...a,method:"GET"}),post:(n,a,r)=>t(n,{...r,method:"POST",body:a}),put:(n,a,r)=>t(n,{...r,method:"PUT",body:a}),delete:(n,a)=>t(n,{...a,method:"DELETE"})}};var w=async e=>{try{return[null,await e]}catch(s){return[s,void 0]}};var me=async e=>{let[s,t]=await w(fetch(e,{method:"HEAD",redirect:"manual"}));return s?e:t.headers.get("location")||e};var y=e=>{let s=new Uint8Array(e),t="";for(let n=0;n<s.byteLength;n++)t+=String.fromCharCode(s[n]);return btoa(t)},A=async()=>{try{let s=await new Function("modulePath","return import(modulePath)")("sharp");return s.default||s}catch{return null}},$=async(e,s,t,n)=>{let a=Buffer.from(s),r=e(a);if(t==="image/jpeg")r=r.jpeg({quality:Math.round(n*100)});else if(t==="image/png"){let i=Math.round((1-n)*9);r=r.png({compressionLevel:i})}let o=await r.toBuffer();return`data:${t};base64,${o.toString("base64")}`},de=async(e,s={})=>{let{quality:t=.92,compressor:n,fetcher:a=fetch}=s;if(!e.startsWith("http"))throw new Error("\u56FE\u7247\u5730\u5740\u5FC5\u987B\u4EE5http\u6216https\u5F00\u5934");let r=await a(e);if(!r.ok)throw new Error(`\u83B7\u53D6\u56FE\u7247\u5931\u8D25: ${r.statusText}`);let o=r.headers.get("Content-Type")||"image/jpeg",i=await r.arrayBuffer();if(o!=="image/jpeg"&&o!=="image/png"){let c=y(i);return`data:${o};base64,${c}`}if(n)return await n(i,o,t);if(typeof OffscreenCanvas<"u"){let c=null;try{let u=new Blob([i],{type:o});c=await createImageBitmap(u);let m=new OffscreenCanvas(c.width,c.height),T=m.getContext("2d");if(!T)throw new Error("\u65E0\u6CD5\u83B7\u53D6 OffscreenCanvas context");T.drawImage(c,0,0),c.close(),c=null;let S=await(await m.convertToBlob({type:o,quality:t})).arrayBuffer(),R=y(S);return`data:${o};base64,${R}`}catch{c?.close();let u=y(i);return`data:${o};base64,${u}`}}let p=await A();if(p)try{return await $(p,i,o,t)}catch{let c=y(i);return`data:${o};base64,${c}`}let f=y(i);return`data:${o};base64,${f}`};var Se=(e,s)=>Math.floor(Math.random()*(s-e+1))+e;var Ce=e=>e.replace(/_([a-zA-Z])/g,(s,t)=>t.toUpperCase()),Pe=e=>e.replace(/([A-Z])/g,(s,t)=>`_${t.toLowerCase()}`),Oe=e=>e.charAt(0).toUpperCase()+e.slice(1),ke=e=>e.charAt(0).toLowerCase()+e.slice(1);var ve=(e="",s)=>{if(!e)return"";let{maxLength:t=1/0,disableNewLineReplace:n=!1,disableWhitespaceCollapse:a=!1,omission:r="..."}=s??{},o=e;return n?o=o.replace(/\r?\n/g," "):o=o.replace(/\r?\n/g,"\\n"),a||(o=o.replace(/\s+/g," ")),o=o.trim(),t>0&&o.length>t?o.slice(0,t)+r:o};var Me=(e,s=300)=>{let t=null;return(...n)=>{t&&clearTimeout(t),t=setTimeout(()=>{e(...n)},s)}};var je=async(e=150)=>new Promise(s=>{setTimeout(s,e)});var Fe=(e,s=300)=>{let t=null;return function(...n){t||(t=setTimeout(()=>{t=null,e.apply(this,n)},s))}};export{Pe as camelToSnake,Oe as capitalize,ve as compactStr,Me as debounce,ke as decapitalize,ce as fetcher,me as getRealURL,de as imageUrlToBase64,q as isFalsy,g as isNil,l as isObject,d as isPrimitive,P as log,v as loopUntil,h as mapKeys,b as mapValues,x as mergeObjects,Se as randomInt,je as sleep,Ce as snakeToCamel,Fe as throttle,w as to,L as withCache};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nickyzj2023/utils",
3
- "version": "1.0.55",
3
+ "version": "1.0.56",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/Nickyzj628/utils.git"
@@ -8,9 +8,13 @@
8
8
  "type": "module",
9
9
  "module": "dist/index.js",
10
10
  "types": "dist/index.d.ts",
11
+ "files": [
12
+ "dist"
13
+ ],
11
14
  "devDependencies": {
12
- "@biomejs/biome": "^2.4.3",
13
- "@types/bun": "^1.3.9",
15
+ "@biomejs/biome": "^2.4.4",
16
+ "@types/node": "^25.3.2",
17
+ "tsup": "^8.5.1",
14
18
  "typedoc": "^0.28.17",
15
19
  "typedoc-material-theme": "^1.4.1"
16
20
  },
@@ -19,6 +23,6 @@
19
23
  },
20
24
  "scripts": {
21
25
  "docs": "typedoc src/index.ts --plugin typedoc-material-theme",
22
- "build": "bun build --target=bun --outdir ./dist --minify ./src/index.ts --packages external && tsc && bun run docs"
26
+ "build": "tsup src/index.ts --format esm --dts --minify --out-dir dist && pnpm docs"
23
27
  }
24
28
  }