@fast-china/utils 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/Fast.png +0 -0
- package/README.md +27 -29
- package/README.zh.md +27 -29
- package/dist/base64/index.mjs.map +1 -1
- package/dist/crypto/index.d.mts +265 -96
- package/dist/crypto/index.mjs +563 -261
- package/dist/crypto/index.mjs.map +1 -1
- package/dist/identity/index.d.mts +5 -5
- package/dist/identity/index.mjs +2 -2
- package/dist/identity/index.mjs.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.global.min.js +3 -0
- package/dist/index.global.min.js.map +1 -0
- package/dist/index.mjs +2 -2
- package/dist/storage/index.d.mts +11 -8
- package/dist/storage/index.mjs +21 -18
- package/dist/storage/index.mjs.map +1 -1
- package/dist/vue/emits.mjs.map +1 -1
- package/dist/vue/index.d.mts +2 -2
- package/dist/vue/install.d.mts +11 -29
- package/dist/vue/install.mjs +25 -25
- package/dist/vue/install.mjs.map +1 -1
- package/dist/vue/props.mjs.map +1 -1
- package/dist/vue/render.d.mts +2 -3
- package/dist/vue/render.mjs +3 -9
- package/dist/vue/render.mjs.map +1 -1
- package/docs/API.md +30 -14
- package/docs/API.zh-CN.md +30 -14
- package/docs/DEVELOPMENT_RELEASE.zh-CN.md +4 -4
- package/docs/RUNTIME_CONTRACT.md +10 -10
- package/package.json +9 -11
- package/dist/array/index.d.mts.map +0 -1
- package/dist/async/index.d.mts.map +0 -1
- package/dist/base64/index.d.mts.map +0 -1
- package/dist/color/index.d.mts.map +0 -1
- package/dist/crypto/index.d.mts.map +0 -1
- package/dist/date/index.d.mts.map +0 -1
- package/dist/dom/style.d.mts.map +0 -1
- package/dist/env/index.d.mts.map +0 -1
- package/dist/identity/index.d.mts.map +0 -1
- package/dist/logger/index.d.mts.map +0 -1
- package/dist/number/index.d.mts.map +0 -1
- package/dist/object/index.d.mts.map +0 -1
- package/dist/storage/index.d.mts.map +0 -1
- package/dist/string/index.d.mts.map +0 -1
- package/dist/vue/emits.d.mts.map +0 -1
- package/dist/vue/expose.d.mts.map +0 -1
- package/dist/vue/func.d.mts.map +0 -1
- package/dist/vue/install.d.mts.map +0 -1
- package/dist/vue/props.d.mts.map +0 -1
- package/dist/vue/render.d.mts.map +0 -1
- package/dist/vue/slots.d.mts.map +0 -1
- package/dist/vue/with.d.mts.map +0 -1
- package/src/array/index.ts +0 -173
- package/src/async/index.ts +0 -475
- package/src/base64/index.ts +0 -374
- package/src/color/index.ts +0 -208
- package/src/crypto/index.ts +0 -670
- package/src/date/index.ts +0 -451
- package/src/dom/index.ts +0 -6
- package/src/dom/style.ts +0 -92
- package/src/env/index.ts +0 -169
- package/src/identity/index.ts +0 -144
- package/src/index.ts +0 -20
- package/src/internal/text.ts +0 -46
- package/src/logger/index.ts +0 -219
- package/src/number/index.ts +0 -235
- package/src/object/index.ts +0 -160
- package/src/storage/index.ts +0 -524
- package/src/string/index.ts +0 -328
- package/src/vue/emits.ts +0 -71
- package/src/vue/expose.ts +0 -11
- package/src/vue/func.ts +0 -17
- package/src/vue/index.ts +0 -13
- package/src/vue/install.ts +0 -185
- package/src/vue/props.ts +0 -39
- package/src/vue/render.ts +0 -41
- package/src/vue/slots.ts +0 -23
- package/src/vue/with.ts +0 -10
package/src/number/index.ts
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
const byteUnits = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] as const;
|
|
2
|
-
const binaryByteUnits = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"] as const;
|
|
3
|
-
|
|
4
|
-
/** 安全随机整数延迟读取的平台全局对象最小视图。 */
|
|
5
|
-
interface RuntimeNumberGlobals {
|
|
6
|
-
/** 可选 Web Crypto 随机填充能力;缺失时安全随机整数明确失败。 */
|
|
7
|
-
crypto?: Partial<Pick<Crypto, "getRandomValues">>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const runtimeNumberGlobals = globalThis as unknown as RuntimeNumberGlobals;
|
|
11
|
-
|
|
12
|
-
/** {@link formatBytes} 的格式化选项。 */
|
|
13
|
-
export interface FormatBytesOptions {
|
|
14
|
-
/** 计量基数。`1000` 生成 SI 单位,`1024` 生成 IEC 单位;默认 `1024`。 */
|
|
15
|
-
base?: 1000 | 1024;
|
|
16
|
-
/** 小数位数,范围 0 至 20;默认 `2`。 */
|
|
17
|
-
decimals?: number;
|
|
18
|
-
/** `Intl.NumberFormat` 使用的语言;默认固定为 `en-US` 以保证输出稳定。 */
|
|
19
|
-
locale?: string | readonly string[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 拒绝 NaN,同时允许具体 API 自行决定是否接受 Infinity。
|
|
24
|
-
*
|
|
25
|
-
* @param value - 待校验数值。
|
|
26
|
-
* @param name - 用于错误消息的参数名称。
|
|
27
|
-
* @throws `RangeError` 当值为 NaN。
|
|
28
|
-
*/
|
|
29
|
-
const assertNotNaN = (value: number, name: string): void => {
|
|
30
|
-
if (Number.isNaN(value)) throw new RangeError(`${name} cannot be NaN.`);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 校验有限数值。
|
|
35
|
-
*
|
|
36
|
-
* @param value - 待校验数值。
|
|
37
|
-
* @param name - 用于错误消息的参数名称。
|
|
38
|
-
* @throws `RangeError` 当值为 NaN 或无穷大。
|
|
39
|
-
*/
|
|
40
|
-
const assertFinite = (value: number, name: string): void => {
|
|
41
|
-
if (!Number.isFinite(value)) throw new RangeError(`${name} must be finite.`);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 使用科学计数法移动十进制位。
|
|
46
|
-
*
|
|
47
|
-
* @remarks 该方式用于 `roundTo`,可减少直接乘除十次幂造成的额外二进制舍入误差。
|
|
48
|
-
* @param value - 原始数值。
|
|
49
|
-
* @param exponent - 十进制移动位数;正数向右移动。
|
|
50
|
-
* @returns 移动后的数值。
|
|
51
|
-
*/
|
|
52
|
-
const shiftDecimal = (value: number, exponent: number): number => {
|
|
53
|
-
if (Object.is(value, -0)) return -0;
|
|
54
|
-
const [coefficient = "0", currentExponent = "0"] = value.toString().split("e");
|
|
55
|
-
return Number(`${coefficient}e${Number(currentExponent) + exponent}`);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* 把数字限制在闭区间内。
|
|
60
|
-
*
|
|
61
|
-
* @param value - 需要限制的数字。
|
|
62
|
-
* @param minimum - 闭区间下界。
|
|
63
|
-
* @param maximum - 闭区间上界。
|
|
64
|
-
* @returns `minimum <= result <= maximum` 的值。
|
|
65
|
-
* @throws `RangeError` 当参数为 `NaN` 或下界大于上界。
|
|
66
|
-
*/
|
|
67
|
-
export function clamp(value: number, minimum: number, maximum: number): number {
|
|
68
|
-
assertNotNaN(value, "value");
|
|
69
|
-
assertNotNaN(minimum, "minimum");
|
|
70
|
-
assertNotNaN(maximum, "maximum");
|
|
71
|
-
if (minimum > maximum) throw new RangeError("minimum cannot be greater than maximum.");
|
|
72
|
-
return Math.min(Math.max(value, minimum), maximum);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* 判断数字是否位于指定区间。
|
|
77
|
-
*
|
|
78
|
-
* @param value - 待检查数字。
|
|
79
|
-
* @param minimum - 包含的下界。
|
|
80
|
-
* @param maximum - 上界。
|
|
81
|
-
* @param includeMaximum - 是否包含上界;默认使用半开区间 `[minimum, maximum)`。
|
|
82
|
-
* @returns 数字满足区间边界时返回 `true`。
|
|
83
|
-
* @throws `RangeError` 当参数为 `NaN` 或下界大于上界。
|
|
84
|
-
*/
|
|
85
|
-
export function inRange(value: number, minimum: number, maximum: number, includeMaximum = false): boolean {
|
|
86
|
-
assertNotNaN(value, "value");
|
|
87
|
-
assertNotNaN(minimum, "minimum");
|
|
88
|
-
assertNotNaN(maximum, "maximum");
|
|
89
|
-
if (minimum > maximum) throw new RangeError("minimum cannot be greater than maximum.");
|
|
90
|
-
return value >= minimum && (includeMaximum ? value <= maximum : value < maximum);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* 按十进制位数四舍五入。
|
|
95
|
-
*
|
|
96
|
-
* @remarks IEEE-754 浮点数仍可能存在不可表示误差;财务金额应使用十进制定点方案。
|
|
97
|
-
* @param value - 有限数字。
|
|
98
|
-
* @param digits - 小数位数;负数表示十位、百位等,范围 -15 至 15。
|
|
99
|
-
* @returns 按 `Math.round` 语义舍入后的数字。
|
|
100
|
-
* @throws `RangeError` 当值非有限或位数超出范围。
|
|
101
|
-
*/
|
|
102
|
-
export function roundTo(value: number, digits = 0): number {
|
|
103
|
-
assertFinite(value, "value");
|
|
104
|
-
if (!Number.isSafeInteger(digits) || digits < -15 || digits > 15) {
|
|
105
|
-
throw new RangeError("digits must be a safe integer between -15 and 15.");
|
|
106
|
-
}
|
|
107
|
-
const shifted = shiftDecimal(value, digits);
|
|
108
|
-
// 对已经没有可表示小数的大数,乘以 10^digits 可能溢出;此时舍入不会改变值。
|
|
109
|
-
if (!Number.isFinite(shifted)) return value;
|
|
110
|
-
return shiftDecimal(Math.round(shifted), -digits);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* 对有限数字求和。
|
|
115
|
-
*
|
|
116
|
-
* @param values - 不会被修改的数字数组。
|
|
117
|
-
* @returns 算术和;空数组返回 `0`。
|
|
118
|
-
* @throws `RangeError` 当任一值非有限或累计结果溢出。
|
|
119
|
-
*/
|
|
120
|
-
export function sum(values: readonly number[]): number {
|
|
121
|
-
let total = 0;
|
|
122
|
-
let compensation = 0;
|
|
123
|
-
values.forEach((value) => {
|
|
124
|
-
assertFinite(value, "value");
|
|
125
|
-
// Kahan 补偿保存上一次浮点加法丢失的低位,减少大量小数累计误差。
|
|
126
|
-
const adjusted = value - compensation;
|
|
127
|
-
const next = total + adjusted;
|
|
128
|
-
if (!Number.isFinite(next)) throw new RangeError("The sum exceeds the finite number range.");
|
|
129
|
-
compensation = next - total - adjusted;
|
|
130
|
-
total = next;
|
|
131
|
-
});
|
|
132
|
-
return total;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* 计算有限数字的算术平均值。
|
|
137
|
-
*
|
|
138
|
-
* @param values - 不会被修改的数字数组。
|
|
139
|
-
* @returns 空数组或只有稀疏空位的数组返回 `undefined`;空位不参与分母。
|
|
140
|
-
* @throws `RangeError` 当任一值非有限。
|
|
141
|
-
*/
|
|
142
|
-
export function average(values: readonly number[]): number | undefined {
|
|
143
|
-
let count = 0;
|
|
144
|
-
let mean = 0;
|
|
145
|
-
values.forEach((value) => {
|
|
146
|
-
assertFinite(value, "value");
|
|
147
|
-
count += 1;
|
|
148
|
-
// 加权增量形式避免先求和导致 MAX_VALUE + MAX_VALUE 溢出。
|
|
149
|
-
mean = mean * ((count - 1) / count) + value / count;
|
|
150
|
-
});
|
|
151
|
-
return count === 0 ? undefined : mean;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* 在两个数字间做线性插值。
|
|
156
|
-
*
|
|
157
|
-
* @remarks `amount` 不限制在 0 至 1;区间外的值会执行线性外推。
|
|
158
|
-
* @param start - `amount = 0` 时的起点。
|
|
159
|
-
* @param end - `amount = 1` 时的终点。
|
|
160
|
-
* @param amount - 插值或外推比例。
|
|
161
|
-
* @returns 线性计算结果。
|
|
162
|
-
* @throws `RangeError` 当任一参数非有限或结果超出有限数字范围。
|
|
163
|
-
*/
|
|
164
|
-
export function lerp(start: number, end: number, amount: number): number {
|
|
165
|
-
assertFinite(start, "start");
|
|
166
|
-
assertFinite(end, "end");
|
|
167
|
-
assertFinite(amount, "amount");
|
|
168
|
-
const result = start * (1 - amount) + end * amount;
|
|
169
|
-
if (!Number.isFinite(result)) throw new RangeError("The interpolation result exceeds the finite number range.");
|
|
170
|
-
return result;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* 将非负字节数格式化为 SI 或 IEC 单位。
|
|
175
|
-
*
|
|
176
|
-
* @param bytes - 非负有限字节数。
|
|
177
|
-
* @param options - 基数、小数位和语言选项。
|
|
178
|
-
* @returns 例如 `1.5 KiB`。
|
|
179
|
-
* @throws `RangeError` 当字节数为负或非有限、基数不是 1000/1024、小数位非法,或 Locale 无效。
|
|
180
|
-
*/
|
|
181
|
-
export function formatBytes(bytes: number, options: FormatBytesOptions = {}): string {
|
|
182
|
-
assertFinite(bytes, "bytes");
|
|
183
|
-
if (bytes < 0) throw new RangeError("bytes cannot be negative.");
|
|
184
|
-
const requestedBase: unknown = options.base ?? 1024;
|
|
185
|
-
if (requestedBase !== 1000 && requestedBase !== 1024) throw new RangeError("base must be 1000 or 1024.");
|
|
186
|
-
const base = requestedBase;
|
|
187
|
-
const decimals = options.decimals ?? 2;
|
|
188
|
-
if (!Number.isSafeInteger(decimals) || decimals < 0 || decimals > 20) {
|
|
189
|
-
throw new RangeError("decimals must be a safe integer between 0 and 20.");
|
|
190
|
-
}
|
|
191
|
-
if (bytes === 0) return "0 B";
|
|
192
|
-
|
|
193
|
-
const units = base === 1024 ? binaryByteUnits : byteUnits;
|
|
194
|
-
const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(base)), units.length - 1);
|
|
195
|
-
const value = bytes / base ** exponent;
|
|
196
|
-
const formatted = new Intl.NumberFormat(options.locale ?? "en-US", {
|
|
197
|
-
maximumFractionDigits: decimals,
|
|
198
|
-
minimumFractionDigits: 0,
|
|
199
|
-
useGrouping: false,
|
|
200
|
-
}).format(value);
|
|
201
|
-
return `${formatted} ${units[exponent] ?? units.at(-1)}`;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* 使用 Web Crypto 在半开区间内生成无偏安全随机整数。
|
|
206
|
-
*
|
|
207
|
-
* @param minimum - 包含的安全整数下界。
|
|
208
|
-
* @param maximumExclusive - 不包含的安全整数上界;区间宽度最大为 2^32。
|
|
209
|
-
* @returns 均匀分布在 `[minimum, maximumExclusive)` 的安全整数。
|
|
210
|
-
* @throws 缺少 Web Crypto 时抛出 `Error`;参数非法时抛出 `RangeError`。
|
|
211
|
-
*/
|
|
212
|
-
export function secureRandomInt(minimum: number, maximumExclusive: number): number {
|
|
213
|
-
if (!Number.isSafeInteger(minimum) || !Number.isSafeInteger(maximumExclusive)) {
|
|
214
|
-
throw new RangeError("minimum and maximumExclusive must be safe integers.");
|
|
215
|
-
}
|
|
216
|
-
const range = maximumExclusive - minimum;
|
|
217
|
-
const uint32Range = 0x1_0000_0000;
|
|
218
|
-
if (range <= 0 || range > uint32Range) {
|
|
219
|
-
throw new RangeError("The interval must be non-empty and no wider than 2^32.");
|
|
220
|
-
}
|
|
221
|
-
const crypto = runtimeNumberGlobals.crypto;
|
|
222
|
-
if (typeof crypto?.getRandomValues !== "function") {
|
|
223
|
-
throw new Error("Web Crypto random generation is unavailable in the current runtime.");
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// 只接受可以被区间宽度整除的最大 2^32 前缀,消除取模偏差。
|
|
227
|
-
const limit = Math.floor(uint32Range / range) * range;
|
|
228
|
-
const values = new Uint32Array(1);
|
|
229
|
-
let sample: number;
|
|
230
|
-
do {
|
|
231
|
-
crypto.getRandomValues(values);
|
|
232
|
-
sample = values[0] ?? uint32Range;
|
|
233
|
-
} while (sample >= limit);
|
|
234
|
-
return minimum + (sample % range);
|
|
235
|
-
}
|
package/src/object/index.ts
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
/** URL 查询参数支持的单值类型。 */
|
|
2
|
-
export type QueryPrimitive = bigint | boolean | number | string | null | undefined;
|
|
3
|
-
|
|
4
|
-
/** URL 查询参数值;数组使用重复键表示。 */
|
|
5
|
-
export type QueryValue = QueryPrimitive | readonly QueryPrimitive[];
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* 判断 Query Value 是否为重复参数数组。
|
|
9
|
-
*
|
|
10
|
-
* @param value - 单值或数组形式的 Query Value。
|
|
11
|
-
* @returns 是只读原始值数组时返回 `true`。
|
|
12
|
-
*/
|
|
13
|
-
const isQueryPrimitiveArray = (value: QueryValue): value is readonly QueryPrimitive[] => Array.isArray(value);
|
|
14
|
-
|
|
15
|
-
/** {@link toQueryString} 的序列化选项。 */
|
|
16
|
-
export interface QueryStringOptions {
|
|
17
|
-
/** 返回非空结果时是否添加 `?`;默认 `false`。 */
|
|
18
|
-
prefixQuestionMark?: boolean;
|
|
19
|
-
/** 是否按键的 UTF-16 码元顺序稳定排序;默认保留对象枚举顺序。 */
|
|
20
|
-
sort?: boolean;
|
|
21
|
-
/** 空格编码方式;默认遵循表单编码并输出 `+`。 */
|
|
22
|
-
space?: "percent" | "plus";
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 安全写入结果对象的自有可枚举属性。
|
|
27
|
-
*
|
|
28
|
-
* @remarks 使用 `defineProperty` 避免 `__proto__` 触发 Setter,并显式拒绝三个原型污染键。
|
|
29
|
-
* @param target - 要写入的结果对象。
|
|
30
|
-
* @param key - 自有属性键。
|
|
31
|
-
* @param value - 属性值。
|
|
32
|
-
* @throws `TypeError` 当键为 `__proto__`、`prototype` 或 `constructor`。
|
|
33
|
-
*/
|
|
34
|
-
const defineEnumerableProperty = (target: object, key: PropertyKey, value: unknown): void => {
|
|
35
|
-
Object.defineProperty(target, key, { configurable: true, enumerable: true, value, writable: true });
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 判断值是否是普通对象。
|
|
40
|
-
*
|
|
41
|
-
* @param value - 任意待检查值。
|
|
42
|
-
* @returns 原型为 `Object.prototype` 或 `null` 时返回 `true`。
|
|
43
|
-
*/
|
|
44
|
-
export function isPlainObject(value: unknown): value is Record<PropertyKey, unknown> {
|
|
45
|
-
if (typeof value !== "object" || value === null) return false;
|
|
46
|
-
const prototype = Object.getPrototypeOf(value) as object | null;
|
|
47
|
-
return prototype === null || prototype === Object.prototype;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* 安全判断对象是否拥有自己的属性。
|
|
52
|
-
*
|
|
53
|
-
* @remarks 不调用可能被对象覆盖的 `hasOwnProperty`。
|
|
54
|
-
* @param value - 待检查对象。
|
|
55
|
-
* @param key - 字符串、数字或 Symbol 属性键。
|
|
56
|
-
* @returns 属性为对象自有属性时返回 `true`,并收窄键类型。
|
|
57
|
-
*/
|
|
58
|
-
export function hasOwn<ObjectType extends object, Key extends PropertyKey>(value: ObjectType, key: Key): key is Key & keyof ObjectType {
|
|
59
|
-
return Object.hasOwn(value, key);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 从对象中选择指定自有可枚举属性。
|
|
64
|
-
*
|
|
65
|
-
* @param source - 不会被修改的源对象。
|
|
66
|
-
* @param keys - 需要保留的键;不存在的键被忽略。
|
|
67
|
-
* @returns 新对象,保持 `keys` 的遍历顺序。
|
|
68
|
-
*/
|
|
69
|
-
export function pick<Source extends object, Keys extends readonly (keyof Source)[]>(source: Source, keys: Keys): Pick<Source, Keys[number]> {
|
|
70
|
-
const result = {} as Pick<Source, Keys[number]>;
|
|
71
|
-
for (const key of keys) {
|
|
72
|
-
if (Object.prototype.propertyIsEnumerable.call(source, key)) defineEnumerableProperty(result, key, source[key]);
|
|
73
|
-
}
|
|
74
|
-
return result;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* 浅复制对象并删除指定属性。
|
|
79
|
-
*
|
|
80
|
-
* @param source - 不会被修改的源对象。
|
|
81
|
-
* @param keys - 需要排除的键。
|
|
82
|
-
* @returns 包含其余自有可枚举字符串与 Symbol 属性的新对象。
|
|
83
|
-
*/
|
|
84
|
-
export function omit<Source extends object, Keys extends readonly (keyof Source)[]>(source: Source, keys: Keys): Omit<Source, Keys[number]> {
|
|
85
|
-
const result = { ...source };
|
|
86
|
-
for (const key of keys) Reflect.deleteProperty(result, key);
|
|
87
|
-
return result;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* 映射对象的自有可枚举属性值。
|
|
92
|
-
*
|
|
93
|
-
* @param source - 不会被修改的源对象。
|
|
94
|
-
* @param mapper - 接收值、键和源对象的映射函数。
|
|
95
|
-
* @returns 保留原键的新对象。
|
|
96
|
-
*/
|
|
97
|
-
export function mapValues<Source extends object, Result>(
|
|
98
|
-
source: Source,
|
|
99
|
-
mapper: (value: Source[keyof Source], key: keyof Source, source: Source) => Result
|
|
100
|
-
): { [Key in keyof Source]: Result } {
|
|
101
|
-
const result = {} as { [Key in keyof Source]: Result };
|
|
102
|
-
for (const key of Reflect.ownKeys(source) as (keyof Source)[]) {
|
|
103
|
-
if (Object.prototype.propertyIsEnumerable.call(source, key)) defineEnumerableProperty(result, key, mapper(source[key], key, source));
|
|
104
|
-
}
|
|
105
|
-
return result;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* 对自有可枚举属性执行 SameValue 浅比较。
|
|
110
|
-
*
|
|
111
|
-
* @remarks 嵌套对象只比较引用;`NaN` 相等,`0` 与 `-0` 不相等。
|
|
112
|
-
* @param left - 第一对象。
|
|
113
|
-
* @param right - 第二对象。
|
|
114
|
-
* @returns 自有可枚举键集合与对应值均满足 SameValue 时返回 `true`。
|
|
115
|
-
*/
|
|
116
|
-
export function shallowEqual(left: object, right: object): boolean {
|
|
117
|
-
if (Object.is(left, right)) return true;
|
|
118
|
-
const leftKeys = Reflect.ownKeys(left).filter((key) => Object.prototype.propertyIsEnumerable.call(left, key));
|
|
119
|
-
const rightKeys = Reflect.ownKeys(right).filter((key) => Object.prototype.propertyIsEnumerable.call(right, key));
|
|
120
|
-
if (leftKeys.length !== rightKeys.length) return false;
|
|
121
|
-
return leftKeys.every((key) => Object.hasOwn(right, key) && Object.is(Reflect.get(left, key), Reflect.get(right, key)));
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* 把 Query 原始值规范化为文本。
|
|
126
|
-
*
|
|
127
|
-
* @param value - 已排除空值的字符串、数字、布尔值或 BigInt。
|
|
128
|
-
* @returns 与 URLSearchParams 兼容的文本值。
|
|
129
|
-
* @throws `RangeError` 当数字不是有限值。
|
|
130
|
-
*/
|
|
131
|
-
const serializeQueryValue = (value: Exclude<QueryPrimitive, null | undefined>): string => {
|
|
132
|
-
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
133
|
-
throw new RangeError("Query parameter numbers must be finite.");
|
|
134
|
-
}
|
|
135
|
-
return String(value);
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* 将对象序列化为标准 URL 查询字符串。
|
|
140
|
-
*
|
|
141
|
-
* @remarks `null` 与 `undefined` 被跳过;数组使用重复键;返回值不会修改输入。
|
|
142
|
-
* @param value - 查询参数对象。
|
|
143
|
-
* @param options - 排序、空格和问号前缀选项。
|
|
144
|
-
* @returns URL 编码后的查询字符串;没有参数时始终返回空字符串。
|
|
145
|
-
* @throws `RangeError` 当参数包含 `NaN` 或无穷数字。
|
|
146
|
-
*/
|
|
147
|
-
export function toQueryString(value: Readonly<Record<string, QueryValue>>, options: QueryStringOptions = {}): string {
|
|
148
|
-
const entries = Object.entries(value);
|
|
149
|
-
if (options.sort) entries.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0));
|
|
150
|
-
const parameters = new URLSearchParams();
|
|
151
|
-
for (const [key, rawValue] of entries) {
|
|
152
|
-
const values = isQueryPrimitiveArray(rawValue) ? rawValue : [rawValue];
|
|
153
|
-
for (const item of values) {
|
|
154
|
-
if (item !== null && item !== undefined) parameters.append(key, serializeQueryValue(item));
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
let result = parameters.toString();
|
|
158
|
-
if (options.space === "percent") result = result.replace(/\+/gu, "%20");
|
|
159
|
-
return result && options.prefixQuestionMark ? `?${result}` : result;
|
|
160
|
-
}
|