@fast-china/utils 2.1.4 → 2.1.5
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 +13 -0
- package/README.md +2 -0
- package/README.zh.md +2 -0
- package/dist/array/index.d.mts +8 -0
- package/dist/array/index.mjs +15 -1
- package/dist/array/index.mjs.map +1 -1
- package/dist/async/index.mjs.map +1 -1
- package/dist/color/index.mjs.map +1 -1
- package/dist/date/index.mjs.map +1 -1
- package/dist/function/index.d.mts +13 -0
- package/dist/function/index.mjs +38 -0
- package/dist/function/index.mjs.map +1 -0
- package/dist/index.d.mts +4 -3
- package/dist/index.global.min.js +2 -2
- package/dist/index.global.min.js.map +1 -1
- package/dist/index.mjs +4 -3
- package/dist/internal/runtime.mjs.map +1 -1
- package/dist/internal/text.mjs.map +1 -1
- package/dist/logger/index.mjs +4 -6
- package/dist/logger/index.mjs.map +1 -1
- package/dist/number/index.mjs.map +1 -1
- package/dist/object/index.d.mts +41 -2
- package/dist/object/index.mjs +258 -14
- package/dist/object/index.mjs.map +1 -1
- package/dist/storage/index.mjs.map +1 -1
- package/dist/string/index.mjs.map +1 -1
- package/dist/vue/emits.mjs.map +1 -1
- package/dist/vue/install.mjs.map +1 -1
- package/dist/vue/render.mjs.map +1 -1
- package/docs/API.md +3 -2
- package/docs/API.zh-CN.md +3 -2
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { allEqualBy, chunk, difference, groupBy, hasDuplicatesBy, intersection, partition, removeNullishValues, unique, uniqueBy } from "./array/index.mjs";
|
|
1
|
+
import { allEqualBy, chunk, difference, groupBy, hasDuplicatesBy, intersection, partition, removeNullishValues, symmetricDifference, unique, uniqueBy } from "./array/index.mjs";
|
|
2
2
|
import { debounce, mapConcurrent, retry, sleep, throttle, withTimeout } from "./async/index.mjs";
|
|
3
3
|
import { average, clamp, formatBytes, inRange, lerp, randomInt, roundTo, sum } from "./number/index.mjs";
|
|
4
4
|
import { decodeBase64, decodeBase64Bytes, decodeBase64Url, decodeBase64UrlBytes, decodeLatin1Base64, decodeSecureBase64, encodeBase64, encodeBase64Bytes, encodeBase64Url, encodeBase64UrlBytes, encodeLatin1Base64, encodeSecureBase64 } from "./base64/index.mjs";
|
|
@@ -7,11 +7,12 @@ import { AESDecrypt, AESDecryptAuthenticated, AESDecryptWithPassword, AESEncrypt
|
|
|
7
7
|
import { addDays, addMonths, addYears, createDateRangeShortcuts, createDateShortcuts, createOneMonthRangeFromToday, endOfDay, formatChineseRelativeTime, formatRelativeTime, getLocalDayBounds, getLocalTimeGreeting, getStartOfToday, isDateAfterNow, isFuture, isSameDay, isValidDate, isWithinInterval, startOfDay, toDate } from "./date/index.mjs";
|
|
8
8
|
import { addCssUnit, serializeStyle } from "./dom/style.mjs";
|
|
9
9
|
import { detectRuntime, hasWebCrypto, isBrowser, isMobileUserAgent, isNode, isTabletUserAgent, isUniApp, isWebWorker } from "./env/index.mjs";
|
|
10
|
+
import { once } from "./function/index.mjs";
|
|
10
11
|
import { Local, Session, base64StorageCodec, configureStorage, isStorageConfigured } from "./storage/index.mjs";
|
|
11
12
|
import { camelCase, copy, decodeURIComponentRepeatedly, escapeHtml, generateUuidV4, isUuidV4, isValidJson, kebabCase, lowerFirst, normalizeWhitespace, parseQueryString, pascalCase, randomString, splitWords, truncateGraphemes, upperFirst } from "./string/index.mjs";
|
|
12
13
|
import { configureInstallationIdentity, getOrCreateInstallationId, installationIdentity } from "./identity/index.mjs";
|
|
13
14
|
import { configureLogger, createLogger, logger } from "./logger/index.mjs";
|
|
14
|
-
import { hasOwn, isPlainObject, mapValues, omit, pick, shallowEqual, toQueryString } from "./object/index.mjs";
|
|
15
|
+
import { cloneDeep, hasOwn, isEqual, isPlainObject, mapValues, omit, omitBy, pick, pickBy, shallowEqual, toQueryString } from "./object/index.mjs";
|
|
15
16
|
import { useEmits } from "./vue/emits.mjs";
|
|
16
17
|
import { useExpose } from "./vue/expose.mjs";
|
|
17
18
|
import { callOptionalFunction } from "./vue/func.mjs";
|
|
@@ -20,4 +21,4 @@ import { definePropType, useProps } from "./vue/props.mjs";
|
|
|
20
21
|
import { useRender } from "./vue/render.mjs";
|
|
21
22
|
import { makeSlots } from "./vue/slots.mjs";
|
|
22
23
|
import { withDefineType } from "./vue/with.mjs";
|
|
23
|
-
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, 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, 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, parseHexColor, parseQueryString, partition, pascalCase, pick, pickHigherContrastColor, randomInt, randomString, relativeLuminance, removeNullishValues, retry, roundTo, serializeStyle, shallowEqual, sleep, splitWords, startOfDay, sum, throttle, toDate, toQueryString, truncateGraphemes, unique, uniqueBy, upperFirst, useEmits, useExpose, useProps, useRender, withDefineType, withInstall, withInstallDirective, withNoopInstall, withTimeout };
|
|
24
|
+
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, useEmits, useExpose, useProps, useRender, withDefineType, withInstall, withInstallDirective, withNoopInstall, withTimeout };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.mjs","names":[],"sources":["../../src/internal/runtime.ts"],"sourcesContent":["/** Web、Node、WebView 与 uni-app 宿主可能按需提供的运行时全局能力。 */\ninterface RuntimeGlobals {\n\treadonly Intl?: {\n\t\treadonly Segmenter?: typeof Intl.Segmenter;\n\t};\n\treadonly crypto?: Omit<Partial<Crypto>, \"subtle\"> & {\n\t\treadonly subtle?: Partial<SubtleCrypto>;\n\t};\n\treadonly document?: Partial<Document>;\n\treadonly importScripts?: unknown;\n\treadonly isSecureContext?: boolean;\n\treadonly localStorage?: Storage;\n\treadonly navigator?: Partial<Navigator>;\n\treadonly plus?: unknown;\n\treadonly process?: unknown;\n\treadonly sessionStorage?: Storage;\n\treadonly uni?: unknown;\n\treadonly window?: Partial<Window>;\n}\n\n/**\n * 以可选能力视图读取全局对象。\n *\n * @remarks TypeScript 的 DOM 声明假定浏览器全局始终存在,但本包也会在 Node、WebView\n * 和 uni-app 中运行。这里只放宽能力是否存在,不改变标准 API 的属性与方法类型。\n */\nexport const runtimeGlobals = globalThis as
|
|
1
|
+
{"version":3,"file":"runtime.mjs","names":[],"sources":["../../src/internal/runtime.ts"],"sourcesContent":["/** Web、Node、WebView 与 uni-app 宿主可能按需提供的运行时全局能力。 */\ninterface RuntimeGlobals {\n\treadonly Intl?: {\n\t\treadonly Segmenter?: typeof Intl.Segmenter;\n\t};\n\treadonly crypto?: Omit<Partial<Crypto>, \"subtle\"> & {\n\t\treadonly subtle?: Partial<SubtleCrypto>;\n\t};\n\treadonly document?: Partial<Document>;\n\treadonly importScripts?: unknown;\n\treadonly isSecureContext?: boolean;\n\treadonly localStorage?: Storage;\n\treadonly navigator?: Partial<Navigator>;\n\treadonly plus?: unknown;\n\treadonly process?: unknown;\n\treadonly sessionStorage?: Storage;\n\treadonly uni?: unknown;\n\treadonly window?: Partial<Window>;\n}\n\n/**\n * 以可选能力视图读取全局对象。\n *\n * @remarks TypeScript 的 DOM 声明假定浏览器全局始终存在,但本包也会在 Node、WebView\n * 和 uni-app 中运行。这里只放宽能力是否存在,不改变标准 API 的属性与方法类型。\n */\nexport const runtimeGlobals = globalThis as RuntimeGlobals;\n"],"mappings":";;;;;;;AA0BA,MAAa,iBAAiB"}
|
|
@@ -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
|
|
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"}
|
package/dist/logger/index.mjs
CHANGED
|
@@ -71,13 +71,11 @@ const defaultConsoleSink = {
|
|
|
71
71
|
* @throws `RangeError` 当级别未知,或前缀、作用域不是有效的非空字符串。
|
|
72
72
|
*/
|
|
73
73
|
function createLogger(options = {}) {
|
|
74
|
-
const
|
|
75
|
-
const
|
|
74
|
+
const level = options.level ?? "debug";
|
|
75
|
+
const prefix = options.prefix ?? "Fast";
|
|
76
76
|
const sink = options.sink ?? defaultConsoleSink;
|
|
77
|
-
if (!isLogLevel(
|
|
78
|
-
if (typeof
|
|
79
|
-
const level = requestedLevel;
|
|
80
|
-
const prefix = requestedPrefix;
|
|
77
|
+
if (!isLogLevel(level)) throw new RangeError(`未知的日志级别:${String(level)}。`);
|
|
78
|
+
if (typeof prefix !== "string" || prefix.length === 0) throw new RangeError("日志前缀必须是非空字符串。");
|
|
81
79
|
const uniAppPlusSplit = options.uniAppPlusSplit ?? false;
|
|
82
80
|
/**
|
|
83
81
|
* 应用级别过滤、标题格式和平台输出策略。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/logger/index.ts"],"sourcesContent":["import { runtimeGlobals } 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 runtimeGlobals.uni !== undefined && runtimeGlobals.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: unknown = JSON.stringify(\n\t\t\tvalue,\n\t\t\t(_key, item: unknown): 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): void => {\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): void => {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(...data);\n\t},\n\twarn: (...data): void => {\n\t\tconsole.warn(...data);\n\t},\n\terror: (...data): void => {\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 requestedLevel: unknown = options.level ?? \"debug\";\n\tconst requestedPrefix: unknown = options.prefix ?? \"Fast\";\n\tconst sink = options.sink ?? defaultConsoleSink;\n\tif (!isLogLevel(requestedLevel)) throw new RangeError(`未知的日志级别:${String(requestedLevel)}。`);\n\tif (typeof requestedPrefix !== \"string\" || requestedPrefix.length === 0) {\n\t\tthrow new RangeError(\"日志前缀必须是非空字符串。\");\n\t}\n\tconst level = requestedLevel;\n\tconst prefix = requestedPrefix;\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[]): void => {\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: keyof LoggerSink = 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): void => {\n\t\t\twrite(\"debug\", scope, content);\n\t\t},\n\t\tlog: (scope, ...content): void => {\n\t\t\twrite(\"log\", scope, content);\n\t\t},\n\t\twarn: (scope, ...content): void => {\n\t\t\twrite(\"warn\", scope, content);\n\t\t},\n\t\terror: (scope, ...content): void => {\n\t\t\twrite(\"error\", scope, content);\n\t\t},\n\t};\n}\n\nlet activeDefaultLogger: Logger = 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): void => {\n\t\tactiveDefaultLogger.debug(scope, ...content);\n\t},\n\tlog: (scope, ...content): void => {\n\t\tactiveDefaultLogger.log(scope, ...content);\n\t},\n\twarn: (scope, ...content): void => {\n\t\tactiveDefaultLogger.warn(scope, ...content);\n\t},\n\terror: (scope, ...content): void => {\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":["import { runtimeGlobals } 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 runtimeGlobals.uni !== undefined && runtimeGlobals.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(`未知的日志级别:${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 +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\"] as const;\nconst binaryByteUnits = [\"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\", \"PiB\", \"EiB\", \"ZiB\", \"YiB\"] as const;\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\tconst exponent = 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":["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\tconst exponent = 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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"}
|
package/dist/object/index.d.mts
CHANGED
|
@@ -28,22 +28,61 @@ export declare function isPlainObject(value: unknown): value is Record<PropertyK
|
|
|
28
28
|
* @returns 属性为对象自有属性时返回 `true`,并收窄键类型。
|
|
29
29
|
*/
|
|
30
30
|
export declare function hasOwn<ObjectType extends object, Key extends PropertyKey>(value: ObjectType, key: Key): key is Key & keyof ObjectType;
|
|
31
|
+
/**
|
|
32
|
+
* 递归创建值的深层副本。
|
|
33
|
+
*
|
|
34
|
+
* @remarks 支持循环引用、共享引用、Symbol 键、对象原型、ArrayBuffer、DataView、Date、Map、RegExp、Set 和 TypedArray。
|
|
35
|
+
* Map 的键保持原引用,函数及其他不可克隆值在嵌套位置保持原引用;只复制自有可枚举属性。
|
|
36
|
+
* @param value - 需要深复制的任意值。
|
|
37
|
+
* @returns 与输入类型一致且不共享可克隆嵌套值的新值;原始类型直接返回自身。
|
|
38
|
+
*/
|
|
39
|
+
export declare function cloneDeep<Value>(value: Value): Value;
|
|
40
|
+
/**
|
|
41
|
+
* 深度比较两个值是否等价。
|
|
42
|
+
*
|
|
43
|
+
* @remarks 原始值使用 SameValueZero 语义;支持循环引用、数组、对象、ArrayBuffer、DataView、Date、Error、Map、RegExp、Set、Symbol 和 TypedArray。
|
|
44
|
+
* 对象只比较自有可枚举字符串与 Symbol 属性,函数及其他不支持的宿主对象仅在引用相同时相等。
|
|
45
|
+
* @param left - 第一待比较值。
|
|
46
|
+
* @param right - 第二待比较值。
|
|
47
|
+
* @returns 两个值深度等价时返回 `true`。
|
|
48
|
+
*/
|
|
49
|
+
export declare function isEqual(left: unknown, right: unknown): boolean;
|
|
31
50
|
/**
|
|
32
51
|
* 从对象中选择指定自有可枚举属性。
|
|
33
52
|
*
|
|
53
|
+
* @remarks 字面量键数组保留精确返回类型;普通 `string[]` 等动态键数组返回 `Partial<Source>`。
|
|
34
54
|
* @param source - 不会被修改的源对象。
|
|
35
55
|
* @param keys - 需要保留的键;不存在的键被忽略。
|
|
36
56
|
* @returns 新对象,保持 `keys` 的遍历顺序。
|
|
37
57
|
*/
|
|
38
|
-
export declare function pick<Source extends object, Keys extends readonly (keyof Source)[]>(source: Source, keys: Keys): Pick<Source, Keys[number]>;
|
|
58
|
+
export declare function pick<Source extends object, const Keys extends readonly (keyof Source)[]>(source: Source, keys: Keys): Pick<Source, Keys[number]>;
|
|
59
|
+
export declare function pick<Source extends object>(source: Source, keys: readonly PropertyKey[]): Partial<Source>;
|
|
39
60
|
/**
|
|
40
61
|
* 浅复制对象并删除指定属性。
|
|
41
62
|
*
|
|
63
|
+
* @remarks 字面量键数组保留精确返回类型;普通 `string[]` 等动态键数组返回 `Partial<Source>`。
|
|
42
64
|
* @param source - 不会被修改的源对象。
|
|
43
65
|
* @param keys - 需要排除的键。
|
|
44
66
|
* @returns 包含其余自有可枚举字符串与 Symbol 属性的新对象。
|
|
45
67
|
*/
|
|
46
|
-
export declare function omit<Source extends object, Keys extends readonly (keyof Source)[]>(source: Source, keys: Keys): Omit<Source, Keys[number]>;
|
|
68
|
+
export declare function omit<Source extends object, const Keys extends readonly (keyof Source)[]>(source: Source, keys: Keys): Omit<Source, Keys[number]>;
|
|
69
|
+
export declare function omit<Source extends object>(source: Source, keys: readonly PropertyKey[]): Partial<Source>;
|
|
70
|
+
/**
|
|
71
|
+
* 按条件排除对象的自有可枚举属性。
|
|
72
|
+
*
|
|
73
|
+
* @param source - 不会被修改的源对象。
|
|
74
|
+
* @param predicate - 接收属性值、键和源对象;返回真值时排除该属性。
|
|
75
|
+
* @returns 由未匹配属性组成的新对象。
|
|
76
|
+
*/
|
|
77
|
+
export declare function omitBy<Source extends object>(source: Source, predicate: (value: Source[keyof Source], key: keyof Source, source: Source) => unknown): Partial<Source>;
|
|
78
|
+
/**
|
|
79
|
+
* 按条件选择对象的自有可枚举属性。
|
|
80
|
+
*
|
|
81
|
+
* @param source - 不会被修改的源对象。
|
|
82
|
+
* @param predicate - 接收属性值、键和源对象;返回真值时保留该属性。
|
|
83
|
+
* @returns 由匹配属性组成的新对象。
|
|
84
|
+
*/
|
|
85
|
+
export declare function pickBy<Source extends object>(source: Source, predicate: (value: Source[keyof Source], key: keyof Source, source: Source) => unknown): Partial<Source>;
|
|
47
86
|
/**
|
|
48
87
|
* 映射对象的自有可枚举属性值。
|
|
49
88
|
*
|
package/dist/object/index.mjs
CHANGED
|
@@ -9,11 +9,10 @@ const isQueryPrimitiveArray = (value) => Array.isArray(value);
|
|
|
9
9
|
/**
|
|
10
10
|
* 安全写入结果对象的自有可枚举属性。
|
|
11
11
|
*
|
|
12
|
-
* @remarks 使用 `defineProperty` 避免 `__proto__` 触发 Setter
|
|
12
|
+
* @remarks 使用 `defineProperty` 避免 `__proto__` 触发 Setter,并统一创建可写、可配置的自有可枚举数据属性。
|
|
13
13
|
* @param target - 要写入的结果对象。
|
|
14
14
|
* @param key - 自有属性键。
|
|
15
15
|
* @param value - 属性值。
|
|
16
|
-
* @throws `TypeError` 当键为 `__proto__`、`prototype` 或 `constructor`。
|
|
17
16
|
*/
|
|
18
17
|
const defineEnumerableProperty = (target, key, value) => {
|
|
19
18
|
Object.defineProperty(target, key, {
|
|
@@ -23,6 +22,215 @@ const defineEnumerableProperty = (target, key, value) => {
|
|
|
23
22
|
writable: true
|
|
24
23
|
});
|
|
25
24
|
};
|
|
25
|
+
const typedArrayTags = /* @__PURE__ */ new Set([
|
|
26
|
+
"[object BigInt64Array]",
|
|
27
|
+
"[object BigUint64Array]",
|
|
28
|
+
"[object Float32Array]",
|
|
29
|
+
"[object Float64Array]",
|
|
30
|
+
"[object Int8Array]",
|
|
31
|
+
"[object Int16Array]",
|
|
32
|
+
"[object Int32Array]",
|
|
33
|
+
"[object Uint8Array]",
|
|
34
|
+
"[object Uint8ClampedArray]",
|
|
35
|
+
"[object Uint16Array]",
|
|
36
|
+
"[object Uint32Array]"
|
|
37
|
+
]);
|
|
38
|
+
/** 使用源值的构造器创建同类空实例。 */
|
|
39
|
+
const createUsingConstructor = (value, arguments_) => {
|
|
40
|
+
const constructor = Reflect.get(value, "constructor");
|
|
41
|
+
return typeof constructor === "function" ? Reflect.construct(constructor, arguments_) : {};
|
|
42
|
+
};
|
|
43
|
+
/** 复制 ArrayBufferLike 的当前字节,不与源值共享底层内存。 */
|
|
44
|
+
const cloneArrayBuffer = (value) => {
|
|
45
|
+
const result = createUsingConstructor(value, [value.byteLength]);
|
|
46
|
+
new Uint8Array(result).set(new Uint8Array(value));
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
49
|
+
/** 递归复制值,并记录已复制对象以还原循环引用和共享引用。 */
|
|
50
|
+
const cloneDeepValue = (value, clones, isRoot) => {
|
|
51
|
+
if (typeof value !== "object" && typeof value !== "function" || value === null) return value;
|
|
52
|
+
const source = value;
|
|
53
|
+
const existing = clones.get(source);
|
|
54
|
+
if (existing !== void 0) return existing;
|
|
55
|
+
if (typeof source === "function" && !isRoot) return source;
|
|
56
|
+
if (Array.isArray(source)) {
|
|
57
|
+
const result = createUsingConstructor(source, [source.length]);
|
|
58
|
+
clones.set(source, result);
|
|
59
|
+
for (let index = 0; index < source.length; index += 1) defineEnumerableProperty(result, index, cloneDeepValue(source[index], clones, false));
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
const tag = Object.prototype.toString.call(source);
|
|
63
|
+
let result;
|
|
64
|
+
if (typeof source === "function") result = {};
|
|
65
|
+
else switch (tag) {
|
|
66
|
+
case "[object Arguments]":
|
|
67
|
+
result = {};
|
|
68
|
+
break;
|
|
69
|
+
case "[object ArrayBuffer]":
|
|
70
|
+
case "[object SharedArrayBuffer]":
|
|
71
|
+
result = cloneArrayBuffer(source);
|
|
72
|
+
break;
|
|
73
|
+
case "[object Boolean]":
|
|
74
|
+
result = createUsingConstructor(source, [source.valueOf()]);
|
|
75
|
+
break;
|
|
76
|
+
case "[object DataView]": {
|
|
77
|
+
const view = source;
|
|
78
|
+
result = createUsingConstructor(source, [
|
|
79
|
+
cloneArrayBuffer(view.buffer),
|
|
80
|
+
view.byteOffset,
|
|
81
|
+
view.byteLength
|
|
82
|
+
]);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case "[object Date]":
|
|
86
|
+
result = createUsingConstructor(source, [source.getTime()]);
|
|
87
|
+
break;
|
|
88
|
+
case "[object Map]":
|
|
89
|
+
case "[object Set]":
|
|
90
|
+
result = createUsingConstructor(source, []);
|
|
91
|
+
break;
|
|
92
|
+
case "[object Number]":
|
|
93
|
+
result = createUsingConstructor(source, [source.valueOf()]);
|
|
94
|
+
break;
|
|
95
|
+
case "[object Object]": {
|
|
96
|
+
const prototype = Object.getPrototypeOf(source);
|
|
97
|
+
result = Object.create(prototype);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
case "[object RegExp]": {
|
|
101
|
+
const expression = source;
|
|
102
|
+
const clonedExpression = new RegExp(expression.source, expression.flags);
|
|
103
|
+
clonedExpression.lastIndex = expression.lastIndex;
|
|
104
|
+
result = clonedExpression;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "[object String]":
|
|
108
|
+
result = createUsingConstructor(source, [source.valueOf()]);
|
|
109
|
+
break;
|
|
110
|
+
case "[object Symbol]":
|
|
111
|
+
result = Object(source.valueOf());
|
|
112
|
+
break;
|
|
113
|
+
default: {
|
|
114
|
+
if (!typedArrayTags.has(tag)) return isRoot ? {} : source;
|
|
115
|
+
const view = source;
|
|
116
|
+
result = createUsingConstructor(source, [
|
|
117
|
+
cloneArrayBuffer(view.buffer),
|
|
118
|
+
view.byteOffset,
|
|
119
|
+
view.length
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
clones.set(source, result);
|
|
124
|
+
if (tag === "[object Map]") for (const [key, item] of source) result.set(key, cloneDeepValue(item, clones, false));
|
|
125
|
+
else if (tag === "[object Set]") for (const item of source) result.add(cloneDeepValue(item, clones, false));
|
|
126
|
+
for (const key of Reflect.ownKeys(source)) {
|
|
127
|
+
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
|
|
128
|
+
const clonedValue = cloneDeepValue(Reflect.get(source, key), clones, false);
|
|
129
|
+
if (key === "__proto__") defineEnumerableProperty(result, key, clonedValue);
|
|
130
|
+
else Reflect.set(result, key, clonedValue);
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
};
|
|
134
|
+
/** 判断两个值是否满足 SameValueZero 相等。 */
|
|
135
|
+
const sameValueZero = (left, right) => left === right || typeof left === "number" && typeof right === "number" && Number.isNaN(left) && Number.isNaN(right);
|
|
136
|
+
/** 返回对象的自有可枚举字符串与 Symbol 键。 */
|
|
137
|
+
const getEnumerableOwnKeys = (value) => Reflect.ownKeys(value).filter((key) => Object.prototype.propertyIsEnumerable.call(value, key));
|
|
138
|
+
/** 在一次递归分支内记录对象对应关系。 */
|
|
139
|
+
const compareTrackedPair = (left, right, state, compare) => {
|
|
140
|
+
const existingRight = state.leftObjects.get(left);
|
|
141
|
+
const existingLeft = state.rightObjects.get(right);
|
|
142
|
+
if (existingRight !== void 0 || existingLeft !== void 0) return existingRight === right && existingLeft === left;
|
|
143
|
+
state.leftObjects.set(left, right);
|
|
144
|
+
state.rightObjects.set(right, left);
|
|
145
|
+
try {
|
|
146
|
+
return compare();
|
|
147
|
+
} finally {
|
|
148
|
+
state.leftObjects.delete(left);
|
|
149
|
+
state.rightObjects.delete(right);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
/** 按字节比较两个 ArrayBufferLike。 */
|
|
153
|
+
const equalArrayBuffers = (left, right) => {
|
|
154
|
+
if (left.byteLength !== right.byteLength) return false;
|
|
155
|
+
const leftBytes = new Uint8Array(left);
|
|
156
|
+
const rightBytes = new Uint8Array(right);
|
|
157
|
+
for (let index = 0; index < leftBytes.length; index += 1) if (leftBytes[index] !== rightBytes[index]) return false;
|
|
158
|
+
return true;
|
|
159
|
+
};
|
|
160
|
+
/** 按顺序比较数组或 TypedArray 的元素。 */
|
|
161
|
+
const equalIndexedValues = (left, right, state) => {
|
|
162
|
+
if (left.length !== right.length) return false;
|
|
163
|
+
return compareTrackedPair(left, right, state, () => {
|
|
164
|
+
for (let index = 0; index < left.length; index += 1) if (!isEqualValue(left[index], right[index], state)) return false;
|
|
165
|
+
return true;
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
/** 无序比较 Map 条目或 Set 元素。 */
|
|
169
|
+
const equalUnorderedValues = (left, right, state) => {
|
|
170
|
+
if (left.length !== right.length) return false;
|
|
171
|
+
const matchedIndexes = /* @__PURE__ */ new Set();
|
|
172
|
+
for (const leftValue of left) {
|
|
173
|
+
let matchedIndex = -1;
|
|
174
|
+
for (let index = 0; index < right.length; index += 1) {
|
|
175
|
+
if (matchedIndexes.has(index) || !isEqualValue(leftValue, right[index], state)) continue;
|
|
176
|
+
matchedIndex = index;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
if (matchedIndex < 0) return false;
|
|
180
|
+
matchedIndexes.add(matchedIndex);
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
};
|
|
184
|
+
/** 比较普通对象或 Arguments 的自有可枚举属性与构造器。 */
|
|
185
|
+
const equalObjects = (left, right, state) => {
|
|
186
|
+
const leftKeys = getEnumerableOwnKeys(left);
|
|
187
|
+
const rightKeys = getEnumerableOwnKeys(right);
|
|
188
|
+
if (leftKeys.length !== rightKeys.length) return false;
|
|
189
|
+
for (const key of leftKeys) if (!Object.hasOwn(right, key)) return false;
|
|
190
|
+
return compareTrackedPair(left, right, state, () => {
|
|
191
|
+
let compareConstructors = true;
|
|
192
|
+
for (const key of leftKeys) {
|
|
193
|
+
if (!isEqualValue(Reflect.get(left, key), Reflect.get(right, key), state)) return false;
|
|
194
|
+
if (key === "constructor") compareConstructors = false;
|
|
195
|
+
}
|
|
196
|
+
if (!compareConstructors) return true;
|
|
197
|
+
const leftConstructor = Reflect.get(left, "constructor");
|
|
198
|
+
const rightConstructor = Reflect.get(right, "constructor");
|
|
199
|
+
if (leftConstructor === rightConstructor || !("constructor" in left && "constructor" in right)) return true;
|
|
200
|
+
return typeof leftConstructor === "function" && typeof rightConstructor === "function" && leftConstructor instanceof leftConstructor && rightConstructor instanceof rightConstructor;
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
/** 递归比较两个值。 */
|
|
204
|
+
function isEqualValue(left, right, state) {
|
|
205
|
+
if (sameValueZero(left, right)) return true;
|
|
206
|
+
if (left === null || left === void 0 || right === null || right === void 0) return false;
|
|
207
|
+
const leftTag = Object.prototype.toString.call(left);
|
|
208
|
+
const rightTag = Object.prototype.toString.call(right);
|
|
209
|
+
const normalizedLeftTag = leftTag === "[object Arguments]" ? "[object Object]" : leftTag;
|
|
210
|
+
if (normalizedLeftTag !== (rightTag === "[object Arguments]" ? "[object Object]" : rightTag)) return false;
|
|
211
|
+
switch (normalizedLeftTag) {
|
|
212
|
+
case "[object Boolean]":
|
|
213
|
+
case "[object Date]":
|
|
214
|
+
case "[object Number]": return sameValueZero(Number(left), Number(right));
|
|
215
|
+
case "[object Error]": return left.name === right.name && left.message === right.message;
|
|
216
|
+
case "[object RegExp]": return RegExp.prototype.toString.call(left) === RegExp.prototype.toString.call(right);
|
|
217
|
+
case "[object String]": return String.prototype.valueOf.call(left) === String.prototype.valueOf.call(right);
|
|
218
|
+
case "[object Symbol]": return Symbol.prototype.valueOf.call(left) === Symbol.prototype.valueOf.call(right);
|
|
219
|
+
}
|
|
220
|
+
if (typeof left !== "object" || typeof right !== "object") return false;
|
|
221
|
+
if (Array.isArray(left) && Array.isArray(right)) return equalIndexedValues(left, right, state);
|
|
222
|
+
if (normalizedLeftTag === "[object ArrayBuffer]" || normalizedLeftTag === "[object SharedArrayBuffer]") return equalArrayBuffers(left, right);
|
|
223
|
+
if (normalizedLeftTag === "[object DataView]") {
|
|
224
|
+
const leftView = left;
|
|
225
|
+
const rightView = right;
|
|
226
|
+
return leftView.byteLength === rightView.byteLength && leftView.byteOffset === rightView.byteOffset && equalArrayBuffers(leftView.buffer, rightView.buffer);
|
|
227
|
+
}
|
|
228
|
+
if (typedArrayTags.has(normalizedLeftTag)) return equalIndexedValues(left, right, state);
|
|
229
|
+
if (normalizedLeftTag === "[object Map]") return compareTrackedPair(left, right, state, () => equalUnorderedValues([...left], [...right], state));
|
|
230
|
+
if (normalizedLeftTag === "[object Set]") return compareTrackedPair(left, right, state, () => equalUnorderedValues([...left], [...right], state));
|
|
231
|
+
if (normalizedLeftTag === "[object Object]") return equalObjects(left, right, state);
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
26
234
|
/**
|
|
27
235
|
* 判断值是否是普通对象。
|
|
28
236
|
*
|
|
@@ -46,27 +254,63 @@ function hasOwn(value, key) {
|
|
|
46
254
|
return Object.hasOwn(value, key);
|
|
47
255
|
}
|
|
48
256
|
/**
|
|
49
|
-
*
|
|
257
|
+
* 递归创建值的深层副本。
|
|
50
258
|
*
|
|
51
|
-
* @
|
|
52
|
-
*
|
|
53
|
-
* @
|
|
259
|
+
* @remarks 支持循环引用、共享引用、Symbol 键、对象原型、ArrayBuffer、DataView、Date、Map、RegExp、Set 和 TypedArray。
|
|
260
|
+
* Map 的键保持原引用,函数及其他不可克隆值在嵌套位置保持原引用;只复制自有可枚举属性。
|
|
261
|
+
* @param value - 需要深复制的任意值。
|
|
262
|
+
* @returns 与输入类型一致且不共享可克隆嵌套值的新值;原始类型直接返回自身。
|
|
54
263
|
*/
|
|
264
|
+
function cloneDeep(value) {
|
|
265
|
+
return cloneDeepValue(value, /* @__PURE__ */ new WeakMap(), true);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* 深度比较两个值是否等价。
|
|
269
|
+
*
|
|
270
|
+
* @remarks 原始值使用 SameValueZero 语义;支持循环引用、数组、对象、ArrayBuffer、DataView、Date、Error、Map、RegExp、Set、Symbol 和 TypedArray。
|
|
271
|
+
* 对象只比较自有可枚举字符串与 Symbol 属性,函数及其他不支持的宿主对象仅在引用相同时相等。
|
|
272
|
+
* @param left - 第一待比较值。
|
|
273
|
+
* @param right - 第二待比较值。
|
|
274
|
+
* @returns 两个值深度等价时返回 `true`。
|
|
275
|
+
*/
|
|
276
|
+
function isEqual(left, right) {
|
|
277
|
+
return isEqualValue(left, right, {
|
|
278
|
+
leftObjects: /* @__PURE__ */ new WeakMap(),
|
|
279
|
+
rightObjects: /* @__PURE__ */ new WeakMap()
|
|
280
|
+
});
|
|
281
|
+
}
|
|
55
282
|
function pick(source, keys) {
|
|
56
283
|
const result = {};
|
|
57
|
-
for (const key of keys) if (Object.prototype.propertyIsEnumerable.call(source, key)) defineEnumerableProperty(result, key, source
|
|
284
|
+
for (const key of keys) if (Object.prototype.propertyIsEnumerable.call(source, key)) defineEnumerableProperty(result, key, Reflect.get(source, key));
|
|
285
|
+
return result;
|
|
286
|
+
}
|
|
287
|
+
function omit(source, keys) {
|
|
288
|
+
const result = { ...source };
|
|
289
|
+
for (const key of keys) Reflect.deleteProperty(result, key);
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* 按条件排除对象的自有可枚举属性。
|
|
294
|
+
*
|
|
295
|
+
* @param source - 不会被修改的源对象。
|
|
296
|
+
* @param predicate - 接收属性值、键和源对象;返回真值时排除该属性。
|
|
297
|
+
* @returns 由未匹配属性组成的新对象。
|
|
298
|
+
*/
|
|
299
|
+
function omitBy(source, predicate) {
|
|
300
|
+
const result = {};
|
|
301
|
+
for (const key of getEnumerableOwnKeys(source)) if (!predicate(source[key], key, source)) defineEnumerableProperty(result, key, source[key]);
|
|
58
302
|
return result;
|
|
59
303
|
}
|
|
60
304
|
/**
|
|
61
|
-
*
|
|
305
|
+
* 按条件选择对象的自有可枚举属性。
|
|
62
306
|
*
|
|
63
307
|
* @param source - 不会被修改的源对象。
|
|
64
|
-
* @param
|
|
65
|
-
* @returns
|
|
308
|
+
* @param predicate - 接收属性值、键和源对象;返回真值时保留该属性。
|
|
309
|
+
* @returns 由匹配属性组成的新对象。
|
|
66
310
|
*/
|
|
67
|
-
function
|
|
68
|
-
const result = {
|
|
69
|
-
for (const key of
|
|
311
|
+
function pickBy(source, predicate) {
|
|
312
|
+
const result = {};
|
|
313
|
+
for (const key of getEnumerableOwnKeys(source)) if (predicate(source[key], key, source)) defineEnumerableProperty(result, key, source[key]);
|
|
70
314
|
return result;
|
|
71
315
|
}
|
|
72
316
|
/**
|
|
@@ -129,6 +373,6 @@ function toQueryString(value, options = {}) {
|
|
|
129
373
|
return result && options.prefixQuestionMark ? `?${result}` : result;
|
|
130
374
|
}
|
|
131
375
|
//#endregion
|
|
132
|
-
export { hasOwn, isPlainObject, mapValues, omit, pick, shallowEqual, toQueryString };
|
|
376
|
+
export { cloneDeep, hasOwn, isEqual, isPlainObject, mapValues, omit, omitBy, pick, pickBy, shallowEqual, toQueryString };
|
|
133
377
|
|
|
134
378
|
//# sourceMappingURL=index.mjs.map
|