npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@minson1994/ms-utils

v1.0.4

Published

小工具集合

Readme

@minson1994/ms-utils

一个实用型 TypeScript 工具库,提供跨运行时 API 编排、时间处理、算法/随机、数据验证、任务队列、并发去重、Promise Hook、Cookie 等常用能力。

  • TypeScript 编写,自带 .d.ts 类型提示。
  • 同时支持 ESM / CommonJS。
  • 核心能力不绑定浏览器、Node、微信小程序、uniapp。
  • 浏览器、微信小程序、uniapp、axios 能力通过子路径按需导入。

安装

npm install @minson1994/ms-utils

使用 axios 适配器时,项目里自行安装:

npm install axios qs

导入

import {
    $Use,
    ApiConfig,
    ApiRequest,
    ApiStatusInterceptor,
    Concurrency,
    AlgorithmUtils,
    Hook,
    TaskQueue,
    TimeUtils,
    ValidateUtils,
} from "@minson1994/ms-utils";

import { AxiosHttpAdapter } from "@minson1994/ms-utils/axios";
import { CookieUtils } from "@minson1994/ms-utils/browser";
import { WxHttpAdapter } from "@minson1994/ms-utils/wx";

CommonJS:

const { TimeUtils, AlgorithmUtils } = require("@minson1994/ms-utils");

功能总览

| 功能 | 导入 | 说明 | | --- | --- | --- | | API 编排器 | @minson1994/ms-utils | 跨 Web、Node、小程序、uniapp 复用业务 API 代码 | | axios 适配器 | @minson1994/ms-utils/axios | 用 axios 作为底层请求实现 | | Cookie | @minson1994/ms-utils/browser | 浏览器 document.cookie 工具 | | 小程序/uni 请求 | @minson1994/ms-utils/wx | 微信小程序 wx.request / uniapp uni.request 适配器 | | 时间 | TimeUtils | 格式化、加减、差值、多久前、延迟 | | 算法/随机 | AlgorithmUtils | Base64、MD5、AES、SHA256、HMAC、RSA、UUID、随机数 | | 数据验证 | ValidateUtils | 空值、类型、邮箱、手机号、身份证、银行卡等 | | 任务队列 | TaskQueue | 异步任务并发控制、超时、暂停恢复 | | 并发去重 | Concurrency | 同 key 并发只执行一次 | | Hook | Hook | 外部回调唤醒 Promise |

1. API 编排器

API 模块不是为了替代 axios、ky、ofetch 这类底层 HTTP client。它解决的是:在 Web、Node、微信小程序、uniapp 等环境里,尽量复用同一份业务 API 接口代码。

核心只暴露 ApiRequestApiConfigApiResponseApiAdapterApiInterceptorApi* 命名;底层适配器保留 FetchHttpAdapterXhrHttpAdapterAxiosHttpAdapter 这类名称,表示它们负责对接具体 HTTP 实现。

快速开始

import { ApiRequest } from "@minson1994/ms-utils";

const api = new ApiRequest({
    url: "https://api.example.com",
});

export function getUser(id) {
    return api
        .use({
            url: "/user/info",
            method: "GET",
            data: { id },
        })
        .useAuth()
        .emit();
}

也可以使用 $Use 创建配置,再传入请求实例执行:

import { $Use, ApiRequest } from "@minson1994/ms-utils";
import { AxiosHttpAdapter } from "@minson1994/ms-utils/axios";
import axios from "axios";
import qs from "qs";

const request = new ApiRequest({
    url: ["https://api.example.com"],
    adapter: new AxiosHttpAdapter(axios, qs),
});

const result = await $Use({
    url: "/user/info",
    method: "GET",
    data: { id: 1 },
})
    .useJsonBody()
    .useBackFailResult()
    .request(request);

分层设计

| 层级 | 类型 | 职责 | | --- | --- | --- | | API 编排器 | ApiRequest | 串起配置、适配器、拦截器、缓存、错误处理和多 base url 重试 | | 单次配置 | ApiConfig | 描述一次请求,并提供链式 useXxx() 能力 | | 响应对象 | ApiResponse | 统一包装 status、data、headers、原始响应 | | 适配器 | ApiAdapter | 抽象底层请求能力,不绑定 axios、fetch、xhr、wx | | 拦截器 | ApiInterceptor | 抽象请求前后处理,业务可继承扩展 |

适配器

| 适配器 | 入口 | 适用环境 | 说明 | | --- | --- | --- | --- | | XhrHttpAdapter | @minson1994/ms-utils | 浏览器 | 支持上传进度、下载进度、取消请求 | | FetchHttpAdapter | @minson1994/ms-utils | Node / 浏览器 / Worker | 通用 fetch 实现,支持取消;下载进度依赖 ReadableStream | | AxiosHttpAdapter | @minson1994/ms-utils/axios | 使用 axios 的项目 | axios 构造注入,不内置打包 axios | | WxHttpAdapter | @minson1994/ms-utils/wx | 微信小程序 / uniapp | 适配 wx.requestwx.uploadFileuni.requestuni.uploadFile | | AjaxHttpAdapter | 源码路径按需引用 | jQuery/uni 风格 ajax | 适合已有 ajax runtime 的老项目 |

默认适配器策略:浏览器有 XMLHttpRequest 时用 XHR,其他环境用 fetch。

const api = new ApiRequest({
    url: "https://api.example.com",
});

微信小程序 / uniapp:

import { ApiRequest } from "@minson1994/ms-utils";
import { WxHttpAdapter } from "@minson1994/ms-utils/wx";

const request = new ApiRequest({
    url: "https://api.example.com",
    adapter: new WxHttpAdapter(wx), // uniapp 项目传 uni
});

容灾与故障切换

ApiRequesturl 支持数组。底层请求失败时,会按数组顺序切换到下一个 base url 重试。

const api = new ApiRequest({
    url: [
        "https://api-a.example.com",
        "https://api-b.example.com",
        "https://api-c.example.com",
    ],
    retryDelay: 1000,
});

边界说明:

  • retryDelay 单位是毫秒,默认 1000
  • 只在 adapter 层请求失败时切换,例如网络错误、超时、取消外的 reject。
  • 业务状态失败不会自动切换域名,例如 { code: 500 } 这种由 ApiStatusInterceptor 处理。
  • 所有 base url 都失败后,抛出最后一次错误。

拦截器

| 拦截器 | 默认类型 | 职责 | | --- | --- | --- | | UI 拦截器 | ApiUIInterceptor | 根据 loadingshowErrorMessage 统一处理 loading 和错误提示钩子 | | 状态拦截器 | ApiStatusInterceptor | 统一处理 HTTP 状态、业务 code、message、data 拆包、未授权回调 | | 缓存拦截器 | ApiCacheInterceptor | 根据 cache + apiKey 缓存最终响应数据;浏览器默认使用 sessionStorage | | 业务拦截器 | ApiInterceptor[] | 项目自定义鉴权、签名、埋点、灰度、错误结构处理 |

缓存不会自动根据请求参数计算 key,避免每次请求都序列化参数。需要缓存时必须显式传入 apiKey

const user = await api
    .use({
        url: "/user/info",
        method: "GET",
        data: { id: 1 },
    })
    .useApiKey("user:1")
    .useCache()
    .emit();

apiKey 是跨功能业务 key,后续也可用于并发去重、请求合并等能力。

如果只调用 useCache(),但没有调用 useApiKey(key),本次请求不会读取缓存,也不会写入缓存:

await api
    .use({
        url: "/user/info",
        method: "GET",
        data: { id: 1 },
    })
    .useCache()
    .emit();

// 没有 apiKey,缓存等同禁用。

缓存存储策略:

  • 浏览器默认使用 sessionStorage,刷新页面后同一会话仍可命中。
  • 非浏览器环境自动回退内存缓存。
  • 可传 new ApiCacheInterceptor({ storage: "memory" }) 强制使用内存。
  • 可传自定义 storage 对接其它缓存实现。
import { ApiCacheInterceptor, ApiRequest } from "@minson1994/ms-utils";

const api = new ApiRequest({
    url: "https://api.example.com",
    cacheInterceptor: new ApiCacheInterceptor({
        storage: "session",
        prefix: "my-app:api:",
    }),
});

简单业务状态:

import { ApiRequest, ApiStatusInterceptor } from "@minson1994/ms-utils";

const api = new ApiRequest({
    url: "https://api.example.com",
    statusInterceptor: new ApiStatusInterceptor({
        status: "code",
        message: "message",
        data: "data",
        success: 200,
        onUnauthorized: (message) => {
            console.log("登录失效", message);
        },
    }),
});

复杂结构可以继承 ApiInterceptor

import { ApiInterceptor, ApiRequest } from "@minson1994/ms-utils";

class CustomStatusInterceptor extends ApiInterceptor {
    async before(_config) {
        return undefined;
    }

    async after(_config, response) {
        const body = response.data;

        if (body.meta.code !== 0) {
            throw new Error(body.error?.message || "请求失败");
        }

        response.data = body.payload;
    }
}

const api = new ApiRequest({
    url: "https://api.example.com",
    statusInterceptor: new CustomStatusInterceptor(),
});

单次请求配置

const result = await api
    .use({
        url: "/user/create",
        method: "POST",
        data: { name: "minson" },
    })
    .useApiKey("user:create")
    .useAuth()
    .useJsonBody()
    .useLoading()
    .useCancel((cancel) => {
        // 需要取消时调用 cancel("用户取消")
    })
    .useProgress((progress) => {
        console.log(progress.percent);
    })
    .emit();

发送纯文本 body 时,data 可以直接传字符串;也可显式调用 useTextBody() 固定为 text/plain;charset=UTF-8

await api
    .use({
        url: "/message/raw",
        method: "POST",
        data: "hello plain text",
    })
    .useTextBody()
    .emit();

| 方法 | 说明 | | --- | --- | | useAuth() | 标记需要登录态,给自定义拦截器使用 | | useTimeout(seconds) | 设置超时时间 | | useHeaders(headers) | 设置请求头 | | useWithCredentials() | 携带 cookie / credentials | | useApiKey(key) | 设置业务 key,用于缓存、并发去重等跨功能能力 | | useUpload() | 标记上传请求,适配器会转 FormData 或平台上传 API | | useJsonBody() | 按 JSON body 发送 | | useTextBody() | 按纯文本 body 发送;非查询方法下字符串 data 会自动按 text/plain;charset=UTF-8 发送 | | useSign() | 标记需要签名,给自定义签名拦截器使用 | | useLoading() | 触发 UI loading 钩子 | | useShowErrorMessage() | 控制是否展示错误消息 | | useCheckHttpStatus() | 检查 HTTP status | | useBackFailResult() | 业务失败时返回原始失败结果 | | useBackOnlyData() | 成功时只返回业务 data | | useCache() | 开启缓存拦截;需要配合 useApiKey(key) 使用 | | useCancel(callback) | 获取取消函数,外部可主动取消请求 | | useUploadProgress(callback) | 上传进度回调,适配器支持多少给多少 | | useDownloadProgress(callback) | 下载进度回调,适配器支持多少给多少 | | useProgress(callback) | 同时绑定上传和下载进度 | | request(api) | 使用指定 ApiRequest 执行当前配置 | | emit() / exec() | 使用绑定的 ApiRequest 执行当前配置 |

API 导出

| API | 说明 | | --- | --- | | ApiRequest | 请求编排器,串起 adapter 和 interceptor | | ApiConfig | 单次请求配置,支持链式 useXxx | | $Use(options) / $Api(options) | ApiConfig.use(options) 的快捷入口 | | ApiGlobalConfig | 全局默认配置 | | ApiResponse | 统一响应对象 | | ApiAdapter | 抽象适配器基类 | | ApiInterceptor | 抽象拦截器基类 | | ApiStatusInterceptor | HTTP / 业务状态码处理 | | ApiCacheInterceptor | 缓存拦截器 | | ApiUIInterceptor | loading / error message UI 钩子 | | ApiError / ApiCancelError / ApiTimeoutError / ApiNetworkError | API 错误类型 |

2. TimeUtils 时间工具

TimeUtils.now();
TimeUtils.timestamp();
TimeUtils.timestamp(new Date(), "second");
TimeUtils.toDate("2026-06-08");
TimeUtils.isValid("2026-06-08");
TimeUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
TimeUtils.add(new Date(), 1, "day");
TimeUtils.subtract(new Date(), 1, "hour");
TimeUtils.diff("2026-06-08", "2026-06-10", "day");
TimeUtils.fromNow(Date.now() - 60 * 1000); // 1 分钟前
TimeUtils.startOfDay(new Date());
TimeUtils.endOfDay(new Date());
await TimeUtils.wait(1000);

| 方法 | 说明 | | --- | --- | | now() | 当前时间 Date | | timestamp(date?, unit?) | 毫秒/秒时间戳 | | toDate(value) | 转成 Date | | isValid(value) | 是否是有效时间 | | format(date?, pattern?) | 格式化时间 | | add(date, amount, unit) | 增加时间 | | subtract(date, amount, unit) | 减少时间 | | diff(start, end, unit?) | 计算差值 | | fromNow(date, base?) | 输出“刚刚 / x 分钟前 / x 小时后” | | startOfDay(date?) | 当天开始时间 | | endOfDay(date?) | 当天结束时间 | | wait(ms) | 延迟等待 |

3. AlgorithmUtils 算法和随机工具

Base64、MD5、AES、SHA256、HMAC、UUID、随机数基于 crypto-js 和运行时随机源;RSA 使用 WebCrypto。老环境没有 crypto.subtle 时,RSA 不可用。

AlgorithmUtils.base64Encode("hello");
AlgorithmUtils.base64Decode("aGVsbG8=");
AlgorithmUtils.md5("hello");
AlgorithmUtils.md5_16("hello");
AlgorithmUtils.md5_32("hello");

const encrypted = AlgorithmUtils.aesEncrypt("content", "password");
const decrypted = AlgorithmUtils.aesDecrypt(encrypted, "password");

AlgorithmUtils.sha256("hello");
AlgorithmUtils.hmacSha256("hello", "secret");

AlgorithmUtils.uuid();
AlgorithmUtils.randomInt(1, 100);
AlgorithmUtils.randomFloat();
AlgorithmUtils.randomString(16);

const keys = await AlgorithmUtils.rsaGeneratePemKeyPair();
const rsaText = await AlgorithmUtils.rsaEncrypt("secret", keys.publicKey);
await AlgorithmUtils.rsaDecrypt(rsaText, keys.privateKey);

| 方法 | 说明 | | --- | --- | | base64Encode(text) / base64Decode(text) | Base64 编解码 | | md5(text, length?, upper?) | MD5,支持 16/32 位和大小写 | | md5_16(text, upper?) / md5_32(text, upper?) | 快捷 MD5 | | aesEncrypt(text, options) / aesDecrypt(text, options) | AES 加解密 | | sha256(text, upper?) | SHA256 | | hmac(text, key, algorithm?, upper?) | HMAC,支持 SHA256/SHA1/MD5 | | hmacSha256(text, key, upper?) | HMAC-SHA256 | | uuid() | UUID v4 | | randomInt(min, max) | 指定范围整数 | | randomFloat() | 0 到 1 的随机小数 | | randomString(length?, chars?) | 随机字符串 | | rsaGenerateKeyPair() / rsaGeneratePemKeyPair() | 生成 RSA 密钥对 | | rsaEncrypt(text, publicKey) / rsaDecrypt(ciphertext, privateKey) | RSA-OAEP 加解密 |

4. ValidateUtils 数据验证工具

ValidateUtils.isEmpty("");
ValidateUtils.isNotEmpty("abc");
ValidateUtils.isString("abc");
ValidateUtils.isNumber(123);
ValidateUtils.isArray([]);
ValidateUtils.isPlainObject({});
ValidateUtils.isEmail("[email protected]");
ValidateUtils.isMobileCN("13800138000");
ValidateUtils.isUrl("https://example.com");
ValidateUtils.isIPv4("127.0.0.1");
ValidateUtils.isJSON('{"a":1}');
ValidateUtils.isBankCard("4111111111111111");
ValidateUtils.isIdCardCN("11010519491231002X");
ValidateUtils.validateIdCardCN("11010519491231002X");
ValidateUtils.passwordStrength("Abcdef12!@");
ValidateUtils.isStrongPassword("Abcdef12!@");

| 方法 | 说明 | | --- | --- | | isNil / isEmpty / isNotEmpty | 空值判断 | | isString / isNumber / isInteger / isBoolean | 基础类型判断 | | isArray / isPlainObject | 数组和普通对象判断 | | lengthBetween / numberBetween / match | 通用规则判断 | | isEmail / isMobileCN / isUrl | 常用格式判断 | | isIPv4 / isIPv6 / isJSON | 网络和 JSON 判断 | | isPostalCodeCN / isPlateNumberCN | 国内邮编、车牌判断 | | isBankCard | 银行卡 Luhn 校验 | | isIdCardCN / validateIdCardCN | 身份证校验,支持生日和性别返回 | | passwordStrength / isStrongPassword | 密码强度判断 |

5. TaskQueue 任务队列

用于限制异步任务并发,支持超时、任务间隔、暂停、恢复、停止和等待空闲。

const queue = new TaskQueue({
    concurrency: 1,
    taskDelayMs: 100,
    timeout: 0,
});

queue.add(async () => {
    await TimeUtils.wait(1000);
    return "done";
});

const results = await queue.addMany([() => 1, async () => 2]);

queue.pause();
queue.resume();
await queue.waitForIdle();

| 方法/属性 | 说明 | | --- | --- | | add(task, timeout?) | 添加单个任务 | | addMany(tasks) | 批量添加任务 | | pause() / resume() | 暂停/恢复队列 | | clear(reason?) | 清空等待队列 | | stop(reason?) | 停止队列并拒绝新任务 | | reset() | 重置队列状态 | | waitForIdle() | 等待队列空闲 | | setConcurrency(n) | 修改并发数 | | setTimeout(ms) | 修改默认超时 | | setTaskDelay(ms) | 修改任务间隔 | | size / activeCount | 等待数 / 运行数 | | TaskQueue.delay(ms, value?) | 静态延迟工具 |

6. Concurrency 并发去重

同名同参数的并发调用只执行一次,后续调用复用同一个 Promise。

const loadUserOnce = Concurrency.createConcurrentCall(
    async (id) => fetchUser(id),
    "load-user",
);

await Promise.all([loadUserOnce(1), loadUserOnce(1)]);

| 方法 | 说明 | | --- | --- | | createConcurrentCall(fn, name, getKey?) | 创建并发去重函数 | | pendingRequests | 当前正在执行的请求 Map |

7. Hook 回调工具

适合“先发起动作,后由外部回调唤醒”的场景,比如扫码、第三方 SDK 回调、postMessage

const hook = new Hook();
const promise = Hook.on(hook);

setTimeout(() => {
    hook.call("ok");
}, 1000);

const result = await promise;

自动生成编号:

const result = await Hook.no(async (no) => {
    window.postMessage({ no });
});

| 方法/属性 | 说明 | | --- | --- | | new Hook(no?, timeout?) | 创建 Hook,timeout 单位秒 | | Hook.on(hook) | 注册等待 | | Hook.call(hook, data) / hook.call(data) | 唤醒等待 | | Hook.clear(hook) | 清理 Hook | | Hook.no(fn, timeout?) | 自动生成编号并等待回调 | | hook.no / hook.timeout | 编号 / 超时时间 |

8. CookieUtils 浏览器 Cookie 工具

CookieUtils 依赖浏览器 document.cookie,请从 browser 子路径导入,避免 Node 环境误加载浏览器对象。

import { CookieUtils } from "@minson1994/ms-utils/browser";

CookieUtils.write("token", "xxx");
CookieUtils.read("token");
CookieUtils.has("token");
CookieUtils.delete("token");
CookieUtils.writeJSON("user", { id: 1 });
CookieUtils.readJSON("user");
CookieUtils.readAll();

9. 发布前检查

npm run typecheck
npm run test
npm run build
npm pack --dry-run

npm pack --dry-run 应只看到 dist/README.mdLICENSE 和 package metadata。