@fast-china/utils 2.1.10 → 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.
Files changed (70) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +5 -2
  3. package/README.zh.md +5 -2
  4. package/dist/array/index.mjs +21 -21
  5. package/dist/array/index.mjs.map +1 -1
  6. package/dist/async/index.mjs +32 -32
  7. package/dist/async/index.mjs.map +1 -1
  8. package/dist/base64/index.mjs +44 -51
  9. package/dist/base64/index.mjs.map +1 -1
  10. package/dist/color/index.mjs +33 -33
  11. package/dist/color/index.mjs.map +1 -1
  12. package/dist/crypto/index.mjs +153 -155
  13. package/dist/crypto/index.mjs.map +1 -1
  14. package/dist/date/index.mjs +79 -46
  15. package/dist/date/index.mjs.map +1 -1
  16. package/dist/dom/style.mjs +7 -7
  17. package/dist/dom/style.mjs.map +1 -1
  18. package/dist/env/index.mjs +9 -14
  19. package/dist/env/index.mjs.map +1 -1
  20. package/dist/function/index.mjs +4 -4
  21. package/dist/function/index.mjs.map +1 -1
  22. package/dist/identity/index.mjs +7 -7
  23. package/dist/identity/index.mjs.map +1 -1
  24. package/dist/index.d.mts +371 -366
  25. package/dist/index.global.min.js +2 -2
  26. package/dist/index.global.min.js.map +1 -1
  27. package/dist/internal/text.mjs +70 -26
  28. package/dist/internal/text.mjs.map +1 -1
  29. package/dist/logger/index.mjs +12 -13
  30. package/dist/logger/index.mjs.map +1 -1
  31. package/dist/number/index.mjs +63 -42
  32. package/dist/number/index.mjs.map +1 -1
  33. package/dist/object/index.mjs +32 -31
  34. package/dist/object/index.mjs.map +1 -1
  35. package/dist/storage/index.mjs +58 -63
  36. package/dist/storage/index.mjs.map +1 -1
  37. package/dist/string/index.mjs +60 -64
  38. package/dist/string/index.mjs.map +1 -1
  39. package/dist/vue/breakpoints.mjs +14 -10
  40. package/dist/vue/breakpoints.mjs.map +1 -1
  41. package/dist/vue/element-size.mjs +4 -4
  42. package/dist/vue/element-size.mjs.map +1 -1
  43. package/dist/vue/emits.mjs +7 -7
  44. package/dist/vue/emits.mjs.map +1 -1
  45. package/dist/vue/event-listener.mjs +5 -5
  46. package/dist/vue/event-listener.mjs.map +1 -1
  47. package/dist/vue/expose.mjs +3 -3
  48. package/dist/vue/expose.mjs.map +1 -1
  49. package/dist/vue/func.mjs +2 -2
  50. package/dist/vue/func.mjs.map +1 -1
  51. package/dist/vue/install.mjs +24 -24
  52. package/dist/vue/install.mjs.map +1 -1
  53. package/dist/vue/now.mjs +4 -5
  54. package/dist/vue/now.mjs.map +1 -1
  55. package/dist/vue/props.mjs +5 -5
  56. package/dist/vue/props.mjs.map +1 -1
  57. package/dist/vue/render.mjs +2 -2
  58. package/dist/vue/render.mjs.map +1 -1
  59. package/dist/vue/resize-observer.mjs +4 -6
  60. package/dist/vue/resize-observer.mjs.map +1 -1
  61. package/dist/vue/slots.mjs.map +1 -1
  62. package/dist/vue/transition.mjs +5 -7
  63. package/dist/vue/transition.mjs.map +1 -1
  64. package/dist/vue/window-size.mjs +2 -4
  65. package/dist/vue/window-size.mjs.map +1 -1
  66. package/dist/vue/with.mjs +1 -1
  67. package/dist/vue/with.mjs.map +1 -1
  68. package/package.json +2 -1
  69. package/dist/internal/runtime.mjs +0 -32
  70. package/dist/internal/runtime.mjs.map +0 -1
@@ -1,34 +1,78 @@
1
1
  //#region src/internal/text.ts
2
2
  /**
3
- * 延迟解析 UTF-8 TextDecoder,确保模块导入阶段不依赖 Encoding API。
3
+ * 使用内部实现解码 UTF-8,不依赖平台 Encoding API。
4
4
  *
5
- * @returns 启用 Fatal 模式的新解码器,非法 UTF-8 会直接失败。
6
- * @throws `Error` 当当前平台没有提供 `TextDecoder`。
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 getTextDecoder = () => {
9
- const TextDecoderConstructor = globalThis.TextDecoder;
10
- if (typeof TextDecoderConstructor !== "function") throw new Error("当前运行环境不支持 TextDecoder。");
11
- return new TextDecoderConstructor("utf-8", { fatal: true });
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
- * 延迟解析 TextEncoder,让纯字节 API 在缺少 Encoding API 的平台仍可导入。
59
+ * 使用内部实现编码 UTF-8,不依赖平台 Encoding API。
15
60
  *
16
- * @returns 新建的 UTF-8 编码器。
17
- * @throws `Error` 当当前平台没有提供 `TextEncoder`。
61
+ * @param value - 待编码文本;孤立的 UTF-16 代理项按 TextEncoder 语义替换为 U+FFFD。
62
+ * @returns 使用独立 ArrayBuffer 的 UTF-8 字节数组
18
63
  */
19
- const getTextEncoder = () => {
20
- const TextEncoderConstructor = globalThis.TextEncoder;
21
- if (typeof TextEncoderConstructor !== "function") throw new Error("当前运行环境不支持 TextEncoder。");
22
- return new TextEncoderConstructor();
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("当前运行环境不允许安装 String.prototype.parseJson。", { cause });
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, encodeUtf8, getTextDecoder, getTextEncoder };
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 * 延迟解析 UTF-8 TextDecoder,确保模块导入阶段不依赖 Encoding API。\n *\n * @returns 启用 Fatal 模式的新解码器,非法 UTF-8 会直接失败。\n * @throws `Error` 当当前平台没有提供 `TextDecoder`。\n */\nexport const getTextDecoder = (): TextDecoder => {\n\tconst TextDecoderConstructor = globalThis.TextDecoder;\n\tif (typeof TextDecoderConstructor !== \"function\") {\n\t\tthrow new Error(\"当前运行环境不支持 TextDecoder。\");\n\t}\n\treturn new TextDecoderConstructor(\"utf-8\", { fatal: true });\n};\n\n/**\n * 延迟解析 TextEncoder,让纯字节 API 在缺少 Encoding API 的平台仍可导入。\n *\n * @returns 新建的 UTF-8 编码器。\n * @throws `Error` 当当前平台没有提供 `TextEncoder`。\n */\nexport const getTextEncoder = (): TextEncoder => {\n\tconst TextEncoderConstructor = globalThis.TextEncoder;\n\tif (typeof TextEncoderConstructor !== \"function\") {\n\t\tthrow new Error(\"当前运行环境不支持 TextEncoder。\");\n\t}\n\treturn new TextEncoderConstructor();\n};\n\n/**\n * 在确认平台能力后把 JavaScript 字符串编码为 UTF-8。\n *\n * @param value - 待编码文本。\n * @returns 使用独立 ArrayBuffer 的 UTF-8 字节数组。\n * @throws `Error` 当当前平台没有提供 `TextEncoder`。\n */\nexport const encodeUtf8 = (value: string): Uint8Array<ArrayBuffer> => getTextEncoder().encode(value);\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 已被其他实现占用。\");\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(\"当前运行环境不允许安装 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":";;;;;;;AAMA,MAAa,uBAAoC;CAChD,MAAM,yBAAyB,WAAW;CAC1C,IAAI,OAAO,2BAA2B,YACrC,MAAM,IAAI,MAAM,wBAAwB;CAEzC,OAAO,IAAI,uBAAuB,SAAS,EAAE,OAAO,KAAK,CAAC;AAC3D;;;;;;;AAQA,MAAa,uBAAoC;CAChD,MAAM,yBAAyB,WAAW;CAC1C,IAAI,OAAO,2BAA2B,YACrC,MAAM,IAAI,MAAM,wBAAwB;CAEzC,OAAO,IAAI,uBAAuB;AACnC;;;;;;;;AASA,MAAa,cAAc,UAA2C,eAAe,CAAC,CAAC,OAAO,KAAK;AAiBnG,MAAM,kBAAkB,OAAO,IAAI,8BAA8B;;AAGjE,MAAM,YAAY,WAAsE;CACvF,MAAM,OAAO,KAAK,QAAQ;CAC1B,IAAI;EACH,OAAO,KAAK,MAAM,IAAI;CACvB,QAAQ;EACP,OAAO;CACR;AACD;AAEA,OAAO,eAAe,WAAW,iBAAiB,EAAE,OAAO,KAAK,CAAC;;AAGjE,MAAM,iCAAuC;CAC5C,MAAM,aAAa,OAAO,yBAAyB,OAAO,WAAW,WAAW;CAChF,IAAI,eAAe,KAAA,GAAW;EAC7B,IAAI,OAAO,WAAW,UAAU,cAAc,QAAQ,IAAI,WAAW,OAAO,eAAe,MAAM,MAAM;EACvG,MAAM,IAAI,UAAU,sCAAsC;CAC3D;CAEA,IAAI;EACH,OAAO,eAAe,OAAO,WAAW,aAAa;GACpD,cAAc;GACd,YAAY;GACZ,OAAO;GACP,UAAU;EACX,CAAC;CACF,SAAS,OAAO;EACf,MAAM,IAAI,UAAU,2CAA2C,EAAE,MAAM,CAAC;CACzE;AACD;;;;;;;AAQA,MAAa,qBAAqB,SAA8B;CAC/D,yBAAyB;CACzB,OAAO;AACR"}
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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"}
@@ -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.hasOwn(levelPriority, value);
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 getRuntimeUni() !== void 0 && getRuntimePlus() !== void 0;
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(`未知的日志级别:${String(level)}。`);
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":["import { getRuntimePlus, getRuntimeUni } from \"../internal/runtime\";\n\n/** 日志严重级别,按从低到高排列。 */\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.hasOwn(levelPriority, value);\n\n/**\n * 检测 uni-app App-Plus 日志环境。\n *\n * @returns 全局 `uni` 与 `plus` 同时存在时返回 `true`。\n */\nconst isUniAppPlus = (): boolean => {\n\treturn getRuntimeUni() !== undefined && getRuntimePlus() !== 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(`未知的日志级别:${String(level)}。`);\n\tif (typeof prefix !== \"string\" || prefix.length === 0) {\n\t\tthrow new RangeError(\"日志前缀必须是非空字符串。\");\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(\"日志作用域必须是字符串。\");\n\t\tif (scope.length === 0 || scope.trim() !== scope) {\n\t\t\tthrow new RangeError(\"日志作用域必须是无外围空白的非空字符串。\");\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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"}
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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"}
@@ -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}\` 不能是 NaN。`);
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` 不能大于 `maximum`。");
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` 不能大于 `maximum`。");
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` 必须是 -15 到 15 之间的安全整数。");
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` 必须是 1000 或 1024。");
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` 必须是 0 到 20 之间的安全整数。");
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.at(-1)}`;
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` 和 `maximumExclusive` 必须是安全整数。");
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("区间不能为空且宽度不能超过 2^32。");
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 (getRandomValues !== void 0) getRandomValues(values);
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);
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/number/index.ts"],"sourcesContent":["import { runtimeGlobals } from \"../internal/runtime\";\n\nconst byteUnits = [\"B\", \"kB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\", \"ZB\", \"YB\"];\nconst binaryByteUnits = [\"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\", \"PiB\", \"EiB\", \"ZiB\", \"YiB\"];\n\n/** {@link formatBytes} 的格式化选项。 */\nexport interface FormatBytesOptions {\n\t/** 计量基数。`1000` 生成 SI 单位,`1024` 生成 IEC 单位;默认 `1024`。 */\n\tbase?: 1000 | 1024;\n\t/** 小数位数,范围 0 至 20;默认 `2`。 */\n\tdecimals?: number;\n\t/** `Intl.NumberFormat` 使用的语言;默认固定为 `en-US` 以保证输出稳定。 */\n\tlocale?: string | readonly string[];\n}\n\n/**\n * 拒绝 NaN,同时允许具体 API 自行决定是否接受 Infinity。\n *\n * @param value - 待校验数值。\n * @param name - 用于错误消息的参数名称。\n * @throws `RangeError` 当值为 NaN。\n */\nconst assertNotNaN = (value: number, name: string): void => {\n\tif (Number.isNaN(value)) throw new RangeError(`\\`${name}\\` 不能是 NaN。`);\n};\n\n/**\n * 校验有限数值。\n *\n * @param value - 待校验数值。\n * @param name - 用于错误消息的参数名称。\n * @throws `RangeError` 当值为 NaN 或无穷大。\n */\nconst assertFinite = (value: number, name: string): void => {\n\tif (!Number.isFinite(value)) throw new RangeError(`\\`${name}\\` 必须是有限数。`);\n};\n\n/**\n * 使用科学计数法移动十进制位。\n *\n * @remarks 该方式用于 `roundTo`,可减少直接乘除十次幂造成的额外二进制舍入误差。\n * @param value - 原始数值。\n * @param exponent - 十进制移动位数;正数向右移动。\n * @returns 移动后的数值。\n */\nconst shiftDecimal = (value: number, exponent: number): number => {\n\tif (Object.is(value, -0)) return -0;\n\tconst [coefficient = \"0\", currentExponent = \"0\"] = value.toString().split(\"e\");\n\treturn Number(`${coefficient}e${Number(currentExponent) + exponent}`);\n};\n\n/**\n * 把数字限制在闭区间内。\n *\n * @param value - 需要限制的数字。\n * @param minimum - 闭区间下界。\n * @param maximum - 闭区间上界。\n * @returns `minimum <= result <= maximum` 的值。\n * @throws `RangeError` 当参数为 `NaN` 或下界大于上界。\n */\nexport function clamp(value: number, minimum: number, maximum: number): number {\n\tassertNotNaN(value, \"value\");\n\tassertNotNaN(minimum, \"minimum\");\n\tassertNotNaN(maximum, \"maximum\");\n\tif (minimum > maximum) throw new RangeError(\"`minimum` 不能大于 `maximum`。\");\n\treturn Math.min(Math.max(value, minimum), maximum);\n}\n\n/**\n * 判断数字是否位于指定区间。\n *\n * @param value - 待检查数字。\n * @param minimum - 包含的下界。\n * @param maximum - 上界。\n * @param includeMaximum - 是否包含上界;默认使用半开区间 `[minimum, maximum)`。\n * @returns 数字满足区间边界时返回 `true`。\n * @throws `RangeError` 当参数为 `NaN` 或下界大于上界。\n */\nexport function inRange(value: number, minimum: number, maximum: number, includeMaximum = false): boolean {\n\tassertNotNaN(value, \"value\");\n\tassertNotNaN(minimum, \"minimum\");\n\tassertNotNaN(maximum, \"maximum\");\n\tif (minimum > maximum) throw new RangeError(\"`minimum` 不能大于 `maximum`。\");\n\treturn value >= minimum && (includeMaximum ? value <= maximum : value < maximum);\n}\n\n/**\n * 按十进制位数四舍五入。\n *\n * @remarks IEEE-754 浮点数仍可能存在不可表示误差;财务金额应使用十进制定点方案。\n * @param value - 有限数字。\n * @param digits - 小数位数;负数表示十位、百位等,范围 -15 至 15。\n * @returns 按 `Math.round` 语义舍入后的数字。\n * @throws `RangeError` 当值非有限或位数超出范围。\n */\nexport function roundTo(value: number, digits = 0): number {\n\tassertFinite(value, \"value\");\n\tif (!Number.isSafeInteger(digits) || digits < -15 || digits > 15) {\n\t\tthrow new RangeError(\"`digits` 必须是 -15 到 15 之间的安全整数。\");\n\t}\n\tconst shifted = shiftDecimal(value, digits);\n\t// 对已经没有可表示小数的大数,乘以 10^digits 可能溢出;此时舍入不会改变值。\n\tif (!Number.isFinite(shifted)) return value;\n\treturn shiftDecimal(Math.round(shifted), -digits);\n}\n\n/**\n * 对有限数字求和。\n *\n * @param values - 不会被修改的数字数组。\n * @returns 算术和;空数组返回 `0`。\n * @throws `RangeError` 当任一值非有限或累计结果溢出。\n */\nexport function sum(values: readonly number[]): number {\n\tlet total = 0;\n\tlet compensation = 0;\n\tvalues.forEach((value) => {\n\t\tassertFinite(value, \"value\");\n\t\t// Kahan 补偿保存上一次浮点加法丢失的低位,减少大量小数累计误差。\n\t\tconst adjusted = value - compensation;\n\t\tconst next = total + adjusted;\n\t\tif (!Number.isFinite(next)) throw new RangeError(\"总和超出有限数范围。\");\n\t\tcompensation = next - total - adjusted;\n\t\ttotal = next;\n\t});\n\treturn total;\n}\n\n/**\n * 计算有限数字的算术平均值。\n *\n * @param values - 不会被修改的数字数组。\n * @returns 空数组或只有稀疏空位的数组返回 `undefined`;空位不参与分母。\n * @throws `RangeError` 当任一值非有限。\n */\nexport function average(values: readonly number[]): number | undefined {\n\tlet count = 0;\n\tlet mean = 0;\n\tvalues.forEach((value) => {\n\t\tassertFinite(value, \"value\");\n\t\tcount += 1;\n\t\t// 加权增量形式避免先求和导致 MAX_VALUE + MAX_VALUE 溢出。\n\t\tmean = mean * ((count - 1) / count) + value / count;\n\t});\n\treturn count === 0 ? undefined : mean;\n}\n\n/**\n * 在两个数字间做线性插值。\n *\n * @remarks `amount` 不限制在 0 至 1;区间外的值会执行线性外推。\n * @param start - `amount = 0` 时的起点。\n * @param end - `amount = 1` 时的终点。\n * @param amount - 插值或外推比例。\n * @returns 线性计算结果。\n * @throws `RangeError` 当任一参数非有限或结果超出有限数字范围。\n */\nexport function lerp(start: number, end: number, amount: number): number {\n\tassertFinite(start, \"start\");\n\tassertFinite(end, \"end\");\n\tassertFinite(amount, \"amount\");\n\tconst result = start * (1 - amount) + end * amount;\n\tif (!Number.isFinite(result)) throw new RangeError(\"插值结果超出有限数范围。\");\n\treturn result;\n}\n\n/**\n * 将非负字节数格式化为 SI 或 IEC 单位。\n *\n * @param bytes - 非负有限字节数。\n * @param options - 基数、小数位和语言选项。\n * @returns 例如 `1.5 KiB`。\n * @throws `RangeError` 当字节数为负或非有限、基数不是 1000/1024、小数位非法,或 Locale 无效。\n */\nexport function formatBytes(bytes: number, options: FormatBytesOptions = {}): string {\n\tassertFinite(bytes, \"bytes\");\n\tif (bytes < 0) throw new RangeError(\"`bytes` 不能为负数。\");\n\tconst requestedBase: unknown = options.base ?? 1024;\n\tif (requestedBase !== 1000 && requestedBase !== 1024) throw new RangeError(\"`base` 必须是 1000 或 1024。\");\n\tconst base = requestedBase;\n\tconst decimals = options.decimals ?? 2;\n\tif (!Number.isSafeInteger(decimals) || decimals < 0 || decimals > 20) {\n\t\tthrow new RangeError(\"`decimals` 必须是 0 到 20 之间的安全整数。\");\n\t}\n\tif (bytes === 0) return \"0 B\";\n\n\tconst units = base === 1024 ? binaryByteUnits : byteUnits;\n\t// 小于 1 的合法字节数仍使用 B,不能产生负下标并回退到最大单位。\n\tconst exponent = Math.max(0, Math.min(Math.floor(Math.log(bytes) / Math.log(base)), units.length - 1));\n\tconst value = bytes / base ** exponent;\n\tconst formatted = new Intl.NumberFormat(options.locale ?? \"en-US\", {\n\t\tmaximumFractionDigits: decimals,\n\t\tminimumFractionDigits: 0,\n\t\tuseGrouping: false,\n\t}).format(value);\n\treturn `${formatted} ${units[exponent] ?? units.at(-1)}`;\n}\n\n/**\n * 在半开区间内生成随机整数。\n *\n * @remarks 优先使用 Web Crypto;平台缺少安全随机能力时回退到 `Math.random()`。\n * @param minimum - 包含的安全整数下界。\n * @param maximumExclusive - 不包含的安全整数上界;区间宽度最大为 2^32。\n * @returns 位于 `[minimum, maximumExclusive)` 的随机整数。\n * @throws 参数非法时抛出 `RangeError`。\n */\nexport function randomInt(minimum: number, maximumExclusive: number): number {\n\tif (!Number.isSafeInteger(minimum) || !Number.isSafeInteger(maximumExclusive)) {\n\t\tthrow new RangeError(\"`minimum` 和 `maximumExclusive` 必须是安全整数。\");\n\t}\n\tconst range = maximumExclusive - minimum;\n\tconst uint32Range = 0x1_0000_0000;\n\tif (range <= 0 || range > uint32Range) {\n\t\tthrow new RangeError(\"区间不能为空且宽度不能超过 2^32。\");\n\t}\n\tconst crypto = runtimeGlobals.crypto;\n\tconst getRandomValues = crypto?.getRandomValues?.bind(crypto);\n\n\t// 只接受可以被区间宽度整除的最大 2^32 前缀,消除取模偏差。\n\tconst limit = Math.floor(uint32Range / range) * range;\n\tconst values = new Uint32Array(1);\n\tlet sample: number;\n\tdo {\n\t\tif (getRandomValues !== undefined) getRandomValues(values);\n\t\telse values[0] = Math.floor(Math.random() * uint32Range);\n\t\tsample = values[0] ?? uint32Range;\n\t} while (sample >= limit);\n\treturn minimum + (sample % range);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/number/index.ts"],"sourcesContent":["const byteUnits = [\"B\", \"kB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\", \"ZB\", \"YB\"];\nconst binaryByteUnits = [\"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\", \"PiB\", \"EiB\", \"ZiB\", \"YiB\"];\n\n/** {@link formatBytes} 的格式化选项 */\nexport interface FormatBytesOptions {\n\t/** 计量基数。`1000` 生成 SI 单位,`1024` 生成 IEC 单位;默认 `1024`。 */\n\tbase?: 1000 | 1024;\n\t/** 小数位数,范围 0 至 20;默认 `2` */\n\tdecimals?: number;\n\t/** 显式语言交给 Intl.NumberFormat;省略时内部按 en-US 数字格式输出。 */\n\tlocale?: string | readonly string[];\n}\n\n/**\n * 拒绝 NaN,同时允许具体 API 自行决定是否接受 Infinity。\n *\n * @param value - 待校验数值\n * @param name - 用于错误消息的参数名称\n * @throws `RangeError` 当值为 NaN。\n */\nconst assertNotNaN = (value: number, name: string): void => {\n\tif (Number.isNaN(value)) throw new RangeError(`\\`${name}\\` must not be NaN.`);\n};\n\n/**\n * 校验有限数值。\n *\n * @param value - 待校验数值\n * @param name - 用于错误消息的参数名称\n * @throws `RangeError` 当值为 NaN 或无穷大。\n */\nconst assertFinite = (value: number, name: string): void => {\n\tif (!Number.isFinite(value)) throw new RangeError(`\\`${name}\\` must be a finite number.`);\n};\n\n/**\n * 使用科学计数法移动十进制位。\n *\n * @remarks 该方式用于 `roundTo`,可减少直接乘除十次幂造成的额外二进制舍入误差。\n * @param value - 原始数值\n * @param exponent - 十进制移动位数;正数向右移动。\n * @returns 移动后的数值\n */\nconst shiftDecimal = (value: number, exponent: number): number => {\n\tif (Object.is(value, -0)) return -0;\n\tconst [coefficient = \"0\", currentExponent = \"0\"] = value.toString().split(\"e\");\n\treturn Number(`${coefficient}e${Number(currentExponent) + exponent}`);\n};\n\n/**\n * 把数字限制在闭区间内。\n *\n * @param value - 需要限制的数字\n * @param minimum - 闭区间下界\n * @param maximum - 闭区间上界\n * @returns `minimum <= result <= maximum` 的值。\n * @throws `RangeError` 当参数为 `NaN` 或下界大于上界。\n */\nexport function clamp(value: number, minimum: number, maximum: number): number {\n\tassertNotNaN(value, \"value\");\n\tassertNotNaN(minimum, \"minimum\");\n\tassertNotNaN(maximum, \"maximum\");\n\tif (minimum > maximum) throw new RangeError(\"`minimum` must not exceed `maximum`.\");\n\treturn Math.min(Math.max(value, minimum), maximum);\n}\n\n/**\n * 判断数字是否位于指定区间。\n *\n * @param value - 待检查数字\n * @param minimum - 包含的下界\n * @param maximum - 上界\n * @param includeMaximum - 是否包含上界;默认使用半开区间 `[minimum, maximum)`。\n * @returns 数字满足区间边界时返回 `true`。\n * @throws `RangeError` 当参数为 `NaN` 或下界大于上界。\n */\nexport function inRange(value: number, minimum: number, maximum: number, includeMaximum = false): boolean {\n\tassertNotNaN(value, \"value\");\n\tassertNotNaN(minimum, \"minimum\");\n\tassertNotNaN(maximum, \"maximum\");\n\tif (minimum > maximum) throw new RangeError(\"`minimum` must not exceed `maximum`.\");\n\treturn value >= minimum && (includeMaximum ? value <= maximum : value < maximum);\n}\n\n/**\n * 按十进制位数四舍五入。\n *\n * @remarks IEEE-754 浮点数仍可能存在不可表示误差;财务金额应使用十进制定点方案。\n * @param value - 有限数字\n * @param digits - 小数位数;负数表示十位、百位等,范围 -15 至 15。\n * @returns 按 `Math.round` 语义舍入后的数字\n * @throws `RangeError` 当值非有限或位数超出范围。\n */\nexport function roundTo(value: number, digits = 0): number {\n\tassertFinite(value, \"value\");\n\tif (!Number.isSafeInteger(digits) || digits < -15 || digits > 15) {\n\t\tthrow new RangeError(\"`digits` must be a safe integer between -15 and 15.\");\n\t}\n\tconst shifted = shiftDecimal(value, digits);\n\t// 对已经没有可表示小数的大数,乘以 10^digits 可能溢出;此时舍入不会改变值。\n\tif (!Number.isFinite(shifted)) return value;\n\treturn shiftDecimal(Math.round(shifted), -digits);\n}\n\n/**\n * 对有限数字求和。\n *\n * @param values - 不会被修改的数字数组\n * @returns 算术和;空数组返回 `0`。\n * @throws `RangeError` 当任一值非有限或累计结果溢出。\n */\nexport function sum(values: readonly number[]): number {\n\tlet total = 0;\n\tlet compensation = 0;\n\tvalues.forEach((value) => {\n\t\tassertFinite(value, \"value\");\n\t\t// Kahan 补偿保存上一次浮点加法丢失的低位,减少大量小数累计误差。\n\t\tconst adjusted = value - compensation;\n\t\tconst next = total + adjusted;\n\t\tif (!Number.isFinite(next)) throw new RangeError(\"The sum exceeds the finite number range.\");\n\t\tcompensation = next - total - adjusted;\n\t\ttotal = next;\n\t});\n\treturn total;\n}\n\n/**\n * 计算有限数字的算术平均值。\n *\n * @param values - 不会被修改的数字数组\n * @returns 空数组或只有稀疏空位的数组返回 `undefined`;空位不参与分母。\n * @throws `RangeError` 当任一值非有限。\n */\nexport function average(values: readonly number[]): number | undefined {\n\tlet count = 0;\n\tlet mean = 0;\n\tvalues.forEach((value) => {\n\t\tassertFinite(value, \"value\");\n\t\tcount += 1;\n\t\t// 加权增量形式避免先求和导致 MAX_VALUE + MAX_VALUE 溢出。\n\t\tmean = mean * ((count - 1) / count) + value / count;\n\t});\n\treturn count === 0 ? undefined : mean;\n}\n\n/**\n * 在两个数字间做线性插值。\n *\n * @remarks `amount` 不限制在 0 至 1;区间外的值会执行线性外推。\n * @param start - `amount = 0` 时的起点\n * @param end - `amount = 1` 时的终点\n * @param amount - 插值或外推比例\n * @returns 线性计算结果\n * @throws `RangeError` 当任一参数非有限或结果超出有限数字范围。\n */\nexport function lerp(start: number, end: number, amount: number): number {\n\tassertFinite(start, \"start\");\n\tassertFinite(end, \"end\");\n\tassertFinite(amount, \"amount\");\n\tconst result = start * (1 - amount) + end * amount;\n\tif (!Number.isFinite(result)) throw new RangeError(\"The interpolated result exceeds the finite number range.\");\n\treturn result;\n}\n\n/** 按十进制表示四舍五入,避免 toFixed 的二进制舍入差异,并展开科学计数法。 */\nconst formatByteAmount = (value: number, decimals: number): string => {\n\tconst [coefficient = \"0\", exponent = \"0\"] = value.toString().split(\"e\");\n\tconst [whole = \"0\", fraction = \"\"] = coefficient.split(\".\");\n\tconst digits = whole + fraction;\n\tconst point = whole.length + Number(exponent);\n\tconst integer = point <= 0 ? \"0\" : digits.slice(0, point).padEnd(point, \"0\");\n\tconst fractional = point < 0 ? \"0\".repeat(-point) + digits : digits.slice(Math.max(0, point));\n\tlet rounded = integer + fractional.slice(0, decimals);\n\tif (Number(fractional[decimals] ?? \"0\") >= 5) {\n\t\tlet index = rounded.length - 1;\n\t\twhile (index >= 0 && rounded[index] === \"9\") index -= 1;\n\t\trounded =\n\t\t\tindex < 0\n\t\t\t\t? `1${\"0\".repeat(rounded.length)}`\n\t\t\t\t: rounded.slice(0, index) + String(Number(rounded[index]) + 1) + \"0\".repeat(rounded.length - index - 1);\n\t}\n\tconst keptDecimals = Math.min(decimals, fractional.length);\n\tif (keptDecimals === 0) return rounded;\n\tconst end = rounded.length - keptDecimals;\n\tconst tail = rounded.slice(end).replace(/0+$/u, \"\");\n\treturn tail === \"\" ? rounded.slice(0, end) : `${rounded.slice(0, end)}.${tail}`;\n};\n\n/**\n * 将非负字节数格式化为 SI 或 IEC 单位。\n *\n * @param bytes - 非负有限字节数\n * @param options - 基数、小数位和语言选项\n * @returns 例如 `1.5 KiB`\n * @remarks 默认数字格式使用内部实现;显式指定语言时使用 Intl.NumberFormat。\n * @throws `Error` 当显式指定语言且环境不支持 `Intl.NumberFormat`。\n * @throws `RangeError` 当字节数为负或非有限、基数不是 1000/1024、小数位非法,或 Locale 无效。\n */\nexport function formatBytes(bytes: number, options: FormatBytesOptions = {}): string {\n\tassertFinite(bytes, \"bytes\");\n\tif (bytes < 0) throw new RangeError(\"`bytes` must not be negative.\");\n\tconst requestedBase: unknown = options.base ?? 1024;\n\tif (requestedBase !== 1000 && requestedBase !== 1024) throw new RangeError(\"`base` must be 1000 or 1024.\");\n\tconst base = requestedBase;\n\tconst decimals = options.decimals ?? 2;\n\tif (!Number.isSafeInteger(decimals) || decimals < 0 || decimals > 20) {\n\t\tthrow new RangeError(\"`decimals` must be a safe integer between 0 and 20.\");\n\t}\n\tif (bytes === 0) return \"0 B\";\n\n\tconst units = base === 1024 ? binaryByteUnits : byteUnits;\n\t// 小于 1 的合法字节数仍使用 B,不能产生负下标并回退到最大单位。\n\tconst exponent = Math.max(0, Math.min(Math.floor(Math.log(bytes) / Math.log(base)), units.length - 1));\n\tconst value = bytes / base ** exponent;\n\tif (options.locale === undefined) return `${formatByteAmount(value, decimals)} ${units[exponent]}`;\n\tif (typeof Intl === \"undefined\" || typeof Intl.NumberFormat !== \"function\") {\n\t\tthrow new Error(\"The current runtime does not support Intl.NumberFormat.\");\n\t}\n\tconst formatted = new Intl.NumberFormat(options.locale ?? \"en-US\", {\n\t\tmaximumFractionDigits: decimals,\n\t\tminimumFractionDigits: 0,\n\t\tuseGrouping: false,\n\t}).format(value);\n\treturn `${formatted} ${units[exponent] ?? units[units.length - 1]}`;\n}\n\n/**\n * 在半开区间内生成随机整数。\n *\n * @remarks 优先使用 Web Crypto;平台缺少安全随机能力时回退到 `Math.random()`。\n * @param minimum - 包含的安全整数下界\n * @param maximumExclusive - 不包含的安全整数上界;区间宽度最大为 2^32。\n * @returns 位于 `[minimum, maximumExclusive)` 的随机整数\n * @throws 参数非法时抛出 `RangeError`。\n */\nexport function randomInt(minimum: number, maximumExclusive: number): number {\n\tif (!Number.isSafeInteger(minimum) || !Number.isSafeInteger(maximumExclusive)) {\n\t\tthrow new RangeError(\"`minimum` and `maximumExclusive` must be safe integers.\");\n\t}\n\tconst range = maximumExclusive - minimum;\n\tconst uint32Range = 0x1_0000_0000;\n\tif (range <= 0 || range > uint32Range) {\n\t\tthrow new RangeError(\"The interval must not be empty and its width must not exceed 2^32.\");\n\t}\n\n\t// 只接受可以被区间宽度整除的最大 2^32 前缀,消除取模偏差。\n\tconst limit = Math.floor(uint32Range / range) * range;\n\tconst values = new Uint32Array(1);\n\tlet sample: number;\n\tdo {\n\t\tif (typeof crypto !== \"undefined\" && typeof crypto?.getRandomValues === \"function\") crypto.getRandomValues(values);\n\t\telse values[0] = Math.floor(Math.random() * uint32Range);\n\t\tsample = values[0] ?? uint32Range;\n\t} while (sample >= limit);\n\treturn minimum + (sample % range);\n}\n"],"mappings":"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"}