@fast-china/utils 2.1.4 → 2.1.6

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 (52) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +14 -4
  3. package/README.zh.md +14 -4
  4. package/dist/array/index.d.mts +8 -0
  5. package/dist/array/index.mjs +15 -1
  6. package/dist/array/index.mjs.map +1 -1
  7. package/dist/async/index.mjs.map +1 -1
  8. package/dist/color/index.mjs.map +1 -1
  9. package/dist/date/index.mjs.map +1 -1
  10. package/dist/function/index.d.mts +13 -0
  11. package/dist/function/index.mjs +38 -0
  12. package/dist/function/index.mjs.map +1 -0
  13. package/dist/index.d.mts +10 -3
  14. package/dist/index.global.min.js +2 -2
  15. package/dist/index.global.min.js.map +1 -1
  16. package/dist/index.mjs +10 -3
  17. package/dist/internal/runtime.mjs.map +1 -1
  18. package/dist/internal/text.mjs.map +1 -1
  19. package/dist/logger/index.mjs +4 -6
  20. package/dist/logger/index.mjs.map +1 -1
  21. package/dist/number/index.mjs.map +1 -1
  22. package/dist/object/index.d.mts +41 -2
  23. package/dist/object/index.mjs +258 -14
  24. package/dist/object/index.mjs.map +1 -1
  25. package/dist/storage/index.mjs.map +1 -1
  26. package/dist/string/index.mjs.map +1 -1
  27. package/dist/vue/breakpoints.d.mts +21 -0
  28. package/dist/vue/breakpoints.mjs +46 -0
  29. package/dist/vue/breakpoints.mjs.map +1 -0
  30. package/dist/vue/element-size.d.mts +25 -0
  31. package/dist/vue/element-size.mjs +33 -0
  32. package/dist/vue/element-size.mjs.map +1 -0
  33. package/dist/vue/emits.mjs.map +1 -1
  34. package/dist/vue/event-listener.d.mts +16 -0
  35. package/dist/vue/event-listener.mjs +30 -0
  36. package/dist/vue/event-listener.mjs.map +1 -0
  37. package/dist/vue/index.d.mts +7 -1
  38. package/dist/vue/install.mjs.map +1 -1
  39. package/dist/vue/now.d.mts +13 -0
  40. package/dist/vue/now.mjs +29 -0
  41. package/dist/vue/now.mjs.map +1 -0
  42. package/dist/vue/render.mjs.map +1 -1
  43. package/dist/vue/resize-observer.d.mts +15 -0
  44. package/dist/vue/resize-observer.mjs +31 -0
  45. package/dist/vue/resize-observer.mjs.map +1 -0
  46. package/dist/vue/window-size.d.mts +16 -0
  47. package/dist/vue/window-size.mjs +32 -0
  48. package/dist/vue/window-size.mjs.map +1 -0
  49. package/docs/API.md +4 -3
  50. package/docs/API.zh-CN.md +4 -3
  51. package/docs/RUNTIME_CONTRACT.md +4 -2
  52. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to Fast.Utils are documented in this file.
4
4
 
5
+ ## [2.1.6] - 2026-09-13
6
+
7
+ ### Added
8
+
9
+ - Added lightweight Vue 3 composables for native event listeners, window size, ResizeObserver, element size, current time, and minimum-width breakpoints.
10
+ - Added automatic Vue scope cleanup, manual stop handles where applicable, SSR-safe initial state, runtime validation, and public type coverage for the new composables.
11
+
12
+ ## [2.1.5] - 2026-09-12
13
+
14
+ ### Added
15
+
16
+ - Added the dependency-free `cloneDeep` object utility with Lodash-compatible recursive cloning, circular-reference tracking, built-in object support, and preserved Map keys.
17
+ - Added `isEqual`, `pickBy`, `omitBy`, `symmetricDifference`, and `once` with public type contracts and runtime coverage.
18
+
19
+ ### Changed
20
+
21
+ - Expanded the applicable JavaScript, TypeScript, import, RegExp, JSON, Markdown, sorting, and Prettier rules from Fast.ESLint.Config 2.1.8 directly in the repository's single `eslint.config.mjs`.
22
+ - Removed obsolete rule suppressions while preserving synchronous validation, Promise identity, public generic signatures, storage behavior, and Vue installation contracts.
23
+
5
24
  ## [2.1.4] - 2026-09-11
6
25
 
7
26
  ### Changed
@@ -101,6 +120,8 @@ All notable changes to Fast.Utils are documented in this file.
101
120
 
102
121
  - Added authenticated ciphertext validation, bounded crypto parameters and payloads, unbiased Web Crypto randomness, prototype-safe query/object transforms, and namespace-scoped Storage cleanup.
103
122
 
123
+ [2.1.6]: https://github.com/China-xiaoFang/Fast.Utils/compare/v2.1.5...v2.1.6
124
+ [2.1.5]: https://github.com/China-xiaoFang/Fast.Utils/compare/v2.1.4...v2.1.5
104
125
  [2.1.4]: https://github.com/China-xiaoFang/Fast.Utils/compare/v2.1.3...v2.1.4
105
126
  [2.1.3]: https://github.com/China-xiaoFang/Fast.Utils/compare/v2.1.2...v2.1.3
106
127
  [2.1.2]: https://github.com/China-xiaoFang/Fast.Utils/compare/v2.1.1...v2.1.2
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  Browser-first TypeScript utilities for modern browsers, WebViews, Vue 3, and uni-app.
12
12
 
13
- [![npm version](https://img.shields.io/npm/v/@fast-china/utils?color=orange)](https://www.npmjs.com/package/@fast-china/utils) [![node](https://img.shields.io/badge/node-%5E22.18%20%7C%7C%20%5E24.18-brightgreen)](https://nodejs.org/) [![vue](https://img.shields.io/badge/vue-%5E3.3-42b883)](https://vuejs.org/) [![license](https://img.shields.io/npm/l/@fast-china/utils)](./LICENSE)
13
+ [![npm version](https://img.shields.io/npm/v/@fast-china/utils?color=orange)](https://www.npmjs.com/package/@fast-china/utils) [![node](https://img.shields.io/badge/node-%5E22.18%20%7C%7C%20%5E24.18-brightgreen)](https://nodejs.org/) [![vue](https://img.shields.io/badge/vue-%5E3.5.11-42b883)](https://vuejs.org/) [![license](https://img.shields.io/npm/l/@fast-china/utils)](./LICENSE)
14
14
 
15
15
  ## Highlights
16
16
 
@@ -136,10 +136,18 @@ Store passwords with `HashPasswordPBKDF2SHA256` and `VerifyPasswordPBKDF2SHA256`
136
136
  ## Vue 3
137
137
 
138
138
  ```ts
139
- import { useEmits, useProps, withInstall } from "@fast-china/utils";
139
+ import { useBreakpoints, useElementSize, useEventListener, useNow, useWindowSize } from "@fast-china/utils";
140
+ import { useTemplateRef } from "vue";
141
+
142
+ const elementRef = useTemplateRef<HTMLElement>("element");
143
+ const { width: windowWidth } = useWindowSize();
144
+ const { width: elementWidth } = useElementSize(elementRef);
145
+ const now = useNow();
146
+ const breakpoints = useBreakpoints({ desktop: 1280, mobile: 0, tablet: 768 });
147
+ useEventListener(document, "visibilitychange", () => console.log(document.visibilityState));
140
148
  ```
141
149
 
142
- The package provides Vue 3 `app.use()` registration, Composition API helpers, typed props/emits/slots, and TSX rendering. Vue remains external to the build and is required as a peer dependency.
150
+ The package provides lightweight native-backed browser composables, Vue 3 `app.use()` registration, typed props/emits/slots, and TSX rendering. Browser composables clean up with the current Vue scope; advanced scheduling, controls, SSR configuration, and device APIs remain outside this package. Vue remains external to the build and is required as a peer dependency.
143
151
 
144
152
  ## Modules
145
153
 
@@ -147,11 +155,13 @@ The package provides Vue 3 `app.use()` registration, Composition API helpers, ty
147
155
 
148
156
  Historical aggregate objects are not public. Supported behavior is exposed through named functions, improving auto-imports and Tree Shaking; this major version does not preserve every former convenience method.
149
157
 
158
+ The `object` module includes dependency-free deep cloning and equality plus predicate-based property selection. Array utilities include a SameValueZero symmetric difference, while `once` preserves the first return value, Promise identity, or synchronous error.
159
+
150
160
  ## Runtime contract
151
161
 
152
162
  - The package-manager entry is pure ESM; the CDN entry is a separately minified IIFE.
153
163
  - ES2022 modern browsers and WebViews.
154
- - Vue 3.3 or newer through a required peer dependency.
164
+ - Vue 3.5.11 or newer through a required peer dependency.
155
165
  - uni-app through automatic global `uni` detection when Storage is configured.
156
166
  - No import-time access to `window`, Storage, or `uni`; unsupported calls fail explicitly.
157
167
  - Web Crypto, URL, Intl, TextEncoder, and related platform capabilities are not polyfilled.
package/README.zh.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  面向现代浏览器、WebView、Vue 3 与 uni-app 的 TypeScript 前端工具库。
12
12
 
13
- [![npm 版本](https://img.shields.io/npm/v/@fast-china/utils?color=orange)](https://www.npmjs.com/package/@fast-china/utils) [![node](https://img.shields.io/badge/node-%5E22.18%20%7C%7C%20%5E24.18-brightgreen)](https://nodejs.org/) [![Vue](https://img.shields.io/badge/vue-%5E3.3-42b883)](https://vuejs.org/) [![开源协议](https://img.shields.io/npm/l/@fast-china/utils)](./LICENSE)
13
+ [![npm 版本](https://img.shields.io/npm/v/@fast-china/utils?color=orange)](https://www.npmjs.com/package/@fast-china/utils) [![node](https://img.shields.io/badge/node-%5E22.18%20%7C%7C%20%5E24.18-brightgreen)](https://nodejs.org/) [![Vue](https://img.shields.io/badge/vue-%5E3.5.11-42b883)](https://vuejs.org/) [![开源协议](https://img.shields.io/npm/l/@fast-china/utils)](./LICENSE)
14
14
 
15
15
  ## 特性
16
16
 
@@ -136,10 +136,18 @@ Base64 与 Crypto 的文本解码/解密入口返回原始字符串类型 `Decod
136
136
  ## Vue 3
137
137
 
138
138
  ```ts
139
- import { useEmits, useProps, withInstall } from "@fast-china/utils";
139
+ import { useBreakpoints, useElementSize, useEventListener, useNow, useWindowSize } from "@fast-china/utils";
140
+ import { useTemplateRef } from "vue";
141
+
142
+ const elementRef = useTemplateRef<HTMLElement>("element");
143
+ const { width: windowWidth } = useWindowSize();
144
+ const { width: elementWidth } = useElementSize(elementRef);
145
+ const now = useNow();
146
+ const breakpoints = useBreakpoints({ desktop: 1280, mobile: 0, tablet: 768 });
147
+ useEventListener(document, "visibilitychange", () => console.log(document.visibilityState));
140
148
  ```
141
149
 
142
- 包内提供 Vue 3 `app.use()` 注册、Composition API Helper、Props/Emits/Slots 类型和 TSX 渲染。Vue 不会打进构建产物,并作为必须安装的 Peer Dependency。
150
+ 包内提供基于原生浏览器 API 的轻量 Composable、Vue 3 `app.use()` 注册、Props/Emits/Slots 类型和 TSX 渲染。浏览器 Composable 随当前 Vue 作用域自动清理;高级调度、控制、SSR 配置和设备 API 不在本包范围内。Vue 不会打进构建产物,并作为必须安装的 Peer Dependency。
143
151
 
144
152
  ## 模块
145
153
 
@@ -147,11 +155,13 @@ import { useEmits, useProps, withInstall } from "@fast-china/utils";
147
155
 
148
156
  历史聚合对象不再公开,继续支持的能力通过具名函数提供,便于自动导入与 Tree Shaking;当前大版本并未保留每一个旧版便捷方法。
149
157
 
158
+ `object` 模块提供不依赖第三方库的深复制、深度比较和按条件筛选属性能力。数组工具支持 SameValueZero 对称差集,`once` 则会保留第一次调用的返回值、Promise 引用或同步错误。
159
+
150
160
  ## 运行时契约
151
161
 
152
162
  - 包管理器入口为纯 ESM;CDN 入口为单独压缩的 IIFE。
153
163
  - 面向 ES2022 现代浏览器与 WebView。
154
- - Vue 3.3 及以上版本通过必须安装的 Peer Dependency 接入。
164
+ - Vue 3.5.11 及以上版本通过必须安装的 Peer Dependency 接入。
155
165
  - 配置 Storage 时自动检测全局 `uni` 并接入 uni-app。
156
166
  - 导入阶段不访问 `window`、Storage 或 `uni`;不支持的调用明确失败。
157
167
  - 不注入 Web Crypto、URL、Intl、TextEncoder 等 Polyfill。
@@ -65,6 +65,14 @@ export declare function difference<Item>(left: readonly Item[], right: readonly
65
65
  * @returns 保留左侧首次出现顺序的去重结果;两侧稀疏空位都被忽略。
66
66
  */
67
67
  export declare function intersection<Item>(left: readonly Item[], right: readonly Item[]): Item[];
68
+ /**
69
+ * 返回只存在于其中一个数组的不同值。
70
+ *
71
+ * @param left - 决定左侧结果顺序的数组。
72
+ * @param right - 决定右侧结果顺序的数组。
73
+ * @returns 先按左侧、再按右侧首次出现顺序排列的对称差集;使用 SameValueZero 比较并忽略稀疏空位。
74
+ */
75
+ export declare function symmetricDifference<Item>(left: readonly Item[], right: readonly Item[]): Item[];
68
76
  /**
69
77
  * 判断选择器产生的键是否重复。
70
78
  *
@@ -114,6 +114,20 @@ function intersection(left, right) {
114
114
  return unique(left).filter((item) => included.has(item));
115
115
  }
116
116
  /**
117
+ * 返回只存在于其中一个数组的不同值。
118
+ *
119
+ * @param left - 决定左侧结果顺序的数组。
120
+ * @param right - 决定右侧结果顺序的数组。
121
+ * @returns 先按左侧、再按右侧首次出现顺序排列的对称差集;使用 SameValueZero 比较并忽略稀疏空位。
122
+ */
123
+ function symmetricDifference(left, right) {
124
+ const leftValues = unique(left);
125
+ const rightValues = unique(right);
126
+ const leftSet = new Set(leftValues);
127
+ const rightSet = new Set(rightValues);
128
+ return [...leftValues.filter((item) => !rightSet.has(item)), ...rightValues.filter((item) => !leftSet.has(item))];
129
+ }
130
+ /**
117
131
  * 判断选择器产生的键是否重复。
118
132
  *
119
133
  * @param items - 不会被修改的输入数组。
@@ -156,6 +170,6 @@ function allEqualBy(items, selectKey) {
156
170
  return true;
157
171
  }
158
172
  //#endregion
159
- export { allEqualBy, chunk, difference, groupBy, hasDuplicatesBy, intersection, partition, removeNullishValues, unique, uniqueBy };
173
+ export { allEqualBy, chunk, difference, groupBy, hasDuplicatesBy, intersection, partition, removeNullishValues, symmetricDifference, unique, uniqueBy };
160
174
 
161
175
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/array/index.ts"],"sourcesContent":["/** 从数组项中提取可比较键的函数。 */\nexport type KeySelector<Item, Key> = (item: Item, index: number) => Key;\n\n/**\n * 将只读数组按固定大小分组。\n *\n * @typeParam Item - 数组项类型。\n * @param items - 不会被修改的输入数组。\n * @param size - 每组最多包含的项目数,必须是正安全整数。\n * @returns 新建的二维数组;最后一组可能小于 `size`。\n * @throws `RangeError` 当 `size` 不是正安全整数。\n */\nexport function chunk<Item>(items: readonly Item[], size: number): Item[][] {\n\tif (!Number.isSafeInteger(size) || size <= 0) {\n\t\tthrow new RangeError(\"`size` 必须是正安全整数。\");\n\t}\n\n\tconst result: Item[][] = [];\n\tfor (let index = 0; index < items.length; index += size) {\n\t\tresult.push(items.slice(index, index + size));\n\t}\n\treturn result;\n}\n\n/**\n * 删除数组中的 `null` 与 `undefined`,保留 `false`、`0` 和空字符串。\n *\n * @param items - 可包含空值的只读数组。\n * @returns 保持原顺序的新数组。\n */\nexport function removeNullishValues<Item>(items: readonly (Item | null | undefined)[]): Item[] {\n\treturn items.filter((item): item is Item => item !== null && item !== undefined);\n}\n\n/**\n * 使用 JavaScript `Set` 的 SameValueZero 语义去重。\n *\n * @param items - 不会被修改的输入数组。\n * @returns 保留每个值首次出现顺序的新数组;稀疏数组空位被忽略。\n */\nexport function unique<Item>(items: readonly Item[]): Item[] {\n\tconst result: Item[] = [];\n\tconst seen = new Set<Item>();\n\tfor (let index = 0; index < items.length; index += 1) {\n\t\tif (!(index in items)) continue;\n\t\tconst item = items[index] as Item;\n\t\tif (seen.has(item)) continue;\n\t\tseen.add(item);\n\t\tresult.push(item);\n\t}\n\treturn result;\n}\n\n/**\n * 按选择器返回的键去重。\n *\n * @param items - 不会被修改的输入数组。\n * @param selectKey - 接收项目与索引并返回去重键的函数。\n * @returns 保留每个键首次出现项目的新数组;稀疏数组空位被忽略。\n */\nexport function uniqueBy<Item, Key>(items: readonly Item[], selectKey: KeySelector<Item, Key>): Item[] {\n\tconst seen = new Set<Key>();\n\tconst result: Item[] = [];\n\tfor (let index = 0; index < items.length; index += 1) {\n\t\tconst item = items[index];\n\t\tif (item === undefined && !(index in items)) continue;\n\t\tconst key = selectKey(item as Item, index);\n\t\tif (seen.has(key)) continue;\n\t\tseen.add(key);\n\t\tresult.push(item as Item);\n\t}\n\treturn result;\n}\n\n/**\n * 按选择器结果分组。\n *\n * @param items - 不会被修改的输入数组。\n * @param selectKey - 返回任意 `Map` 键的函数。\n * @returns 按键首次出现顺序排列的 `Map`;每个分组保持输入顺序,稀疏空位被忽略。\n */\nexport function groupBy<Item, Key>(items: readonly Item[], selectKey: KeySelector<Item, Key>): Map<Key, Item[]> {\n\tconst groups = new Map<Key, Item[]>();\n\titems.forEach((item, index) => {\n\t\tconst key = selectKey(item, index);\n\t\tconst group = groups.get(key);\n\t\tif (group === undefined) groups.set(key, [item]);\n\t\telse group.push(item);\n\t});\n\treturn groups;\n}\n\n/**\n * 按谓词把数组拆分为匹配项和非匹配项。\n *\n * @param items - 不会被修改的输入数组。\n * @param predicate - 接收项目与索引的判断函数。\n * @returns 二元组:第一项匹配谓词,第二项不匹配;两组都保持原顺序并忽略稀疏空位。\n */\nexport function partition<Item>(items: readonly Item[], predicate: (item: Item, index: number) => boolean): [matched: Item[], unmatched: Item[]] {\n\tconst matched: Item[] = [];\n\tconst unmatched: Item[] = [];\n\titems.forEach((item, index) => (predicate(item, index) ? matched : unmatched).push(item));\n\treturn [matched, unmatched];\n}\n\n/**\n * 返回只出现在左侧数组中的不同值。\n *\n * @param left - 主输入数组。\n * @param right - 需要排除的值。\n * @returns 保留左侧首次出现顺序的去重结果;两侧稀疏空位都被忽略。\n */\nexport function difference<Item>(left: readonly Item[], right: readonly Item[]): Item[] {\n\tconst excluded = new Set(unique(right));\n\treturn unique(left).filter((item) => !excluded.has(item));\n}\n\n/**\n * 返回两个数组共有的不同值。\n *\n * @param left - 决定结果顺序的数组。\n * @param right - 用于成员判断的数组。\n * @returns 保留左侧首次出现顺序的去重结果;两侧稀疏空位都被忽略。\n */\nexport function intersection<Item>(left: readonly Item[], right: readonly Item[]): Item[] {\n\tconst included = new Set(unique(right));\n\treturn unique(left).filter((item) => included.has(item));\n}\n\n/**\n * 判断选择器产生的键是否重复。\n *\n * @param items - 不会被修改的输入数组。\n * @param selectKey - 返回比较键的函数;键使用 SameValueZero 语义比较。\n * @returns 存在至少一个重复键时返回 `true`。\n */\nexport function hasDuplicatesBy<Item, Key>(items: readonly Item[], selectKey: KeySelector<Item, Key>): boolean {\n\tconst seen = new Set<Key>();\n\tfor (let index = 0; index < items.length; index += 1) {\n\t\tconst item = items[index];\n\t\tif (item === undefined && !(index in items)) continue;\n\t\tconst key = selectKey(item as Item, index);\n\t\tif (seen.has(key)) return true;\n\t\tseen.add(key);\n\t}\n\treturn false;\n}\n\n/**\n * 判断所有项目是否具有相同的选择器结果。\n *\n * @remarks 空数组、只有稀疏空位的数组和单项数组按数学惯例返回 `true`;空位不会调用选择器。\n * @param items - 不会被修改的输入数组。\n * @param selectKey - 返回比较键的函数。\n * @returns 所有键都满足 SameValueZero 相等时返回 `true`。\n */\nexport function allEqualBy<Item, Key>(items: readonly Item[], selectKey: KeySelector<Item, Key>): boolean {\n\tlet first: Key | undefined;\n\tlet hasFirst = false;\n\tfor (let index = 0; index < items.length; index += 1) {\n\t\tconst item = items[index];\n\t\tif (item === undefined && !(index in items)) continue;\n\t\tconst current = selectKey(item as Item, index);\n\t\tif (!hasFirst) {\n\t\t\tfirst = current;\n\t\t\thasFirst = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (!(first === current || (Number.isNaN(first) && Number.isNaN(current)))) return false;\n\t}\n\treturn true;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/array/index.ts"],"sourcesContent":["/** 从数组项中提取可比较键的函数。 */\nexport type KeySelector<Item, Key> = (item: Item, index: number) => Key;\n\n/**\n * 将只读数组按固定大小分组。\n *\n * @typeParam Item - 数组项类型。\n * @param items - 不会被修改的输入数组。\n * @param size - 每组最多包含的项目数,必须是正安全整数。\n * @returns 新建的二维数组;最后一组可能小于 `size`。\n * @throws `RangeError` 当 `size` 不是正安全整数。\n */\nexport function chunk<Item>(items: readonly Item[], size: number): Item[][] {\n\tif (!Number.isSafeInteger(size) || size <= 0) {\n\t\tthrow new RangeError(\"`size` 必须是正安全整数。\");\n\t}\n\n\tconst result: Item[][] = [];\n\tfor (let index = 0; index < items.length; index += size) {\n\t\tresult.push(items.slice(index, index + size));\n\t}\n\treturn result;\n}\n\n/**\n * 删除数组中的 `null` 与 `undefined`,保留 `false`、`0` 和空字符串。\n *\n * @param items - 可包含空值的只读数组。\n * @returns 保持原顺序的新数组。\n */\nexport function removeNullishValues<Item>(items: readonly (Item | null | undefined)[]): Item[] {\n\treturn items.filter((item): item is Item => item !== null && item !== undefined);\n}\n\n/**\n * 使用 JavaScript `Set` 的 SameValueZero 语义去重。\n *\n * @param items - 不会被修改的输入数组。\n * @returns 保留每个值首次出现顺序的新数组;稀疏数组空位被忽略。\n */\nexport function unique<Item>(items: readonly Item[]): Item[] {\n\tconst result: Item[] = [];\n\tconst seen = new Set<Item>();\n\tfor (let index = 0; index < items.length; index += 1) {\n\t\tif (!(index in items)) continue;\n\t\tconst item = items[index] as Item;\n\t\tif (seen.has(item)) continue;\n\t\tseen.add(item);\n\t\tresult.push(item);\n\t}\n\treturn result;\n}\n\n/**\n * 按选择器返回的键去重。\n *\n * @param items - 不会被修改的输入数组。\n * @param selectKey - 接收项目与索引并返回去重键的函数。\n * @returns 保留每个键首次出现项目的新数组;稀疏数组空位被忽略。\n */\nexport function uniqueBy<Item, Key>(items: readonly Item[], selectKey: KeySelector<Item, Key>): Item[] {\n\tconst seen = new Set<Key>();\n\tconst result: Item[] = [];\n\tfor (let index = 0; index < items.length; index += 1) {\n\t\tconst item = items[index];\n\t\tif (item === undefined && !(index in items)) continue;\n\t\tconst key = selectKey(item as Item, index);\n\t\tif (seen.has(key)) continue;\n\t\tseen.add(key);\n\t\tresult.push(item as Item);\n\t}\n\treturn result;\n}\n\n/**\n * 按选择器结果分组。\n *\n * @param items - 不会被修改的输入数组。\n * @param selectKey - 返回任意 `Map` 键的函数。\n * @returns 按键首次出现顺序排列的 `Map`;每个分组保持输入顺序,稀疏空位被忽略。\n */\nexport function groupBy<Item, Key>(items: readonly Item[], selectKey: KeySelector<Item, Key>): Map<Key, Item[]> {\n\tconst groups = new Map<Key, Item[]>();\n\titems.forEach((item, index) => {\n\t\tconst key = selectKey(item, index);\n\t\tconst group = groups.get(key);\n\t\tif (group === undefined) groups.set(key, [item]);\n\t\telse group.push(item);\n\t});\n\treturn groups;\n}\n\n/**\n * 按谓词把数组拆分为匹配项和非匹配项。\n *\n * @param items - 不会被修改的输入数组。\n * @param predicate - 接收项目与索引的判断函数。\n * @returns 二元组:第一项匹配谓词,第二项不匹配;两组都保持原顺序并忽略稀疏空位。\n */\nexport function partition<Item>(items: readonly Item[], predicate: (item: Item, index: number) => boolean): [matched: Item[], unmatched: Item[]] {\n\tconst matched: Item[] = [];\n\tconst unmatched: Item[] = [];\n\titems.forEach((item, index) => (predicate(item, index) ? matched : unmatched).push(item));\n\treturn [matched, unmatched];\n}\n\n/**\n * 返回只出现在左侧数组中的不同值。\n *\n * @param left - 主输入数组。\n * @param right - 需要排除的值。\n * @returns 保留左侧首次出现顺序的去重结果;两侧稀疏空位都被忽略。\n */\nexport function difference<Item>(left: readonly Item[], right: readonly Item[]): Item[] {\n\tconst excluded = new Set(unique(right));\n\treturn unique(left).filter((item) => !excluded.has(item));\n}\n\n/**\n * 返回两个数组共有的不同值。\n *\n * @param left - 决定结果顺序的数组。\n * @param right - 用于成员判断的数组。\n * @returns 保留左侧首次出现顺序的去重结果;两侧稀疏空位都被忽略。\n */\nexport function intersection<Item>(left: readonly Item[], right: readonly Item[]): Item[] {\n\tconst included = new Set(unique(right));\n\treturn unique(left).filter((item) => included.has(item));\n}\n\n/**\n * 返回只存在于其中一个数组的不同值。\n *\n * @param left - 决定左侧结果顺序的数组。\n * @param right - 决定右侧结果顺序的数组。\n * @returns 先按左侧、再按右侧首次出现顺序排列的对称差集;使用 SameValueZero 比较并忽略稀疏空位。\n */\nexport function symmetricDifference<Item>(left: readonly Item[], right: readonly Item[]): Item[] {\n\tconst leftValues = unique(left);\n\tconst rightValues = unique(right);\n\tconst leftSet = new Set(leftValues);\n\tconst rightSet = new Set(rightValues);\n\treturn [...leftValues.filter((item) => !rightSet.has(item)), ...rightValues.filter((item) => !leftSet.has(item))];\n}\n\n/**\n * 判断选择器产生的键是否重复。\n *\n * @param items - 不会被修改的输入数组。\n * @param selectKey - 返回比较键的函数;键使用 SameValueZero 语义比较。\n * @returns 存在至少一个重复键时返回 `true`。\n */\nexport function hasDuplicatesBy<Item, Key>(items: readonly Item[], selectKey: KeySelector<Item, Key>): boolean {\n\tconst seen = new Set<Key>();\n\tfor (let index = 0; index < items.length; index += 1) {\n\t\tconst item = items[index];\n\t\tif (item === undefined && !(index in items)) continue;\n\t\tconst key = selectKey(item as Item, index);\n\t\tif (seen.has(key)) return true;\n\t\tseen.add(key);\n\t}\n\treturn false;\n}\n\n/**\n * 判断所有项目是否具有相同的选择器结果。\n *\n * @remarks 空数组、只有稀疏空位的数组和单项数组按数学惯例返回 `true`;空位不会调用选择器。\n * @param items - 不会被修改的输入数组。\n * @param selectKey - 返回比较键的函数。\n * @returns 所有键都满足 SameValueZero 相等时返回 `true`。\n */\nexport function allEqualBy<Item, Key>(items: readonly Item[], selectKey: KeySelector<Item, Key>): boolean {\n\tlet first: Key | undefined;\n\tlet hasFirst = false;\n\tfor (let index = 0; index < items.length; index += 1) {\n\t\tconst item = items[index];\n\t\tif (item === undefined && !(index in items)) continue;\n\t\tconst current = selectKey(item as Item, index);\n\t\tif (!hasFirst) {\n\t\t\tfirst = current;\n\t\t\thasFirst = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (!(first === current || (Number.isNaN(first) && Number.isNaN(current)))) return false;\n\t}\n\treturn true;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/async/index.ts"],"sourcesContent":["/** 统一同步返回值与 PromiseLike 返回值的内部回调签名。 */\ntype AsyncCallback<Arguments extends unknown[], Result> = (...arguments_: Arguments) => Result | PromiseLike<Result>;\n\n/** 记录同一防抖批次中每个调用方独立的 Promise 结算函数。 */\ninterface PromiseWaiter<Result> {\n\t/**\n\t * 使用批次失败原因拒绝当前调用方。\n\t * @param reason - `cancel` 提供的原因或共享回调抛出的原始错误。\n\t */\n\treject: (reason?: unknown) => void;\n\t/**\n\t * 使用共享回调结果完成当前调用方,并采用传入 PromiseLike 的最终状态。\n\t * @param value - 当前防抖批次唯一一次回调执行产生的共享结果。\n\t */\n\tresolve: (value: Result | PromiseLike<Result>) => void;\n}\n\n// 浏览器和 Node.js 的计时器普遍以有符号 32 位整数保存延迟;更大的值可能被\n// 静默截断为约 1 ms,因此公共 API 在进入平台计时器前统一拒绝它。\nconst maximumTimerDelay = 2_147_483_647;\n\n/** 可接收取消信号的通用选项。 */\nexport interface AbortOptions {\n\t/** 已取消时立即失败;运行期间取消时停止等待并拒绝 Promise。 */\n\tsignal?: AbortSignal;\n}\n\n/** {@link withTimeout} 的行为选项。 */\nexport interface TimeoutOptions extends AbortOptions {\n\t/** 超时时使用的开发者消息。 */\n\tmessage?: string;\n}\n\n/** 每次重试操作接收的上下文。 */\nexport interface RetryContext {\n\t/** 从 1 开始的当前尝试次数。 */\n\tattempt: number;\n\t/** 调用方提供的取消信号。 */\n\tsignal?: AbortSignal;\n}\n\n/** {@link retry} 的策略选项。 */\nexport interface RetryOptions extends AbortOptions {\n\t/** 最大尝试次数,包含首次调用;默认 `3`。 */\n\tattempts?: number;\n\t/** 首次重试前的等待毫秒数,最大 2,147,483,647;默认 `200`。 */\n\tdelayMs?: number;\n\t/** 每次失败后的退避倍数,必须不小于 1;默认 `2`。 */\n\tfactor?: number;\n\t/** 单次等待上限,最大 2,147,483,647;默认 `30_000` 毫秒。 */\n\tmaxDelayMs?: number;\n\t/**\n\t * 决定当前失败后是否继续下一次尝试;默认重试所有尚未到达上限的错误。\n\t * @param error - 当前操作抛出或拒绝的原始值。\n\t * @param context - 当前尝试次数和调用方取消信号。\n\t * @returns `false` 时立即原样抛出当前错误;支持同步值或 PromiseLike。\n\t */\n\tshouldRetry?: (error: unknown, context: RetryContext) => boolean | PromiseLike<boolean>;\n}\n\n/** {@link mapConcurrent} 的执行选项。 */\nexport interface ConcurrentMapOptions {\n\t/** 已取消时停止调度新任务;已经开始的映射器需要自行响应同一信号。 */\n\tsignal?: AbortSignal;\n}\n\n/** Promise 感知的防抖函数。 */\nexport interface DebouncedFunction<Arguments extends unknown[], Result> {\n\t/**\n\t * 调度一次调用;同一等待窗口内的调用共享最后一组参数对应的结果。\n\t * @param arguments_ - 传给原始回调的参数;后续调用会覆盖尚未执行批次保存的参数。\n\t * @returns 当前批次的独立 Promise,最终与共享回调结果保持相同状态。\n\t */\n\t(...arguments_: Arguments): Promise<Result>;\n\t/**\n\t * 取消尚未执行的批次,并拒绝该批次的所有 Promise。\n\t * @param reason - 可选拒绝原因;省略时使用内部取消错误。\n\t */\n\tcancel: (reason?: unknown) => void;\n\t/**\n\t * 立即执行待处理批次,不创建第二次回调执行。\n\t * @returns 待处理批次的共享执行 Promise;没有批次时返回 `undefined`。\n\t */\n\tflush: () => Promise<Result> | undefined;\n\t/** @returns 当前存在尚未开始的批次时返回 `true`;正在执行但没有等待批次时返回 `false`。 */\n\tpending: () => boolean;\n}\n\n/** Promise 感知的前缘节流函数。 */\nexport interface ThrottledFunction<Arguments extends unknown[], Result> {\n\t/**\n\t * 在空闲时立即调用原始回调;执行期和冷却期内的调用共享首次调用的 Promise。\n\t * @param arguments_ - 仅窗口内首次调用的参数会传给原始回调。\n\t * @returns 当前执行窗口共享的 Promise。\n\t */\n\t(...arguments_: Arguments): Promise<Result>;\n\t/** 提前结束冷却期;已经开始的操作不会被取消,结束前仍禁止并发重入。 */\n\tcancel: () => void;\n\t/** @returns 原始回调正在执行或计时器仍处于冷却期时返回 `true`。 */\n\tpending: () => boolean;\n}\n\n/**\n * 创建符合 Web Platform 约定的取消错误。\n *\n * @param signal - 已进入取消状态的信号;其 `reason` 会保存在错误的 `cause` 中。\n * @returns 名称为 `AbortError` 的新错误实例。\n */\nconst createAbortError = (signal: AbortSignal): Error => {\n\tconst error = new Error(\"操作已取消。\", { cause: signal.reason });\n\terror.name = \"AbortError\";\n\treturn error;\n};\n\n/**\n * 在启动异步工作前同步拒绝已经取消的信号。\n *\n * @param signal - 可选取消信号;省略或尚未取消时不执行操作。\n * @throws `Error` 当信号已经取消,错误名称为 `AbortError`。\n */\nconst throwIfAborted = (signal: AbortSignal | undefined): void => {\n\tif (signal?.aborted) throw createAbortError(signal);\n};\n\n/**\n * 校验宿主计时器可以稳定表示的延迟。\n *\n * @param milliseconds - 待校验的毫秒数。\n * @param name - 用于错误消息的参数名称。\n * @returns 原始延迟值,便于调用方在校验后直接使用。\n * @throws `RangeError` 当值非有限、为负数或超过 32 位计时器上限。\n */\nconst assertDelay = (milliseconds: number, name = \"milliseconds\"): number => {\n\tif (!Number.isFinite(milliseconds) || milliseconds < 0 || milliseconds > maximumTimerDelay) {\n\t\tthrow new RangeError(`\\`${name}\\` 必须是 0 到 ${maximumTimerDelay} 之间的有限数。`);\n\t}\n\treturn milliseconds;\n};\n\n/**\n * 等待指定时间,并支持 `AbortSignal`。\n *\n * @param milliseconds - 0 至 2,147,483,647 的有限毫秒数。\n * @param options - 可选取消信号。\n * @returns 到期后完成的 Promise。\n * @throws 取消时抛出名称为 `AbortError` 的 `Error`;参数非法时抛出 `RangeError`。\n */\n// eslint-disable-next-line @typescript-eslint/promise-function-async -- 参数校验必须在调用时同步抛错,async 会把异常改成 rejected Promise。\nexport function sleep(milliseconds: number, options: AbortOptions = {}): Promise<void> {\n\tconst delay = assertDelay(milliseconds);\n\tconst signal = options.signal;\n\tthrowIfAborted(signal);\n\n\treturn new Promise<void>((resolve, reject) => {\n\t\tlet timer: ReturnType<typeof setTimeout>;\n\t\t/** 取消计时器并使用标准取消错误拒绝等待。 */\n\t\tfunction onAbort(): void {\n\t\t\tif (signal === undefined) return;\n\t\t\tclearTimeout(timer);\n\t\t\treject(createAbortError(signal));\n\t\t}\n\t\ttimer = setTimeout(() => {\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tresolve();\n\t\t}, delay);\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n\n/**\n * 为 Promise 增加等待上限。\n *\n * @remarks 超时或取消只停止等待,不能自动取消底层操作;需要真正取消时应同时把\n * 同一个 `AbortSignal` 传给底层 API。\n * @param promise - 需要等待的 Promise 或 PromiseLike。\n * @param timeoutMs - 0 至 2,147,483,647 的有限等待时间。\n * @param options - 取消信号与自定义消息。\n * @returns 底层 Promise 的结果。\n * @throws 超时抛出 `Error`,取消时抛出名称为 `AbortError` 的 `Error`;等待时间非法时抛出 `RangeError`。\n */\n// eslint-disable-next-line @typescript-eslint/promise-function-async -- 参数校验必须同步抛错,且返回值直接代表本次竞争结果。\nexport function withTimeout<Result>(promise: PromiseLike<Result>, timeoutMs: number, options: TimeoutOptions = {}): Promise<Result> {\n\tconst delay = assertDelay(timeoutMs, \"timeoutMs\");\n\tconst signal = options.signal;\n\tthrowIfAborted(signal);\n\n\treturn new Promise<Result>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tlet timer: ReturnType<typeof setTimeout>;\n\t\t/** 清理竞争结束后不再需要的计时器和监听器。 */\n\t\tfunction cleanup(): void {\n\t\t\tclearTimeout(timer);\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t}\n\t\t/**\n\t\t * 只允许 Promise、超时和取消三个竞争来源中的首个结果生效。\n\t\t *\n\t\t * @param action - 首个完成来源的结算动作。\n\t\t */\n\t\tfunction settle(action: () => void): void {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\taction();\n\t\t}\n\t\t/** 使用调用方取消原因结束当前等待。 */\n\t\tfunction onAbort(): void {\n\t\t\tif (signal === undefined) return;\n\t\t\tsettle(() => {\n\t\t\t\treject(createAbortError(signal));\n\t\t\t});\n\t\t}\n\t\ttimer = setTimeout(() => {\n\t\t\tsettle(() => {\n\t\t\t\treject(new Error(options.message ?? `操作超过 ${delay} 毫秒仍未完成。`));\n\t\t\t});\n\t\t}, delay);\n\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\tPromise.resolve(promise).then(\n\t\t\t(value) => {\n\t\t\t\tsettle(() => {\n\t\t\t\t\tresolve(value);\n\t\t\t\t});\n\t\t\t},\n\t\t\t(error: unknown) => {\n\t\t\t\tsettle(() => {\n\t\t\t\t\treject(error);\n\t\t\t\t});\n\t\t\t}\n\t\t);\n\t});\n}\n\n/**\n * 使用有上限的指数退避重试操作。\n *\n * @typeParam Result - 操作结果类型。\n * @param operation - 每次尝试都会调用的函数;`attempt` 从 1 开始。\n * @param options - 尝试次数、退避和取消策略。\n * @returns 首次成功结果。\n * @throws 最后一次操作错误、`shouldRetry` 错误或名称为 `AbortError` 的取消错误;策略参数非法时抛出 `RangeError`。\n */\nexport async function retry<Result>(\n\toperation: (context: RetryContext) => Result | PromiseLike<Result>,\n\toptions: RetryOptions = {}\n): Promise<Awaited<Result>> {\n\tconst attempts = options.attempts ?? 3;\n\tconst initialDelay = assertDelay(options.delayMs ?? 200, \"delayMs\");\n\tconst maximumDelay = assertDelay(options.maxDelayMs ?? 30_000, \"maxDelayMs\");\n\tconst factor = options.factor ?? 2;\n\tif (!Number.isSafeInteger(attempts) || attempts <= 0) throw new RangeError(\"`attempts` 必须是正安全整数。\");\n\tif (!Number.isFinite(factor) || factor < 1) throw new RangeError(\"`factor` 必须是大于或等于 1 的有限数。\");\n\n\tfor (let attempt = 1; attempt <= attempts; attempt += 1) {\n\t\tthrowIfAborted(options.signal);\n\t\tconst context: RetryContext = options.signal === undefined ? { attempt } : { attempt, signal: options.signal };\n\t\ttry {\n\t\t\treturn await operation(context);\n\t\t} catch (error) {\n\t\t\tif (attempt === attempts || (options.shouldRetry !== undefined && !(await options.shouldRetry(error, context)))) throw error;\n\t\t\t// `0 * Infinity` is `NaN`; a zero initial delay must remain zero even when\n\t\t\t// a very large factor overflows during a later attempt.\n\t\t\tconst delay = initialDelay === 0 ? 0 : Math.min(initialDelay * factor ** (attempt - 1), maximumDelay);\n\t\t\tawait sleep(delay, options.signal === undefined ? {} : { signal: options.signal });\n\t\t}\n\t}\n\n\tthrow new Error(\"重试结束但未获得结果。\");\n}\n\n/**\n * 以固定并发度映射数组,并保持结果顺序。\n *\n * @remarks 任一映射失败后不会再调度新项目,但已经开始的映射无法自动取消;映射器\n * 应使用传入的 `signal` 取消底层工作。\n * @param items - 不会被修改的输入数组。\n * @param concurrency - 同时运行的最大任务数,必须为正安全整数。\n * @param mapper - 接收项目、索引和取消信号的映射函数。\n * @param options - 可选取消信号。\n * @returns 与输入长度和顺序一致的结果数组;稀疏空位保持为空位且不会调用映射器。\n * @throws `RangeError` 当 `concurrency` 不是正安全整数;取消时抛出名称为 `AbortError` 的 `Error`。\n */\nexport async function mapConcurrent<Item, Result>(\n\titems: readonly Item[],\n\tconcurrency: number,\n\tmapper: (item: Item, index: number, signal: AbortSignal | undefined) => Result | PromiseLike<Result>,\n\toptions: ConcurrentMapOptions = {}\n): Promise<Awaited<Result>[]> {\n\tif (!Number.isSafeInteger(concurrency) || concurrency <= 0) {\n\t\tthrow new RangeError(\"`concurrency` 必须是正安全整数。\");\n\t}\n\tthrowIfAborted(options.signal);\n\n\tconst results = new Array<Awaited<Result>>(items.length);\n\tlet nextIndex = 0;\n\tlet failed = false;\n\t/**\n\t * 从共享游标持续领取映射任务。\n\t *\n\t * @remarks JavaScript 单线程执行保证“读取索引并递增”不会被另一个 Worker 插入,因此每个索引只会领取一次。\n\t * @returns 当前 Worker 没有剩余任务时完成。\n\t * @throws 原样传播取消错误或 Mapper 错误,并阻止其他 Worker 领取新任务。\n\t */\n\tconst worker = async (): Promise<void> => {\n\t\twhile (!failed) {\n\t\t\tthrowIfAborted(options.signal);\n\t\t\tconst index = nextIndex;\n\t\t\tif (index >= items.length) return;\n\t\t\tnextIndex += 1;\n\t\t\tif (!(index in items)) continue;\n\t\t\ttry {\n\t\t\t\tresults[index] = await mapper(items[index] as Item, index, options.signal);\n\t\t\t} catch (error) {\n\t\t\t\tfailed = true;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t};\n\n\tconst workerCount = Math.min(concurrency, items.length);\n\tawait Promise.all(Array.from({ length: workerCount }, worker));\n\treturn results;\n}\n\n/**\n * 创建 Promise 感知的防抖函数。\n *\n * @remarks 同一窗口内的所有调用都会等待最后一组参数对应的执行结果;回调错误会原样\n * 拒绝该批次的全部调用,不会留下永久 pending 的 Promise。\n * @param callback - 同步或异步回调。\n * @param delayMs - 0 至 2,147,483,647 的有限等待时间,默认 300 毫秒。\n * @returns 具有取消、立即执行和状态方法的防抖函数。\n * @throws `RangeError` 当延迟不在平台计时器支持范围内。\n */\nexport function debounce<Arguments extends unknown[], Result>(\n\tcallback: AsyncCallback<Arguments, Result>,\n\tdelayMs = 300\n): DebouncedFunction<Arguments, Awaited<Result>> {\n\tconst delay = assertDelay(delayMs, \"delayMs\");\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tlet latestArguments: Arguments | undefined;\n\tlet waiters: PromiseWaiter<Awaited<Result>>[] = [];\n\n\t/**\n\t * 执行并结算当前防抖批次。\n\t *\n\t * @returns 最后一组参数对应的回调结果。\n\t * @throws 没有待处理批次时抛出 `Error`;回调错误会原样传播给批次中的全部调用方。\n\t */\n\tconst execute = async (): Promise<Awaited<Result>> => {\n\t\tconst arguments_ = latestArguments;\n\t\tif (arguments_ === undefined) {\n\t\t\tthrow new Error(\"当前没有待处理的防抖调用。\");\n\t\t}\n\t\tlatestArguments = undefined;\n\t\ttimer = undefined;\n\t\tconst currentWaiters = waiters;\n\t\twaiters = [];\n\t\ttry {\n\t\t\tconst result = await callback(...arguments_);\n\t\t\tcurrentWaiters.forEach((waiter) => {\n\t\t\t\twaiter.resolve(result);\n\t\t\t});\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\tcurrentWaiters.forEach((waiter) => {\n\t\t\t\twaiter.reject(error);\n\t\t\t});\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\t/**\n\t * 更新批次参数并返回当前调用方专属的等待 Promise。\n\t *\n\t * @param arguments_ - 本次调用参数;同批次中只有最后一组参数会执行。\n\t * @returns 与当前批次共享结果、但可独立结算的 Promise。\n\t */\n\t// eslint-disable-next-line @typescript-eslint/promise-function-async -- 每次调用返回独立的可取消等待 Promise,不增加 async 包装层。\n\tconst debounced = (...arguments_: Arguments): Promise<Awaited<Result>> => {\n\t\tlatestArguments = arguments_;\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t\ttimer = setTimeout(() => {\n\t\t\texecute().catch(() => undefined);\n\t\t}, delay);\n\t\treturn new Promise<Awaited<Result>>((resolve, reject) => {\n\t\t\twaiters.push({ reject, resolve });\n\t\t});\n\t};\n\n\tdebounced.cancel = (reason?: unknown): void => {\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t\ttimer = undefined;\n\t\tlatestArguments = undefined;\n\t\tconst error = reason ?? new Error(\"防抖调用已取消。\");\n\t\twaiters.forEach((waiter) => {\n\t\t\twaiter.reject(error);\n\t\t});\n\t\twaiters = [];\n\t};\n\tdebounced.flush = (): Promise<Awaited<Result>> | undefined => {\n\t\tif (timer === undefined) return undefined;\n\t\tclearTimeout(timer);\n\t\treturn execute();\n\t};\n\tdebounced.pending = (): boolean => timer !== undefined;\n\treturn debounced;\n}\n\n/**\n * 创建 Promise 感知的前缘节流函数。\n *\n * @remarks 窗口内的调用共享首次调用结果。若回调执行时间超过窗口,后续调用仍会等待\n * 当前回调,避免异步操作重入;该函数不安排尾缘调用。\n * @param callback - 同步或异步回调。\n * @param delayMs - 0 至 2,147,483,647 的有限冷却时间,默认 300 毫秒。\n * @returns 具有取消和状态方法的前缘节流函数。\n * @throws `RangeError` 当延迟不在平台计时器支持范围内。\n */\nexport function throttle<Arguments extends unknown[], Result>(\n\tcallback: AsyncCallback<Arguments, Result>,\n\tdelayMs = 300\n): ThrottledFunction<Arguments, Awaited<Result>> {\n\tconst delay = assertDelay(delayMs, \"delayMs\");\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tlet current: Promise<Awaited<Result>> | undefined;\n\tlet cooling = false;\n\tlet settled = false;\n\n\t/**\n\t * 尝试释放当前节流窗口。\n\t *\n\t * @remarks 只有回调和冷却计时器都结束后才清空共享 Promise,避免长回调发生重入。\n\t */\n\tconst release = (): void => {\n\t\tif (!cooling && settled) current = undefined;\n\t};\n\t/**\n\t * 执行前缘调用或复用当前窗口的共享 Promise。\n\t *\n\t * @param arguments_ - 仅新窗口首个调用会使用的参数。\n\t * @returns 当前窗口首次调用的 Promise。\n\t */\n\t// eslint-disable-next-line @typescript-eslint/promise-function-async -- 同一节流窗口必须返回完全相同的 Promise 引用。\n\tconst throttled = (...arguments_: Arguments): Promise<Awaited<Result>> => {\n\t\tif (current !== undefined) return current;\n\t\tcooling = true;\n\t\tsettled = false;\n\t\tlet invocation: Promise<Awaited<Result>>;\n\t\ttry {\n\t\t\tinvocation = Promise.resolve(callback(...arguments_));\n\t\t} catch (error) {\n\t\t\tinvocation = Promise.reject(error);\n\t\t}\n\t\tcurrent = invocation;\n\t\tinvocation.then(\n\t\t\t() => {\n\t\t\t\tsettled = true;\n\t\t\t\trelease();\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tsettled = true;\n\t\t\t\trelease();\n\t\t\t}\n\t\t);\n\t\ttimer = setTimeout(() => {\n\t\t\ttimer = undefined;\n\t\t\tcooling = false;\n\t\t\trelease();\n\t\t}, delay);\n\t\treturn invocation;\n\t};\n\n\tthrottled.cancel = (): void => {\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t\ttimer = undefined;\n\t\tcooling = false;\n\t\trelease();\n\t};\n\tthrottled.pending = (): boolean => current !== undefined;\n\treturn throttled;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/async/index.ts"],"sourcesContent":["/** 统一同步返回值与 PromiseLike 返回值的内部回调签名。 */\ntype AsyncCallback<Arguments extends unknown[], Result> = (...arguments_: Arguments) => Result | PromiseLike<Result>;\n\n/** 记录同一防抖批次中每个调用方独立的 Promise 结算函数。 */\ninterface PromiseWaiter<Result> {\n\t/**\n\t * 使用批次失败原因拒绝当前调用方。\n\t * @param reason - `cancel` 提供的原因或共享回调抛出的原始错误。\n\t */\n\treject: (reason?: unknown) => void;\n\t/**\n\t * 使用共享回调结果完成当前调用方,并采用传入 PromiseLike 的最终状态。\n\t * @param value - 当前防抖批次唯一一次回调执行产生的共享结果。\n\t */\n\tresolve: (value: Result | PromiseLike<Result>) => void;\n}\n\n// 浏览器和 Node.js 的计时器普遍以有符号 32 位整数保存延迟;更大的值可能被\n// 静默截断为约 1 ms,因此公共 API 在进入平台计时器前统一拒绝它。\nconst maximumTimerDelay = 2_147_483_647;\n\n/** 可接收取消信号的通用选项。 */\nexport interface AbortOptions {\n\t/** 已取消时立即失败;运行期间取消时停止等待并拒绝 Promise。 */\n\tsignal?: AbortSignal;\n}\n\n/** {@link withTimeout} 的行为选项。 */\nexport interface TimeoutOptions extends AbortOptions {\n\t/** 超时时使用的开发者消息。 */\n\tmessage?: string;\n}\n\n/** 每次重试操作接收的上下文。 */\nexport interface RetryContext {\n\t/** 从 1 开始的当前尝试次数。 */\n\tattempt: number;\n\t/** 调用方提供的取消信号。 */\n\tsignal?: AbortSignal;\n}\n\n/** {@link retry} 的策略选项。 */\nexport interface RetryOptions extends AbortOptions {\n\t/** 最大尝试次数,包含首次调用;默认 `3`。 */\n\tattempts?: number;\n\t/** 首次重试前的等待毫秒数,最大 2,147,483,647;默认 `200`。 */\n\tdelayMs?: number;\n\t/** 每次失败后的退避倍数,必须不小于 1;默认 `2`。 */\n\tfactor?: number;\n\t/** 单次等待上限,最大 2,147,483,647;默认 `30_000` 毫秒。 */\n\tmaxDelayMs?: number;\n\t/**\n\t * 决定当前失败后是否继续下一次尝试;默认重试所有尚未到达上限的错误。\n\t * @param error - 当前操作抛出或拒绝的原始值。\n\t * @param context - 当前尝试次数和调用方取消信号。\n\t * @returns `false` 时立即原样抛出当前错误;支持同步值或 PromiseLike。\n\t */\n\tshouldRetry?: (error: unknown, context: RetryContext) => boolean | PromiseLike<boolean>;\n}\n\n/** {@link mapConcurrent} 的执行选项。 */\nexport interface ConcurrentMapOptions {\n\t/** 已取消时停止调度新任务;已经开始的映射器需要自行响应同一信号。 */\n\tsignal?: AbortSignal;\n}\n\n/** Promise 感知的防抖函数。 */\nexport interface DebouncedFunction<Arguments extends unknown[], Result> {\n\t/**\n\t * 调度一次调用;同一等待窗口内的调用共享最后一组参数对应的结果。\n\t * @param arguments_ - 传给原始回调的参数;后续调用会覆盖尚未执行批次保存的参数。\n\t * @returns 当前批次的独立 Promise,最终与共享回调结果保持相同状态。\n\t */\n\t(...arguments_: Arguments): Promise<Result>;\n\t/**\n\t * 取消尚未执行的批次,并拒绝该批次的所有 Promise。\n\t * @param reason - 可选拒绝原因;省略时使用内部取消错误。\n\t */\n\tcancel: (reason?: unknown) => void;\n\t/**\n\t * 立即执行待处理批次,不创建第二次回调执行。\n\t * @returns 待处理批次的共享执行 Promise;没有批次时返回 `undefined`。\n\t */\n\tflush: () => Promise<Result> | undefined;\n\t/** @returns 当前存在尚未开始的批次时返回 `true`;正在执行但没有等待批次时返回 `false`。 */\n\tpending: () => boolean;\n}\n\n/** Promise 感知的前缘节流函数。 */\nexport interface ThrottledFunction<Arguments extends unknown[], Result> {\n\t/**\n\t * 在空闲时立即调用原始回调;执行期和冷却期内的调用共享首次调用的 Promise。\n\t * @param arguments_ - 仅窗口内首次调用的参数会传给原始回调。\n\t * @returns 当前执行窗口共享的 Promise。\n\t */\n\t(...arguments_: Arguments): Promise<Result>;\n\t/** 提前结束冷却期;已经开始的操作不会被取消,结束前仍禁止并发重入。 */\n\tcancel: () => void;\n\t/** @returns 原始回调正在执行或计时器仍处于冷却期时返回 `true`。 */\n\tpending: () => boolean;\n}\n\n/**\n * 创建符合 Web Platform 约定的取消错误。\n *\n * @param signal - 已进入取消状态的信号;其 `reason` 会保存在错误的 `cause` 中。\n * @returns 名称为 `AbortError` 的新错误实例。\n */\nconst createAbortError = (signal: AbortSignal): Error => {\n\tconst error = new Error(\"操作已取消。\", { cause: signal.reason });\n\terror.name = \"AbortError\";\n\treturn error;\n};\n\n/**\n * 在启动异步工作前同步拒绝已经取消的信号。\n *\n * @param signal - 可选取消信号;省略或尚未取消时不执行操作。\n * @throws `Error` 当信号已经取消,错误名称为 `AbortError`。\n */\nconst throwIfAborted = (signal: AbortSignal | undefined): void => {\n\tif (signal?.aborted) throw createAbortError(signal);\n};\n\n/**\n * 校验宿主计时器可以稳定表示的延迟。\n *\n * @param milliseconds - 待校验的毫秒数。\n * @param name - 用于错误消息的参数名称。\n * @returns 原始延迟值,便于调用方在校验后直接使用。\n * @throws `RangeError` 当值非有限、为负数或超过 32 位计时器上限。\n */\nconst assertDelay = (milliseconds: number, name = \"milliseconds\"): number => {\n\tif (!Number.isFinite(milliseconds) || milliseconds < 0 || milliseconds > maximumTimerDelay) {\n\t\tthrow new RangeError(`\\`${name}\\` 必须是 0 到 ${maximumTimerDelay} 之间的有限数。`);\n\t}\n\treturn milliseconds;\n};\n\n/**\n * 等待指定时间,并支持 `AbortSignal`。\n *\n * @param milliseconds - 0 至 2,147,483,647 的有限毫秒数。\n * @param options - 可选取消信号。\n * @returns 到期后完成的 Promise。\n * @throws 取消时抛出名称为 `AbortError` 的 `Error`;参数非法时抛出 `RangeError`。\n */\nexport function sleep(milliseconds: number, options: AbortOptions = {}): Promise<void> {\n\tconst delay = assertDelay(milliseconds);\n\tconst signal = options.signal;\n\tthrowIfAborted(signal);\n\n\treturn new Promise<void>((resolve, reject) => {\n\t\tlet timer: ReturnType<typeof setTimeout>;\n\t\t/** 取消计时器并使用标准取消错误拒绝等待。 */\n\t\tfunction onAbort() {\n\t\t\tif (signal === undefined) return;\n\t\t\tclearTimeout(timer);\n\t\t\treject(createAbortError(signal));\n\t\t}\n\t\ttimer = setTimeout(() => {\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tresolve();\n\t\t}, delay);\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n\n/**\n * 为 Promise 增加等待上限。\n *\n * @remarks 超时或取消只停止等待,不能自动取消底层操作;需要真正取消时应同时把\n * 同一个 `AbortSignal` 传给底层 API。\n * @param promise - 需要等待的 Promise 或 PromiseLike。\n * @param timeoutMs - 0 至 2,147,483,647 的有限等待时间。\n * @param options - 取消信号与自定义消息。\n * @returns 底层 Promise 的结果。\n * @throws 超时抛出 `Error`,取消时抛出名称为 `AbortError` 的 `Error`;等待时间非法时抛出 `RangeError`。\n */\nexport function withTimeout<Result>(promise: PromiseLike<Result>, timeoutMs: number, options: TimeoutOptions = {}): Promise<Result> {\n\tconst delay = assertDelay(timeoutMs, \"timeoutMs\");\n\tconst signal = options.signal;\n\tthrowIfAborted(signal);\n\n\treturn new Promise<Result>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tlet timer: ReturnType<typeof setTimeout>;\n\t\t/** 清理竞争结束后不再需要的计时器和监听器。 */\n\t\tfunction cleanup() {\n\t\t\tclearTimeout(timer);\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t}\n\t\t/**\n\t\t * 只允许 Promise、超时和取消三个竞争来源中的首个结果生效。\n\t\t *\n\t\t * @param action - 首个完成来源的结算动作。\n\t\t */\n\t\tfunction settle(action: () => void) {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\taction();\n\t\t}\n\t\t/** 使用调用方取消原因结束当前等待。 */\n\t\tfunction onAbort() {\n\t\t\tif (signal === undefined) return;\n\t\t\tsettle(() => {\n\t\t\t\treject(createAbortError(signal));\n\t\t\t});\n\t\t}\n\t\ttimer = setTimeout(() => {\n\t\t\tsettle(() => {\n\t\t\t\treject(new Error(options.message ?? `操作超过 ${delay} 毫秒仍未完成。`));\n\t\t\t});\n\t\t}, delay);\n\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\tPromise.resolve(promise).then(\n\t\t\t(value) => {\n\t\t\t\tsettle(() => {\n\t\t\t\t\tresolve(value);\n\t\t\t\t});\n\t\t\t},\n\t\t\t(error: unknown) => {\n\t\t\t\tsettle(() => {\n\t\t\t\t\treject(error);\n\t\t\t\t});\n\t\t\t}\n\t\t);\n\t});\n}\n\n/**\n * 使用有上限的指数退避重试操作。\n *\n * @typeParam Result - 操作结果类型。\n * @param operation - 每次尝试都会调用的函数;`attempt` 从 1 开始。\n * @param options - 尝试次数、退避和取消策略。\n * @returns 首次成功结果。\n * @throws 最后一次操作错误、`shouldRetry` 错误或名称为 `AbortError` 的取消错误;策略参数非法时抛出 `RangeError`。\n */\nexport async function retry<Result>(\n\toperation: (context: RetryContext) => Result | PromiseLike<Result>,\n\toptions: RetryOptions = {}\n): Promise<Awaited<Result>> {\n\tconst attempts = options.attempts ?? 3;\n\tconst initialDelay = assertDelay(options.delayMs ?? 200, \"delayMs\");\n\tconst maximumDelay = assertDelay(options.maxDelayMs ?? 30_000, \"maxDelayMs\");\n\tconst factor = options.factor ?? 2;\n\tif (!Number.isSafeInteger(attempts) || attempts <= 0) throw new RangeError(\"`attempts` 必须是正安全整数。\");\n\tif (!Number.isFinite(factor) || factor < 1) throw new RangeError(\"`factor` 必须是大于或等于 1 的有限数。\");\n\n\tfor (let attempt = 1; attempt <= attempts; attempt += 1) {\n\t\tthrowIfAborted(options.signal);\n\t\tconst context: RetryContext = options.signal === undefined ? { attempt } : { attempt, signal: options.signal };\n\t\ttry {\n\t\t\treturn await operation(context);\n\t\t} catch (error) {\n\t\t\tif (attempt === attempts || (options.shouldRetry !== undefined && !(await options.shouldRetry(error, context)))) throw error;\n\t\t\t// `0 * Infinity` is `NaN`; a zero initial delay must remain zero even when\n\t\t\t// a very large factor overflows during a later attempt.\n\t\t\tconst delay = initialDelay === 0 ? 0 : Math.min(initialDelay * factor ** (attempt - 1), maximumDelay);\n\t\t\tawait sleep(delay, options.signal === undefined ? {} : { signal: options.signal });\n\t\t}\n\t}\n\n\tthrow new Error(\"重试结束但未获得结果。\");\n}\n\n/**\n * 以固定并发度映射数组,并保持结果顺序。\n *\n * @remarks 任一映射失败后不会再调度新项目,但已经开始的映射无法自动取消;映射器\n * 应使用传入的 `signal` 取消底层工作。\n * @param items - 不会被修改的输入数组。\n * @param concurrency - 同时运行的最大任务数,必须为正安全整数。\n * @param mapper - 接收项目、索引和取消信号的映射函数。\n * @param options - 可选取消信号。\n * @returns 与输入长度和顺序一致的结果数组;稀疏空位保持为空位且不会调用映射器。\n * @throws `RangeError` 当 `concurrency` 不是正安全整数;取消时抛出名称为 `AbortError` 的 `Error`。\n */\nexport async function mapConcurrent<Item, Result>(\n\titems: readonly Item[],\n\tconcurrency: number,\n\tmapper: (item: Item, index: number, signal: AbortSignal | undefined) => Result | PromiseLike<Result>,\n\toptions: ConcurrentMapOptions = {}\n): Promise<Awaited<Result>[]> {\n\tif (!Number.isSafeInteger(concurrency) || concurrency <= 0) {\n\t\tthrow new RangeError(\"`concurrency` 必须是正安全整数。\");\n\t}\n\tthrowIfAborted(options.signal);\n\n\tconst results = new Array<Awaited<Result>>(items.length);\n\tlet nextIndex = 0;\n\tlet failed = false;\n\t/**\n\t * 从共享游标持续领取映射任务。\n\t *\n\t * @remarks JavaScript 单线程执行保证“读取索引并递增”不会被另一个 Worker 插入,因此每个索引只会领取一次。\n\t * @returns 当前 Worker 没有剩余任务时完成。\n\t * @throws 原样传播取消错误或 Mapper 错误,并阻止其他 Worker 领取新任务。\n\t */\n\tconst worker = async () => {\n\t\twhile (!failed) {\n\t\t\tthrowIfAborted(options.signal);\n\t\t\tconst index = nextIndex;\n\t\t\tif (index >= items.length) return;\n\t\t\tnextIndex += 1;\n\t\t\tif (!(index in items)) continue;\n\t\t\ttry {\n\t\t\t\tresults[index] = await mapper(items[index] as Item, index, options.signal);\n\t\t\t} catch (error) {\n\t\t\t\tfailed = true;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t};\n\n\tconst workerCount = Math.min(concurrency, items.length);\n\tawait Promise.all(Array.from({ length: workerCount }, worker));\n\treturn results;\n}\n\n/**\n * 创建 Promise 感知的防抖函数。\n *\n * @remarks 同一窗口内的所有调用都会等待最后一组参数对应的执行结果;回调错误会原样\n * 拒绝该批次的全部调用,不会留下永久 pending 的 Promise。\n * @param callback - 同步或异步回调。\n * @param delayMs - 0 至 2,147,483,647 的有限等待时间,默认 300 毫秒。\n * @returns 具有取消、立即执行和状态方法的防抖函数。\n * @throws `RangeError` 当延迟不在平台计时器支持范围内。\n */\nexport function debounce<Arguments extends unknown[], Result>(\n\tcallback: AsyncCallback<Arguments, Result>,\n\tdelayMs = 300\n): DebouncedFunction<Arguments, Awaited<Result>> {\n\tconst delay = assertDelay(delayMs, \"delayMs\");\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tlet latestArguments: Arguments | undefined;\n\tlet waiters: PromiseWaiter<Awaited<Result>>[] = [];\n\n\t/**\n\t * 执行并结算当前防抖批次。\n\t *\n\t * @returns 最后一组参数对应的回调结果。\n\t * @throws 没有待处理批次时抛出 `Error`;回调错误会原样传播给批次中的全部调用方。\n\t */\n\tconst execute = async (): Promise<Awaited<Result>> => {\n\t\tconst arguments_ = latestArguments;\n\t\tif (arguments_ === undefined) {\n\t\t\tthrow new Error(\"当前没有待处理的防抖调用。\");\n\t\t}\n\t\tlatestArguments = undefined;\n\t\ttimer = undefined;\n\t\tconst currentWaiters = waiters;\n\t\twaiters = [];\n\t\ttry {\n\t\t\tconst result = await callback(...arguments_);\n\t\t\tcurrentWaiters.forEach((waiter) => {\n\t\t\t\twaiter.resolve(result);\n\t\t\t});\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\tcurrentWaiters.forEach((waiter) => {\n\t\t\t\twaiter.reject(error);\n\t\t\t});\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\t/**\n\t * 更新批次参数并返回当前调用方专属的等待 Promise。\n\t *\n\t * @param arguments_ - 本次调用参数;同批次中只有最后一组参数会执行。\n\t * @returns 与当前批次共享结果、但可独立结算的 Promise。\n\t */\n\tconst debounced = (...arguments_: Arguments) => {\n\t\tlatestArguments = arguments_;\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t\ttimer = setTimeout(() => {\n\t\t\texecute().catch(() => undefined);\n\t\t}, delay);\n\t\treturn new Promise<Awaited<Result>>((resolve, reject) => {\n\t\t\twaiters.push({ reject, resolve });\n\t\t});\n\t};\n\n\tdebounced.cancel = (reason?: unknown) => {\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t\ttimer = undefined;\n\t\tlatestArguments = undefined;\n\t\tconst error = reason ?? new Error(\"防抖调用已取消。\");\n\t\twaiters.forEach((waiter) => {\n\t\t\twaiter.reject(error);\n\t\t});\n\t\twaiters = [];\n\t};\n\tdebounced.flush = () => {\n\t\tif (timer === undefined) return undefined;\n\t\tclearTimeout(timer);\n\t\treturn execute();\n\t};\n\tdebounced.pending = () => timer !== undefined;\n\treturn debounced;\n}\n\n/**\n * 创建 Promise 感知的前缘节流函数。\n *\n * @remarks 窗口内的调用共享首次调用结果。若回调执行时间超过窗口,后续调用仍会等待\n * 当前回调,避免异步操作重入;该函数不安排尾缘调用。\n * @param callback - 同步或异步回调。\n * @param delayMs - 0 至 2,147,483,647 的有限冷却时间,默认 300 毫秒。\n * @returns 具有取消和状态方法的前缘节流函数。\n * @throws `RangeError` 当延迟不在平台计时器支持范围内。\n */\nexport function throttle<Arguments extends unknown[], Result>(\n\tcallback: AsyncCallback<Arguments, Result>,\n\tdelayMs = 300\n): ThrottledFunction<Arguments, Awaited<Result>> {\n\tconst delay = assertDelay(delayMs, \"delayMs\");\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tlet current: Promise<Awaited<Result>> | undefined;\n\tlet cooling = false;\n\tlet settled = false;\n\n\t/**\n\t * 尝试释放当前节流窗口。\n\t *\n\t * @remarks 只有回调和冷却计时器都结束后才清空共享 Promise,避免长回调发生重入。\n\t */\n\tconst release = () => {\n\t\tif (!cooling && settled) current = undefined;\n\t};\n\t/**\n\t * 执行前缘调用或复用当前窗口的共享 Promise。\n\t *\n\t * @param arguments_ - 仅新窗口首个调用会使用的参数。\n\t * @returns 当前窗口首次调用的 Promise。\n\t */\n\tconst throttled = (...arguments_: Arguments) => {\n\t\tif (current !== undefined) return current;\n\t\tcooling = true;\n\t\tsettled = false;\n\t\tlet invocation: Promise<Awaited<Result>>;\n\t\ttry {\n\t\t\tinvocation = Promise.resolve(callback(...arguments_));\n\t\t} catch (error) {\n\t\t\tinvocation = Promise.reject(error);\n\t\t}\n\t\tcurrent = invocation;\n\t\tinvocation.then(\n\t\t\t() => {\n\t\t\t\tsettled = true;\n\t\t\t\trelease();\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tsettled = true;\n\t\t\t\trelease();\n\t\t\t}\n\t\t);\n\t\ttimer = setTimeout(() => {\n\t\t\ttimer = undefined;\n\t\t\tcooling = false;\n\t\t\trelease();\n\t\t}, delay);\n\t\treturn invocation;\n\t};\n\n\tthrottled.cancel = () => {\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t\ttimer = undefined;\n\t\tcooling = false;\n\t\trelease();\n\t};\n\tthrottled.pending = () => current !== undefined;\n\treturn throttled;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/color/index.ts"],"sourcesContent":["/** 0 至 255 范围的 RGB 颜色。 */\nexport interface RgbColor {\n\t/** 蓝色通道;必须是闭区间 `[0, 255]` 内的有限数,格式化时舍入到整数。 */\n\tblue: number;\n\t/** 绿色通道;必须是闭区间 `[0, 255]` 内的有限数,格式化时舍入到整数。 */\n\tgreen: number;\n\t/** 红色通道;必须是闭区间 `[0, 255]` 内的有限数,格式化时舍入到整数。 */\n\tred: number;\n}\n\n/** 带 0 至 1 Alpha 通道的 RGB 颜色。 */\nexport interface RgbaColor extends RgbColor {\n\t/** 不透明度;必须是闭区间 `[0, 1]` 内的有限数,`0` 完全透明,`1` 完全不透明。 */\n\talpha: number;\n}\n\n/**\n * 校验 RGB 颜色通道。\n *\n * @param value - 待校验通道值。\n * @param channel - 用于错误消息的通道名称。\n * @throws `RangeError` 当值非有限或超出 0 至 255。\n */\nconst assertRgbChannel = (value: number, channel: string): void => {\n\tif (!Number.isFinite(value) || value < 0 || value > 255) {\n\t\tthrow new RangeError(`\\`${channel}\\` 必须是 0 到 255 之间的有限数。`);\n\t}\n};\n\n/**\n * 校验透明度通道。\n *\n * @param value - 待校验 Alpha 值。\n * @throws `RangeError` 当值非有限或超出闭区间 `[0, 1]`。\n */\nconst assertAlpha = (value: number): void => {\n\tif (!Number.isFinite(value) || value < 0 || value > 1) {\n\t\tthrow new RangeError(\"`alpha` 必须是 0 到 1 之间的有限数。\");\n\t}\n};\n\n/**\n * 规范化十六进制颜色文本。\n *\n * @param value - 可带 `#` 的 3、4、6 或 8 位颜色文本。\n * @returns 不带 `#` 的 6 或 8 位文本。\n * @throws `TypeError` 当长度或字符不符合十六进制颜色格式。\n */\nconst normalizeHexColor = (value: string): string => {\n\tconst normalized = value.startsWith(\"#\") ? value.slice(1) : value;\n\tif (![3, 4, 6, 8].includes(normalized.length) || !/^[\\dA-F]+$/iu.test(normalized)) {\n\t\tthrow new TypeError(\"十六进制颜色必须包含 3、4、6 或 8 个十六进制字符。\");\n\t}\n\treturn normalized.length <= 4 ? Array.from(normalized, (character) => `${character}${character}`).join(\"\") : normalized;\n};\n\n/**\n * 格式化单个颜色通道。\n *\n * @param value - 已校验的 0 至 255 通道值。\n * @returns 舍入后的两位小写十六进制文本。\n */\nconst formatHexChannel = (value: number): string => Math.round(value).toString(16).padStart(2, \"0\");\n\n/**\n * 解析可带可不带 `#` 的 `rgb`、`rgba`、`rrggbb` 或 `rrggbbaa`。\n *\n * @param value - 十六进制颜色文本。\n * @returns 标准化的 RGBA 对象;省略 Alpha 时为 1。\n * @throws 输入非法时抛出 `TypeError`。\n */\nexport function parseHexColor(value: string): RgbaColor {\n\tconst normalized = normalizeHexColor(value);\n\treturn {\n\t\tred: Number.parseInt(normalized.slice(0, 2), 16),\n\t\tgreen: Number.parseInt(normalized.slice(2, 4), 16),\n\t\tblue: Number.parseInt(normalized.slice(4, 6), 16),\n\t\talpha: normalized.length === 8 ? Number.parseInt(normalized.slice(6, 8), 16) / 255 : 1,\n\t};\n}\n\n/**\n * 把 RGB 或 RGBA 对象格式化为小写十六进制颜色。\n *\n * @param color - 颜色通道;RGB 会四舍五入到最近整数。\n * @param includeAlpha - 是否输出 Alpha;默认只在传入 Alpha 且小于 1 时输出。\n * @returns 小写 `#rrggbb` 或 `#rrggbbaa` 文本。\n * @throws 通道或 Alpha 非法时抛出 `RangeError`。\n */\nexport function formatHexColor(color: RgbColor | RgbaColor, includeAlpha: boolean = \"alpha\" in color && color.alpha < 1): string {\n\tassertRgbChannel(color.red, \"red\");\n\tassertRgbChannel(color.green, \"green\");\n\tassertRgbChannel(color.blue, \"blue\");\n\tconst alpha = \"alpha\" in color ? color.alpha : 1;\n\tassertAlpha(alpha);\n\tconst rgb = `${formatHexChannel(color.red)}${formatHexChannel(color.green)}${formatHexChannel(color.blue)}`;\n\treturn `#${rgb}${includeAlpha ? formatHexChannel(alpha * 255) : \"\"}`;\n}\n\n/**\n * 线性混合两种十六进制颜色,包括 Alpha 通道。\n *\n * @param first - `amount = 0` 时的颜色。\n * @param second - `amount = 1` 时的颜色。\n * @param amount - 0 至 1 的混合比例。\n * @returns 小写十六进制颜色;任一输入含透明度时保留 Alpha。\n * @throws 颜色非法时抛出 `TypeError`;比例非法时抛出 `RangeError`。\n */\nexport function mixHexColors(first: string, second: string, amount: number): string {\n\tif (!Number.isFinite(amount) || amount < 0 || amount > 1) {\n\t\tthrow new RangeError(\"`amount` 必须是 0 到 1 之间的有限数。\");\n\t}\n\tconst left = parseHexColor(first);\n\tconst right = parseHexColor(second);\n\t/**\n\t * 在单个 RGBA 通道上执行与外层相同权重的线性混合。\n\t *\n\t * @param start - 第一个颜色的通道值。\n\t * @param end - 第二个颜色的通道值。\n\t * @returns 按外层 `amount` 线性混合后的通道值。\n\t */\n\tconst mix = (start: number, end: number): number => start + (end - start) * amount;\n\treturn formatHexColor(\n\t\t{\n\t\t\tred: mix(left.red, right.red),\n\t\t\tgreen: mix(left.green, right.green),\n\t\t\tblue: mix(left.blue, right.blue),\n\t\t\talpha: mix(left.alpha, right.alpha),\n\t\t},\n\t\tleft.alpha < 1 || right.alpha < 1\n\t);\n}\n\n/**\n * 按比例向黑色混合。\n *\n * @param color - 合法十六进制颜色。\n * @param amount - 0 至 1 的混合比例。\n * @returns 混入黑色后的十六进制颜色;参数与异常语义见 {@link mixHexColors}。\n */\nexport function mixHexColorWithBlack(color: string, amount: number): string {\n\treturn mixHexColors(color, \"#000000\", amount);\n}\n\n/**\n * 按比例向白色混合。\n *\n * @param color - 合法十六进制颜色。\n * @param amount - 0 至 1 的混合比例。\n * @returns 混入白色后的十六进制颜色;参数与异常语义见 {@link mixHexColors}。\n */\nexport function mixHexColorWithWhite(color: string, amount: number): string {\n\treturn mixHexColors(color, \"#ffffff\", amount);\n}\n\n/**\n * 按 WCAG sRGB 转换曲线线性化颜色通道。\n *\n * @param channel - 已校验的 0 至 255 通道值。\n * @returns 0 至 1 的线性光值。\n */\nconst linearizeSrgbChannel = (channel: number): number => {\n\tconst value = channel / 255;\n\treturn value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;\n};\n\n/**\n * 计算 WCAG sRGB 相对亮度。\n *\n * @remarks Alpha 通道不会参与计算;半透明颜色应先与实际背景混合。\n * @param color - 合法十六进制颜色。\n * @returns 0 至 1 的相对亮度。\n * @throws 输入非法时抛出 `TypeError`。\n */\nexport function relativeLuminance(color: string): number {\n\tconst { red, green, blue } = parseHexColor(color);\n\treturn 0.2126 * linearizeSrgbChannel(red) + 0.7152 * linearizeSrgbChannel(green) + 0.0722 * linearizeSrgbChannel(blue);\n}\n\n/**\n * 计算两种不透明颜色的 WCAG 对比度,范围 1 至 21。\n *\n * @remarks 返回比值本身,不代表特定字号或 WCAG 等级必然通过。\n * @param first - 第一种十六进制颜色。\n * @param second - 第二种十六进制颜色。\n * @returns 较亮颜色与较暗颜色的对比度。\n * @throws 输入非法时抛出 `TypeError`。\n */\nexport function contrastRatio(first: string, second: string): number {\n\tconst firstLuminance = relativeLuminance(first);\n\tconst secondLuminance = relativeLuminance(second);\n\tconst lighter = Math.max(firstLuminance, secondLuminance);\n\tconst darker = Math.min(firstLuminance, secondLuminance);\n\treturn (lighter + 0.05) / (darker + 0.05);\n}\n\n/**\n * 从两个候选颜色中选择与背景对比度更高的一项。\n *\n * @param background - 实际不透明背景色。\n * @param first - 第一候选,默认黑色。\n * @param second - 第二候选,默认白色。\n * @returns 对比度较高的原始候选字符串;相同时返回 `first`。\n * @throws 任一颜色非法时抛出 `TypeError`。\n */\nexport function pickHigherContrastColor(background: string, first = \"#000000\", second = \"#ffffff\"): string {\n\treturn contrastRatio(background, first) >= contrastRatio(background, second) ? first : second;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/color/index.ts"],"sourcesContent":["/** 0 至 255 范围的 RGB 颜色。 */\nexport interface RgbColor {\n\t/** 蓝色通道;必须是闭区间 `[0, 255]` 内的有限数,格式化时舍入到整数。 */\n\tblue: number;\n\t/** 绿色通道;必须是闭区间 `[0, 255]` 内的有限数,格式化时舍入到整数。 */\n\tgreen: number;\n\t/** 红色通道;必须是闭区间 `[0, 255]` 内的有限数,格式化时舍入到整数。 */\n\tred: number;\n}\n\n/** 带 0 至 1 Alpha 通道的 RGB 颜色。 */\nexport interface RgbaColor extends RgbColor {\n\t/** 不透明度;必须是闭区间 `[0, 1]` 内的有限数,`0` 完全透明,`1` 完全不透明。 */\n\talpha: number;\n}\n\n/**\n * 校验 RGB 颜色通道。\n *\n * @param value - 待校验通道值。\n * @param channel - 用于错误消息的通道名称。\n * @throws `RangeError` 当值非有限或超出 0 至 255。\n */\nconst assertRgbChannel = (value: number, channel: string): void => {\n\tif (!Number.isFinite(value) || value < 0 || value > 255) {\n\t\tthrow new RangeError(`\\`${channel}\\` 必须是 0 到 255 之间的有限数。`);\n\t}\n};\n\n/**\n * 校验透明度通道。\n *\n * @param value - 待校验 Alpha 值。\n * @throws `RangeError` 当值非有限或超出闭区间 `[0, 1]`。\n */\nconst assertAlpha = (value: number): void => {\n\tif (!Number.isFinite(value) || value < 0 || value > 1) {\n\t\tthrow new RangeError(\"`alpha` 必须是 0 到 1 之间的有限数。\");\n\t}\n};\n\n/**\n * 规范化十六进制颜色文本。\n *\n * @param value - 可带 `#` 的 3、4、6 或 8 位颜色文本。\n * @returns 不带 `#` 的 6 或 8 位文本。\n * @throws `TypeError` 当长度或字符不符合十六进制颜色格式。\n */\nconst normalizeHexColor = (value: string): string => {\n\tconst normalized = value.startsWith(\"#\") ? value.slice(1) : value;\n\tif (![3, 4, 6, 8].includes(normalized.length) || !/^[\\dA-F]+$/iu.test(normalized)) {\n\t\tthrow new TypeError(\"十六进制颜色必须包含 3、4、6 或 8 个十六进制字符。\");\n\t}\n\treturn normalized.length <= 4 ? Array.from(normalized, (character) => `${character}${character}`).join(\"\") : normalized;\n};\n\n/**\n * 格式化单个颜色通道。\n *\n * @param value - 已校验的 0 至 255 通道值。\n * @returns 舍入后的两位小写十六进制文本。\n */\nconst formatHexChannel = (value: number): string => Math.round(value).toString(16).padStart(2, \"0\");\n\n/**\n * 解析可带可不带 `#` 的 `rgb`、`rgba`、`rrggbb` 或 `rrggbbaa`。\n *\n * @param value - 十六进制颜色文本。\n * @returns 标准化的 RGBA 对象;省略 Alpha 时为 1。\n * @throws 输入非法时抛出 `TypeError`。\n */\nexport function parseHexColor(value: string): RgbaColor {\n\tconst normalized = normalizeHexColor(value);\n\treturn {\n\t\tred: Number.parseInt(normalized.slice(0, 2), 16),\n\t\tgreen: Number.parseInt(normalized.slice(2, 4), 16),\n\t\tblue: Number.parseInt(normalized.slice(4, 6), 16),\n\t\talpha: normalized.length === 8 ? Number.parseInt(normalized.slice(6, 8), 16) / 255 : 1,\n\t};\n}\n\n/**\n * 把 RGB 或 RGBA 对象格式化为小写十六进制颜色。\n *\n * @param color - 颜色通道;RGB 会四舍五入到最近整数。\n * @param includeAlpha - 是否输出 Alpha;默认只在传入 Alpha 且小于 1 时输出。\n * @returns 小写 `#rrggbb` 或 `#rrggbbaa` 文本。\n * @throws 通道或 Alpha 非法时抛出 `RangeError`。\n */\nexport function formatHexColor(color: RgbColor | RgbaColor, includeAlpha: boolean = \"alpha\" in color && color.alpha < 1): string {\n\tassertRgbChannel(color.red, \"red\");\n\tassertRgbChannel(color.green, \"green\");\n\tassertRgbChannel(color.blue, \"blue\");\n\tconst alpha = \"alpha\" in color ? color.alpha : 1;\n\tassertAlpha(alpha);\n\tconst rgb = `${formatHexChannel(color.red)}${formatHexChannel(color.green)}${formatHexChannel(color.blue)}`;\n\treturn `#${rgb}${includeAlpha ? formatHexChannel(alpha * 255) : \"\"}`;\n}\n\n/**\n * 线性混合两种十六进制颜色,包括 Alpha 通道。\n *\n * @param first - `amount = 0` 时的颜色。\n * @param second - `amount = 1` 时的颜色。\n * @param amount - 0 至 1 的混合比例。\n * @returns 小写十六进制颜色;任一输入含透明度时保留 Alpha。\n * @throws 颜色非法时抛出 `TypeError`;比例非法时抛出 `RangeError`。\n */\nexport function mixHexColors(first: string, second: string, amount: number): string {\n\tif (!Number.isFinite(amount) || amount < 0 || amount > 1) {\n\t\tthrow new RangeError(\"`amount` 必须是 0 到 1 之间的有限数。\");\n\t}\n\tconst left = parseHexColor(first);\n\tconst right = parseHexColor(second);\n\t/**\n\t * 在单个 RGBA 通道上执行与外层相同权重的线性混合。\n\t *\n\t * @param start - 第一个颜色的通道值。\n\t * @param end - 第二个颜色的通道值。\n\t * @returns 按外层 `amount` 线性混合后的通道值。\n\t */\n\tconst mix = (start: number, end: number) => start + (end - start) * amount;\n\treturn formatHexColor(\n\t\t{\n\t\t\tred: mix(left.red, right.red),\n\t\t\tgreen: mix(left.green, right.green),\n\t\t\tblue: mix(left.blue, right.blue),\n\t\t\talpha: mix(left.alpha, right.alpha),\n\t\t},\n\t\tleft.alpha < 1 || right.alpha < 1\n\t);\n}\n\n/**\n * 按比例向黑色混合。\n *\n * @param color - 合法十六进制颜色。\n * @param amount - 0 至 1 的混合比例。\n * @returns 混入黑色后的十六进制颜色;参数与异常语义见 {@link mixHexColors}。\n */\nexport function mixHexColorWithBlack(color: string, amount: number): string {\n\treturn mixHexColors(color, \"#000000\", amount);\n}\n\n/**\n * 按比例向白色混合。\n *\n * @param color - 合法十六进制颜色。\n * @param amount - 0 至 1 的混合比例。\n * @returns 混入白色后的十六进制颜色;参数与异常语义见 {@link mixHexColors}。\n */\nexport function mixHexColorWithWhite(color: string, amount: number): string {\n\treturn mixHexColors(color, \"#ffffff\", amount);\n}\n\n/**\n * 按 WCAG sRGB 转换曲线线性化颜色通道。\n *\n * @param channel - 已校验的 0 至 255 通道值。\n * @returns 0 至 1 的线性光值。\n */\nconst linearizeSrgbChannel = (channel: number): number => {\n\tconst value = channel / 255;\n\treturn value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;\n};\n\n/**\n * 计算 WCAG sRGB 相对亮度。\n *\n * @remarks Alpha 通道不会参与计算;半透明颜色应先与实际背景混合。\n * @param color - 合法十六进制颜色。\n * @returns 0 至 1 的相对亮度。\n * @throws 输入非法时抛出 `TypeError`。\n */\nexport function relativeLuminance(color: string): number {\n\tconst { red, green, blue } = parseHexColor(color);\n\treturn 0.2126 * linearizeSrgbChannel(red) + 0.7152 * linearizeSrgbChannel(green) + 0.0722 * linearizeSrgbChannel(blue);\n}\n\n/**\n * 计算两种不透明颜色的 WCAG 对比度,范围 1 至 21。\n *\n * @remarks 返回比值本身,不代表特定字号或 WCAG 等级必然通过。\n * @param first - 第一种十六进制颜色。\n * @param second - 第二种十六进制颜色。\n * @returns 较亮颜色与较暗颜色的对比度。\n * @throws 输入非法时抛出 `TypeError`。\n */\nexport function contrastRatio(first: string, second: string): number {\n\tconst firstLuminance = relativeLuminance(first);\n\tconst secondLuminance = relativeLuminance(second);\n\tconst lighter = Math.max(firstLuminance, secondLuminance);\n\tconst darker = Math.min(firstLuminance, secondLuminance);\n\treturn (lighter + 0.05) / (darker + 0.05);\n}\n\n/**\n * 从两个候选颜色中选择与背景对比度更高的一项。\n *\n * @param background - 实际不透明背景色。\n * @param first - 第一候选,默认黑色。\n * @param second - 第二候选,默认白色。\n * @returns 对比度较高的原始候选字符串;相同时返回 `first`。\n * @throws 任一颜色非法时抛出 `TypeError`。\n */\nexport function pickHigherContrastColor(background: string, first = \"#000000\", second = \"#ffffff\"): string {\n\treturn contrastRatio(background, first) >= contrastRatio(background, second) ? first : second;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/date/index.ts"],"sourcesContent":["/** 可转换为日期的输入;数字始终按 Unix 毫秒时间戳处理。 */\nexport type DateInput = Date | number | string;\n\n/** {@link formatRelativeTime} 的语言与基准时间选项。 */\nexport interface RelativeTimeOptions {\n\t/** `Intl.RelativeTimeFormat` 使用的语言;默认固定为 `zh-CN`。 */\n\tlocale?: string | readonly string[];\n\t/** 比较基准,默认当前时间。 */\n\tnow?: DateInput;\n\t/** 是否允许“昨天”“明天”等文本;默认 `auto`。 */\n\tnumeric?: Intl.RelativeTimeFormatNumeric;\n\t/** 输出长度;默认 `long`。 */\n\tstyle?: Intl.RelativeTimeFormatStyle;\n}\n\n/**\n * 校验日期算术移动量。\n *\n * @param amount - 待校验的日、月或年移动量。\n * @throws `RangeError` 当值不是安全整数。\n */\nconst assertIntegerAmount = (amount: number): void => {\n\tif (!Number.isSafeInteger(amount)) throw new RangeError(\"`amount` 必须是安全整数。\");\n};\n\n/**\n * 转换并克隆有效日期。\n *\n * @remarks 数字不进行秒/毫秒猜测;字符串遵循运行时 `Date` 解析规则,跨平台代码应传带显式时区的完整 ISO 8601。\n * @param value - Date、Unix 毫秒时间戳或运行时可解析字符串。\n * @returns 与输入不共享可变状态的新 Date。\n * @throws 输入无效时抛出 `TypeError`。\n */\nexport function toDate(value: DateInput): Date {\n\tconst date = value instanceof Date ? new Date(value.getTime()) : new Date(value);\n\tif (!Number.isFinite(date.getTime())) {\n\t\tthrow new TypeError(\"该值不是有效日期。\");\n\t}\n\treturn date;\n}\n\n/**\n * 判断输入能否转换为有效日期。\n *\n * @param value - 任意待检查值。\n * @returns 仅 Date、数字或字符串且时间戳有限时返回 `true`。\n */\nexport function isValidDate(value: unknown): value is DateInput {\n\tif (!(value instanceof Date || typeof value === \"number\" || typeof value === \"string\")) return false;\n\treturn Number.isFinite(new Date(value).getTime());\n}\n\n/**\n * 返回输入日期所在本地时区日期的 `00:00:00.000`,不修改输入。\n *\n * @param value - 有效日期输入。\n * @returns 新建的本地日开始时间。\n * @throws 输入无效时抛出 `TypeError`。\n */\nexport function startOfDay(value: DateInput): Date {\n\tconst date = toDate(value);\n\tdate.setHours(0, 0, 0, 0);\n\treturn toDate(date);\n}\n\n/**\n * 返回输入日期所在本地时区日期的 `23:59:59.999`,不修改输入。\n *\n * @param value - 有效日期输入。\n * @returns 新建的本地日结束时间。\n * @throws 输入无效时抛出 `TypeError`。\n */\nexport function endOfDay(value: DateInput): Date {\n\tconst date = toDate(value);\n\tdate.setHours(23, 59, 59, 999);\n\treturn toDate(date);\n}\n\n/**\n * 按本地日历增加整数天,不修改输入。\n *\n * @param value - 基准日期。\n * @param amount - 可为负数的安全整数日数。\n * @returns 本地日历运算后的新 Date;夏令时变化可能使实际毫秒差不等于 24 小时。\n * @throws 日期无效时抛出 `TypeError`;数量或结果非法时抛出 `RangeError`。\n */\nexport function addDays(value: DateInput, amount: number): Date {\n\tassertIntegerAmount(amount);\n\tconst date = toDate(value);\n\tdate.setDate(date.getDate() + amount);\n\treturn toDate(date);\n}\n\n/**\n * 按本地日历增加整数月,并把不存在的日期夹到目标月末。\n *\n * @example 1 月 31 日增加一个月会落在 2 月最后一天。\n * @param value - 基准日期。\n * @param amount - 可为负数的安全整数月数。\n * @returns 月份运算后的新 Date。\n * @throws 日期无效时抛出 `TypeError`;数量或结果非法时抛出 `RangeError`。\n */\nexport function addMonths(value: DateInput, amount: number): Date {\n\tassertIntegerAmount(amount);\n\tconst date = toDate(value);\n\tconst originalDay = date.getDate();\n\tdate.setDate(1);\n\tdate.setMonth(date.getMonth() + amount);\n\tconst targetMonthEnd = new Date(date.getTime());\n\t// 避免 `new Date(year, ...)` 把 0 至 99 年解释为 1900 至 1999 年。\n\ttargetMonthEnd.setMonth(targetMonthEnd.getMonth() + 1, 0);\n\tconst lastDay = targetMonthEnd.getDate();\n\tdate.setDate(Math.min(originalDay, lastDay));\n\treturn toDate(date);\n}\n\n/**\n * 按本地日历增加整数年,并沿用 {@link addMonths} 的月末夹取规则。\n *\n * @param value - 基准日期。\n * @param amount - 可为负数的安全整数年数。\n * @returns 年份运算后的新 Date。\n * @throws 日期无效时抛出 `TypeError`;数量或结果非法时抛出 `RangeError`。\n */\nexport function addYears(value: DateInput, amount: number): Date {\n\tassertIntegerAmount(amount);\n\treturn addMonths(value, amount * 12);\n}\n\n/**\n * 判断两个输入是否属于同一本地日历日。\n *\n * @param left - 第一日期。\n * @param right - 第二日期。\n * @returns 本地年、月、日均相同时返回 `true`。\n * @throws 任一输入无效时抛出 `TypeError`。\n */\nexport function isSameDay(left: DateInput, right: DateInput): boolean {\n\tconst first = toDate(left);\n\tconst second = toDate(right);\n\treturn first.getFullYear() === second.getFullYear() && first.getMonth() === second.getMonth() && first.getDate() === second.getDate();\n}\n\n/**\n * 判断时间是否晚于基准时间。\n *\n * @param value - 待比较时间。\n * @param now - 比较基准,默认调用时的当前时刻。\n * @returns `value` 严格晚于基准时返回 `true`。\n * @throws 任一输入无效时抛出 `TypeError`。\n */\nexport function isFuture(value: DateInput, now: DateInput = Date.now()): boolean {\n\treturn toDate(value).getTime() > toDate(now).getTime();\n}\n\n/**\n * 返回指定基准所在本地日历日的完整闭区间。\n *\n * @param value - 日期基准,默认调用时当前日期。\n * @returns 新建的本地日开始和结束时间二元组。\n * @throws 输入无效时抛出 `TypeError`。\n */\nexport function getLocalDayBounds(value: DateInput = Date.now()): [start: Date, end: Date] {\n\treturn [startOfDay(value), endOfDay(value)];\n}\n\n/**\n * 判断日期是否位于包含首尾的时间区间。\n *\n * @param value - 待检查日期。\n * @param start - 包含的起点。\n * @param end - 包含的终点。\n * @returns 时间戳位于闭区间内时返回 `true`。\n * @throws 无效日期抛出 `TypeError`;首尾反向时抛出 `RangeError`。\n */\nexport function isWithinInterval(value: DateInput, start: DateInput, end: DateInput): boolean {\n\tconst timestamp = toDate(value).getTime();\n\tconst startTimestamp = toDate(start).getTime();\n\tconst endTimestamp = toDate(end).getTime();\n\tif (startTimestamp > endTimestamp) throw new RangeError(\"`start` 不能晚于 `end`。\");\n\treturn timestamp >= startTimestamp && timestamp <= endTimestamp;\n}\n\n/**\n * 使用 `Intl.RelativeTimeFormat` 生成人类可读相对时间。\n *\n * @remarks 秒、分钟、小时、天、周、月和年按固定时长阈值选择;这适合展示,不适合计费或日历运算。\n * @param value - 目标时间。\n * @param options - 语言、样式与比较基准。\n * @returns 由 `Intl.RelativeTimeFormat` 生成的本地化文本。\n * @throws 日期无效时抛出 `TypeError`;Locale 或 Intl 选项非法时抛出 `RangeError`。\n */\nexport function formatRelativeTime(value: DateInput, options: RelativeTimeOptions = {}): string {\n\tconst differenceSeconds = (toDate(value).getTime() - toDate(options.now ?? Date.now()).getTime()) / 1000;\n\tconst absolute = Math.abs(differenceSeconds);\n\tlet divisor: number;\n\tlet unit: Intl.RelativeTimeFormatUnit;\n\tif (absolute < 60) {\n\t\tdivisor = 1;\n\t\tunit = \"second\";\n\t} else if (absolute < 3_600) {\n\t\tdivisor = 60;\n\t\tunit = \"minute\";\n\t} else if (absolute < 86_400) {\n\t\tdivisor = 3_600;\n\t\tunit = \"hour\";\n\t} else if (absolute < 604_800) {\n\t\tdivisor = 86_400;\n\t\tunit = \"day\";\n\t} else if (absolute < 2_629_800) {\n\t\tdivisor = 604_800;\n\t\tunit = \"week\";\n\t} else if (absolute < 31_557_600) {\n\t\tdivisor = 2_629_800;\n\t\tunit = \"month\";\n\t} else {\n\t\tdivisor = 31_557_600;\n\t\tunit = \"year\";\n\t}\n\tconst formatter = new Intl.RelativeTimeFormat(options.locale ?? \"zh-CN\", {\n\t\tnumeric: options.numeric ?? \"auto\",\n\t\tstyle: options.style ?? \"long\",\n\t});\n\treturn formatter.format(Math.round(differenceSeconds / divisor), unit);\n}\n\n/** 日期选择器单日期快捷项。 */\nexport interface DateShortcut {\n\t/** 面向中文日期选择器的显示文本;调用方可直接用于菜单标签。 */\n\ttext: string;\n\t/**\n\t * 计算快捷项对应日期。\n\t * @returns 每次调用时基于当前本地时间创建的新 `Date`,调用方可安全修改。\n\t */\n\tvalue: () => Date;\n}\n\n/** 日期选择器范围快捷项。 */\nexport interface DateRangeShortcut {\n\t/** 面向中文日期范围选择器的显示文本;调用方可直接用于菜单标签。 */\n\ttext: string;\n\t/**\n\t * 计算快捷项对应的本地日期范围。\n\t * @returns 每次调用时创建的新元组;起点为 `00:00:00.000`,终点为 `23:59:59.999`。\n\t */\n\tvalue: () => [start: Date, end: Date];\n}\n\n/** 历史快捷项允许移动的本地日历单位。 */\ntype CalendarUnit = \"day\" | \"month\" | \"year\";\n\n/**\n * 移动本地日历字段。\n *\n * @remarks 直接使用 Date Setter,以保留历史快捷项在月底和闰年的溢出语义。\n * @param date - 会被原地修改的日期。\n * @param amount - 对目标字段增加的整数。\n * @param unit - 要移动的日历字段。\n */\nconst shiftCalendarFieldInPlace = (date: Date, amount: number, unit: CalendarUnit): void => {\n\tswitch (unit) {\n\t\tcase \"day\":\n\t\t\tdate.setDate(date.getDate() + amount);\n\t\t\tbreak;\n\t\tcase \"month\":\n\t\t\tdate.setMonth(date.getMonth() + amount);\n\t\t\tbreak;\n\t\tcase \"year\":\n\t\t\tdate.setFullYear(date.getFullYear() + amount);\n\t\t\tbreak;\n\t}\n};\n\n/**\n * 创建动态单日期快捷项。\n *\n * @param text - 日期选择器显示文本。\n * @param amount - 相对当前时间的移动量。\n * @param unit - 移动使用的日历单位。\n * @returns 每次执行 `value` 都重新读取当前时间的快捷项。\n */\nconst createDateShortcut = (text: string, amount: number, unit: CalendarUnit): DateShortcut => ({\n\ttext,\n\tvalue: (): Date => {\n\t\tconst date = new Date();\n\t\tshiftCalendarFieldInPlace(date, amount, unit);\n\t\tdate.setHours(0, 0, 0, 0);\n\t\treturn date;\n\t},\n});\n\n/**\n * 创建动态日期范围快捷项。\n *\n * @param text - 日期选择器显示文本。\n * @param amount - 范围边界相对当前时间的移动量。\n * @param unit - 移动使用的日历单位。\n * @param towardFuture - `true` 移动结束边界,`false` 移动开始边界。\n * @returns 每次求值都覆盖完整本地日边界的范围快捷项。\n */\nconst createRangeShortcut = (text: string, amount: number, unit: CalendarUnit, towardFuture: boolean): DateRangeShortcut => ({\n\ttext,\n\tvalue: (): [Date, Date] => {\n\t\tconst start = new Date();\n\t\tconst end = new Date();\n\t\tshiftCalendarFieldInPlace(towardFuture ? end : start, towardFuture ? amount : -amount, unit);\n\t\tstart.setHours(0, 0, 0, 0);\n\t\tend.setHours(23, 59, 59, 999);\n\t\treturn [start, end];\n\t},\n});\n\n/**\n * 把日期转换为固定中文相对时间文本。\n *\n * @remarks 10 位以内数字按 Unix 秒处理,其余数字按毫秒处理;月份与年份按本地日历月差计算。\n * @param value - Date、时间戳、可解析字符串或空值。\n * @returns 例如“3分钟前”“半年后”;非法或空输入返回空字符串。\n */\nexport function formatChineseRelativeTime(value: Date | number | string | null | undefined): string {\n\tif (value === null || value === undefined) return \"\";\n\tlet timestamp: number;\n\tif (typeof value === \"string\") timestamp = new Date(value).getTime();\n\telse if (typeof value === \"number\") timestamp = value.toString().length <= 10 ? value * 1000 : value;\n\telse timestamp = value.getTime();\n\tif (!Number.isFinite(timestamp)) return \"\";\n\n\tconst minute = 60_000;\n\tconst hour = minute * 60;\n\tconst day = hour * 24;\n\tconst currentTimestamp = Date.now();\n\tconst difference = currentTimestamp - timestamp;\n\tconst minuteDifference = Math.abs(difference) / minute;\n\tconst hourDifference = Math.abs(difference) / hour;\n\tconst dayDifference = Math.abs(difference) / day;\n\tconst currentDate = new Date(currentTimestamp);\n\tconst targetDate = new Date(timestamp);\n\tconst monthDifference = (currentDate.getFullYear() - targetDate.getFullYear()) * 12 + currentDate.getMonth() - targetDate.getMonth();\n\tconst suffix = difference < 0 ? \"后\" : \"前\";\n\tif (Math.abs(monthDifference) >= 12) return `${Math.floor(Math.abs(monthDifference) / 12)}年${suffix}`;\n\tif (Math.abs(monthDifference) >= 6) return `半年${suffix}`;\n\tif (Math.abs(monthDifference) >= 1) return `${Math.abs(monthDifference)}月${suffix}`;\n\tif (dayDifference >= 15) return `半月${suffix}`;\n\tif (dayDifference >= 7) return `${Math.floor(dayDifference / 7)}周${suffix}`;\n\tif (dayDifference >= 1) return `${Math.floor(dayDifference)}天${suffix}`;\n\tif (hourDifference >= 1) return `${Math.floor(hourDifference)}小时${suffix}`;\n\tif (minuteDifference >= 1) return `${Math.floor(minuteDifference)}分钟${suffix}`;\n\treturn \"刚刚\";\n}\n\n/**\n * 创建从今天到前后一个月日期的完整本地日范围。\n *\n * @param towardFuture - `true` 返回今天至一个月后,默认返回一个月前至今天。\n * @returns 每次调用新建的本地日首尾边界。\n */\nexport function createOneMonthRangeFromToday(towardFuture = false): [start: Date, end: Date] {\n\tconst start = new Date();\n\tconst end = new Date();\n\tshiftCalendarFieldInPlace(towardFuture ? end : start, towardFuture ? 1 : -1, \"month\");\n\tstart.setHours(0, 0, 0, 0);\n\tend.setHours(23, 59, 59, 999);\n\treturn [start, end];\n}\n\n/**\n * 判断日期是否晚于调用时的当前时刻。\n *\n * @param time - 待比较日期。\n * @returns 时间戳严格晚于 `Date.now()` 时返回 `true`。\n */\nexport function isDateAfterNow(time: Date): boolean {\n\treturn time.getTime() > Date.now();\n}\n\n/**\n * 根据浏览器本地小时返回固定中文问候语。\n *\n * @returns 与当前时段对应的中文欢迎文本。\n */\nexport function getLocalTimeGreeting(): string {\n\tconst hour = new Date().getHours();\n\tif (hour < 5) return \"夜深了,注意身体哦!\";\n\tif (hour < 9) return \"早上好!欢迎回来!\";\n\tif (hour < 12) return \"上午好!欢迎回来!\";\n\tif (hour < 14) return \"中午好!欢迎回来!\";\n\tif (hour < 18) return \"下午好!欢迎回来!\";\n\tif (hour < 24) return \"晚上好!欢迎回来!\";\n\treturn \"您好!欢迎回来!\";\n}\n\n/**\n * 创建面向过去或未来的常用完整日期范围快捷项。\n *\n * @param towardFuture - `true` 创建未来范围,默认创建历史范围。\n * @returns 每次求值都会重新读取当前时间的范围快捷项。\n */\nexport function createDateRangeShortcuts(towardFuture = false): DateRangeShortcut[] {\n\treturn towardFuture\n\t\t? [\n\t\t\t\tcreateRangeShortcut(\"后1天\", 1, \"day\", true),\n\t\t\t\tcreateRangeShortcut(\"后3天\", 3, \"day\", true),\n\t\t\t\tcreateRangeShortcut(\"后1周\", 7, \"day\", true),\n\t\t\t\tcreateRangeShortcut(\"后1月\", 1, \"month\", true),\n\t\t\t\tcreateRangeShortcut(\"后3月\", 3, \"month\", true),\n\t\t\t\tcreateRangeShortcut(\"后6月\", 6, \"month\", true),\n\t\t\t\tcreateRangeShortcut(\"后1年\", 1, \"year\", true),\n\t\t\t]\n\t\t: [\n\t\t\t\tcreateRangeShortcut(\"近1天\", 1, \"day\", false),\n\t\t\t\tcreateRangeShortcut(\"近3天\", 3, \"day\", false),\n\t\t\t\tcreateRangeShortcut(\"近1周\", 7, \"day\", false),\n\t\t\t\tcreateRangeShortcut(\"近1月\", 1, \"month\", false),\n\t\t\t\tcreateRangeShortcut(\"近3月\", 3, \"month\", false),\n\t\t\t\tcreateRangeShortcut(\"近6月\", 6, \"month\", false),\n\t\t\t\tcreateRangeShortcut(\"近1年\", 1, \"year\", false),\n\t\t\t];\n}\n\n/**\n * 创建面向过去或未来的常用单日期快捷项。\n *\n * @param towardFuture - `true` 创建未来日期,默认创建历史日期。\n * @returns 每次求值都会重新读取当前时间的单日期快捷项。\n */\nexport function createDateShortcuts(towardFuture = false): DateShortcut[] {\n\treturn towardFuture\n\t\t? [\n\t\t\t\tcreateDateShortcut(\"今天\", 0, \"day\"),\n\t\t\t\tcreateDateShortcut(\"明天\", 1, \"day\"),\n\t\t\t\tcreateDateShortcut(\"一周后\", 7, \"day\"),\n\t\t\t\tcreateDateShortcut(\"一月后\", 1, \"month\"),\n\t\t\t\tcreateDateShortcut(\"一年后\", 1, \"year\"),\n\t\t\t]\n\t\t: [\n\t\t\t\tcreateDateShortcut(\"今天\", 0, \"day\"),\n\t\t\t\tcreateDateShortcut(\"昨天\", -1, \"day\"),\n\t\t\t\tcreateDateShortcut(\"一周前\", -7, \"day\"),\n\t\t\t\tcreateDateShortcut(\"一月前\", -1, \"month\"),\n\t\t\t\tcreateDateShortcut(\"一年前\", -1, \"year\"),\n\t\t\t];\n}\n\n/**\n * 返回今天的本地零点。\n *\n * @returns 新建的 `00:00:00.000` Date。\n */\nexport function getStartOfToday(): Date {\n\treturn startOfDay(new Date());\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/date/index.ts"],"sourcesContent":["/** 可转换为日期的输入;数字始终按 Unix 毫秒时间戳处理。 */\nexport type DateInput = Date | number | string;\n\n/** {@link formatRelativeTime} 的语言与基准时间选项。 */\nexport interface RelativeTimeOptions {\n\t/** `Intl.RelativeTimeFormat` 使用的语言;默认固定为 `zh-CN`。 */\n\tlocale?: string | readonly string[];\n\t/** 比较基准,默认当前时间。 */\n\tnow?: DateInput;\n\t/** 是否允许“昨天”“明天”等文本;默认 `auto`。 */\n\tnumeric?: Intl.RelativeTimeFormatNumeric;\n\t/** 输出长度;默认 `long`。 */\n\tstyle?: Intl.RelativeTimeFormatStyle;\n}\n\n/**\n * 校验日期算术移动量。\n *\n * @param amount - 待校验的日、月或年移动量。\n * @throws `RangeError` 当值不是安全整数。\n */\nconst assertIntegerAmount = (amount: number): void => {\n\tif (!Number.isSafeInteger(amount)) throw new RangeError(\"`amount` 必须是安全整数。\");\n};\n\n/**\n * 转换并克隆有效日期。\n *\n * @remarks 数字不进行秒/毫秒猜测;字符串遵循运行时 `Date` 解析规则,跨平台代码应传带显式时区的完整 ISO 8601。\n * @param value - Date、Unix 毫秒时间戳或运行时可解析字符串。\n * @returns 与输入不共享可变状态的新 Date。\n * @throws 输入无效时抛出 `TypeError`。\n */\nexport function toDate(value: DateInput): Date {\n\tconst date = value instanceof Date ? new Date(value.getTime()) : new Date(value);\n\tif (!Number.isFinite(date.getTime())) {\n\t\tthrow new TypeError(\"该值不是有效日期。\");\n\t}\n\treturn date;\n}\n\n/**\n * 判断输入能否转换为有效日期。\n *\n * @param value - 任意待检查值。\n * @returns 仅 Date、数字或字符串且时间戳有限时返回 `true`。\n */\nexport function isValidDate(value: unknown): value is DateInput {\n\tif (!(value instanceof Date || typeof value === \"number\" || typeof value === \"string\")) return false;\n\treturn Number.isFinite(new Date(value).getTime());\n}\n\n/**\n * 返回输入日期所在本地时区日期的 `00:00:00.000`,不修改输入。\n *\n * @param value - 有效日期输入。\n * @returns 新建的本地日开始时间。\n * @throws 输入无效时抛出 `TypeError`。\n */\nexport function startOfDay(value: DateInput): Date {\n\tconst date = toDate(value);\n\tdate.setHours(0, 0, 0, 0);\n\treturn toDate(date);\n}\n\n/**\n * 返回输入日期所在本地时区日期的 `23:59:59.999`,不修改输入。\n *\n * @param value - 有效日期输入。\n * @returns 新建的本地日结束时间。\n * @throws 输入无效时抛出 `TypeError`。\n */\nexport function endOfDay(value: DateInput): Date {\n\tconst date = toDate(value);\n\tdate.setHours(23, 59, 59, 999);\n\treturn toDate(date);\n}\n\n/**\n * 按本地日历增加整数天,不修改输入。\n *\n * @param value - 基准日期。\n * @param amount - 可为负数的安全整数日数。\n * @returns 本地日历运算后的新 Date;夏令时变化可能使实际毫秒差不等于 24 小时。\n * @throws 日期无效时抛出 `TypeError`;数量或结果非法时抛出 `RangeError`。\n */\nexport function addDays(value: DateInput, amount: number): Date {\n\tassertIntegerAmount(amount);\n\tconst date = toDate(value);\n\tdate.setDate(date.getDate() + amount);\n\treturn toDate(date);\n}\n\n/**\n * 按本地日历增加整数月,并把不存在的日期夹到目标月末。\n *\n * @example 1 月 31 日增加一个月会落在 2 月最后一天。\n * @param value - 基准日期。\n * @param amount - 可为负数的安全整数月数。\n * @returns 月份运算后的新 Date。\n * @throws 日期无效时抛出 `TypeError`;数量或结果非法时抛出 `RangeError`。\n */\nexport function addMonths(value: DateInput, amount: number): Date {\n\tassertIntegerAmount(amount);\n\tconst date = toDate(value);\n\tconst originalDay = date.getDate();\n\tdate.setDate(1);\n\tdate.setMonth(date.getMonth() + amount);\n\tconst targetMonthEnd = new Date(date.getTime());\n\t// 避免 `new Date(year, ...)` 把 0 至 99 年解释为 1900 至 1999 年。\n\ttargetMonthEnd.setMonth(targetMonthEnd.getMonth() + 1, 0);\n\tconst lastDay = targetMonthEnd.getDate();\n\tdate.setDate(Math.min(originalDay, lastDay));\n\treturn toDate(date);\n}\n\n/**\n * 按本地日历增加整数年,并沿用 {@link addMonths} 的月末夹取规则。\n *\n * @param value - 基准日期。\n * @param amount - 可为负数的安全整数年数。\n * @returns 年份运算后的新 Date。\n * @throws 日期无效时抛出 `TypeError`;数量或结果非法时抛出 `RangeError`。\n */\nexport function addYears(value: DateInput, amount: number): Date {\n\tassertIntegerAmount(amount);\n\treturn addMonths(value, amount * 12);\n}\n\n/**\n * 判断两个输入是否属于同一本地日历日。\n *\n * @param left - 第一日期。\n * @param right - 第二日期。\n * @returns 本地年、月、日均相同时返回 `true`。\n * @throws 任一输入无效时抛出 `TypeError`。\n */\nexport function isSameDay(left: DateInput, right: DateInput): boolean {\n\tconst first = toDate(left);\n\tconst second = toDate(right);\n\treturn first.getFullYear() === second.getFullYear() && first.getMonth() === second.getMonth() && first.getDate() === second.getDate();\n}\n\n/**\n * 判断时间是否晚于基准时间。\n *\n * @param value - 待比较时间。\n * @param now - 比较基准,默认调用时的当前时刻。\n * @returns `value` 严格晚于基准时返回 `true`。\n * @throws 任一输入无效时抛出 `TypeError`。\n */\nexport function isFuture(value: DateInput, now: DateInput = Date.now()): boolean {\n\treturn toDate(value).getTime() > toDate(now).getTime();\n}\n\n/**\n * 返回指定基准所在本地日历日的完整闭区间。\n *\n * @param value - 日期基准,默认调用时当前日期。\n * @returns 新建的本地日开始和结束时间二元组。\n * @throws 输入无效时抛出 `TypeError`。\n */\nexport function getLocalDayBounds(value: DateInput = Date.now()): [start: Date, end: Date] {\n\treturn [startOfDay(value), endOfDay(value)];\n}\n\n/**\n * 判断日期是否位于包含首尾的时间区间。\n *\n * @param value - 待检查日期。\n * @param start - 包含的起点。\n * @param end - 包含的终点。\n * @returns 时间戳位于闭区间内时返回 `true`。\n * @throws 无效日期抛出 `TypeError`;首尾反向时抛出 `RangeError`。\n */\nexport function isWithinInterval(value: DateInput, start: DateInput, end: DateInput): boolean {\n\tconst timestamp = toDate(value).getTime();\n\tconst startTimestamp = toDate(start).getTime();\n\tconst endTimestamp = toDate(end).getTime();\n\tif (startTimestamp > endTimestamp) throw new RangeError(\"`start` 不能晚于 `end`。\");\n\treturn timestamp >= startTimestamp && timestamp <= endTimestamp;\n}\n\n/**\n * 使用 `Intl.RelativeTimeFormat` 生成人类可读相对时间。\n *\n * @remarks 秒、分钟、小时、天、周、月和年按固定时长阈值选择;这适合展示,不适合计费或日历运算。\n * @param value - 目标时间。\n * @param options - 语言、样式与比较基准。\n * @returns 由 `Intl.RelativeTimeFormat` 生成的本地化文本。\n * @throws 日期无效时抛出 `TypeError`;Locale 或 Intl 选项非法时抛出 `RangeError`。\n */\nexport function formatRelativeTime(value: DateInput, options: RelativeTimeOptions = {}): string {\n\tconst differenceSeconds = (toDate(value).getTime() - toDate(options.now ?? Date.now()).getTime()) / 1000;\n\tconst absolute = Math.abs(differenceSeconds);\n\tlet divisor: number;\n\tlet unit: Intl.RelativeTimeFormatUnit;\n\tif (absolute < 60) {\n\t\tdivisor = 1;\n\t\tunit = \"second\";\n\t} else if (absolute < 3_600) {\n\t\tdivisor = 60;\n\t\tunit = \"minute\";\n\t} else if (absolute < 86_400) {\n\t\tdivisor = 3_600;\n\t\tunit = \"hour\";\n\t} else if (absolute < 604_800) {\n\t\tdivisor = 86_400;\n\t\tunit = \"day\";\n\t} else if (absolute < 2_629_800) {\n\t\tdivisor = 604_800;\n\t\tunit = \"week\";\n\t} else if (absolute < 31_557_600) {\n\t\tdivisor = 2_629_800;\n\t\tunit = \"month\";\n\t} else {\n\t\tdivisor = 31_557_600;\n\t\tunit = \"year\";\n\t}\n\tconst formatter = new Intl.RelativeTimeFormat(options.locale ?? \"zh-CN\", {\n\t\tnumeric: options.numeric ?? \"auto\",\n\t\tstyle: options.style ?? \"long\",\n\t});\n\treturn formatter.format(Math.round(differenceSeconds / divisor), unit);\n}\n\n/** 日期选择器单日期快捷项。 */\nexport interface DateShortcut {\n\t/** 面向中文日期选择器的显示文本;调用方可直接用于菜单标签。 */\n\ttext: string;\n\t/**\n\t * 计算快捷项对应日期。\n\t * @returns 每次调用时基于当前本地时间创建的新 `Date`,调用方可安全修改。\n\t */\n\tvalue: () => Date;\n}\n\n/** 日期选择器范围快捷项。 */\nexport interface DateRangeShortcut {\n\t/** 面向中文日期范围选择器的显示文本;调用方可直接用于菜单标签。 */\n\ttext: string;\n\t/**\n\t * 计算快捷项对应的本地日期范围。\n\t * @returns 每次调用时创建的新元组;起点为 `00:00:00.000`,终点为 `23:59:59.999`。\n\t */\n\tvalue: () => [start: Date, end: Date];\n}\n\n/** 历史快捷项允许移动的本地日历单位。 */\ntype CalendarUnit = \"day\" | \"month\" | \"year\";\n\n/**\n * 移动本地日历字段。\n *\n * @remarks 直接使用 Date Setter,以保留历史快捷项在月底和闰年的溢出语义。\n * @param date - 会被原地修改的日期。\n * @param amount - 对目标字段增加的整数。\n * @param unit - 要移动的日历字段。\n */\nconst shiftCalendarFieldInPlace = (date: Date, amount: number, unit: CalendarUnit): void => {\n\tswitch (unit) {\n\t\tcase \"day\":\n\t\t\tdate.setDate(date.getDate() + amount);\n\t\t\tbreak;\n\t\tcase \"month\":\n\t\t\tdate.setMonth(date.getMonth() + amount);\n\t\t\tbreak;\n\t\tcase \"year\":\n\t\t\tdate.setFullYear(date.getFullYear() + amount);\n\t\t\tbreak;\n\t}\n};\n\n/**\n * 创建动态单日期快捷项。\n *\n * @param text - 日期选择器显示文本。\n * @param amount - 相对当前时间的移动量。\n * @param unit - 移动使用的日历单位。\n * @returns 每次执行 `value` 都重新读取当前时间的快捷项。\n */\nconst createDateShortcut = (text: string, amount: number, unit: CalendarUnit): DateShortcut => ({\n\ttext,\n\tvalue: () => {\n\t\tconst date = new Date();\n\t\tshiftCalendarFieldInPlace(date, amount, unit);\n\t\tdate.setHours(0, 0, 0, 0);\n\t\treturn date;\n\t},\n});\n\n/**\n * 创建动态日期范围快捷项。\n *\n * @param text - 日期选择器显示文本。\n * @param amount - 范围边界相对当前时间的移动量。\n * @param unit - 移动使用的日历单位。\n * @param towardFuture - `true` 移动结束边界,`false` 移动开始边界。\n * @returns 每次求值都覆盖完整本地日边界的范围快捷项。\n */\nconst createRangeShortcut = (text: string, amount: number, unit: CalendarUnit, towardFuture: boolean): DateRangeShortcut => ({\n\ttext,\n\tvalue: (): [Date, Date] => {\n\t\tconst start = new Date();\n\t\tconst end = new Date();\n\t\tshiftCalendarFieldInPlace(towardFuture ? end : start, towardFuture ? amount : -amount, unit);\n\t\tstart.setHours(0, 0, 0, 0);\n\t\tend.setHours(23, 59, 59, 999);\n\t\treturn [start, end];\n\t},\n});\n\n/**\n * 把日期转换为固定中文相对时间文本。\n *\n * @remarks 10 位以内数字按 Unix 秒处理,其余数字按毫秒处理;月份与年份按本地日历月差计算。\n * @param value - Date、时间戳、可解析字符串或空值。\n * @returns 例如“3分钟前”“半年后”;非法或空输入返回空字符串。\n */\nexport function formatChineseRelativeTime(value: Date | number | string | null | undefined): string {\n\tif (value === null || value === undefined) return \"\";\n\tlet timestamp: number;\n\tif (typeof value === \"string\") timestamp = new Date(value).getTime();\n\telse if (typeof value === \"number\") timestamp = value.toString().length <= 10 ? value * 1000 : value;\n\telse timestamp = value.getTime();\n\tif (!Number.isFinite(timestamp)) return \"\";\n\n\tconst minute = 60_000;\n\tconst hour = minute * 60;\n\tconst day = hour * 24;\n\tconst currentTimestamp = Date.now();\n\tconst difference = currentTimestamp - timestamp;\n\tconst minuteDifference = Math.abs(difference) / minute;\n\tconst hourDifference = Math.abs(difference) / hour;\n\tconst dayDifference = Math.abs(difference) / day;\n\tconst currentDate = new Date(currentTimestamp);\n\tconst targetDate = new Date(timestamp);\n\tconst monthDifference = (currentDate.getFullYear() - targetDate.getFullYear()) * 12 + currentDate.getMonth() - targetDate.getMonth();\n\tconst suffix = difference < 0 ? \"后\" : \"前\";\n\tif (Math.abs(monthDifference) >= 12) return `${Math.floor(Math.abs(monthDifference) / 12)}年${suffix}`;\n\tif (Math.abs(monthDifference) >= 6) return `半年${suffix}`;\n\tif (Math.abs(monthDifference) >= 1) return `${Math.abs(monthDifference)}月${suffix}`;\n\tif (dayDifference >= 15) return `半月${suffix}`;\n\tif (dayDifference >= 7) return `${Math.floor(dayDifference / 7)}周${suffix}`;\n\tif (dayDifference >= 1) return `${Math.floor(dayDifference)}天${suffix}`;\n\tif (hourDifference >= 1) return `${Math.floor(hourDifference)}小时${suffix}`;\n\tif (minuteDifference >= 1) return `${Math.floor(minuteDifference)}分钟${suffix}`;\n\treturn \"刚刚\";\n}\n\n/**\n * 创建从今天到前后一个月日期的完整本地日范围。\n *\n * @param towardFuture - `true` 返回今天至一个月后,默认返回一个月前至今天。\n * @returns 每次调用新建的本地日首尾边界。\n */\nexport function createOneMonthRangeFromToday(towardFuture = false): [start: Date, end: Date] {\n\tconst start = new Date();\n\tconst end = new Date();\n\tshiftCalendarFieldInPlace(towardFuture ? end : start, towardFuture ? 1 : -1, \"month\");\n\tstart.setHours(0, 0, 0, 0);\n\tend.setHours(23, 59, 59, 999);\n\treturn [start, end];\n}\n\n/**\n * 判断日期是否晚于调用时的当前时刻。\n *\n * @param time - 待比较日期。\n * @returns 时间戳严格晚于 `Date.now()` 时返回 `true`。\n */\nexport function isDateAfterNow(time: Date): boolean {\n\treturn time.getTime() > Date.now();\n}\n\n/**\n * 根据浏览器本地小时返回固定中文问候语。\n *\n * @returns 与当前时段对应的中文欢迎文本。\n */\nexport function getLocalTimeGreeting(): string {\n\tconst hour = new Date().getHours();\n\tif (hour < 5) return \"夜深了,注意身体哦!\";\n\tif (hour < 9) return \"早上好!欢迎回来!\";\n\tif (hour < 12) return \"上午好!欢迎回来!\";\n\tif (hour < 14) return \"中午好!欢迎回来!\";\n\tif (hour < 18) return \"下午好!欢迎回来!\";\n\tif (hour < 24) return \"晚上好!欢迎回来!\";\n\treturn \"您好!欢迎回来!\";\n}\n\n/**\n * 创建面向过去或未来的常用完整日期范围快捷项。\n *\n * @param towardFuture - `true` 创建未来范围,默认创建历史范围。\n * @returns 每次求值都会重新读取当前时间的范围快捷项。\n */\nexport function createDateRangeShortcuts(towardFuture = false): DateRangeShortcut[] {\n\treturn towardFuture\n\t\t? [\n\t\t\t\tcreateRangeShortcut(\"后1天\", 1, \"day\", true),\n\t\t\t\tcreateRangeShortcut(\"后3天\", 3, \"day\", true),\n\t\t\t\tcreateRangeShortcut(\"后1周\", 7, \"day\", true),\n\t\t\t\tcreateRangeShortcut(\"后1月\", 1, \"month\", true),\n\t\t\t\tcreateRangeShortcut(\"后3月\", 3, \"month\", true),\n\t\t\t\tcreateRangeShortcut(\"后6月\", 6, \"month\", true),\n\t\t\t\tcreateRangeShortcut(\"后1年\", 1, \"year\", true),\n\t\t\t]\n\t\t: [\n\t\t\t\tcreateRangeShortcut(\"近1天\", 1, \"day\", false),\n\t\t\t\tcreateRangeShortcut(\"近3天\", 3, \"day\", false),\n\t\t\t\tcreateRangeShortcut(\"近1周\", 7, \"day\", false),\n\t\t\t\tcreateRangeShortcut(\"近1月\", 1, \"month\", false),\n\t\t\t\tcreateRangeShortcut(\"近3月\", 3, \"month\", false),\n\t\t\t\tcreateRangeShortcut(\"近6月\", 6, \"month\", false),\n\t\t\t\tcreateRangeShortcut(\"近1年\", 1, \"year\", false),\n\t\t\t];\n}\n\n/**\n * 创建面向过去或未来的常用单日期快捷项。\n *\n * @param towardFuture - `true` 创建未来日期,默认创建历史日期。\n * @returns 每次求值都会重新读取当前时间的单日期快捷项。\n */\nexport function createDateShortcuts(towardFuture = false): DateShortcut[] {\n\treturn towardFuture\n\t\t? [\n\t\t\t\tcreateDateShortcut(\"今天\", 0, \"day\"),\n\t\t\t\tcreateDateShortcut(\"明天\", 1, \"day\"),\n\t\t\t\tcreateDateShortcut(\"一周后\", 7, \"day\"),\n\t\t\t\tcreateDateShortcut(\"一月后\", 1, \"month\"),\n\t\t\t\tcreateDateShortcut(\"一年后\", 1, \"year\"),\n\t\t\t]\n\t\t: [\n\t\t\t\tcreateDateShortcut(\"今天\", 0, \"day\"),\n\t\t\t\tcreateDateShortcut(\"昨天\", -1, \"day\"),\n\t\t\t\tcreateDateShortcut(\"一周前\", -7, \"day\"),\n\t\t\t\tcreateDateShortcut(\"一月前\", -1, \"month\"),\n\t\t\t\tcreateDateShortcut(\"一年前\", -1, \"year\"),\n\t\t\t];\n}\n\n/**\n * 返回今天的本地零点。\n *\n * @returns 新建的 `00:00:00.000` Date。\n */\nexport function getStartOfToday(): Date {\n\treturn startOfDay(new Date());\n}\n"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ //#region src/function/index.d.ts
2
+ /**
3
+ * 创建最多执行一次并缓存首次结果的函数。
4
+ *
5
+ * @remarks 首次成功返回后,后续调用返回同一结果;Promise 会保持引用不变。首次同步抛错时缓存错误,后续调用重新抛出同一错误。
6
+ * 包装函数使用首次调用时的参数和 `this`,之后传入的参数不会再次执行原函数。
7
+ * @param callback - 只允许执行一次的函数。
8
+ * @returns 保持原参数与返回类型的包装函数。
9
+ * @throws `TypeError` 当 `callback` 不是函数。
10
+ */
11
+ export declare function once<This, Arguments extends unknown[], Result>(callback: (this: This, ...arguments_: Arguments) => Result): (this: This, ...arguments_: Arguments) => Result;
12
+ //#endregion
13
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1,38 @@
1
+ //#region src/function/index.ts
2
+ /**
3
+ * 创建最多执行一次并缓存首次结果的函数。
4
+ *
5
+ * @remarks 首次成功返回后,后续调用返回同一结果;Promise 会保持引用不变。首次同步抛错时缓存错误,后续调用重新抛出同一错误。
6
+ * 包装函数使用首次调用时的参数和 `this`,之后传入的参数不会再次执行原函数。
7
+ * @param callback - 只允许执行一次的函数。
8
+ * @returns 保持原参数与返回类型的包装函数。
9
+ * @throws `TypeError` 当 `callback` 不是函数。
10
+ */
11
+ function once(callback) {
12
+ if (typeof callback !== "function") throw new TypeError("`callback` 必须是函数。");
13
+ let state = { status: "pending" };
14
+ return function(...arguments_) {
15
+ switch (state.status) {
16
+ case "returned": return state.value;
17
+ case "threw": throw state.error;
18
+ case "pending": try {
19
+ const value = callback.apply(this, arguments_);
20
+ state = {
21
+ status: "returned",
22
+ value
23
+ };
24
+ return value;
25
+ } catch (error) {
26
+ state = {
27
+ error,
28
+ status: "threw"
29
+ };
30
+ throw error;
31
+ }
32
+ }
33
+ };
34
+ }
35
+ //#endregion
36
+ export { once };
37
+
38
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/function/index.ts"],"sourcesContent":["/** 函数首次调用前的内部状态。 */\ninterface PendingOnceState {\n\treadonly status: \"pending\";\n}\n\n/** 函数成功返回后的内部状态。 */\ninterface ReturnedOnceState<Result> {\n\treadonly status: \"returned\";\n\treadonly value: Result;\n}\n\n/** 函数同步抛错后的内部状态。 */\ninterface ThrewOnceState {\n\treadonly error: unknown;\n\treadonly status: \"threw\";\n}\n\ntype OnceState<Result> = PendingOnceState | ReturnedOnceState<Result> | ThrewOnceState;\n\n/**\n * 创建最多执行一次并缓存首次结果的函数。\n *\n * @remarks 首次成功返回后,后续调用返回同一结果;Promise 会保持引用不变。首次同步抛错时缓存错误,后续调用重新抛出同一错误。\n * 包装函数使用首次调用时的参数和 `this`,之后传入的参数不会再次执行原函数。\n * @param callback - 只允许执行一次的函数。\n * @returns 保持原参数与返回类型的包装函数。\n * @throws `TypeError` 当 `callback` 不是函数。\n */\nexport function once<This, Arguments extends unknown[], Result>(\n\tcallback: (this: This, ...arguments_: Arguments) => Result\n): (this: This, ...arguments_: Arguments) => Result {\n\tif (typeof callback !== \"function\") throw new TypeError(\"`callback` 必须是函数。\");\n\tlet state: OnceState<Result> = { status: \"pending\" };\n\treturn function (this: This, ...arguments_: Arguments): Result {\n\t\tswitch (state.status) {\n\t\t\tcase \"returned\":\n\t\t\t\treturn state.value;\n\t\t\tcase \"threw\":\n\t\t\t\tthrow state.error;\n\t\t\tcase \"pending\":\n\t\t\t\ttry {\n\t\t\t\t\tconst value = callback.apply(this, arguments_);\n\t\t\t\t\tstate = { status: \"returned\", value };\n\t\t\t\t\treturn value;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tstate = { error, status: \"threw\" };\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t}\n\t};\n}\n"],"mappings":";;;;;;;;;;AA4BA,SAAgB,KACf,UACmD;CACnD,IAAI,OAAO,aAAa,YAAY,MAAM,IAAI,UAAU,mBAAmB;CAC3E,IAAI,QAA2B,EAAE,QAAQ,UAAU;CACnD,OAAO,SAAsB,GAAG,YAA+B;EAC9D,QAAQ,MAAM,QAAd;GACC,KAAK,YACJ,OAAO,MAAM;GACd,KAAK,SACJ,MAAM,MAAM;GACb,KAAK,WACJ,IAAI;IACH,MAAM,QAAQ,SAAS,MAAM,MAAM,UAAU;IAC7C,QAAQ;KAAE,QAAQ;KAAY;IAAM;IACpC,OAAO;GACR,SAAS,OAAO;IACf,QAAQ;KAAE;KAAO,QAAQ;IAAQ;IACjC,MAAM;GACP;EACF;CACD;AACD"}