@axutils/common
v0.1.0
Published
axutils 的公共工具子包
Readme
@axutils/common
@axutils/common 是 axutils monorepo 中的公共工具子包,当前提供一组常用类型判断、格式校验、运行时平台判断、对象工具、JSON 序列化与 MD5 工具方法,作为后续公共工具集合的基础能力。
兼容性
- 包消费运行时:
Node.js >= 14.18.0
安装
pnpm add @axutils/common如果需要使用 RxJS HTTP 子路径,请按需安装:
pnpm add @axutils/common rxjs axios safe-stable-stringify spark-md5@axutils/common/rxjs/http 不会从包主入口加载;不使用该功能时无需安装这些依赖。
如果只使用不依赖 RxJS 的 Axios Promise HTTP 子路径,请安装:
pnpm add @axutils/common axios safe-stable-stringify spark-md5@axutils/common/axios/http 不会从包主入口加载,也不需要安装 rxjs。
如果需要使用可选子路径依赖,请额外安装对应 peer 依赖:
| 子路径 | 需要安装的方法 | peer 依赖 | 安装命令 |
| --- | --- | --- | --- |
| @axutils/common/rxjs/http | RxHttpClient、HttpRequestError | rxjs、axios、safe-stable-stringify、spark-md5 | pnpm add rxjs axios safe-stable-stringify spark-md5 |
| @axutils/common/axios/http | PromiseHttpClient、PromiseHttpRequestError | axios、safe-stable-stringify、spark-md5 | pnpm add axios safe-stable-stringify spark-md5 |
| @axutils/common/object/json | jsonStringify、jsonStringifySafe | safe-stable-stringify | pnpm add safe-stable-stringify |
| @axutils/common/crypto/md5 | Md5 | spark-md5 | pnpm add spark-md5 |
| @axutils/common/date | PlainDate、PlainTime、PlainDateTime、ZonedDateTime、Instant、Duration、Now | date-fns、date-fns-tz | pnpm add date-fns date-fns-tz |
jsonParse、jsonParseSafe不依赖第三方库;Node 侧@axutils/common/node/crypto/md5基于node:crypto,均无需额外安装。
Axios Promise HTTP 请求
从 @axutils/common/axios/http 按需导入。Promise 版本在调用 request、get、post 等方法时立即开始配置解析和网络请求;它不提供 Observable 式懒执行,也不支持 cancelOnNoSubscribers。Axios 默认适配器兼容浏览器、Node.js 和 Nuxt SSR。
import { PromiseHttpClient, PromiseHttpRequestError } from "@axutils/common/axios/http";
const client = new PromiseHttpClient({
baseUrl: "https://api.example.com",
retryCount: 3, // 最多三次总尝试,不是额外重试三次
retryDelay: 100,
timeout: 10_000,
});
try {
const result = await client.get<{ id: number }>("/users/1");
console.log(result.code, result.data);
} catch (error) {
if (error instanceof PromiseHttpRequestError) {
console.error(error.error.kind, error.code, error.error.cause);
}
}如果配置需要异步获取,可以使用静态 create。构造客户端不会执行工厂;第一次请求时并发调用共享一次初始化,成功配置会缓存,失败结果不会缓存。AbortSignal 只取消当前调用方等待配置和请求的 Promise,不会中止或污染客户端级共享初始化;因此首个带 signal 的请求被取消后,无 signal 的并发请求仍会继续等待同一个初始化,工厂成功后配置仍会缓存。配置工厂返回的普通 Promise 无法被强制中止,但调用方的请求 Promise 仍会在 AbortSignal 触发后立即结束:
const client = PromiseHttpClient.create(
async () => ({ baseUrl: await loadApiUrl(), retryCount: 3 }),
{ retryDelay: 100 },
);默认情况下,相同 method、完整 URL、params、headers、data、timeout 和重试选项的未完成请求只执行一次,并共享同一个成功结果对象或 PromiseHttpRequestError 实例;请求完成、失败或取消后不会缓存响应。Map、Set、FormData、流、类实例和循环引用等不稳定值默认不去重,可传入 dedupeKey 声明其业务身份。带 signal 的请求始终独立执行,不参与自动去重:
const controller = new AbortController();
const request = client.get("/profile", {
params: { tenant: "demo" },
signal: controller.signal,
});
controller.abort();
await request.catch((error) => console.log(error.error.kind)); // cancel默认只对 GET、HEAD、OPTIONS 的明确 Axios 网络错误(ERR_NETWORK)、超时、429 和 5xx 重试;4xx(429 除外)、取消、普通 Error 和无 response 的未知 AxiosError 都不会重试。POST、PUT、PATCH、DELETE 只有显式传入 retryNonIdempotent: true 才允许重试。错误统一分为 config、http、network、timeout、cancel 和 unknown,无 HTTP 响应时 code 为 0。
retryCount 表示总尝试次数,范围为 1 到 100;retryDelay 和 timeout 的最大值为 2_147_483_647 毫秒,以避免超出浏览器和 Node.js 定时器可可靠表达的范围。请求级覆盖、同步客户端配置和异步配置工厂使用相同的边界校验。传入的 signal 需要同时具备布尔值 aborted 以及 addEventListener、removeEventListener 方法,支持跨 Realm 的 AbortSignal 兼容实现。
RxJS HTTP 请求
从 @axutils/common/rxjs/http 按需导入。请求方法返回 Observable,只有订阅时才会读取异步配置并调用 Axios;Axios 的默认适配器兼容浏览器、Node.js 和 Nuxt SSR。
import { RxHttpClient } from "@axutils/common/rxjs/http";
const client = new RxHttpClient({
baseUrl: "https://api.example.com",
retryCount: 3, // 最多三次总尝试,不是额外重试三次
retryDelay: 100,
timeout: 10_000,
});
client.get<{ id: number }>("/users/1").subscribe({
next: (result) => {
console.log(result.code, result.data);
},
error: (error) => {
console.error(error);
},
});如果配置需要异步获取,可以使用静态 create;工厂返回 Observable,首次请求成功后配置会缓存,失败时默认最多按同步选项的 retryCount 尝试三次:
import { of } from "rxjs";
import { RxHttpClient } from "@axutils/common/rxjs/http";
const client = RxHttpClient.create(
() => of({ baseUrl: "https://api.example.com", retryCount: 3 }),
{ retryCount: 3 },
);相同 method、完整 URL、params、data、headers、timeout 和重试选项的请求,在上一个请求结束前只会执行一次;订阅者共享同一个成功结果对象或错误实例。默认情况下,即使最后一个订阅者提前取消订阅,仍会等待底层请求结束并复用它,避免重复发起;开启 cancelOnNoSubscribers 后才会在最后一个订阅者离开时中止请求。请求结束后不会保留响应缓存,下一次调用会重新请求。传入 signal 的请求不会自动去重,以保证每个调用方都能独立取消;abort() 也会立即终止异步配置和 retryDelay 等尚未发起网络请求的等待阶段。
const request$ = client.get("/profile", { params: { tenant: "demo" } });
request$.subscribe(renderProfile);
client.get("/profile", { params: { tenant: "demo" } }).subscribe(renderProfile);失败通过 Observable 的 error 通道发出 HttpRequestError,其 code 只表示 HTTP 状态码;无 HTTP 响应时为 0,错误分类为 config、http、network、timeout、cancel 或 unknown。默认只对 GET、HEAD、OPTIONS 的网络错误、超时、429 和 5xx 重试,4xx 不重试;可用 retryable: false 关闭单个请求的重试。POST、PUT、PATCH、DELETE 默认不重试;只有明确传入 retryNonIdempotent: true 时才允许这些方法重试,以避免网络异常但服务端已完成写入时造成重复提交。
import { HttpRequestError } from "@axutils/common/rxjs/http";
client.get("/profile").subscribe({
error: (error) => {
if (error instanceof HttpRequestError) {
console.log(error.error.kind, error.code, error.error.cause);
}
},
});FormData、流、Map、Set 和循环引用等无法稳定 JSON 序列化的请求体默认不会自动去重;需要显式传入相同的 dedupeKey。显式 key 只负责声明不稳定请求体的去重身份;method、完整 URL、重试选项以及可稳定序列化的 params、headers 仍会参与区分,因此不同 URL 不会因为复用了同一个 key 而错误合并。如果 params 或 headers 本身也无法稳定序列化,应由 key 一并表达其业务身份:
client.post("/upload", formData, { dedupeKey: "upload:avatar:1" });如果希望在最后一个订阅者取消时中止底层 Axios 请求,可以开启 cancelOnNoSubscribers;默认值为 false,取消订阅时只停止当前订阅者接收结果。使用请求去重时,只有所有订阅者都取消后才会触发 abort:
const request$ = client.get("/search", {
params: { keyword: "rxjs" },
cancelOnNoSubscribers: true,
});
const firstSubscription = request$.subscribe(renderResult);
const secondSubscription = request$.subscribe(renderResult);
firstSubscription.unsubscribe(); // 请求继续执行
secondSubscription.unsubscribe(); // 最后一个订阅者离开,取消 Axios 请求写请求或后台任务如果需要在调用方取消订阅后继续执行,请保持 cancelOnNoSubscribers: false。
UMD 全量包会内置 RxJS、Axios、safe-stable-stringify 和 spark-md5;ESM/CJS 的 rxjs/http 与 axios/http 子路径则将各自依赖作为可选 peer 依赖按需安装。
防抖、节流与深拷贝
三个工具均不依赖第三方包,可从主入口或对应子路径导入:
import { debounce, deepClone, throttle } from "@axutils/common";
const saveDraft = debounce((content: string) => {
console.log("保存草稿", content);
}, 300);
saveDraft("latest");
saveDraft.cancel(); // 取消尚未执行的保存
const handleResize = throttle(() => {
console.log("处理一次尺寸变化");
}, 100);
handleResize(); // 首次调用立即执行,周期内最后一次调用会在周期末补执行
const copiedState = deepClone({ user: { id: 1 }, tags: ["common"] });也可以按需导入:
import { debounce, throttle } from "@axutils/common/object/timing";
import { deepClone } from "@axutils/common/object/object";debounce 和 throttle 的 wait 必须是 0 到 2_147_483_647 之间的有限数字;非有限值、负数或超出定时器上限的值分别抛出 TypeError 或 RangeError,0 合法。两个包装函数都会保留调用时的 this 和参数,并提供 cancel();防抖默认只执行停止调用后的最后一次,节流默认首次立即执行并在周期末执行最后一次。如果周期边界仍有待执行的 trailing 定时器,边界调用会并入该调度并返回 undefined;其他节流周期内排队的调用也返回 undefined,只有同步执行的回调结果会返回给调用方。
deepClone 支持原始值、数组、当前或其他 Realm 创建的普通对象、Date、RegExp、Map、Set、循环引用和共享引用,复制可枚举自有字符串/Symbol 属性并保留 Object.create(null) 原型。函数、自定义 class 实例、TypedArray、WeakMap、WeakSet 和 Promise 等未声明支持的对象会原样保留;属性描述符和非枚举属性不会复制。
缓存
通用缓存从主入口或 @axutils/common/object/storage 导入。浏览器中默认使用 localStorage,传入 type: "session" 时使用 sessionStorage;实例创建时会用临时 key 探测目标 Web Storage 是否真正可读写,在 Node 或探测失败时固定降级为对应类型的进程内 Map。
import { StorageUtils } from "@axutils/common/object/storage";
const storage = new StorageUtils({
prefix: "app:",
expired: 300, // 默认 300 秒;小于等于 0 表示不过期
type: "local",
});
storage.set("user", { id: 1 });
console.log(storage.get<{ id: number }>("user"));
storage.remove("user");
storage.clear(); // 只清理 prefix 为 "app:" 的缓存通用缓存值通过 JSON 编解码,不支持循环引用、BigInt、undefined、函数和 Symbol。其中 undefined、函数和 Symbol 即使位于对象字段或数组元素中也会被拒绝:set 抛出 TypeError,setSafe 返回 false 且不会写入数据。Date、NaN、Infinity 和返回可序列化值的自定义 toJSON() 保持原生 JSON.stringify 语义;Map、Set、Symbol 属性键等其他类型仍按原生 JSON 规则转换或忽略。
过期时间单位为秒,小于等于 0 表示不过期;非有限数字会抛出 TypeError,计算后的绝对时间超出 JavaScript 安全整数范围时会抛出 RangeError。对应的 setSafe 会吞掉异常并返回 false。
配置 key 处理函数时,函数接收已经拼接 prefix 的 key;例如使用 MD5 时可以这样写(需要先安装 spark-md5):
import { Md5 } from "@axutils/common/crypto/md5";
import { StorageUtils } from "@axutils/common/object/storage";
const storage = new StorageUtils({
prefix: "app:",
key: (key) => new Md5().update(key).toHex(),
});Node 端如需明确使用高性能 Map 实现,可从 @axutils/common/node/object/storage 导入。它不做 JSON 编解码,直接保存值引用;缓存仅在当前 Node 进程内有效,不跨进程或重启持久化。
import { StorageUtils } from "@axutils/common/node/object/storage";
const storage = new StorageUtils({ prefix: "worker:" });
storage.set("job", { id: 1 });两套实现都提供 get、set、remove、clear 以及对应的 getSafe、setSafe、removeSafe、clearSafe。safe 方法不会抛错:getSafe 失败返回 null,其他 safe 方法失败返回 false,成功返回 true。
时间工具
时间工具从 @axutils/common/date 按需导入,API 按 Temporal 的命名空间和方法名组织,但当前返回的是轻量 Date、epoch 毫秒或 ZonedDateTimeValue,不是原生 Temporal 实例。使用此子路径需要安装可选 peer 依赖:
pnpm add date-fns date-fns-tz所有 from() 在输入无效时抛出 RangeError。纯日期、纯时间和无时区日期时间从 Date 提取字段时统一使用 UTC getter;日期时间字符串的日期和时间部分可以用 T、t 或空格分隔,例如 2024-06-15 10:30:00,模块会直接解析,避免运行时本地时区导致日期偏移。需要时区的参数使用 IANA 标识符,例如 Asia/Shanghai、America/New_York 和 UTC;省略时使用运行时本地时区。
import {
Duration,
DATE_FORMAT,
Instant,
Now,
PlainDate,
PlainDateTime,
PlainTime,
TIMEZONE,
ZonedDateTime,
} from "@axutils/common/date";
const date = PlainDate.add("2024-01-31", { months: 1 });
console.log(PlainDate.toString(date)); // 2024-02-29,月末溢出会 clamp 到目标月最后一天
console.log(PlainTime.toString(PlainTime.add("23:30:00", { hours: 1 }))); // 00:30:00
console.log(PlainDateTime.toString(PlainDateTime.from("2024-06-15 10:30:00")));
console.log(PlainDateTime.format(PlainDateTime.from("2024-01-02T03:04:05"), DATE_FORMAT.CN_DATE_TIME));
const zdt = ZonedDateTime.from("2024-06-15T10:00:00", { timezone: TIMEZONE.CHINA });
console.log(ZonedDateTime.toString(ZonedDateTime.withTimeZone(zdt, TIMEZONE.AMERICA_NEW_YORK)));
console.log(Instant.epochMilliseconds(Instant.from("2024-06-15T10:00:00Z")));
console.log(Duration.fromMilliseconds(90_061_000));
console.log(Now.plainDateISO("Asia/Shanghai"));公开命名空间和主要方法如下:
PlainDate:from、of、toZonedDateTime、toPlainDateTime、add、subtract、since、equals、compare、isBefore、isAfter、isBetween、yearOf、monthOf、dayOf、dayOfWeek、daysInMonth、startOfWeek、endOfWeek、toString、format。年月日按日历运算,format固定使用 UTC。PlainTime:from、of、add、subtract、since、equals、compare、isBefore、isAfter、hourOf、minuteOf、secondOf、millisecondOf、toString。加减不跨日,超过 24 小时按周期取模。PlainDateTime:from、toZonedDateTime、add、subtract、since、equals、compare、toPlainDate、toPlainTime、isBefore、isAfter、format、toString。可通过format的options.timezone指定格式化时区。ZonedDateTime:from、toInstant、toPlainDate、toPlainTime、toPlainDateTime、withTimeZone、add、subtract、since、equals、compare、format、toString。内部以{ epochMs, timezone }表示,切换时区保持绝对时刻;加减按实际经过时间计算,因此跨 DST 时不保证保持相同挂钟时间。Instant:from、fromEpochMilliseconds、toZonedDateTime、epochMilliseconds、add、subtract、since、equals、compare。from只接受带Z或 UTC 偏移的 ISO 字符串,add/subtract对非零years、months抛出RangeError。Duration:from、fromMilliseconds、totalMilliseconds、negated、abs、add、subtract。from、add、subtract保留各字段、不自动归约;fromMilliseconds才会按天到毫秒完整拆解。Now:plainDateISO、plainTimeISO、plainDateTimeISO、zonedDateTimeISO、instant。
DATE_FORMAT 提供 DATE、DATE_TIME、DATE_TIME_MS、SLASH_DATE、SLASH_DATE_TIME、CN_DATE、CN_DATE_TIME、TIME、TIME_MS、ISO_OFFSET 和 ISO_UTC 常用格式;format() 的 pattern 参数使用 DateFormatPattern 类型,IDE 会提示这些预设,同时仍允许传入自定义 date-fns 格式字符串。CN_DATE 和 CN_DATE_TIME 使用不补零的月份、日期,更符合中文页面的自然展示。
TIMEZONE 按亚洲、欧洲、美洲、非洲和大洋洲提供全球主要国家及地区常用的 IANA 时区标识符,例如 ASIA_SHANGHAI、ASIA_TOKYO、EUROPE_LONDON、EUROPE_PARIS、AMERICA_NEW_YORK、AMERICA_LOS_ANGELES、AFRICA_CAIRO、AUSTRALIA_SYDNEY 和 PACIFIC_AUCKLAND。所有公开的 timezone 参数都使用 Timezone 类型,IDE 会提示 TIMEZONE. 下的常用值,同时仍允许传入自定义 IANA 字符串。CHINA 是 Asia/Shanghai 的语义化别名。列表覆盖高频城市和商业场景,不等于完整 IANA 时区数据库;特殊地区仍可直接传入自定义 IANA 字符串。不要使用 CST、IST 等有歧义的缩写。
Now.plainTimeISO 会保留当前毫秒字段;返回值仍是以 1970-01-01T...Z 表示目标时区墙上时间的 UTC 对齐 Date。
format 的 options.locale 接受已导入的 date-fns locale 对象(例如 import { zhCN } from "date-fns/locale"),不接受字符串名称;DATE_FORMAT 和 TIMEZONE 可帮助 IDE 补全常用值。时间常量 MS_PER_SECOND、MS_PER_MINUTE、MS_PER_HOUR、MS_PER_DAY、SECONDS_PER_MINUTE、SECONDS_PER_HOUR 不依赖第三方库,可直接从 @axutils/common 主入口导入。
使用方式
从包主入口导入:
import {
isBoolean,
isArrowFunction,
isAsyncArrowFunction,
isAsyncFunction,
isBrowser,
isBrowserLike,
isBun,
isDate,
isDeno,
isEmail,
isFunction,
isHexColor,
isHttpUrl,
isIdCardCn,
isIpv4,
isNil,
isNode,
isNormalFunction,
isNumber,
isObject,
isPhoneCn,
isPlainObject,
objectToQuery,
queryToObject,
isServer,
isString,
isWebWorker,
} from "@axutils/common";
console.log(isNumber(1));
console.log(isString("common"));
console.log(isBoolean(true));
console.log(isNil(null));
console.log(isFunction(() => {}));
console.log(isNormalFunction(function () {}));
console.log(isArrowFunction(() => {}));
console.log(isAsyncFunction(async () => {}));
console.log(isAsyncArrowFunction(async () => {}));
console.log(isDate(new Date()));
console.log(isObject({ name: "common" }));
console.log(isPlainObject({}));
console.log(objectToQuery({ page: 1, tag: ["typescript", "utils"] }));
console.log(queryToObject("?tag=typescript&tag=utils"));
console.log(isPhoneCn("13800138000"));
console.log(isEmail("[email protected]"));
console.log(isHttpUrl("https://example.com"));
console.log(isIpv4("192.168.1.1"));
console.log(isIdCardCn("11010519491231002X"));
console.log(isHexColor("#ffffff"));
console.log(isBrowser());
console.log(isNode());
console.log(isServer());从子路径导入:
import {
isBoolean,
isArrowFunction,
isAsyncArrowFunction,
isAsyncFunction,
isDate,
isFunction,
isNil,
isNormalFunction,
isNumber,
isObject,
isPlainObject,
isString,
} from "@axutils/common/check/type";
import {
isEmail,
isHexColor,
isHttpUrl,
isIdCardCn,
isIpv4,
isPhoneCn,
} from "@axutils/common/check/reg";
import {
isBrowser,
isBrowserLike,
isBun,
isDeno,
isNode,
isServer,
isWebWorker,
} from "@axutils/common/check/platform";
import { Md5 } from "@axutils/common/crypto/md5";
import { bytesToBase64, bytesToHex } from "@axutils/common/crypto/convert";
import { Md5 as NodeMd5 } from "@axutils/common/node/crypto/md5";
import {
decodeBase64,
decodeHex,
normalizeMd5Input,
} from "@axutils/common/node/crypto/convert";
import {
jsonParse,
jsonParseSafe,
jsonStringify,
jsonStringifySafe,
} from "@axutils/common/object/json";
import { objectToQuery, queryToObject } from "@axutils/common/object/url";
console.log(isBoolean(true));
console.log(isObject({ source: "subpath" }));
console.log(isPhoneCn("13800138000"));
console.log(isEmail("[email protected]"));
console.log(isHttpUrl("https://example.com"));
console.log(isIpv4("192.168.1.1"));
console.log(isIdCardCn("11010519491231002X"));
console.log(isHexColor("#ffffff"));
console.log(isBrowser());
console.log(isNode());
console.log(isServer());
console.log(jsonStringify({ b: 2, a: 1 }, { sortKeys: true }));
console.log(jsonParse('{"a":1}'));
console.log(objectToQuery({ page: 1, tag: ["typescript", "utils"] }));
console.log(queryToObject("https://example.com/?tag=typescript&tag=utils"));
console.log(new Md5().update("hello").toHex());
console.log(
bytesToHex([
93, 65, 64, 42, 188, 75, 42, 118, 185, 113, 157, 145, 16, 23, 197, 146,
]),
);
console.log(
bytesToBase64([
93, 65, 64, 42, 188, 75, 42, 118, 185, 113, 157, 145, 16, 23, 197, 146,
]),
);
console.log(new NodeMd5().update("hello").toBase64());
console.log(decodeHex("68656c6c6f"));
console.log(decodeBase64("aGVsbG8="));
console.log(normalizeMd5Input("hello"));浏览器端也可通过 UMD 全量包直接引入所有浏览器侧工具(无需模块系统):
<script src="https://unpkg.com/@axutils/common/dist/index.umd.cjs"></script>
<script>
console.log(AxutilsCommon.isNumber(1));
console.log(AxutilsCommon.isEmail("[email protected]"));
console.log(
AxutilsCommon.jsonStringify({ b: 2, a: 1 }, { sortKeys: true }),
);
console.log(
AxutilsCommon.objectToQuery({ page: 1, tag: ["typescript", "utils"] }),
);
console.log(new AxutilsCommon.Md5().update("hello").toHex());
</script>注意:
- ESM/CJS 主入口
@axutils/common不包含object/json、crypto/md5和crypto/convertobject/json、crypto/md5、crypto/convert需要走子路径按需导入- UMD 全量包会内联
safe-stable-stringify、spark-md5等第三方依赖,体积大于 ESM/CJS 产物。若仅需局部能力且对体积敏感,建议使用 ESM 按需导入
方法说明
类型判断(@axutils/common/check/type)
isNumber(value):判断是否为有效数字,NaN返回falseisString(value):判断是否为字符串isBoolean(value):判断是否为布尔值isArray(value):判断是否为数组isObject(value):判断是否为普通对象语义下的对象值,不包含null和数组,但不严格区分字面量对象与Date、RegExp、包装对象等isNil(value):判断是否为null或undefined,0、""、false等“假值”不视为 nilisFunction(value):判断是否为函数,覆盖普通函数、箭头函数、async函数、生成器函数和class声明isNormalFunction(value):判断是否为常规非箭头函数形态,覆盖function声明/表达式及对象方法简写;轻量校验,bound 包装后或 native 函数无法识别isArrowFunction(value):判断是否为箭头函数(含async箭头函数);轻量校验,依赖函数源码扫描,bound 包装后或 native 函数无法识别isAsyncFunction(value):判断是否为async函数(含async箭头函数),普通函数、生成器函数和class声明返回falseisAsyncArrowFunction(value):判断是否为async箭头函数,对async function声明和同步箭头返回false;轻量校验,依赖函数源码扫描,bound 包装后或 native 函数无法识别isDate(value):判断是否为有效的Date实例,Invalid Date返回falseisPlainObject(value):判断是否为字面量对象(plain object),严格区分Date、RegExp、Map、Set、包装对象和class实例,Object.create(null)视为字面量对象
格式校验(@axutils/common/check/reg)
isPhoneCn(value):判断是否为中国大陆 11 位手机号,不支持+86、空格或分隔符isEmail(value):判断是否为国际通用邮箱格式,@前后必须有内容、不允许空白字符、域名至少含一个.,且点不能出现在首尾或连续出现,支持任意 Unicode 字符isHttpUrl(value):判断是否为http://或https://开头的 URL,不校验域名合法性、端口范围或路径合法性isIpv4(value):判断是否为合法的 IPv4 地址,每段取值0-255,不允许前导零isIdCardCn(value):判断是否为合法的中国大陆 18 位居民身份证号,校验末位校验码(GB 11643-1999),不校验出生日期真实性和地区码isHexColor(value):判断是否为十六进制颜色值,支持#fff(3 位)和#ffffff(6 位),不支持 alpha 通道
平台判断(@axutils/common/check/platform)
isBrowser():判断当前运行时是否为浏览器主线程,同时要求存在window/document且window === globalThis,jsdom 等模拟环境可能误判isNode():判断当前运行时是否为 Node.js,校验process.versions.node为字符串,Electron 主进程也返回trueisWebWorker():判断当前运行时是否为 Web Worker,要求存在self和importScripts且self.window不存在,不覆盖 Service WorkerisBrowserLike():判断当前运行时是否为类浏览器环境,仅校验window存在,语义宽松,适合「能否使用浏览器 API」的快速预判isServer():判断当前运行时是否为服务端环境,即isBrowser()取反,包含 Node.js/Deno/Bun/Worker 等非浏览器主线程环境isDeno():判断当前运行时是否为 Deno,校验全局Deno对象和Deno.version.denoisBun():判断当前运行时是否为 Bun,校验全局Bun对象和Bun.version
JSON 序列化(@axutils/common/object/json)
在原生 JSON.stringify / JSON.parse 基础上增加可配置项,未传入配置时走 FastPath 直接调用原生方法,性能与原生一致。配置化路径底层使用 safe-stable-stringify(已知最快的稳定序列化实现)。
依赖提示:使用
@axutils/common/object/json子路径需要安装 peer 依赖safe-stable-stringify(npm i safe-stable-stringify)。不使用 JSON 序列化功能的用户无需安装。jsonParse不依赖任何第三方库。
jsonStringify(value, options?):序列化值为 JSON 字符串;当根值为undefined、函数或Symbol时,与原生一致返回undefined;支持以下配置:sortKeys:对象 key 排序,true/"asc"升序、"desc"降序、或自定义比较函数,不影响数组元素顺序filterNullish:过滤值为null/undefined的对象字段(不影响数组元素,也不影响根值)space:缩进配置,number为空格数、string为缩进字符串onCycle:循环引用处理,"throw"抛错(默认)、"skip"将循环引用值替换为null
BigInt 行为差异:FastPath(无配置或仅传
space)走原生JSON.stringify,遇到BigInt会抛TypeError;配置化路径(传入sortKeys/filterNullish/onCycle等触发配置化的选项)底层safe-stable-stringify会将BigInt序列化为数字。如需序列化BigInt,请显式传入这些配置项。jsonParse(text, options?):反序列化 JSON 文本,支持以下配置:sortKeys:对结果对象的 key 排序,语义同序列化filterNullish:过滤值为null的字段(JSON 文本中不存在undefined)
JsonCircularReferenceError:循环引用错误类,当onCycle为"throw"(默认)且显式传入配置时抛出;无配置时走 FastPath 抛原生TypeErrorjsonStringifySafe(value, options?):安全版jsonStringify,参数和行为完全一致,区别是任何异常(循环引用、TypeError等)都不抛出,直接返回null。返回类型string | null | undefined,适用于日志、缓存写入等容错场景。如需区分错误类型请使用jsonStringifyjsonParseSafe(text, options?):安全版jsonParse,参数和行为完全一致,区别是任何异常(如SyntaxError)都不抛出,直接返回null。返回类型T | null,适用于解析不可信外部输入的容错场景。注意:合法 JSON 文本"null"解析结果也是null,调用方无法仅凭返回值区分"解析失败"与"原文就是 null"
URL 查询工具(@axutils/common / @axutils/common/object/url)
objectToQuery(value, options?):将对象序列化为不带前导问号的 query 字符串。默认过滤null和undefined(包括数组元素);数组会按原有元素顺序展开为重复 key。值使用标准URLSearchParams编码。filterNullish:设为false时保留null/undefined,并分别转换为字符串。sortKeys:控制 key 排序;false或不传时保留对象键顺序,true/"asc"按 Unicode 代码点升序,"desc"降序,也可传入自定义比较函数。同一 key 的数组元素顺序始终不变。
queryToObject(value):解析裸 query、带 query 的相对/绝对路径或完整 HTTP(S) URL;忽略 hash,重复 key 按出现顺序转换为字符串数组。没有 query 的 HTTP(S) URL 或绝对路径返回空对象。未带前导斜杠的相对路径与裸 query 存在歧义:问号前含=或&时按裸 query 处理;若裸 query 的 key 本身包含未编码问号,可加前导?明确按裸 query 解析。
MD5(@axutils/common/crypto/md5 / @axutils/common/node/crypto/md5)
提供一套增量 MD5 工具类,浏览器/通用侧基于 spark-md5,Node 侧基于 node:crypto,两边 API 和行为保持一致。
依赖提示:
- 使用
@axutils/common/crypto/md5需要安装 peer 依赖spark-md5(pnpm add spark-md5)- 使用
@axutils/common/node/crypto/md5不需要额外运行时依赖
new Md5():创建一个可增量update()的 MD5 实例update(input, encoding?):追加待摘要内容,支持string、number[]、Uint8Array- 字符串默认按
utf8处理 - 也支持显式指定
hex、base64 - 返回实例自身,便于链式调用
- 字符串默认按
toBytes():返回摘要对应的 16 字节数组toHex():返回 32 位小写十六进制字符串toBase64():返回标准 base64 字符串
转换工具(@axutils/common/crypto/convert / @axutils/common/node/crypto/convert)
toByteArray(input):把number[]或Uint8Array归一化为新的Uint8ArraynormalizeMd5Input(input, encoding?):按utf8/hex/base64统一解码 MD5 输入decodeHex(value):把十六进制字符串解码为字节数组decodeBase64(value):把标准 base64 字符串解码为字节数组binaryStringToBytes(value):把二进制字符串拆成字节数组,主要用于 raw 摘要适配bytesToHex(bytes):把字节数组转成小写十六进制字符串bytesToBase64(bytes):把字节数组转成标准 base64 字符串
行为边界:
- 非法字节值(非整数、负数、超过
255)会抛错 toBytes()/toHex()/toBase64()首次调用后会固定摘要结果- 摘要生成后不可继续
update()
