@fast-china/utils 2.1.9 → 2.1.11
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 +29 -0
- package/README.md +5 -2
- package/README.zh.md +5 -2
- package/dist/array/index.mjs +21 -21
- package/dist/array/index.mjs.map +1 -1
- package/dist/async/index.mjs +32 -32
- package/dist/async/index.mjs.map +1 -1
- package/dist/base64/index.mjs +44 -51
- package/dist/base64/index.mjs.map +1 -1
- package/dist/color/index.mjs +33 -33
- package/dist/color/index.mjs.map +1 -1
- package/dist/crypto/index.mjs +153 -155
- package/dist/crypto/index.mjs.map +1 -1
- package/dist/date/index.mjs +79 -46
- package/dist/date/index.mjs.map +1 -1
- package/dist/dom/style.mjs +7 -7
- package/dist/dom/style.mjs.map +1 -1
- package/dist/env/index.mjs +9 -14
- package/dist/env/index.mjs.map +1 -1
- package/dist/function/index.mjs +4 -4
- package/dist/function/index.mjs.map +1 -1
- package/dist/identity/index.mjs +7 -7
- package/dist/identity/index.mjs.map +1 -1
- package/dist/index.d.mts +393 -366
- package/dist/index.global.min.js +2 -2
- package/dist/index.global.min.js.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/internal/text.mjs +70 -26
- package/dist/internal/text.mjs.map +1 -1
- package/dist/logger/index.mjs +12 -13
- package/dist/logger/index.mjs.map +1 -1
- package/dist/number/index.mjs +63 -42
- package/dist/number/index.mjs.map +1 -1
- package/dist/object/index.mjs +32 -31
- package/dist/object/index.mjs.map +1 -1
- package/dist/storage/index.mjs +58 -63
- package/dist/storage/index.mjs.map +1 -1
- package/dist/string/index.mjs +60 -64
- package/dist/string/index.mjs.map +1 -1
- package/dist/vue/breakpoints.mjs +14 -10
- package/dist/vue/breakpoints.mjs.map +1 -1
- package/dist/vue/element-size.mjs +4 -4
- package/dist/vue/element-size.mjs.map +1 -1
- package/dist/vue/emits.mjs +7 -7
- package/dist/vue/emits.mjs.map +1 -1
- package/dist/vue/event-listener.mjs +5 -5
- package/dist/vue/event-listener.mjs.map +1 -1
- package/dist/vue/expose.mjs +3 -3
- package/dist/vue/expose.mjs.map +1 -1
- package/dist/vue/func.mjs +2 -2
- package/dist/vue/func.mjs.map +1 -1
- package/dist/vue/index.mjs +2 -1
- package/dist/vue/install.mjs +24 -24
- package/dist/vue/install.mjs.map +1 -1
- package/dist/vue/now.mjs +4 -5
- package/dist/vue/now.mjs.map +1 -1
- package/dist/vue/props.mjs +5 -5
- package/dist/vue/props.mjs.map +1 -1
- package/dist/vue/render.mjs +2 -2
- package/dist/vue/render.mjs.map +1 -1
- package/dist/vue/resize-observer.mjs +4 -6
- package/dist/vue/resize-observer.mjs.map +1 -1
- package/dist/vue/slots.mjs.map +1 -1
- package/dist/vue/transition.mjs +58 -0
- package/dist/vue/transition.mjs.map +1 -0
- package/dist/vue/window-size.mjs +2 -4
- package/dist/vue/window-size.mjs.map +1 -1
- package/dist/vue/with.mjs +1 -1
- package/dist/vue/with.mjs.map +1 -1
- package/package.json +2 -1
- package/dist/internal/runtime.mjs +0 -32
- package/dist/internal/runtime.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -26,7 +26,8 @@ import { useNow } from "./vue/now.mjs";
|
|
|
26
26
|
import { definePropType, useProps } from "./vue/props.mjs";
|
|
27
27
|
import { useRender } from "./vue/render.mjs";
|
|
28
28
|
import { makeSlots } from "./vue/slots.mjs";
|
|
29
|
+
import { useTransition } from "./vue/transition.mjs";
|
|
29
30
|
import { useWindowSize } from "./vue/window-size.mjs";
|
|
30
31
|
import { withDefineType } from "./vue/with.mjs";
|
|
31
32
|
import "./vue/index.mjs";
|
|
32
|
-
export { AESDecrypt, AESDecryptAuthenticated, AESDecryptWithPassword, AESEncrypt, AESEncryptAuthenticated, AESEncryptWithPassword, DeriveECDHKeySHA256, DeriveECDHSecret, ECDSASign, ECDSAVerify, FixedTimeEquals, GenerateECDHKeyPair, GenerateECDSAKeyPair, GenerateRSAKeyPair, GenerateRandomBytes, HKDFSHA256, HMACSHA256Encrypt, HMACSHA384Encrypt, HMACSHA512Encrypt, HashPasswordPBKDF2SHA256, Local, MD5Encrypt, PBKDF2SHA256, RSADecryptOAEP, RSAEncryptOAEP, RSASignPSS, RSAVerifyPSS, SHA1Encrypt, SHA256Bytes, SHA256Encrypt, SHA384Bytes, SHA384Encrypt, SHA512Bytes, SHA512Encrypt, Session, VerifyPasswordPBKDF2SHA256, addCssUnit, addDays, addMonths, addYears, allEqualBy, average, base64StorageCodec, callOptionalFunction, camelCase, chunk, clamp, cloneDeep, configureInstallationIdentity, configureLogger, configureStorage, contrastRatio, copy, createDateRangeShortcuts, createDateShortcuts, createLogger, createOneMonthRangeFromToday, debounce, decodeBase64, decodeBase64Bytes, decodeBase64Url, decodeBase64UrlBytes, decodeLatin1Base64, decodeSecureBase64, decodeURIComponentRepeatedly, definePropType, detectRuntime, difference, encodeBase64, encodeBase64Bytes, encodeBase64Url, encodeBase64UrlBytes, encodeLatin1Base64, encodeSecureBase64, endOfDay, escapeHtml, formatBytes, formatChineseRelativeTime, formatHexColor, formatRelativeTime, generateUuidV4, getLocalDayBounds, getLocalTimeGreeting, getOrCreateInstallationId, getStartOfToday, groupBy, hasDuplicatesBy, hasOwn, hasWebCrypto, inRange, installationIdentity, intersection, isBrowser, isDateAfterNow, isEqual, isFuture, isMobileUserAgent, isNode, isPlainObject, isSameDay, isStorageConfigured, isTabletUserAgent, isUniApp, isUuidV4, isValidDate, isValidJson, isWebWorker, isWithinInterval, kebabCase, lerp, logger, lowerFirst, makeSlots, mapConcurrent, mapValues, mixHexColorWithBlack, mixHexColorWithWhite, mixHexColors, normalizeWhitespace, omit, omitBy, once, parseHexColor, parseQueryString, partition, pascalCase, pick, pickBy, pickHigherContrastColor, randomInt, randomString, relativeLuminance, removeNullishValues, retry, roundTo, serializeStyle, shallowEqual, sleep, splitWords, startOfDay, sum, symmetricDifference, throttle, toDate, toQueryString, truncateGraphemes, unique, uniqueBy, upperFirst, useBreakpoints, useElementSize, useEmits, useEventListener, useExpose, useNow, useProps, useRender, useResizeObserver, useWindowSize, withDefineType, withInstall, withInstallDirective, withNoopInstall, withTimeout };
|
|
33
|
+
export { AESDecrypt, AESDecryptAuthenticated, AESDecryptWithPassword, AESEncrypt, AESEncryptAuthenticated, AESEncryptWithPassword, DeriveECDHKeySHA256, DeriveECDHSecret, ECDSASign, ECDSAVerify, FixedTimeEquals, GenerateECDHKeyPair, GenerateECDSAKeyPair, GenerateRSAKeyPair, GenerateRandomBytes, HKDFSHA256, HMACSHA256Encrypt, HMACSHA384Encrypt, HMACSHA512Encrypt, HashPasswordPBKDF2SHA256, Local, MD5Encrypt, PBKDF2SHA256, RSADecryptOAEP, RSAEncryptOAEP, RSASignPSS, RSAVerifyPSS, SHA1Encrypt, SHA256Bytes, SHA256Encrypt, SHA384Bytes, SHA384Encrypt, SHA512Bytes, SHA512Encrypt, Session, VerifyPasswordPBKDF2SHA256, addCssUnit, addDays, addMonths, addYears, allEqualBy, average, base64StorageCodec, callOptionalFunction, camelCase, chunk, clamp, cloneDeep, configureInstallationIdentity, configureLogger, configureStorage, contrastRatio, copy, createDateRangeShortcuts, createDateShortcuts, createLogger, createOneMonthRangeFromToday, debounce, decodeBase64, decodeBase64Bytes, decodeBase64Url, decodeBase64UrlBytes, decodeLatin1Base64, decodeSecureBase64, decodeURIComponentRepeatedly, definePropType, detectRuntime, difference, encodeBase64, encodeBase64Bytes, encodeBase64Url, encodeBase64UrlBytes, encodeLatin1Base64, encodeSecureBase64, endOfDay, escapeHtml, formatBytes, formatChineseRelativeTime, formatHexColor, formatRelativeTime, generateUuidV4, getLocalDayBounds, getLocalTimeGreeting, getOrCreateInstallationId, getStartOfToday, groupBy, hasDuplicatesBy, hasOwn, hasWebCrypto, inRange, installationIdentity, intersection, isBrowser, isDateAfterNow, isEqual, isFuture, isMobileUserAgent, isNode, isPlainObject, isSameDay, isStorageConfigured, isTabletUserAgent, isUniApp, isUuidV4, isValidDate, isValidJson, isWebWorker, isWithinInterval, kebabCase, lerp, logger, lowerFirst, makeSlots, mapConcurrent, mapValues, mixHexColorWithBlack, mixHexColorWithWhite, mixHexColors, normalizeWhitespace, omit, omitBy, once, parseHexColor, parseQueryString, partition, pascalCase, pick, pickBy, pickHigherContrastColor, randomInt, randomString, relativeLuminance, removeNullishValues, retry, roundTo, serializeStyle, shallowEqual, sleep, splitWords, startOfDay, sum, symmetricDifference, throttle, toDate, toQueryString, truncateGraphemes, unique, uniqueBy, upperFirst, useBreakpoints, useElementSize, useEmits, useEventListener, useExpose, useNow, useProps, useRender, useResizeObserver, useTransition, useWindowSize, withDefineType, withInstall, withInstallDirective, withNoopInstall, withTimeout };
|
package/dist/internal/text.mjs
CHANGED
|
@@ -1,34 +1,78 @@
|
|
|
1
1
|
//#region src/internal/text.ts
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* 使用内部实现解码 UTF-8,不依赖平台 Encoding API。
|
|
4
4
|
*
|
|
5
|
-
* @
|
|
6
|
-
* @
|
|
5
|
+
* @param input - 不会被修改的字节或 ArrayBuffer
|
|
6
|
+
* @param options - 默认严格拒绝非法 UTF-8 并移除开头的 BOM;`fatal: false` 替换非法序列,`ignoreBOM: true` 保留 BOM。
|
|
7
|
+
* @returns 解码文本
|
|
8
|
+
* @throws `TypeError` 当严格模式的输入包含非法 UTF-8。
|
|
7
9
|
*/
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const decodeUtf8 = (input, options = {}) => {
|
|
11
|
+
const { fatal = true, ignoreBOM = false } = options;
|
|
12
|
+
const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
13
|
+
let result = "";
|
|
14
|
+
for (let index = 0; index < bytes.length;) {
|
|
15
|
+
const start = index;
|
|
16
|
+
const first = bytes[index++] ?? 0;
|
|
17
|
+
let codePoint = first;
|
|
18
|
+
let remaining = 0;
|
|
19
|
+
let lower = 128;
|
|
20
|
+
let upper = 191;
|
|
21
|
+
if (first >= 194 && first <= 223) {
|
|
22
|
+
codePoint = first & 31;
|
|
23
|
+
remaining = 1;
|
|
24
|
+
} else if (first >= 224 && first <= 239) {
|
|
25
|
+
codePoint = first & 15;
|
|
26
|
+
remaining = 2;
|
|
27
|
+
if (first === 224) lower = 160;
|
|
28
|
+
if (first === 237) upper = 159;
|
|
29
|
+
} else if (first >= 240 && first <= 244) {
|
|
30
|
+
codePoint = first & 7;
|
|
31
|
+
remaining = 3;
|
|
32
|
+
if (first === 240) lower = 144;
|
|
33
|
+
if (first === 244) upper = 143;
|
|
34
|
+
} else if (first > 127) {
|
|
35
|
+
if (fatal) throw new TypeError("The input is not valid UTF-8.");
|
|
36
|
+
result += "�";
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
let valid = true;
|
|
40
|
+
for (let continuation = 0; continuation < remaining; continuation += 1) {
|
|
41
|
+
const byte = bytes[index];
|
|
42
|
+
if (byte === void 0 || byte < lower || byte > upper) {
|
|
43
|
+
valid = false;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
index += 1;
|
|
47
|
+
codePoint = codePoint << 6 | byte & 63;
|
|
48
|
+
lower = 128;
|
|
49
|
+
upper = 191;
|
|
50
|
+
}
|
|
51
|
+
if (!valid) {
|
|
52
|
+
if (fatal) throw new TypeError("The input is not valid UTF-8.");
|
|
53
|
+
result += "�";
|
|
54
|
+
} else if (ignoreBOM || start !== 0 || codePoint !== 65279) result += String.fromCodePoint(codePoint);
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
12
57
|
};
|
|
13
58
|
/**
|
|
14
|
-
*
|
|
59
|
+
* 使用内部实现编码 UTF-8,不依赖平台 Encoding API。
|
|
15
60
|
*
|
|
16
|
-
* @
|
|
17
|
-
* @
|
|
61
|
+
* @param value - 待编码文本;孤立的 UTF-16 代理项按 TextEncoder 语义替换为 U+FFFD。
|
|
62
|
+
* @returns 使用独立 ArrayBuffer 的 UTF-8 字节数组
|
|
18
63
|
*/
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
64
|
+
const encodeUtf8 = (value) => {
|
|
65
|
+
const bytes = [];
|
|
66
|
+
for (const character of value) {
|
|
67
|
+
let codePoint = character.codePointAt(0) ?? 0;
|
|
68
|
+
if (codePoint >= 55296 && codePoint <= 57343) codePoint = 65533;
|
|
69
|
+
if (codePoint <= 127) bytes.push(codePoint);
|
|
70
|
+
else if (codePoint <= 2047) bytes.push(192 | codePoint >> 6, 128 | codePoint & 63);
|
|
71
|
+
else if (codePoint <= 65535) bytes.push(224 | codePoint >> 12, 128 | codePoint >> 6 & 63, 128 | codePoint & 63);
|
|
72
|
+
else bytes.push(240 | codePoint >> 18, 128 | codePoint >> 12 & 63, 128 | codePoint >> 6 & 63, 128 | codePoint & 63);
|
|
73
|
+
}
|
|
74
|
+
return Uint8Array.from(bytes);
|
|
23
75
|
};
|
|
24
|
-
/**
|
|
25
|
-
* 在确认平台能力后把 JavaScript 字符串编码为 UTF-8。
|
|
26
|
-
*
|
|
27
|
-
* @param value - 待编码文本。
|
|
28
|
-
* @returns 使用独立 ArrayBuffer 的 UTF-8 字节数组。
|
|
29
|
-
* @throws `Error` 当当前平台没有提供 `TextEncoder`。
|
|
30
|
-
*/
|
|
31
|
-
const encodeUtf8 = (value) => getTextEncoder().encode(value);
|
|
32
76
|
const parseJsonMarker = Symbol.for("@fast-china/utils/parse-json");
|
|
33
77
|
/** 把当前字符串解析为 JSON 值。 */
|
|
34
78
|
const parseJson = function() {
|
|
@@ -45,7 +89,7 @@ const ensureParseJsonExtension = () => {
|
|
|
45
89
|
const descriptor = Object.getOwnPropertyDescriptor(String.prototype, "parseJson");
|
|
46
90
|
if (descriptor !== void 0) {
|
|
47
91
|
if (typeof descriptor.value === "function" && Reflect.get(descriptor.value, parseJsonMarker) === true) return;
|
|
48
|
-
throw new TypeError("String.prototype.parseJson
|
|
92
|
+
throw new TypeError("String.prototype.parseJson is already defined by another implementation.");
|
|
49
93
|
}
|
|
50
94
|
try {
|
|
51
95
|
Object.defineProperty(String.prototype, "parseJson", {
|
|
@@ -55,13 +99,13 @@ const ensureParseJsonExtension = () => {
|
|
|
55
99
|
writable: true
|
|
56
100
|
});
|
|
57
101
|
} catch (cause) {
|
|
58
|
-
throw new TypeError("
|
|
102
|
+
throw new TypeError("The current runtime does not allow installing String.prototype.parseJson.", { cause });
|
|
59
103
|
}
|
|
60
104
|
};
|
|
61
105
|
/**
|
|
62
106
|
* 创建可链式解析 JSON 的原始字符串。
|
|
63
107
|
*
|
|
64
|
-
* @param text -
|
|
108
|
+
* @param text - 解码或解密后的原始文本
|
|
65
109
|
* @returns 可直接作为字符串使用或显式调用 `.parseJson<Value>()` 的结果。
|
|
66
110
|
*/
|
|
67
111
|
const createDecodedText = (text) => {
|
|
@@ -69,6 +113,6 @@ const createDecodedText = (text) => {
|
|
|
69
113
|
return text;
|
|
70
114
|
};
|
|
71
115
|
//#endregion
|
|
72
|
-
export { createDecodedText,
|
|
116
|
+
export { createDecodedText, decodeUtf8, encodeUtf8 };
|
|
73
117
|
|
|
74
118
|
//# sourceMappingURL=text.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.mjs","names":[],"sources":["../../src/internal/text.ts"],"sourcesContent":["/**\n *
|
|
1
|
+
{"version":3,"file":"text.mjs","names":[],"sources":["../../src/internal/text.ts"],"sourcesContent":["/**\n * 使用内部实现解码 UTF-8,不依赖平台 Encoding API。\n *\n * @param input - 不会被修改的字节或 ArrayBuffer\n * @param options - 默认严格拒绝非法 UTF-8 并移除开头的 BOM;`fatal: false` 替换非法序列,`ignoreBOM: true` 保留 BOM。\n * @returns 解码文本\n * @throws `TypeError` 当严格模式的输入包含非法 UTF-8。\n */\nexport const decodeUtf8 = (input: Uint8Array | ArrayBuffer, options: { fatal?: boolean; ignoreBOM?: boolean } = {}): string => {\n\tconst { fatal = true, ignoreBOM = false } = options;\n\tconst bytes = input instanceof Uint8Array ? input : new Uint8Array(input);\n\tlet result = \"\";\n\tfor (let index = 0; index < bytes.length;) {\n\t\tconst start = index;\n\t\tconst first = bytes[index++] ?? 0;\n\t\tlet codePoint = first;\n\t\tlet remaining = 0;\n\t\tlet lower = 0x80;\n\t\tlet upper = 0xbf;\n\t\tif (first >= 0xc2 && first <= 0xdf) {\n\t\t\tcodePoint = first & 0x1f;\n\t\t\tremaining = 1;\n\t\t} else if (first >= 0xe0 && first <= 0xef) {\n\t\t\tcodePoint = first & 0x0f;\n\t\t\tremaining = 2;\n\t\t\tif (first === 0xe0) lower = 0xa0;\n\t\t\tif (first === 0xed) upper = 0x9f;\n\t\t} else if (first >= 0xf0 && first <= 0xf4) {\n\t\t\tcodePoint = first & 0x07;\n\t\t\tremaining = 3;\n\t\t\tif (first === 0xf0) lower = 0x90;\n\t\t\tif (first === 0xf4) upper = 0x8f;\n\t\t} else if (first > 0x7f) {\n\t\t\tif (fatal) throw new TypeError(\"The input is not valid UTF-8.\");\n\t\t\tresult += \"\\ufffd\";\n\t\t\tcontinue;\n\t\t}\n\t\tlet valid = true;\n\t\tfor (let continuation = 0; continuation < remaining; continuation += 1) {\n\t\t\tconst byte = bytes[index];\n\t\t\tif (byte === undefined || byte < lower || byte > upper) {\n\t\t\t\tvalid = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tindex += 1;\n\t\t\tcodePoint = (codePoint << 6) | (byte & 0x3f);\n\t\t\tlower = 0x80;\n\t\t\tupper = 0xbf;\n\t\t}\n\t\tif (!valid) {\n\t\t\tif (fatal) throw new TypeError(\"The input is not valid UTF-8.\");\n\t\t\t// 非法续字节留给下一轮处理,保持 WHATWG 的替换字符数量。\n\t\t\tresult += \"\\ufffd\";\n\t\t} else if (ignoreBOM || start !== 0 || codePoint !== 0xfeff) {\n\t\t\tresult += String.fromCodePoint(codePoint);\n\t\t}\n\t}\n\treturn result;\n};\n\n/**\n * 使用内部实现编码 UTF-8,不依赖平台 Encoding API。\n *\n * @param value - 待编码文本;孤立的 UTF-16 代理项按 TextEncoder 语义替换为 U+FFFD。\n * @returns 使用独立 ArrayBuffer 的 UTF-8 字节数组\n */\nexport const encodeUtf8 = (value: string): Uint8Array<ArrayBuffer> => {\n\tconst bytes: number[] = [];\n\tfor (const character of value) {\n\t\tlet codePoint = character.codePointAt(0) ?? 0;\n\t\tif (codePoint >= 0xd800 && codePoint <= 0xdfff) codePoint = 0xfffd;\n\t\tif (codePoint <= 0x7f) bytes.push(codePoint);\n\t\telse if (codePoint <= 0x7ff) bytes.push(0xc0 | (codePoint >> 6), 0x80 | (codePoint & 0x3f));\n\t\telse if (codePoint <= 0xffff) bytes.push(0xe0 | (codePoint >> 12), 0x80 | ((codePoint >> 6) & 0x3f), 0x80 | (codePoint & 0x3f));\n\t\telse bytes.push(0xf0 | (codePoint >> 18), 0x80 | ((codePoint >> 12) & 0x3f), 0x80 | ((codePoint >> 6) & 0x3f), 0x80 | (codePoint & 0x3f));\n\t}\n\treturn Uint8Array.from(bytes);\n};\n\n/** 解码或解密后的字符串扩展 */\ninterface DecodedTextExtension {\n\t/**\n\t * 显式把原始文本解析为 JSON 值。\n\t *\n\t * @remarks 泛型只描述调用方期望的类型,不验证实际 JSON 结构;不可信数据仍需执行运行时校验。\n\t * @returns `JSON.parse` 生成的对象、数组、标量或 `null`;文本不是合法 JSON 时返回原始字符串。\n\t */\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- 保留已发布的默认 any 与调用方指定返回类型。\n\tparseJson: <Value = any>() => Value;\n}\n\n/** 可直接作为原始字符串使用,并支持显式 JSON 解析的解码或解密结果。 */\nexport type DecodedText = string & DecodedTextExtension;\n\nconst parseJsonMarker = Symbol.for(\"@fast-china/utils/parse-json\");\n\n/** 把当前字符串解析为 JSON 值。 */\nconst parseJson = function <Value = ReturnType<typeof JSON.parse>>(this: string): Value {\n\tconst text = this.valueOf();\n\ttry {\n\t\treturn JSON.parse(text) as Value;\n\t} catch {\n\t\treturn text as Value;\n\t}\n};\n\nObject.defineProperty(parseJson, parseJsonMarker, { value: true });\n\n/** 按需安装不可枚举的字符串 JSON 解析扩展。 */\nconst ensureParseJsonExtension = (): void => {\n\tconst descriptor = Object.getOwnPropertyDescriptor(String.prototype, \"parseJson\");\n\tif (descriptor !== undefined) {\n\t\tif (typeof descriptor.value === \"function\" && Reflect.get(descriptor.value, parseJsonMarker) === true) return;\n\t\tthrow new TypeError(\"String.prototype.parseJson is already defined by another implementation.\");\n\t}\n\n\ttry {\n\t\tObject.defineProperty(String.prototype, \"parseJson\", {\n\t\t\tconfigurable: true,\n\t\t\tenumerable: false,\n\t\t\tvalue: parseJson,\n\t\t\twritable: true,\n\t\t});\n\t} catch (cause) {\n\t\tthrow new TypeError(\"The current runtime does not allow installing String.prototype.parseJson.\", { cause });\n\t}\n};\n\n/**\n * 创建可链式解析 JSON 的原始字符串。\n *\n * @param text - 解码或解密后的原始文本\n * @returns 可直接作为字符串使用或显式调用 `.parseJson<Value>()` 的结果。\n */\nexport const createDecodedText = (text: string): DecodedText => {\n\tensureParseJsonExtension();\n\treturn text as DecodedText;\n};\n"],"mappings":"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"}
|
package/dist/logger/index.mjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getRuntimePlus, getRuntimeUni } from "../internal/runtime.mjs";
|
|
2
1
|
//#region src/logger/index.ts
|
|
3
2
|
const levelPriority = {
|
|
4
3
|
debug: 10,
|
|
@@ -9,23 +8,23 @@ const levelPriority = {
|
|
|
9
8
|
/**
|
|
10
9
|
* 判断未知值是否为受支持日志级别。
|
|
11
10
|
*
|
|
12
|
-
* @param value -
|
|
11
|
+
* @param value - 待检查配置值
|
|
13
12
|
* @returns 值是 `debug`、`log`、`warn` 或 `error` 时返回 `true`。
|
|
14
13
|
*/
|
|
15
|
-
const isLogLevel = (value) => typeof value === "string" && Object.
|
|
14
|
+
const isLogLevel = (value) => typeof value === "string" && Object.prototype.hasOwnProperty.call(levelPriority, value);
|
|
16
15
|
/**
|
|
17
16
|
* 检测 uni-app App-Plus 日志环境。
|
|
18
17
|
*
|
|
19
18
|
* @returns 全局 `uni` 与 `plus` 同时存在时返回 `true`。
|
|
20
19
|
*/
|
|
21
20
|
const isUniAppPlus = () => {
|
|
22
|
-
return
|
|
21
|
+
return typeof uni !== "undefined" && typeof plus !== "undefined";
|
|
23
22
|
};
|
|
24
23
|
/**
|
|
25
24
|
* 把日志附加值转换为适合 HBuilderX 单行输出的文本。
|
|
26
25
|
*
|
|
27
26
|
* @remarks 循环引用会替换为 `[Circular]`,BigInt 保留 `n` 后缀,Error 优先输出堆栈。
|
|
28
|
-
* @param value -
|
|
27
|
+
* @param value - 任意日志附加值
|
|
29
28
|
* @returns 不会因 JSON 序列化失败而中断日志调用的文本。
|
|
30
29
|
*/
|
|
31
30
|
const formatSplitValue = (value) => {
|
|
@@ -66,7 +65,7 @@ const defaultConsoleSink = {
|
|
|
66
65
|
*
|
|
67
66
|
* @remarks 本库其他模块不会自动记录、吞掉或转换异常。日志内容可能进入持久化平台,
|
|
68
67
|
* 调用方不得传入密码、令牌、密钥或完整个人数据。
|
|
69
|
-
* @param options - 级别、前缀、输出目标和 uni-app App-Plus
|
|
68
|
+
* @param options - 级别、前缀、输出目标和 uni-app App-Plus 拆分选项
|
|
70
69
|
* @returns 不会修改全局控制台或其他日志器配置的新实例。
|
|
71
70
|
* @throws `RangeError` 当级别未知,或前缀、作用域不是有效的非空字符串。
|
|
72
71
|
*/
|
|
@@ -74,20 +73,20 @@ function createLogger(options = {}) {
|
|
|
74
73
|
const level = options.level ?? "debug";
|
|
75
74
|
const prefix = options.prefix ?? "Fast";
|
|
76
75
|
const sink = options.sink ?? defaultConsoleSink;
|
|
77
|
-
if (!isLogLevel(level)) throw new RangeError(
|
|
78
|
-
if (typeof prefix !== "string" || prefix.length === 0) throw new RangeError("
|
|
76
|
+
if (!isLogLevel(level)) throw new RangeError(`Unknown log level: ${String(level)}.`);
|
|
77
|
+
if (typeof prefix !== "string" || prefix.length === 0) throw new RangeError("The log prefix must be a nonempty string.");
|
|
79
78
|
const uniAppPlusSplit = options.uniAppPlusSplit ?? false;
|
|
80
79
|
/**
|
|
81
80
|
* 应用级别过滤、标题格式和平台输出策略。
|
|
82
81
|
*
|
|
83
|
-
* @param messageLevel -
|
|
84
|
-
* @param scope -
|
|
85
|
-
* @param content -
|
|
82
|
+
* @param messageLevel - 本条消息的严重级别
|
|
83
|
+
* @param scope - 模块、组件或业务来源名称
|
|
84
|
+
* @param content - 可选的消息与保持原始类型的附加值
|
|
86
85
|
* @throws `RangeError` 当作用域不是非空字符串或包含外围空白。
|
|
87
86
|
*/
|
|
88
87
|
const write = (messageLevel, scope, content) => {
|
|
89
|
-
if (typeof scope !== "string") throw new TypeError("
|
|
90
|
-
if (scope.length === 0 || scope.trim() !== scope) throw new RangeError("
|
|
88
|
+
if (typeof scope !== "string") throw new TypeError("The log scope must be a string.");
|
|
89
|
+
if (scope.length === 0 || scope.trim() !== scope) throw new RangeError("The log scope must be a nonempty string without surrounding whitespace.");
|
|
91
90
|
if (levelPriority[messageLevel] < levelPriority[level]) return;
|
|
92
91
|
const heading = `[${prefix}:${scope}]`;
|
|
93
92
|
const sinkMethod = messageLevel;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/logger/index.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/logger/index.ts"],"sourcesContent":["/** 日志严重级别,按从低到高排列 */\nexport type LogLevel = \"debug\" | \"log\" | \"warn\" | \"error\";\n\n/** 日志输出目标需要实现的最小控制台接口 */\nexport interface LoggerSink {\n\t/**\n\t * 接收通过级别过滤后的调试参数。\n\t * @param data - 已格式化的品牌、作用域、消息以及保持原始类型的附加值\n\t */\n\tdebug: (...data: unknown[]) => void;\n\t/**\n\t * 接收通过级别过滤后的普通日志参数;对应 Logger 的 `log` 级别。\n\t * @param data - 已格式化的品牌、作用域、消息以及保持原始类型的附加值\n\t */\n\tlog: (...data: unknown[]) => void;\n\t/**\n\t * 接收通过级别过滤后的警告参数。\n\t * @param data - 已格式化的品牌、作用域、消息以及保持原始类型的附加值\n\t */\n\twarn: (...data: unknown[]) => void;\n\t/**\n\t * 接收通过级别过滤后的错误参数。\n\t * @param data - 已格式化的品牌、作用域、消息以及保持原始类型的附加值\n\t */\n\terror: (...data: unknown[]) => void;\n}\n\n/** {@link createLogger} 的不可变配置 */\nexport interface LoggerOptions {\n\t/** 最低输出级别,默认 `debug`;低于该优先级的消息不会传给 Sink。 */\n\tlevel?: LogLevel;\n\t/** 日志品牌前缀,默认 `Fast`;必须是无外围空白的非空字符串。 */\n\tprefix?: string;\n\t/** 可注入输出目标,默认当前运行时的 `console`;Logger 不会修改该对象。 */\n\tsink?: LoggerSink;\n\t/** uni-app App-Plus/HBuilderX 中把附加参数逐条转成单行文本输出,默认 `false`;其他平台忽略。 */\n\tuniAppPlusSplit?: boolean;\n}\n\n/** 配置隔离的轻量日志器 */\nexport interface Logger {\n\t/**\n\t * 输出指定作用域的调试信息或数据。\n\t * @param scope - 模块、组件或业务来源名称\n\t * @param content - 可选的消息与附加值;非字符串值保持原始类型。\n\t * @throws `TypeError` 或 `RangeError` 当作用域不是无外围空白的非空字符串。\n\t */\n\tdebug: (scope: string, ...content: unknown[]) => void;\n\t/**\n\t * 输出指定作用域的普通信息或数据。\n\t * @param scope - 模块、组件或业务来源名称\n\t * @param content - 可选的消息与附加值;非字符串值保持原始类型。\n\t * @throws `TypeError` 或 `RangeError` 当作用域不是无外围空白的非空字符串。\n\t */\n\tlog: (scope: string, ...content: unknown[]) => void;\n\t/**\n\t * 输出指定作用域的警告信息或数据。\n\t * @param scope - 模块、组件或业务来源名称\n\t * @param content - 可选的消息与附加值;非字符串值保持原始类型。\n\t * @throws `TypeError` 或 `RangeError` 当作用域不是无外围空白的非空字符串。\n\t */\n\twarn: (scope: string, ...content: unknown[]) => void;\n\t/**\n\t * 输出指定作用域的错误信息或数据。\n\t * @param scope - 模块、组件或业务来源名称\n\t * @param content - 可选的消息与附加值;非字符串值保持原始类型。\n\t * @throws `TypeError` 或 `RangeError` 当作用域不是无外围空白的非空字符串。\n\t */\n\terror: (scope: string, ...content: unknown[]) => void;\n}\n\nconst levelPriority: Readonly<Record<LogLevel, number>> = {\n\tdebug: 10,\n\tlog: 20,\n\twarn: 30,\n\terror: 40,\n};\n\n/**\n * 判断未知值是否为受支持日志级别。\n *\n * @param value - 待检查配置值\n * @returns 值是 `debug`、`log`、`warn` 或 `error` 时返回 `true`。\n */\nconst isLogLevel = (value: unknown): value is LogLevel => typeof value === \"string\" && Object.prototype.hasOwnProperty.call(levelPriority, value);\n\n/**\n * 检测 uni-app App-Plus 日志环境。\n *\n * @returns 全局 `uni` 与 `plus` 同时存在时返回 `true`。\n */\nconst isUniAppPlus = (): boolean => {\n\treturn typeof uni !== \"undefined\" && typeof plus !== \"undefined\";\n};\n\n/**\n * 把日志附加值转换为适合 HBuilderX 单行输出的文本。\n *\n * @remarks 循环引用会替换为 `[Circular]`,BigInt 保留 `n` 后缀,Error 优先输出堆栈。\n * @param value - 任意日志附加值\n * @returns 不会因 JSON 序列化失败而中断日志调用的文本。\n */\nconst formatSplitValue = (value: unknown): string => {\n\tif (typeof value === \"string\") return value;\n\tif (typeof value === \"bigint\") return `${value.toString()}n`;\n\tif (value instanceof Error) return value.stack ?? `${value.name}: ${value.message}`;\n\tconst visited = new WeakSet();\n\ttry {\n\t\tconst serialized = JSON.stringify(\n\t\t\tvalue,\n\t\t\t(_key, item: unknown) => {\n\t\t\t\tif (typeof item === \"bigint\") return `${item.toString()}n`;\n\t\t\t\tif (typeof item !== \"object\" || item === null) return item;\n\t\t\t\tif (visited.has(item)) return \"[Circular]\";\n\t\t\t\tvisited.add(item);\n\t\t\t\treturn item;\n\t\t\t},\n\t\t\t2\n\t\t);\n\t\treturn typeof serialized === \"string\" ? serialized : String(value);\n\t} catch {\n\t\treturn String(value);\n\t}\n};\n\nconst defaultConsoleSink: LoggerSink = {\n\tdebug: (...data) => {\n\t\t// eslint-disable-next-line no-console\n\t\tif (typeof console.debug === \"function\") console.debug(...data);\n\t\t// eslint-disable-next-line no-console\n\t\telse console.log(...data);\n\t},\n\tlog: (...data) => {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(...data);\n\t},\n\twarn: (...data) => {\n\t\tconsole.warn(...data);\n\t},\n\terror: (...data) => {\n\t\tconsole.error(...data);\n\t},\n};\n\n/**\n * 创建独立日志器。\n *\n * @remarks 本库其他模块不会自动记录、吞掉或转换异常。日志内容可能进入持久化平台,\n * 调用方不得传入密码、令牌、密钥或完整个人数据。\n * @param options - 级别、前缀、输出目标和 uni-app App-Plus 拆分选项\n * @returns 不会修改全局控制台或其他日志器配置的新实例。\n * @throws `RangeError` 当级别未知,或前缀、作用域不是有效的非空字符串。\n */\nexport function createLogger(options: LoggerOptions = {}): Logger {\n\tconst level: unknown = options.level ?? \"debug\";\n\tconst prefix: unknown = options.prefix ?? \"Fast\";\n\tconst sink = options.sink ?? defaultConsoleSink;\n\tif (!isLogLevel(level)) throw new RangeError(`Unknown log level: ${String(level)}.`);\n\tif (typeof prefix !== \"string\" || prefix.length === 0) {\n\t\tthrow new RangeError(\"The log prefix must be a nonempty string.\");\n\t}\n\tconst uniAppPlusSplit = options.uniAppPlusSplit ?? false;\n\n\t/**\n\t * 应用级别过滤、标题格式和平台输出策略。\n\t *\n\t * @param messageLevel - 本条消息的严重级别\n\t * @param scope - 模块、组件或业务来源名称\n\t * @param content - 可选的消息与保持原始类型的附加值\n\t * @throws `RangeError` 当作用域不是非空字符串或包含外围空白。\n\t */\n\tconst write = (messageLevel: LogLevel, scope: string, content: readonly unknown[]) => {\n\t\tif (typeof scope !== \"string\") throw new TypeError(\"The log scope must be a string.\");\n\t\tif (scope.length === 0 || scope.trim() !== scope) {\n\t\t\tthrow new RangeError(\"The log scope must be a nonempty string without surrounding whitespace.\");\n\t\t}\n\t\tif (levelPriority[messageLevel] < levelPriority[level]) return;\n\t\tconst heading = `[${prefix}:${scope}]`;\n\t\tconst sinkMethod = messageLevel;\n\t\tif (uniAppPlusSplit && isUniAppPlus()) {\n\t\t\tconst [first, ...remaining] = content;\n\t\t\tif (typeof first === \"string\") {\n\t\t\t\tsink[sinkMethod](`${heading} ${first}`);\n\t\t\t\tfor (const item of remaining) sink[sinkMethod](formatSplitValue(item));\n\t\t\t} else {\n\t\t\t\tsink[sinkMethod](heading);\n\t\t\t\tfor (const item of content) sink[sinkMethod](formatSplitValue(item));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tsink[sinkMethod](heading, ...content);\n\t};\n\n\treturn {\n\t\tdebug: (scope, ...content) => {\n\t\t\twrite(\"debug\", scope, content);\n\t\t},\n\t\tlog: (scope, ...content) => {\n\t\t\twrite(\"log\", scope, content);\n\t\t},\n\t\twarn: (scope, ...content) => {\n\t\t\twrite(\"warn\", scope, content);\n\t\t},\n\t\terror: (scope, ...content) => {\n\t\t\twrite(\"error\", scope, content);\n\t\t},\n\t};\n}\n\nlet activeDefaultLogger = createLogger();\n\n/**\n * 替换默认 {@link logger} 的完整配置。\n *\n * @remarks 已创建的独立 Logger 不受影响;默认 Logger 对象引用保持稳定,并立即转发到新配置。\n * 省略选项会恢复 `createLogger()` 的全部默认值。\n * @param options - 默认 Logger 使用的级别、前缀、输出目标和 uni-app App-Plus 拆分选项。\n */\nexport function configureLogger(options: LoggerOptions = {}): void {\n\tactiveDefaultLogger = createLogger(options);\n}\n\n/** 默认使用 `Fast` 前缀和 `debug` 级别、可通过 {@link configureLogger} 配置的便捷日志器。 */\nexport const logger: Logger = {\n\tdebug: (scope, ...content) => {\n\t\tactiveDefaultLogger.debug(scope, ...content);\n\t},\n\tlog: (scope, ...content) => {\n\t\tactiveDefaultLogger.log(scope, ...content);\n\t},\n\twarn: (scope, ...content) => {\n\t\tactiveDefaultLogger.warn(scope, ...content);\n\t},\n\terror: (scope, ...content) => {\n\t\tactiveDefaultLogger.error(scope, ...content);\n\t},\n};\n"],"mappings":"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"}
|
package/dist/number/index.mjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { runtimeGlobals } from "../internal/runtime.mjs";
|
|
2
1
|
//#region src/number/index.ts
|
|
3
2
|
const byteUnits = [
|
|
4
3
|
"B",
|
|
@@ -25,30 +24,30 @@ const binaryByteUnits = [
|
|
|
25
24
|
/**
|
|
26
25
|
* 拒绝 NaN,同时允许具体 API 自行决定是否接受 Infinity。
|
|
27
26
|
*
|
|
28
|
-
* @param value -
|
|
29
|
-
* @param name -
|
|
27
|
+
* @param value - 待校验数值
|
|
28
|
+
* @param name - 用于错误消息的参数名称
|
|
30
29
|
* @throws `RangeError` 当值为 NaN。
|
|
31
30
|
*/
|
|
32
31
|
const assertNotNaN = (value, name) => {
|
|
33
|
-
if (Number.isNaN(value)) throw new RangeError(`\`${name}\`
|
|
32
|
+
if (Number.isNaN(value)) throw new RangeError(`\`${name}\` must not be NaN.`);
|
|
34
33
|
};
|
|
35
34
|
/**
|
|
36
35
|
* 校验有限数值。
|
|
37
36
|
*
|
|
38
|
-
* @param value -
|
|
39
|
-
* @param name -
|
|
37
|
+
* @param value - 待校验数值
|
|
38
|
+
* @param name - 用于错误消息的参数名称
|
|
40
39
|
* @throws `RangeError` 当值为 NaN 或无穷大。
|
|
41
40
|
*/
|
|
42
41
|
const assertFinite = (value, name) => {
|
|
43
|
-
if (!Number.isFinite(value)) throw new RangeError(`\`${name}\`
|
|
42
|
+
if (!Number.isFinite(value)) throw new RangeError(`\`${name}\` must be a finite number.`);
|
|
44
43
|
};
|
|
45
44
|
/**
|
|
46
45
|
* 使用科学计数法移动十进制位。
|
|
47
46
|
*
|
|
48
47
|
* @remarks 该方式用于 `roundTo`,可减少直接乘除十次幂造成的额外二进制舍入误差。
|
|
49
|
-
* @param value -
|
|
48
|
+
* @param value - 原始数值
|
|
50
49
|
* @param exponent - 十进制移动位数;正数向右移动。
|
|
51
|
-
* @returns
|
|
50
|
+
* @returns 移动后的数值
|
|
52
51
|
*/
|
|
53
52
|
const shiftDecimal = (value, exponent) => {
|
|
54
53
|
if (Object.is(value, -0)) return -0;
|
|
@@ -58,9 +57,9 @@ const shiftDecimal = (value, exponent) => {
|
|
|
58
57
|
/**
|
|
59
58
|
* 把数字限制在闭区间内。
|
|
60
59
|
*
|
|
61
|
-
* @param value -
|
|
62
|
-
* @param minimum -
|
|
63
|
-
* @param maximum -
|
|
60
|
+
* @param value - 需要限制的数字
|
|
61
|
+
* @param minimum - 闭区间下界
|
|
62
|
+
* @param maximum - 闭区间上界
|
|
64
63
|
* @returns `minimum <= result <= maximum` 的值。
|
|
65
64
|
* @throws `RangeError` 当参数为 `NaN` 或下界大于上界。
|
|
66
65
|
*/
|
|
@@ -68,15 +67,15 @@ function clamp(value, minimum, maximum) {
|
|
|
68
67
|
assertNotNaN(value, "value");
|
|
69
68
|
assertNotNaN(minimum, "minimum");
|
|
70
69
|
assertNotNaN(maximum, "maximum");
|
|
71
|
-
if (minimum > maximum) throw new RangeError("`minimum`
|
|
70
|
+
if (minimum > maximum) throw new RangeError("`minimum` must not exceed `maximum`.");
|
|
72
71
|
return Math.min(Math.max(value, minimum), maximum);
|
|
73
72
|
}
|
|
74
73
|
/**
|
|
75
74
|
* 判断数字是否位于指定区间。
|
|
76
75
|
*
|
|
77
|
-
* @param value -
|
|
78
|
-
* @param minimum -
|
|
79
|
-
* @param maximum -
|
|
76
|
+
* @param value - 待检查数字
|
|
77
|
+
* @param minimum - 包含的下界
|
|
78
|
+
* @param maximum - 上界
|
|
80
79
|
* @param includeMaximum - 是否包含上界;默认使用半开区间 `[minimum, maximum)`。
|
|
81
80
|
* @returns 数字满足区间边界时返回 `true`。
|
|
82
81
|
* @throws `RangeError` 当参数为 `NaN` 或下界大于上界。
|
|
@@ -85,21 +84,21 @@ function inRange(value, minimum, maximum, includeMaximum = false) {
|
|
|
85
84
|
assertNotNaN(value, "value");
|
|
86
85
|
assertNotNaN(minimum, "minimum");
|
|
87
86
|
assertNotNaN(maximum, "maximum");
|
|
88
|
-
if (minimum > maximum) throw new RangeError("`minimum`
|
|
87
|
+
if (minimum > maximum) throw new RangeError("`minimum` must not exceed `maximum`.");
|
|
89
88
|
return value >= minimum && (includeMaximum ? value <= maximum : value < maximum);
|
|
90
89
|
}
|
|
91
90
|
/**
|
|
92
91
|
* 按十进制位数四舍五入。
|
|
93
92
|
*
|
|
94
93
|
* @remarks IEEE-754 浮点数仍可能存在不可表示误差;财务金额应使用十进制定点方案。
|
|
95
|
-
* @param value -
|
|
94
|
+
* @param value - 有限数字
|
|
96
95
|
* @param digits - 小数位数;负数表示十位、百位等,范围 -15 至 15。
|
|
97
|
-
* @returns 按 `Math.round`
|
|
96
|
+
* @returns 按 `Math.round` 语义舍入后的数字
|
|
98
97
|
* @throws `RangeError` 当值非有限或位数超出范围。
|
|
99
98
|
*/
|
|
100
99
|
function roundTo(value, digits = 0) {
|
|
101
100
|
assertFinite(value, "value");
|
|
102
|
-
if (!Number.isSafeInteger(digits) || digits < -15 || digits > 15) throw new RangeError("`digits`
|
|
101
|
+
if (!Number.isSafeInteger(digits) || digits < -15 || digits > 15) throw new RangeError("`digits` must be a safe integer between -15 and 15.");
|
|
103
102
|
const shifted = shiftDecimal(value, digits);
|
|
104
103
|
if (!Number.isFinite(shifted)) return value;
|
|
105
104
|
return shiftDecimal(Math.round(shifted), -digits);
|
|
@@ -107,7 +106,7 @@ function roundTo(value, digits = 0) {
|
|
|
107
106
|
/**
|
|
108
107
|
* 对有限数字求和。
|
|
109
108
|
*
|
|
110
|
-
* @param values -
|
|
109
|
+
* @param values - 不会被修改的数字数组
|
|
111
110
|
* @returns 算术和;空数组返回 `0`。
|
|
112
111
|
* @throws `RangeError` 当任一值非有限或累计结果溢出。
|
|
113
112
|
*/
|
|
@@ -118,7 +117,7 @@ function sum(values) {
|
|
|
118
117
|
assertFinite(value, "value");
|
|
119
118
|
const adjusted = value - compensation;
|
|
120
119
|
const next = total + adjusted;
|
|
121
|
-
if (!Number.isFinite(next)) throw new RangeError("
|
|
120
|
+
if (!Number.isFinite(next)) throw new RangeError("The sum exceeds the finite number range.");
|
|
122
121
|
compensation = next - total - adjusted;
|
|
123
122
|
total = next;
|
|
124
123
|
});
|
|
@@ -127,7 +126,7 @@ function sum(values) {
|
|
|
127
126
|
/**
|
|
128
127
|
* 计算有限数字的算术平均值。
|
|
129
128
|
*
|
|
130
|
-
* @param values -
|
|
129
|
+
* @param values - 不会被修改的数字数组
|
|
131
130
|
* @returns 空数组或只有稀疏空位的数组返回 `undefined`;空位不参与分母。
|
|
132
131
|
* @throws `RangeError` 当任一值非有限。
|
|
133
132
|
*/
|
|
@@ -145,10 +144,10 @@ function average(values) {
|
|
|
145
144
|
* 在两个数字间做线性插值。
|
|
146
145
|
*
|
|
147
146
|
* @remarks `amount` 不限制在 0 至 1;区间外的值会执行线性外推。
|
|
148
|
-
* @param start - `amount = 0`
|
|
149
|
-
* @param end - `amount = 1`
|
|
150
|
-
* @param amount -
|
|
151
|
-
* @returns
|
|
147
|
+
* @param start - `amount = 0` 时的起点
|
|
148
|
+
* @param end - `amount = 1` 时的终点
|
|
149
|
+
* @param amount - 插值或外推比例
|
|
150
|
+
* @returns 线性计算结果
|
|
152
151
|
* @throws `RangeError` 当任一参数非有限或结果超出有限数字范围。
|
|
153
152
|
*/
|
|
154
153
|
function lerp(start, end, amount) {
|
|
@@ -156,56 +155,78 @@ function lerp(start, end, amount) {
|
|
|
156
155
|
assertFinite(end, "end");
|
|
157
156
|
assertFinite(amount, "amount");
|
|
158
157
|
const result = start * (1 - amount) + end * amount;
|
|
159
|
-
if (!Number.isFinite(result)) throw new RangeError("
|
|
158
|
+
if (!Number.isFinite(result)) throw new RangeError("The interpolated result exceeds the finite number range.");
|
|
160
159
|
return result;
|
|
161
160
|
}
|
|
161
|
+
/** 按十进制表示四舍五入,避免 toFixed 的二进制舍入差异,并展开科学计数法。 */
|
|
162
|
+
const formatByteAmount = (value, decimals) => {
|
|
163
|
+
const [coefficient = "0", exponent = "0"] = value.toString().split("e");
|
|
164
|
+
const [whole = "0", fraction = ""] = coefficient.split(".");
|
|
165
|
+
const digits = whole + fraction;
|
|
166
|
+
const point = whole.length + Number(exponent);
|
|
167
|
+
const integer = point <= 0 ? "0" : digits.slice(0, point).padEnd(point, "0");
|
|
168
|
+
const fractional = point < 0 ? "0".repeat(-point) + digits : digits.slice(Math.max(0, point));
|
|
169
|
+
let rounded = integer + fractional.slice(0, decimals);
|
|
170
|
+
if (Number(fractional[decimals] ?? "0") >= 5) {
|
|
171
|
+
let index = rounded.length - 1;
|
|
172
|
+
while (index >= 0 && rounded[index] === "9") index -= 1;
|
|
173
|
+
rounded = index < 0 ? `1${"0".repeat(rounded.length)}` : rounded.slice(0, index) + String(Number(rounded[index]) + 1) + "0".repeat(rounded.length - index - 1);
|
|
174
|
+
}
|
|
175
|
+
const keptDecimals = Math.min(decimals, fractional.length);
|
|
176
|
+
if (keptDecimals === 0) return rounded;
|
|
177
|
+
const end = rounded.length - keptDecimals;
|
|
178
|
+
const tail = rounded.slice(end).replace(/0+$/u, "");
|
|
179
|
+
return tail === "" ? rounded.slice(0, end) : `${rounded.slice(0, end)}.${tail}`;
|
|
180
|
+
};
|
|
162
181
|
/**
|
|
163
182
|
* 将非负字节数格式化为 SI 或 IEC 单位。
|
|
164
183
|
*
|
|
165
|
-
* @param bytes -
|
|
166
|
-
* @param options -
|
|
167
|
-
* @returns 例如 `1.5 KiB
|
|
184
|
+
* @param bytes - 非负有限字节数
|
|
185
|
+
* @param options - 基数、小数位和语言选项
|
|
186
|
+
* @returns 例如 `1.5 KiB`
|
|
187
|
+
* @remarks 默认数字格式使用内部实现;显式指定语言时使用 Intl.NumberFormat。
|
|
188
|
+
* @throws `Error` 当显式指定语言且环境不支持 `Intl.NumberFormat`。
|
|
168
189
|
* @throws `RangeError` 当字节数为负或非有限、基数不是 1000/1024、小数位非法,或 Locale 无效。
|
|
169
190
|
*/
|
|
170
191
|
function formatBytes(bytes, options = {}) {
|
|
171
192
|
assertFinite(bytes, "bytes");
|
|
172
|
-
if (bytes < 0) throw new RangeError("`bytes`
|
|
193
|
+
if (bytes < 0) throw new RangeError("`bytes` must not be negative.");
|
|
173
194
|
const requestedBase = options.base ?? 1024;
|
|
174
|
-
if (requestedBase !== 1e3 && requestedBase !== 1024) throw new RangeError("`base`
|
|
195
|
+
if (requestedBase !== 1e3 && requestedBase !== 1024) throw new RangeError("`base` must be 1000 or 1024.");
|
|
175
196
|
const base = requestedBase;
|
|
176
197
|
const decimals = options.decimals ?? 2;
|
|
177
|
-
if (!Number.isSafeInteger(decimals) || decimals < 0 || decimals > 20) throw new RangeError("`decimals`
|
|
198
|
+
if (!Number.isSafeInteger(decimals) || decimals < 0 || decimals > 20) throw new RangeError("`decimals` must be a safe integer between 0 and 20.");
|
|
178
199
|
if (bytes === 0) return "0 B";
|
|
179
200
|
const units = base === 1024 ? binaryByteUnits : byteUnits;
|
|
180
201
|
const exponent = Math.max(0, Math.min(Math.floor(Math.log(bytes) / Math.log(base)), units.length - 1));
|
|
181
202
|
const value = bytes / base ** exponent;
|
|
203
|
+
if (options.locale === void 0) return `${formatByteAmount(value, decimals)} ${units[exponent]}`;
|
|
204
|
+
if (typeof Intl === "undefined" || typeof Intl.NumberFormat !== "function") throw new Error("The current runtime does not support Intl.NumberFormat.");
|
|
182
205
|
return `${new Intl.NumberFormat(options.locale ?? "en-US", {
|
|
183
206
|
maximumFractionDigits: decimals,
|
|
184
207
|
minimumFractionDigits: 0,
|
|
185
208
|
useGrouping: false
|
|
186
|
-
}).format(value)} ${units[exponent] ?? units.
|
|
209
|
+
}).format(value)} ${units[exponent] ?? units[units.length - 1]}`;
|
|
187
210
|
}
|
|
188
211
|
/**
|
|
189
212
|
* 在半开区间内生成随机整数。
|
|
190
213
|
*
|
|
191
214
|
* @remarks 优先使用 Web Crypto;平台缺少安全随机能力时回退到 `Math.random()`。
|
|
192
|
-
* @param minimum -
|
|
215
|
+
* @param minimum - 包含的安全整数下界
|
|
193
216
|
* @param maximumExclusive - 不包含的安全整数上界;区间宽度最大为 2^32。
|
|
194
|
-
* @returns 位于 `[minimum, maximumExclusive)`
|
|
217
|
+
* @returns 位于 `[minimum, maximumExclusive)` 的随机整数
|
|
195
218
|
* @throws 参数非法时抛出 `RangeError`。
|
|
196
219
|
*/
|
|
197
220
|
function randomInt(minimum, maximumExclusive) {
|
|
198
|
-
if (!Number.isSafeInteger(minimum) || !Number.isSafeInteger(maximumExclusive)) throw new RangeError("`minimum`
|
|
221
|
+
if (!Number.isSafeInteger(minimum) || !Number.isSafeInteger(maximumExclusive)) throw new RangeError("`minimum` and `maximumExclusive` must be safe integers.");
|
|
199
222
|
const range = maximumExclusive - minimum;
|
|
200
223
|
const uint32Range = 4294967296;
|
|
201
|
-
if (range <= 0 || range > uint32Range) throw new RangeError("
|
|
202
|
-
const crypto = runtimeGlobals.crypto;
|
|
203
|
-
const getRandomValues = crypto?.getRandomValues?.bind(crypto);
|
|
224
|
+
if (range <= 0 || range > uint32Range) throw new RangeError("The interval must not be empty and its width must not exceed 2^32.");
|
|
204
225
|
const limit = Math.floor(uint32Range / range) * range;
|
|
205
226
|
const values = /* @__PURE__ */ new Uint32Array(1);
|
|
206
227
|
let sample;
|
|
207
228
|
do {
|
|
208
|
-
if (
|
|
229
|
+
if (typeof crypto !== "undefined" && typeof crypto?.getRandomValues === "function") crypto.getRandomValues(values);
|
|
209
230
|
else values[0] = Math.floor(Math.random() * uint32Range);
|
|
210
231
|
sample = values[0] ?? uint32Range;
|
|
211
232
|
} while (sample >= limit);
|