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

@nsnanocat/util

v2.5.17

Published

Pure JS's util module for well-known iOS network tools

Readme

@nsnanocat/util

用于统一 Quantumult X / Loon / Shadowrocket / Worker / Node.js / Egern / Surge / Stash 脚本接口的通用工具库。

核心目标:

  • 统一不同平台的 HTTP、通知、持久化、结束脚本等调用方式。
  • 在一个脚本里尽量少写平台分支。
  • 提供一组可直接复用的 polyfill(fetch / Storage / Console / Lodash / qs)。

目录

安装与导入

发布源:

如果你不确定该选哪个,直接用 npm 源即可。 如果你从 GitHub Packages 安装,需要先配置 GitHub 认证(PAT Token)。

1) 使用 npm 源(推荐,最省事)

# 首次安装:拉取并安装这个包
npm i @nsnanocat/util

# 更新到最新版本:升级已安装的 util
npm i @nsnanocat/util@latest
# 你也可以使用 update(效果类似)
# npm update @nsnanocat/util

2) 使用 GitHub Packages 源(同步源,需要 GitHub 鉴权)

# 把 @nsnanocat 作用域的包下载源切到 GitHub Packages
npm config set @nsnanocat:registry https://npm.pkg.github.com

# 配置 GitHub Token(用于下载 GitHub Packages)
# 建议把 YOUR_GITHUB_PAT 换成你的真实 Token,再执行
# echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_PAT" >> ~/.npmrc

# 首次安装:从 GitHub Packages 安装 util
npm i @nsnanocat/util

# 更新到最新版本:从 GitHub Packages 拉取最新 util
npm i @nsnanocat/util@latest
import {
  $app,       // 当前平台名(如 "Surge" / "Loon" / "Quantumult X" / "Worker" / "Node.js")
  $argument,  // 已标准化的模块参数对象(导入包时自动处理字符串 -> 对象)
  done,       // 统一结束脚本函数(内部自动适配各平台 $done 差异)
  fetch,      // 统一 HTTP 请求函数(内部自动适配 $httpClient / $task / fetch)
  notification, // 统一通知函数(内部自动适配 $notify / $notification.post)
  time,       // 时间格式化工具
  wait,       // 延时等待工具(Promise)
  Console,    // 统一日志工具(支持 logLevel)
  Lodash as _, // Lodash 建议按官方示例惯例使用 `_` 作为工具对象别名
  qs,         // 查询字符串工具(parse / stringify)
  Storage,    // 统一持久化存储接口(适配 $prefs / $persistentStore / 内存 / 文件)
} from "@nsnanocat/util";

导出清单

包主入口(index.js)已导出

  • lib/app.mjs
  • lib/argument.mjs$argument 参数标准化模块,导入时自动执行)
  • lib/done.mjs
  • lib/notification.mjs
  • lib/time.mjs
  • lib/wait.mjs
  • polyfill/Console.mjs
  • polyfill/fetch.mjs
  • polyfill/Lodash.mjs
  • polyfill/qs.mjs
  • polyfill/StatusTexts.mjs
  • polyfill/Storage.mjs

仓库中存在但未从主入口导出

  • lib/environment.mjs
  • lib/runScript.mjs
  • getStorage.mjs(薯条项目自用,仅当你的存储结构与薯条项目一致时再使用;请通过子路径 @nsnanocat/util/getStorage.mjs 导入)

模块依赖关系

说明:

  • 下表只描述“模块之间”的依赖关系、调用到的函数/常量、以及依赖原因。
  • 你在业务脚本中通常只需要调用对外 API;底层跨平台差异已在这些依赖链里处理。

| 模块 | 依赖模块 | 使用的函数/常量 | 为什么依赖 | | --- | --- | --- | --- | | lib/app.mjs | 无 | 无 | 核心平台识别源头,供其他差异模块分流 | | lib/environment.mjs | lib/app.mjs | $app | 按平台生成统一 $environment(尤其补齐 app 字段) | | lib/argument.mjs | polyfill/Console.mjs, polyfill/qs.mjs | Console.debug, Console.logLevel, qs.parse | 统一 $argument 结构,并委托 qs.parse 处理字符串/对象/空值输入 | | lib/done.mjs | lib/app.mjs, polyfill/Console.mjs, polyfill/Lodash.mjs, polyfill/StatusTexts.mjs | $app, Console.log, Lodash.set, Lodash.pick, StatusTexts | 将各平台 $done 参数格式拉平并兼容状态码/策略字段 | | lib/notification.mjs | lib/app.mjs, polyfill/Console.mjs | $app, Console.group, Console.log, Console.groupEnd, Console.error | 将通知参数映射到各平台通知接口并统一日志输出 | | lib/runScript.mjs | polyfill/Console.mjs, polyfill/fetch.mjs, polyfill/Storage.mjs, polyfill/Lodash.mjs | Console.error, fetch, Storage.getItemLodash 当前版本未实际调用) | 读取 BoxJS 配置并发起统一 HTTP 调用执行脚本 | | getStorage.mjs | lib/argument.mjs, polyfill/Console.mjs, polyfill/Lodash.mjs, polyfill/Storage.mjs | Console.debug, Console.logLevel, Lodash.merge, Storage.getItem | 先标准化 $argument,再合并默认配置/持久化配置/运行参数 | | polyfill/Console.mjs | lib/app.mjs | $app | 日志在 Worker / Node.js 与 iOS 脚本环境使用不同错误输出策略 | | polyfill/fetch.mjs | lib/app.mjs, polyfill/Lodash.mjs, polyfill/StatusTexts.mjs, polyfill/Console.mjs | $app, Lodash.set, StatusTextsConsole 当前版本未实际调用) | 按平台选请求引擎并做参数映射、响应结构统一 | | polyfill/Storage.mjs | lib/app.mjs, polyfill/Lodash.mjs | $app, Lodash.get, Lodash.set, Lodash.unset | 按平台选持久化后端并支持 @key.path 读写 | | polyfill/Lodash.mjs | 无 | 无 | 提供路径/合并等基础能力,被多个模块复用 | | polyfill/qs.mjs | polyfill/Lodash.mjs | Lodash.get, Lodash.set, Lodash.toPath | 提供查询字符串与对象之间的解析/序列化能力 | | polyfill/StatusTexts.mjs | 无 | 无 | 提供 HTTP 状态文案,供 fetch/done 使用 | | index.js / lib/index.js / polyfill/index.js | 多个模块 | export * | 聚合导出,不含业务逻辑 |

API 参考(按 mjs 文件)

lib/app.mjslib/environment.mjs(平台识别与环境)

$app

  • 类型:"Quantumult X" | "Loon" | "Shadowrocket" | "Egern" | "Surge" | "Stash" | "Worker" | "Node.js" | undefined
  • 角色:核心模块。库内所有存在平台行为差异的模块都会先读取 $app 再分流(如 donenotificationfetchStorageConsoleenvironment)。
  • 读取方式:
import { $app } from "@nsnanocat/util";
const appName = $app; // 读取 $app,返回平台字符串
console.log(appName);
  • 识别顺序(lib/app.mjs):
  1. 存在 $task -> Quantumult X
  2. 存在 $loon -> Loon
  3. 存在 $rocket -> Shadowrocket
  4. 存在 Egern -> Egern
  5. 存在 $environment 且有 surge-version -> Surge
  6. 存在 $environment 且有 stash-version -> Stash
  7. 存在 Cloudflare -> Worker
  8. 存在 process.versions.node -> Node.js
  9. 默认回落 -> undefined
  • 实现细节:内部使用 'key' in globalThis 检测平台标记,避免 Object.keys(globalThis) 漏掉不可枚举全局变量;当前 Worker 识别以 Cloudflare 全局标记为准。

$environment / environment()

  • 路径:lib/environment.mjs(未从包主入口导出)
  • 签名:environment(): object
  • 调用方式:
import { $environment, environment } from "@nsnanocat/util/lib/environment.mjs";
console.log($environment.app); // 统一平台名
console.log(environment()); // 当前环境对象
  • 规则:会为已识别平台统一生成 $environment.app = "平台名称"

| 平台 | 调用路径(读取来源) | 读取结果示例 | | --- | --- | --- | | Surge | 读取全局 $environment,再写入 app | { ..., "surge-version": "x", app: "Surge" } | | Stash | 读取全局 $environment,再写入 app | { ..., "stash-version": "x", app: "Stash" } | | Egern | 读取全局 $environment,再写入 app | { ..., app: "Egern" } | | Loon | 读取全局 $loon 字符串并拆分 | { device, ios, "loon-version", app: "Loon" } | | Quantumult X | 不读取额外环境字段,直接构造对象 | { app: "Quantumult X" } | | Worker | 直接构造对象 | { app: "Worker" } | | Node.js | 读取 process.env 并写入 process.env.app | { ..., app: "Node.js" } | | 其他 | 无 | {} |

lib/argument.mjs$argument 参数标准化模块)

此文件无显式导出;import 后立即执行。这是为了统一各平台 $argument 的输入差异。

行为

  • 通过包入口导入(import ... from "@nsnanocat/util")时会自动执行本模块。
  • JSCore 环境不支持 await import,请使用静态导入或直接走包入口导入。
  • 读取到的 $argument 会按 URL Params 样式格式化为对象,并支持深路径。
  • 内部实现统一委托给 qs.parse(globalThis.$argument)
  • 你也可以通过 import { $argument } from "@nsnanocat/util" 读取当前已标准化的 $argument 快照。
  • 平台输入差异说明:
    • Surge / Stash / Egern:脚本参数通常以字符串形式传入(如 a=1&b=2)。
    • Loon:支持字符串和对象两种 $argument 形式。
    • Quantumult X / Shadowrocket:不提供 $argument
  • 当全局 $argumentstring(如 "a.b=1&x=2")时:
    • & / = 切分。
    • 去掉值中的双引号。
    • 使用点路径展开对象(a.b=1 -> { a: { b: "1" } })。
  • 当全局 $argumentobject 时:
    • 将 key 当路径写回新对象({"a.b":"1"} -> {a:{b:"1"}})。
  • $argumentnullundefined:会归一化为 {}
  • $argument.LogLevel 存在:同步到 Console.logLevel

用法

import { $argument } from "@nsnanocat/util";

// $argument = "mode=on&a.b=1"; // 示例入参,实际由模块参数注入
console.log($argument); // { mode: "on", a: { b: "1" } }

lib/done.mjs

done(object = {})

  • 签名:done(object?: object): void
  • 作用:统一不同平台的脚本结束接口($done / Worker 日志结束 / Node 退出)。

说明:下表描述的是各 App 原生接口差异与本库内部映射逻辑。调用方只需要按 done 的统一参数传值即可,不需要自己再写平台分支。

支持字段(输入):

  • status: number | string
  • url: string
  • headers: object
  • body: string | ArrayBuffer | TypedArray
  • bodyBytes: ArrayBuffer
  • policy: string

平台行为差异:

| 平台 | policy 处理 | status 处理 | body/bodyBytes 处理 | 最终行为 | | --- | --- | --- | --- | --- | | Surge | 写入 headers.X-Surge-Policy | 透传 | 透传 | $done(object) | | Loon | object.node = policy | 透传 | 透传 | $done(object) | | Stash | 写入 headers.X-Stash-Selected-Proxy(URL 编码) | 透传 | 透传 | $done(object) | | Egern | 不转换 | 透传 | 透传 | $done(object) | | Shadowrocket | 不转换 | 透传 | 透传 | $done(object) | | Quantumult X | 写入 opts.policy | number 会转 HTTP/1.1 200 OK 字符串 | 仅保留 status/url/headers/body/bodyBytesArrayBuffer/TypedArraybodyBytes | $done(object) | | Worker | 不适用 | 不适用 | 不适用 | 仅记录结束日志 | | Node.js | 不适用 | 不适用 | 不适用 | process.exit(1) |

不可用/差异点:

  • policy 在 Egern / Shadowrocket 分支不做映射。
  • Quantumult X 会丢弃未在白名单内的字段。
  • Quantumult X 的 status 在部分场景要求完整状态行(如 HTTP/1.1 200 OK),本库会在传入数字状态码时自动拼接(依赖 StatusTexts)。
  • Worker 不调用 $done,仅记录结束日志。
  • Node.js 不调用 $done,而是直接退出进程,且退出码固定为 1
  • 未识别平台($app === undefined)只记录结束日志,不会尝试调用 $done 或退出进程。

lib/notification.mjs

notification(title, subtitle, body, content)

  • 签名:
    • title?: string
    • subtitle?: string
    • body?: string
    • content?: string | number | boolean | object
  • 默认值:title = "ℹ️ ${$app} 通知"
  • 作用:统一 notify/notification 参数格式并发送通知。

content 可用 key(对象形式):

  • 跳转:open / open-url / url / openUrl
  • 复制:copy / update-pasteboard / updatePasteboard
  • 媒体:media / media-url / mediaUrl
  • 其他:auto-dismisssoundmime

平台映射:

| 平台 | 调用接口 | 字符串 content 行为 | 对象字段支持 | | --- | --- | --- | --- | | Surge | $notification.post | { url: content } | open-url/clipboard 动作、media-urlmedia-base64auto-dismisssound | | Stash | $notification.post | { url: content } | 同 Surge 分支(是否全部展示取决于 Stash 支持) | | Egern | $notification.post | { url: content } | 同 Surge 分支(是否全部展示取决于 Egern 支持) | | Shadowrocket | $notification.post | { openUrl: content } | 走 Surge 分支的 action/url/text/media 字段 | | Loon | $notification.post | { openUrl: content } | openUrlmediaUrl(仅 http/https) | | Quantumult X | $notify | { "open-url": content } | open-urlmedia-url(仅 http/https)、update-pasteboard | | Worker | 不发送通知(非 iOS App 环境) | 无 | 无 | | Node.js | 不发送通知(非 iOS App 环境) | 无 | 无 |

不可用/差异点:

  • copy/update-pasteboard 在 Loon 分支不会生效。
  • Loon / Quantumult X 对 media 仅接受网络 URL;Base64 媒体不会自动映射。
  • Worker 不是 iOS App 脚本环境,不支持 iOS 通知行为;当前分支仅日志输出。
  • Node.js 不是 iOS App 脚本环境,不支持 iOS 通知行为;当前分支仅日志输出。

lib/time.mjs

time(format, ts)

  • 签名:time(format: string, ts?: number): string
  • ts:可选时间戳(传给 new Date(ts))。
  • 支持占位符:YYyyyyMMddHHmmsssssS(季度)。
time("yyyy-MM-dd HH:mm:ss.sss");
time("yyyyMMddHHmmss", Date.now());

注意:当前实现对每个 token 只替换一次(String.replace 非全局)。

lib/wait.mjs

wait(delay = 1000)

  • 签名:wait(delay?: number): Promise<void>
  • 用法:
await wait(500);

lib/runScript.mjs(未主入口导出)

runScript(script, runOpts)

  • 签名:runScript(script: string, runOpts?: { timeout?: number }): Promise<void>
  • 作用:通过 BoxJS httpapi 调用本地脚本执行接口:/v1/scripting/evaluate
  • 读取存储键:
    • @chavy_boxjs_userCfgs.httpapi
    • @chavy_boxjs_userCfgs.httpapi_timeout
  • 请求体:
    • script_text
    • mock_type: "cron"
    • timeout

示例:

import { runScript } from "./lib/runScript.mjs";
await runScript("$done({})", { timeout: 20 });

注意:

  • 依赖你本地已正确配置 httpapipassword@host:port)。
  • 函数不返回接口响应,仅在失败时 Console.error

getStorage.mjs

⚠️ 注意:该模块主要为薯条项目的存储结构设计,不作为通用默认 API。
仅当你的持久化结构与薯条项目一致时才建议使用。

getStorage(key, names, database)

  • 签名:
    • key: string(持久化主键)
    • names: string | string[](平台名/配置组名,可嵌套数组)
    • database: object(默认数据库)
  • 返回:{ Settings, Configs, Caches }

合并顺序由 $argument.Storage 控制(持久化读取统一使用 PersistentStore = Storage.getItem(key, {});支持别名):

  • 可用值(大小写敏感):undefined | Argument | $argument | PersistentStore | BoxJs | boxjs | $persistentStore | database
  1. undefineddatabase[name] -> $argument -> PersistentStore[name]
  2. Argument / $argumentdatabase[name] -> PersistentStore[name] -> $argument
  3. PersistentStore / BoxJs / $persistentStore(默认):database[name] -> PersistentStore[name]
  4. database:仅 database[name]

注意:ConfigsCaches 始终按每个 name 合并(与 $argument.Storage 无关)。

自动类型转换(Root.Settings):

  • 字符串 "true"/"false" -> boolean
  • 纯数字字符串 -> number
  • 含逗号字符串 -> array,并尝试逐项转数字

示例:

import getStorage from "@nsnanocat/util/getStorage.mjs";

const store = getStorage("@my_box", ["YouTube", "Global"], database);

命名导出(辅助函数)

getStorage.mjs 同时导出以下辅助函数:

  • traverseObject(o, c):深度遍历对象并替换叶子值
  • string2number(string):将纯数字字符串转换为数字
  • value2array(value):字符串按逗号拆分;数字/布尔值会被包装为单元素数组

示例:

import getStorage, {
  traverseObject,
  string2number,
  value2array,
} from "@nsnanocat/util/getStorage.mjs";

const store = getStorage("@my_box", ["YouTube", "Global"], database);

polyfill/fetch.mjs

fetch 是仿照 Web API Window.fetch 设计的跨平台适配实现:

  • 参考文档:https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch
  • 中文文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/fetch
  • 目标:尽量保持 Web fetch 调用习惯,同时补齐各平台扩展参数映射

fetch(resource, options = {})

  • 签名:fetch(resource: object | string, options?: object): Promise<object>
  • 参数合并:
    • resource 为对象:{ ...options, ...resource }
    • resource 为字符串:{ ...options, url: resource }
  • 默认方法:无 method 时,若有 body/bodyBytes -> POST,否则 GET
  • 会删除 headers:Host:authorityContent-Length/content-length
  • timeout 规则:
    • 缺省 -> 5(秒)
    • > 500 视为毫秒并转为秒

通用请求字段:

  • url
  • method
  • headers
  • body
  • bodyBytes
  • timeout
  • policy
  • redirection / auto-redirect
  • auto-cookie(Worker / Node.js 共享分支识别;默认启用,传入 false / 0 / -1 可关闭)

说明:下表是各 App 原生 HTTP 接口的差异补充,以及本库 fetch 的内部映射方式。调用方使用统一入参即可。

平台行为差异:

| 平台 | 请求发送接口 | timeout 单位 | policy 映射 | 重定向字段 | 二进制处理 | | --- | --- | --- | --- | --- | --- | | Surge | $httpClient[method] | 秒 | 无专门映射 | auto-redirect | Accept 命中二进制类型时设置 binary-mode | | Loon | $httpClient[method] | 毫秒(内部乘 1000) | node = policy | auto-redirect | 同上 | | Stash | $httpClient[method] | 秒 | headers.X-Stash-Selected-Proxy | auto-redirect | 同上 | | Egern | $httpClient[method] | 秒 | 无专门映射 | auto-redirect | 同上 | | Shadowrocket | $httpClient[method] | 秒 | headers.X-Surge-Proxy | auto-redirect | 同上 | | Quantumult X | $task.fetch | 毫秒(内部乘 1000) | opts.policy | opts.redirection | body(ArrayBuffer/TypedArray)bodyBytes;响应按 Content-Type 恢复到 body | | Worker | globalThis.fetch(不存在时回退 node-fetch);共享 auto-cookie 处理 | 毫秒(内部乘 1000) | 无 | redirect: follow/manual | 返回 body(UTF-8 string) + bodyBytes(ArrayBuffer) | | Node.js | globalThis.fetch(不存在时回退 node-fetch);默认按需包裹 fetch-cookie | 毫秒(内部乘 1000) | 无 | redirect: follow/manual | 返回 body(UTF-8 string) + bodyBytes(ArrayBuffer) |

返回对象(统一后)常见字段:

  • ok
  • status
  • statusCode
  • statusText
  • headers
  • body
  • bodyBytes

不可用/差异点:

  • policy 在 Surge / Egern / Worker / Node.js 分支没有额外适配逻辑。
  • redirection 在部分平台会映射为 auto-redirectopts.redirection
  • Worker / Node.js 共享基于 fetch 的请求分支;若 globalThis.fetch 不存在则回退到 node-fetch,并在 auto-cookie 未关闭时按需包裹 fetch-cookie
  • 传入 timeout 时,55000 都会被接受;库会先将用户输入归一化,再按平台要求转换为秒或毫秒。
  • 返回结构是统一兼容结构,不等同于浏览器 Response 对象。

polyfill/Storage.mjs

Storage 是仿照 Web Storage 接口(Storage)设计的跨平台持久化适配器:

  • 参考文档:https://developer.mozilla.org/en-US/docs/Web/API/Storage
  • 中文文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Storage
  • 目标:统一 VPN App 脚本环境中的持久化读写接口,并尽量贴近 Web Storage 行为

Storage.getItem(keyName, defaultValue = null)

  • 支持普通 key:按平台读持久化。
  • 支持路径 key:@root.path.to.key

Storage.setItem(keyName, keyValue)

  • 普通 key:按平台写持久化。
  • 路径 key:@root.path 写入嵌套对象。
  • keyValue 为对象时自动 JSON.stringify

Storage.removeItem(keyName)

  • Quantumult X:可用($prefs.removeValueForKey)。
  • Surge:通过 $persistentStore.write(null, keyName) 删除。
  • Worker:可用(仅删除内存缓存中的对应 key,不持久化)。
  • Node.js:可用(删除 box.dat 中对应 key 并落盘)。
  • Loon / Stash / Egern / Shadowrocket:返回 false

Storage.clear()

  • Quantumult X:可用($prefs.removeAllValues)。
  • Worker:可用(仅清空内存缓存,不持久化)。
  • Node.js:可用(清空 box.dat 并落盘)。
  • 其他平台:返回 false

Worker / Node.js 特性

  • Worker:使用进程内内存缓存,不写文件。
  • 数据文件默认:box.dat
  • 读取路径优先级:当前目录 -> process.cwd()

与 Web Storage 的行为差异:

  • 支持 @key.path 深路径读写(Web Storage 原生不支持)。
  • removeItem/clear 仅部分平台可用(目前为 Quantumult X、Worker、Node.js,以及 Surge 的 removeItem)。
  • getItem 会尝试 JSON.parsesetItem 写入对象会 JSON.stringify

平台后端映射:

| 平台 | 读写接口 | | --- | --- | | Surge / Loon / Stash / Egern / Shadowrocket | $persistentStore.read/write | | Quantumult X | $prefs.valueForKey/setValueForKey | | Worker | 进程内内存缓存 | | Node.js | 本地 box.dat |

polyfill/Console.mjs

Console 是统一日志工具(静态类)。

日志级别

  • Console.logLevel 可读写。
  • 支持:OFF(0) / ERROR(1) / WARN(2) / INFO(3) / DEBUG(4) / ALL(5)

logLevel 用法示例:

import { Console } from "@nsnanocat/util";

Console.logLevel = "debug"; // 或 4
Console.debug("debug message");

Console.logLevel = 2; // WARN
Console.info("won't print at WARN level");
Console.warn("will print");

console.log(Console.logLevel); // "WARN"

方法

  • clear()
  • count(label = "default")
  • countReset(label = "default")
  • debug(...msg)
  • error(...msg)
  • exception(...msg)
  • group(label)
  • groupEnd()
  • info(...msg)
  • log(...msg)
  • time(label = "default")
  • timeLog(label = "default")
  • timeEnd(label = "default")
  • warn(...msg)

参数与返回值:

| 方法 | 参数 | 返回值 | 说明 | | --- | --- | --- | --- | | clear() | 无 | void | 当前实现为空函数 | | count(label) | label?: string | void | 计数并输出 | | countReset(label) | label?: string | void | 重置计数器 | | debug(...msg) | ...msg: any[] | void | 仅 DEBUG/ALL 级别输出 | | error(...msg) | ...msg: any[] | void | Worker / Node.js 优先输出 stack | | exception(...msg) | ...msg: any[] | void | error 别名 | | group(label) | label: string | void | 压栈分组 | | groupEnd() | 无 | void | 出栈分组 | | info(...msg) | ...msg: any[] | void | INFO 及以上 | | log(...msg) | ...msg: any[] | void | 通用日志 | | time(label) | label?: string | void | 记录起始时间 | | timeLog(label) | label?: string | void | 输出耗时 | | timeEnd(label) | label?: string | void | 清除计时器 | | warn(...msg) | ...msg: any[] | void | WARN 及以上 |

平台差异:

  • Worker / Node.js 下 error 会优先打印 Error.stack
  • 其他平台统一加前缀符号输出(❌/⚠️/ℹ️/🅱️)。

polyfill/Lodash.mjs

Lodash 为“部分方法的简化实现”,不是完整 Lodash。各方法语义可参考:

  • https://www.lodashjs.com
  • https://lodash.com

导入约定(建议):

  • 这是 lodash 官方示例中常见的惯例写法:使用 _ 作为工具对象别名。
import { Lodash as _ } from "@nsnanocat/util";

const data = {};
_.set(data, "a.b", 1);
console.log(data); // { a: { b: 1 } }

const value = _.get(data, "a.b", 0);
console.log(value); // 1

示例对应的 lodash 官方文档页面:

  • set(object, path, value)
    • 官方文档:https://lodash.com/docs/#set
    • 中文文档:https://www.lodashjs.com/docs/lodash.set
  • get(object, path, defaultValue)
    • 官方文档:https://lodash.com/docs/#get
    • 中文文档:https://www.lodashjs.com/docs/lodash.get

当前实现包含:

  • escape(string)
  • unescape(string)
  • toPath(value)
  • get(object, path, defaultValue)
  • set(object, path, value)
  • unset(object, path)
  • pick(object, paths)
  • omit(object, paths)
  • merge(object, ...sources)

对应 lodash 官方文档页面:

  • escape(string)
    • 官方文档:https://lodash.com/docs/#escape
    • 中文文档:https://www.lodashjs.com/docs/lodash.escape
  • unescape(string)
    • 官方文档:https://lodash.com/docs/#unescape
    • 中文文档:https://www.lodashjs.com/docs/lodash.unescape
  • toPath(value)
    • 官方文档:https://lodash.com/docs/#toPath
    • 中文文档:https://www.lodashjs.com/docs/lodash.toPath
  • get(object, path, defaultValue)
    • 官方文档:https://lodash.com/docs/#get
    • 中文文档:https://www.lodashjs.com/docs/lodash.get
  • set(object, path, value)
    • 官方文档:https://lodash.com/docs/#set
    • 中文文档:https://www.lodashjs.com/docs/lodash.set
  • unset(object, path)
    • 官方文档:https://lodash.com/docs/#unset
    • 中文文档:https://www.lodashjs.com/docs/lodash.unset
  • pick(object, paths)
    • 官方文档:https://lodash.com/docs/#pick
    • 中文文档:https://www.lodashjs.com/docs/lodash.pick
  • omit(object, paths)
    • 官方文档:https://lodash.com/docs/#omit
    • 中文文档:https://www.lodashjs.com/docs/lodash.omit
  • merge(object, ...sources)
    • 官方文档:https://lodash.com/docs/#merge
    • 中文文档:https://www.lodashjs.com/docs/lodash.merge

参数与返回值:

| 方法 | 参数 | 返回值 | 说明 | | --- | --- | --- | --- | | escape | string: string | string | HTML 转义 | | unescape | string: string | string | HTML 反转义 | | toPath | value: string | string[] | a[0].b -> ['a','0','b'] | | get | object?: object, path?: string\\|string[], defaultValue?: any | any | 路径读取 | | set | object: object, path: string\\|string[], value: any | object | 路径写入(会创建中间层) | | unset | object?: object, path?: string\\|string[] | boolean | 删除路径并返回结果 | | pick | object?: object, paths?: string\\|string[] | object | 挑选 key(仅第一层) | | omit | object?: object, paths?: string\\|string[] | object | 删除 key(会修改原对象) | | merge | object: object, ...sources: object[] | object | 深合并(非完整 lodash 行为) |

merge 行为(与 lodash 官方有差异):

  • 深度合并 Plain Object。
  • Array 直接覆盖;空数组不覆盖已存在值。
  • Map/Set 支持同类型合并;空 Map/Set 不覆盖已存在值。
  • undefined 不覆盖,null 会覆盖。
  • 直接修改目标对象(mutates target)。

polyfill/qs.mjs

当前实现为项目内使用的轻量子集,不追求与官方 qs 完全一致。

qs.parse(query)

  • 签名:qs.parse(query?: string | object | null): object
  • 作用:将查询字符串或对象输入归一化为支持深路径的对象。
  • 依赖:内部使用 Lodash.set 展开路径。

当前行为:

  • querystring 时:
    • & / = 切分。
    • 去掉值中的双引号。
    • 使用点路径或数组下标路径展开对象。
  • queryobject 时:
    • 将 key 当路径写入新对象({"a.b":"1"} -> { a: { b: "1" } })。
  • querynullundefined 时:
    • 返回 {}
import { qs } from "@nsnanocat/util";

console.log(qs.parse("mode=on&a.b=1"));
// { mode: "on", a: { b: "1" } }

console.log(qs.parse({ "list[0]": "x", "list[1]": "y" }));
// { list: ["x", "y"] }

qs.stringify(object)

  • 签名:qs.stringify(object?: object): string
  • 作用:将对象按深路径展开为查询字符串。
  • 依赖:内部使用 Lodash.getLodash.toPath 读取并格式化路径。

当前行为:

  • 普通对象输出为点路径(a.b=1)。
  • 数组输出为索引路径(list[0]=x)。
  • 值始终执行 encodeURIComponent 编码。
  • null 会序列化为空值(key=),undefined 会跳过。
import { qs } from "@nsnanocat/util";

console.log(qs.stringify({ a: { b: "1" }, list: ["x", "y"] }));
// a.b=1&list%5B0%5D=x&list%5B1%5D=y

polyfill/StatusTexts.mjs

StatusTexts

  • 类型:Record<number, string>
  • 内容:HTTP 状态码到状态文本映射(100~511 的常见码)。
  • 主要用途:给 Quantumult X 的 $done 状态行补全文本(如 HTTP/1.1 200 OK)。
  • 参考示例:https://github.com/crossutility/Quantumult-X/raw/refs/heads/master/sample-rewrite-response-header.js

平台差异总览

说明:本节展示的是各平台原生脚本接口差异。实际在本库中,这些差异已由 donefetchnotificationStorage 等模块做了统一适配。

| 能力 | Quantumult X | Loon | Surge | Stash | Egern | Shadowrocket | Worker | Node.js | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | HTTP 请求 | $task.fetch | $httpClient | $httpClient | $httpClient | $httpClient | $httpClient | fetch | fetch | | 通知 | $notify | $notification.post | $notification.post | $notification.post | $notification.post | $notification.post | 无 | 无 | | 持久化 | $prefs | $persistentStore | $persistentStore | $persistentStore | $persistentStore | $persistentStore | 内存缓存 | box.dat | | 结束脚本 | $done | $done | $done | $done | $done | $done | 仅日志 | process.exit(1) | | removeItem/clear | 可用 | 不可用 | removeItem 可用 / clear 不可用 | 不可用 | 不可用 | 不可用 | 可用 | 可用 | | policy 注入(fetch/done) | opts.policy | node | X-Surge-Policy(done) | X-Stash-Selected-Proxy | 无专门映射 | X-Surge-Proxy(fetch) | 无 | 无 |

已知限制与注意事项

  • lib/argument.mjs$argument 标准化模块,import 时会按规则重写全局 $argument
  • lib/done.mjs 在 Worker 仅记录结束日志。
  • lib/done.mjs 在 Node.js 固定 process.exit(1)
  • Storage.removeItem("@a.b") 分支存在未声明变量写入风险;如要大量使用路径删除,建议先本地验证。
  • lib/runScript.mjs 未从包主入口导出,需要按文件路径直接导入。

参考资料

以下资料用于对齐不同平台 $ API 语义;README 的“平台差异”优先以本仓库实现为准。

Surge

Stash

Loon

Quantumult X

Worker

  • Cloudflare 全局标记识别 Worker 运行时。

Node.js

Web API / Lodash

Egern / Shadowrocket

说明:Egern 与 Shadowrocket 暂未检索到等价于 Surge/Loon/Stash 的完整公开脚本 API 页面;相关差异说明以本库实际代码分支行为为准。