@fast-china/utils 2.1.0 → 2.1.2
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 +24 -0
- package/README.md +32 -3
- package/README.zh.md +32 -3
- package/dist/array/index.mjs +1 -1
- package/dist/array/index.mjs.map +1 -1
- package/dist/async/index.mjs +21 -19
- package/dist/async/index.mjs.map +1 -1
- package/dist/base64/index.d.mts +10 -9
- package/dist/base64/index.mjs +15 -15
- package/dist/base64/index.mjs.map +1 -1
- package/dist/color/index.mjs +4 -4
- package/dist/color/index.mjs.map +1 -1
- package/dist/crypto/index.d.mts +9 -8
- package/dist/crypto/index.mjs +37 -37
- package/dist/crypto/index.mjs.map +1 -1
- package/dist/date/index.mjs +3 -3
- package/dist/date/index.mjs.map +1 -1
- package/dist/dom/style.mjs +3 -3
- package/dist/dom/style.mjs.map +1 -1
- package/dist/identity/index.mjs +4 -4
- package/dist/identity/index.mjs.map +1 -1
- 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 +2 -2
- package/dist/internal/text.d.mts +17 -0
- package/dist/internal/text.mjs +41 -3
- package/dist/internal/text.mjs.map +1 -1
- package/dist/logger/index.d.mts +26 -22
- package/dist/logger/index.mjs +54 -25
- package/dist/logger/index.mjs.map +1 -1
- package/dist/number/index.mjs +12 -12
- package/dist/number/index.mjs.map +1 -1
- package/dist/object/index.mjs +1 -1
- package/dist/object/index.mjs.map +1 -1
- package/dist/storage/index.d.mts +14 -5
- package/dist/storage/index.mjs +39 -27
- package/dist/storage/index.mjs.map +1 -1
- package/dist/string/index.mjs +13 -13
- package/dist/string/index.mjs.map +1 -1
- package/dist/vue/emits.mjs +2 -2
- package/dist/vue/emits.mjs.map +1 -1
- package/dist/vue/func.mjs +1 -1
- package/dist/vue/func.mjs.map +1 -1
- package/dist/vue/install.mjs +12 -12
- package/dist/vue/install.mjs.map +1 -1
- package/dist/vue/props.d.mts +1 -1
- package/dist/vue/props.mjs.map +1 -1
- package/dist/vue/render.mjs +1 -1
- package/dist/vue/render.mjs.map +1 -1
- package/docs/API.md +34 -6
- package/docs/API.zh-CN.md +33 -6
- package/docs/RUNTIME_CONTRACT.md +9 -4
- package/package.json +9 -9
package/dist/vue/func.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
async function callOptionalFunction(function_, ...arguments_) {
|
|
10
10
|
if (function_ === null || function_ === void 0) return void 0;
|
|
11
|
-
return
|
|
11
|
+
return Promise.resolve(function_(...arguments_));
|
|
12
12
|
}
|
|
13
13
|
//#endregion
|
|
14
14
|
export { callOptionalFunction };
|
package/dist/vue/func.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"func.mjs","names":[],"sources":["../../src/vue/func.ts"],"sourcesContent":["/** 可同步或异步返回结果的函数。 */\nexport type AwaitableFunction<Arguments extends readonly unknown[], Result> = (...arguments_: Arguments) => Result | PromiseLike<Result>;\n\n/**\n * 统一执行同步或异步函数,异常保持原样向调用方传播。\n *\n * @param function_ - 可选的待执行函数。\n * @param arguments_ - 原样传入函数的参数。\n * @returns 函数结果;未传函数时返回 `undefined`。\n */\nexport async function callOptionalFunction<Arguments extends readonly unknown[], Result>(\n\tfunction_: AwaitableFunction<Arguments, Result> | null | undefined,\n\t...arguments_: Arguments\n): Promise<Awaited<Result> | undefined> {\n\tif (function_ === null || function_ === undefined) return undefined;\n\treturn
|
|
1
|
+
{"version":3,"file":"func.mjs","names":[],"sources":["../../src/vue/func.ts"],"sourcesContent":["/** 可同步或异步返回结果的函数。 */\nexport type AwaitableFunction<Arguments extends readonly unknown[], Result> = (...arguments_: Arguments) => Result | PromiseLike<Result>;\n\n/**\n * 统一执行同步或异步函数,异常保持原样向调用方传播。\n *\n * @param function_ - 可选的待执行函数。\n * @param arguments_ - 原样传入函数的参数。\n * @returns 函数结果;未传函数时返回 `undefined`。\n */\nexport async function callOptionalFunction<Arguments extends readonly unknown[], Result>(\n\tfunction_: AwaitableFunction<Arguments, Result> | null | undefined,\n\t...arguments_: Arguments\n): Promise<Awaited<Result> | undefined> {\n\tif (function_ === null || function_ === undefined) return undefined;\n\treturn Promise.resolve(function_(...arguments_));\n}\n"],"mappings":";;;;;;;;AAUA,eAAsB,qBACrB,WACA,GAAG,YACoC;CACvC,IAAI,cAAc,QAAQ,cAAc,KAAA,GAAW,OAAO,KAAA;CAC1D,OAAO,QAAQ,QAAQ,UAAU,GAAG,UAAU,CAAC;AAChD"}
|
package/dist/vue/install.mjs
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* @throws `TypeError` 当目标不是对象或缺少 `component`、`directive` 方法。
|
|
8
8
|
*/
|
|
9
9
|
const assertApp = (value) => {
|
|
10
|
-
if (typeof value !== "object" || value === null) throw new TypeError("Vue
|
|
10
|
+
if (typeof value !== "object" || value === null) throw new TypeError("安装 Vue 插件需要 Vue 3 App 实例。");
|
|
11
11
|
const app = value;
|
|
12
|
-
if (typeof app.component !== "function" || typeof app.directive !== "function") throw new TypeError("Vue
|
|
12
|
+
if (typeof app.component !== "function" || typeof app.directive !== "function") throw new TypeError("安装 Vue 插件需要 `component()` 和 `directive()` 注册方法。");
|
|
13
13
|
return app;
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
@@ -21,7 +21,7 @@ const assertApp = (value) => {
|
|
|
21
21
|
*/
|
|
22
22
|
const getComponentName = (component) => {
|
|
23
23
|
const name = component.name;
|
|
24
|
-
if (typeof name !== "string" || name.length === 0 || /\s/u.test(name)) throw new TypeError("
|
|
24
|
+
if (typeof name !== "string" || name.length === 0 || /\s/u.test(name)) throw new TypeError("可安装的 Vue 组件必须公开不含空白的非空名称。");
|
|
25
25
|
return name;
|
|
26
26
|
};
|
|
27
27
|
/**
|
|
@@ -36,7 +36,7 @@ const getComponentName = (component) => {
|
|
|
36
36
|
const prepareComponentRegistration = (app, component) => {
|
|
37
37
|
const name = getComponentName(component);
|
|
38
38
|
const existing = app.component(name);
|
|
39
|
-
if (existing !== void 0 && existing !== component) throw new Error(`Vue
|
|
39
|
+
if (existing !== void 0 && existing !== component) throw new Error(`Vue 组件名称“${name}”已被其他组件注册。`);
|
|
40
40
|
return {
|
|
41
41
|
component,
|
|
42
42
|
name,
|
|
@@ -56,14 +56,14 @@ const prepareComponentRegistration = (app, component) => {
|
|
|
56
56
|
*/
|
|
57
57
|
function withInstall(main, extras) {
|
|
58
58
|
const componentNames = /* @__PURE__ */ new Set([getComponentName(main)]);
|
|
59
|
-
if ("install" in Object(main)) throw new TypeError("
|
|
59
|
+
if ("install" in Object(main)) throw new TypeError("Vue 组件已定义 `install` 属性。");
|
|
60
60
|
const extraEntries = Object.entries(extras ?? {});
|
|
61
|
-
if (extras !== void 0 && Object.getOwnPropertySymbols(extras).some((key) => Object.prototype.propertyIsEnumerable.call(extras, key))) throw new TypeError("Vue
|
|
61
|
+
if (extras !== void 0 && Object.getOwnPropertySymbols(extras).some((key) => Object.prototype.propertyIsEnumerable.call(extras, key))) throw new TypeError("Vue 组件附属项必须使用字符串属性名。");
|
|
62
62
|
for (const [key, component] of extraEntries) {
|
|
63
63
|
const componentName = getComponentName(component);
|
|
64
|
-
if (componentNames.has(componentName)) throw new TypeError(`Vue
|
|
64
|
+
if (componentNames.has(componentName)) throw new TypeError(`Vue 组件名称“${componentName}”被重复注册。`);
|
|
65
65
|
componentNames.add(componentName);
|
|
66
|
-
if (key === "install" || key in Object(main)) throw new TypeError(`Vue
|
|
66
|
+
if (key === "install" || key in Object(main)) throw new TypeError(`Vue 组件附属项“${key}”会覆盖主组件上的属性。`);
|
|
67
67
|
}
|
|
68
68
|
const installable = main;
|
|
69
69
|
for (const [key, component] of extraEntries) Object.defineProperty(installable, key, {
|
|
@@ -89,7 +89,7 @@ function withInstall(main, extras) {
|
|
|
89
89
|
* @throws `TypeError` 当组件自身或原型链已经存在 `install`。
|
|
90
90
|
*/
|
|
91
91
|
function withNoopInstall(component) {
|
|
92
|
-
if ("install" in Object(component)) throw new TypeError("
|
|
92
|
+
if ("install" in Object(component)) throw new TypeError("Vue 组件已定义 `install` 属性。");
|
|
93
93
|
const installable = component;
|
|
94
94
|
installable.install = () => void 0;
|
|
95
95
|
return installable;
|
|
@@ -106,13 +106,13 @@ function withNoopInstall(component) {
|
|
|
106
106
|
* @throws `Error` 当 App 中同名位置已经注册其他指令。
|
|
107
107
|
*/
|
|
108
108
|
function withInstallDirective(directive, name) {
|
|
109
|
-
if (name.length === 0 || /\s/u.test(name) || name.startsWith("v-")) throw new TypeError("
|
|
110
|
-
if ("install" in Object(directive)) throw new TypeError("
|
|
109
|
+
if (name.length === 0 || /\s/u.test(name) || name.startsWith("v-")) throw new TypeError("可安装的 Vue 指令名称不能包含空白或 `v-` 前缀。");
|
|
110
|
+
if ("install" in Object(directive)) throw new TypeError("Vue 指令已定义 `install` 属性。");
|
|
111
111
|
const installable = directive;
|
|
112
112
|
installable.install = (value) => {
|
|
113
113
|
const app = assertApp(value);
|
|
114
114
|
const existing = app.directive(name);
|
|
115
|
-
if (existing !== void 0 && existing !== directive) throw new Error(`Vue
|
|
115
|
+
if (existing !== void 0 && existing !== directive) throw new Error(`Vue 指令名称“${name}”已被其他指令注册。`);
|
|
116
116
|
if (existing !== directive) app.directive(name, directive);
|
|
117
117
|
};
|
|
118
118
|
return installable;
|
package/dist/vue/install.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.mjs","names":[],"sources":["../../src/vue/install.ts"],"sourcesContent":["import type { App } from \"vue\";\n\n/** Vue 组件对象、函数组件或指令对象可接受的最小结构类型。 */\nexport type VueInstallValue = object | ((...arguments_: never[]) => unknown);\n\n/** 为 Vue 组件或指令附加供 Vue 3 `app.use()` 调用的安装能力。 */\nexport type Installable<Value> = Value & {\n\t/**\n\t * 把当前组件或指令安装到 Vue 3 App。\n\t * @param app - Vue 3 App 实例。\n\t */\n\tinstall: (app: App) => void;\n};\n\n/** TSX 组件安装类型;与 {@link Installable} 保持同一运行时契约。 */\nexport type TSXWithInstall<Value> = Installable<Value>;\n\n/** 组件、指令注册所需的 Vue 3 App 能力。 */\ninterface VueAppRegistrationTarget {\n\t/**\n\t * 读取或注册全局组件。\n\t *\n\t * @param name - 全局组件名。\n\t * @param component - 注册时传入的组件;省略时读取现有组件。\n\t * @returns Vue App 返回的现有组件、注册结果或 App 自身。\n\t */\n\tcomponent: (name: string, component?: unknown) => unknown;\n\t/**\n\t * 读取或注册全局指令。\n\t *\n\t * @param name - 不带 `v-` 的全局指令名。\n\t * @param directive - 注册时传入的指令;省略时读取现有指令。\n\t * @returns Vue App 返回的现有指令、注册结果或 App 自身。\n\t */\n\tdirective: (name: string, directive?: unknown) => unknown;\n}\n\n/**\n * 校验 Vue 3 插件安装目标。\n *\n * @param value - Vue 3 App 实例。\n * @returns 只包含组件和指令注册能力的 App。\n * @throws `TypeError` 当目标不是对象或缺少 `component`、`directive` 方法。\n */\nconst assertApp = (value: App): VueAppRegistrationTarget => {\n\tif (typeof value !== \"object\" || value === null) {\n\t\tthrow new TypeError(\"Vue
|
|
1
|
+
{"version":3,"file":"install.mjs","names":[],"sources":["../../src/vue/install.ts"],"sourcesContent":["import type { App } from \"vue\";\n\n/** Vue 组件对象、函数组件或指令对象可接受的最小结构类型。 */\nexport type VueInstallValue = object | ((...arguments_: never[]) => unknown);\n\n/** 为 Vue 组件或指令附加供 Vue 3 `app.use()` 调用的安装能力。 */\nexport type Installable<Value> = Value & {\n\t/**\n\t * 把当前组件或指令安装到 Vue 3 App。\n\t * @param app - Vue 3 App 实例。\n\t */\n\tinstall: (app: App) => void;\n};\n\n/** TSX 组件安装类型;与 {@link Installable} 保持同一运行时契约。 */\nexport type TSXWithInstall<Value> = Installable<Value>;\n\n/** 组件、指令注册所需的 Vue 3 App 能力。 */\ninterface VueAppRegistrationTarget {\n\t/**\n\t * 读取或注册全局组件。\n\t *\n\t * @param name - 全局组件名。\n\t * @param component - 注册时传入的组件;省略时读取现有组件。\n\t * @returns Vue App 返回的现有组件、注册结果或 App 自身。\n\t */\n\tcomponent: (name: string, component?: unknown) => unknown;\n\t/**\n\t * 读取或注册全局指令。\n\t *\n\t * @param name - 不带 `v-` 的全局指令名。\n\t * @param directive - 注册时传入的指令;省略时读取现有指令。\n\t * @returns Vue App 返回的现有指令、注册结果或 App 自身。\n\t */\n\tdirective: (name: string, directive?: unknown) => unknown;\n}\n\n/**\n * 校验 Vue 3 插件安装目标。\n *\n * @param value - Vue 3 App 实例。\n * @returns 只包含组件和指令注册能力的 App。\n * @throws `TypeError` 当目标不是对象或缺少 `component`、`directive` 方法。\n */\nconst assertApp = (value: App): VueAppRegistrationTarget => {\n\tif (typeof value !== \"object\" || value === null) {\n\t\tthrow new TypeError(\"安装 Vue 插件需要 Vue 3 App 实例。\");\n\t}\n\tconst app = value as unknown as Partial<VueAppRegistrationTarget>;\n\tif (typeof app.component !== \"function\" || typeof app.directive !== \"function\") {\n\t\tthrow new TypeError(\"安装 Vue 插件需要 `component()` 和 `directive()` 注册方法。\");\n\t}\n\treturn app as VueAppRegistrationTarget;\n};\n\n/**\n * 提取组件的全局注册名称。\n *\n * @param component - 待注册组件。\n * @returns 经过校验的显式名称。\n * @throws `TypeError` 当组件没有非空字符串名称,或名称包含空白。\n */\nconst getComponentName = (component: VueInstallValue): string => {\n\tconst name = (component as { name?: unknown }).name;\n\tif (typeof name !== \"string\" || name.length === 0 || /\\s/u.test(name)) {\n\t\tthrow new TypeError(\"可安装的 Vue 组件必须公开不含空白的非空名称。\");\n\t}\n\treturn name;\n};\n\n/** 预检完成、可以无失败注册的组件动作。 */\ninterface ComponentRegistration {\n\t/** 已校验的组件引用。 */\n\tcomponent: VueInstallValue;\n\t/** 已校验的组件名称。 */\n\tname: string;\n\t/** 目标中是否已经注册了完全相同的组件引用。 */\n\tregistered: boolean;\n}\n\n/**\n * 预检单个组件注册。\n *\n * @remarks 先读取同名组件并完成冲突判断,再返回延迟执行动作;调用方可以在所有组件预检通过后统一提交。\n * @param app - 已校验的 Vue 3 App 注册目标。\n * @param component - 待注册组件。\n * @returns 已校验的组件引用、名称和目标中是否已经存在同一引用。\n * @throws `Error` 当同名位置已由其他组件占用。\n */\nconst prepareComponentRegistration = (app: VueAppRegistrationTarget, component: VueInstallValue): ComponentRegistration => {\n\tconst name = getComponentName(component);\n\tconst existing = app.component(name);\n\tif (existing !== undefined && existing !== component) {\n\t\tthrow new Error(`Vue 组件名称“${name}”已被其他组件注册。`);\n\t}\n\treturn { component, name, registered: existing === component };\n};\n\n/**\n * 为主组件附加 Vue 3 `app.use()` 安装能力。\n *\n * @remarks 函数会直接为 `main` 定义附属组件属性和 `install`。所有组件名称、附属属性\n * 冲突会在修改 `main` 前完成校验;安装到 App 时也会先预检全部全局名称,再统一注册。\n * @param main - 具有非空 `name` 的组件。\n * @param extras - 同时注册并以可枚举属性挂到主组件的附属组件映射。\n * @returns 原始 `main` 引用,并附加类型化的 `install` 与 `extras` 属性。\n * @throws `TypeError` 当组件缺少合法名称、已有 `install`、附属键或名称发生冲突。\n * @throws `Error` 当 App 中同名位置已经注册其他组件。\n */\nexport function withInstall<Main extends VueInstallValue, Extras extends Record<string, VueInstallValue> = Record<never, never>>(\n\tmain: Main,\n\textras?: Extras\n): Installable<Main> & Extras {\n\tconst componentNames = new Set([getComponentName(main)]);\n\tif (\"install\" in Object(main)) throw new TypeError(\"Vue 组件已定义 `install` 属性。\");\n\tconst extraEntries = Object.entries(extras ?? {});\n\tif (extras !== undefined && Object.getOwnPropertySymbols(extras).some((key) => Object.prototype.propertyIsEnumerable.call(extras, key))) {\n\t\tthrow new TypeError(\"Vue 组件附属项必须使用字符串属性名。\");\n\t}\n\tfor (const [key, component] of extraEntries) {\n\t\tconst componentName = getComponentName(component);\n\t\tif (componentNames.has(componentName)) throw new TypeError(`Vue 组件名称“${componentName}”被重复注册。`);\n\t\tcomponentNames.add(componentName);\n\t\tif (key === \"install\" || key in Object(main)) {\n\t\t\tthrow new TypeError(`Vue 组件附属项“${key}”会覆盖主组件上的属性。`);\n\t\t}\n\t}\n\tconst installable = main as Installable<Main> & Extras;\n\tfor (const [key, component] of extraEntries) {\n\t\tObject.defineProperty(installable, key, { configurable: true, enumerable: true, value: component, writable: true });\n\t}\n\tinstallable.install = (value: App): void => {\n\t\tconst app = assertApp(value);\n\t\t// 先完成全部冲突检查,再统一注册,避免安装到一半留下部分全局组件。\n\t\tconst registrations = [main, ...extraEntries.map(([, component]) => component)].map((component) =>\n\t\t\tprepareComponentRegistration(app, component)\n\t\t);\n\t\tfor (const registration of registrations) {\n\t\t\tif (!registration.registered) app.component(registration.name, registration.component);\n\t\t}\n\t};\n\treturn installable;\n}\n\n/**\n * 为不需要单独注册的附属组件附加空安装函数。\n *\n * @remarks 适用于只能作为主组件附属属性使用、但仍需满足 Vue Plugin 类型的组件。\n * 函数直接修改并返回传入组件,不会向 Vue 3 App 注册内容。\n * @param component - 尚未定义或继承 `install` 属性的组件。\n * @returns 原组件引用及无副作用的 `install` 方法。\n * @throws `TypeError` 当组件自身或原型链已经存在 `install`。\n */\nexport function withNoopInstall<Value extends VueInstallValue>(component: Value): TSXWithInstall<Value> {\n\tif (\"install\" in Object(component)) throw new TypeError(\"Vue 组件已定义 `install` 属性。\");\n\tconst installable = component as TSXWithInstall<Value>;\n\tinstallable.install = (): void => undefined;\n\treturn installable;\n}\n\n/**\n * 为 Vue 3 指令附加插件安装能力。\n *\n * @remarks 函数直接修改并返回指令。安装时重复注册同一引用保持幂等,不会覆盖同名的\n * 其他指令。名称只传给 `directive()`,不得包含 `v-` 前缀。\n * @param directive - 尚未定义或继承 `install` 属性的 Vue 指令对象。\n * @param name - 非空、无空白且不以 `v-` 开头的全局指令名。\n * @returns 原指令引用及 Vue Plugin `install` 方法。\n * @throws `TypeError` 当名称非法、指令已有 `install`,或安装目标无效。\n * @throws `Error` 当 App 中同名位置已经注册其他指令。\n */\nexport function withInstallDirective<Value extends VueInstallValue>(directive: Value, name: string): Installable<Value> {\n\tif (name.length === 0 || /\\s/u.test(name) || name.startsWith(\"v-\")) {\n\t\tthrow new TypeError(\"可安装的 Vue 指令名称不能包含空白或 `v-` 前缀。\");\n\t}\n\tif (\"install\" in Object(directive)) throw new TypeError(\"Vue 指令已定义 `install` 属性。\");\n\tconst installable = directive as Installable<Value>;\n\tinstallable.install = (value: App): void => {\n\t\tconst app = assertApp(value);\n\t\tconst existing = app.directive(name);\n\t\tif (existing !== undefined && existing !== directive) {\n\t\t\tthrow new Error(`Vue 指令名称“${name}”已被其他指令注册。`);\n\t\t}\n\t\tif (existing !== directive) app.directive(name, directive);\n\t};\n\treturn installable;\n}\n"],"mappings":"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"}
|
package/dist/vue/props.d.mts
CHANGED
|
@@ -17,7 +17,7 @@ declare function definePropType<Value>(runtimeType: unknown): PropType<Value>;
|
|
|
17
17
|
* @param ignoredProps - 不需要透传的 Props 名称。
|
|
18
18
|
* @returns 只包含 `rawProps` 声明键且随 Props 更新的 ComputedRef。
|
|
19
19
|
*/
|
|
20
|
-
declare function useProps<Props extends object, RawProps extends object>(props: Props, rawProps: RawProps, ignoredProps?: readonly
|
|
20
|
+
declare function useProps<Props extends object, RawProps extends object, IgnoredProp extends keyof RawProps = never>(props: Props, rawProps: RawProps, ignoredProps?: readonly IgnoredProp[]): ComputedRef<Omit<Pick<Props, Extract<keyof Props, keyof RawProps>>, Extract<IgnoredProp, keyof Props>>>;
|
|
21
21
|
//#endregion
|
|
22
22
|
export { definePropType, useProps };
|
|
23
23
|
//# sourceMappingURL=props.d.mts.map
|
package/dist/vue/props.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"props.mjs","names":[],"sources":["../../src/vue/props.ts"],"sourcesContent":["import { computed } from \"vue\";\nimport type { ComputedRef, PropType } from \"vue\";\n\n/**\n * 为 Vue 运行时 Props 构造器附加泛型类型。\n *\n * @remarks 该函数只帮助 TypeScript 建模,不验证运行时值与 `Value` 一致;调用方仍应\n * 传入 Vue 支持的构造器或构造器数组。\n * @param runtimeType - Vue 支持的运行时构造器或构造器数组。\n * @returns 同一引用,仅在类型层收窄为 `PropType<Value>`。\n */\nexport function definePropType<Value>(runtimeType: unknown): PropType<Value> {\n\treturn runtimeType as PropType<Value>;\n}\n\n/**\n * 构建需要透传给子组件的响应式 Props。\n *\n * @param props - Vue `setup` 接收的只读响应式 Props 对象。\n * @param rawProps - 子组件的运行时 Props 配置。\n * @param ignoredProps - 不需要透传的 Props 名称。\n * @returns 只包含 `rawProps` 声明键且随 Props 更新的 ComputedRef。\n */\nexport function useProps<Props extends object, RawProps extends object>(\n\tprops: Props,\n\trawProps: RawProps,\n\tignoredProps: readonly
|
|
1
|
+
{"version":3,"file":"props.mjs","names":[],"sources":["../../src/vue/props.ts"],"sourcesContent":["import { computed } from \"vue\";\nimport type { ComputedRef, PropType } from \"vue\";\n\n/**\n * 为 Vue 运行时 Props 构造器附加泛型类型。\n *\n * @remarks 该函数只帮助 TypeScript 建模,不验证运行时值与 `Value` 一致;调用方仍应\n * 传入 Vue 支持的构造器或构造器数组。\n * @param runtimeType - Vue 支持的运行时构造器或构造器数组。\n * @returns 同一引用,仅在类型层收窄为 `PropType<Value>`。\n */\nexport function definePropType<Value>(runtimeType: unknown): PropType<Value> {\n\treturn runtimeType as PropType<Value>;\n}\n\n/**\n * 构建需要透传给子组件的响应式 Props。\n *\n * @param props - Vue `setup` 接收的只读响应式 Props 对象。\n * @param rawProps - 子组件的运行时 Props 配置。\n * @param ignoredProps - 不需要透传的 Props 名称。\n * @returns 只包含 `rawProps` 声明键且随 Props 更新的 ComputedRef。\n */\nexport function useProps<Props extends object, RawProps extends object, IgnoredProp extends keyof RawProps = never>(\n\tprops: Props,\n\trawProps: RawProps,\n\tignoredProps: readonly IgnoredProp[] = []\n): ComputedRef<Omit<Pick<Props, Extract<keyof Props, keyof RawProps>>, Extract<IgnoredProp, keyof Props>>> {\n\tconst ignored = new Set<PropertyKey>(ignoredProps);\n\ttype Result = Omit<Pick<Props, Extract<keyof Props, keyof RawProps>>, Extract<IgnoredProp, keyof Props>>;\n\treturn computed<Result>(() => {\n\t\tconst result = {} as Result;\n\t\tfor (const key of Reflect.ownKeys(rawProps)) {\n\t\t\tif (ignored.has(key) || !Object.hasOwn(props, key)) continue;\n\t\t\tObject.defineProperty(result, key, { configurable: true, enumerable: true, value: Reflect.get(props, key), writable: true });\n\t\t}\n\t\treturn result;\n\t});\n}\n"],"mappings":";;;;;;;;;;AAWA,SAAgB,eAAsB,aAAuC;CAC5E,OAAO;AACR;;;;;;;;;AAUA,SAAgB,SACf,OACA,UACA,eAAuC,CAAC,GACkE;CAC1G,MAAM,UAAU,IAAI,IAAiB,YAAY;CAEjD,OAAO,eAAuB;EAC7B,MAAM,SAAS,CAAC;EAChB,KAAK,MAAM,OAAO,QAAQ,QAAQ,QAAQ,GAAG;GAC5C,IAAI,QAAQ,IAAI,GAAG,KAAK,CAAC,OAAO,OAAO,OAAO,GAAG,GAAG;GACpD,OAAO,eAAe,QAAQ,KAAK;IAAE,cAAc;IAAM,YAAY;IAAM,OAAO,QAAQ,IAAI,OAAO,GAAG;IAAG,UAAU;GAAK,CAAC;EAC5H;EACA,OAAO;CACR,CAAC;AACF"}
|
package/dist/vue/render.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { getCurrentInstance } from "vue";
|
|
|
8
8
|
*/
|
|
9
9
|
function useRender(render) {
|
|
10
10
|
const instance = getCurrentInstance();
|
|
11
|
-
if (instance === null) throw new Error("useRender
|
|
11
|
+
if (instance === null) throw new Error("`useRender` 必须在 `setup` 函数内部调用。");
|
|
12
12
|
instance.render = render;
|
|
13
13
|
}
|
|
14
14
|
//#endregion
|
package/dist/vue/render.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.mjs","names":[],"sources":["../../src/vue/render.ts"],"sourcesContent":["import { getCurrentInstance } from \"vue\";\nimport type { VNode } from \"vue\";\n\n/** `useRender` 需要写入的 Vue 3 内部组件实例字段。 */\ninterface MutableVueComponentInstance {\n\trender?: () => VNode;\n}\n\n/**\n * 在当前 Vue 3 组件实例上安装 TSX 渲染函数。\n * @remarks `setup` 仍可返回状态对象,因此状态能够显示在 Vue Devtools 中。\n * @param render - 当前组件的渲染函数。\n * @throws 不在组件 `setup` 调用栈中使用时抛出 `Error`。\n */\nexport function useRender(render: () => VNode): void {\n\tconst instance = getCurrentInstance();\n\tif (instance === null) throw new Error(\"useRender
|
|
1
|
+
{"version":3,"file":"render.mjs","names":[],"sources":["../../src/vue/render.ts"],"sourcesContent":["import { getCurrentInstance } from \"vue\";\nimport type { VNode } from \"vue\";\n\n/** `useRender` 需要写入的 Vue 3 内部组件实例字段。 */\ninterface MutableVueComponentInstance {\n\trender?: () => VNode;\n}\n\n/**\n * 在当前 Vue 3 组件实例上安装 TSX 渲染函数。\n * @remarks `setup` 仍可返回状态对象,因此状态能够显示在 Vue Devtools 中。\n * @param render - 当前组件的渲染函数。\n * @throws 不在组件 `setup` 调用栈中使用时抛出 `Error`。\n */\nexport function useRender(render: () => VNode): void {\n\tconst instance = getCurrentInstance();\n\tif (instance === null) throw new Error(\"`useRender` 必须在 `setup` 函数内部调用。\");\n\t(instance as unknown as MutableVueComponentInstance).render = render;\n}\n"],"mappings":";;;;;;;;AAcA,SAAgB,UAAU,QAA2B;CACpD,MAAM,WAAW,mBAAmB;CACpC,IAAI,aAAa,MAAM,MAAM,IAAI,MAAM,iCAAiC;CACxE,SAAqD,SAAS;AAC/D"}
|
package/docs/API.md
CHANGED
|
@@ -21,10 +21,15 @@ import { Local, Session } from "@fast-china/utils";
|
|
|
21
21
|
|
|
22
22
|
Local.set("profile", { name: "Ada" }, { ttlMs: 3_600_000 });
|
|
23
23
|
Session.set("draft", { step: 2 });
|
|
24
|
+
|
|
25
|
+
Local.set("private-profile", { name: "Ada" }, { crypto: true });
|
|
26
|
+
Local.get<{ name: string }>("private-profile", { crypto: true });
|
|
24
27
|
```
|
|
25
28
|
|
|
26
29
|
`Local` and `Session` provide `get`, `set`, `has`, `remove`, `removeByPrefix`, `keys`, `pruneExpired`, and namespace-scoped `clear`. Missing and expired values return `undefined`. Invalid TTL values, empty prefixes, malformed stored envelopes, unavailable platform storage, and conflicting repeated configuration throw errors. Native storage quota and privacy errors are propagated. Custom options must be configured before the first Storage operation.
|
|
27
30
|
|
|
31
|
+
`get<Value = string>()` has the static return type `string | undefined` when its generic is omitted, so string entries can be read directly. The codec still restores the original JSON value at runtime and does not convert objects, arrays, or other non-string values to strings; pass an explicit generic when accurate type information is required.
|
|
32
|
+
|
|
28
33
|
For uni-app, the first Storage operation or an explicit `configureStorage` call detects the global `uni` object and uses its synchronous Storage API. uni-app has no separate session backend, so `Session` throws when called in this mode.
|
|
29
34
|
|
|
30
35
|
```ts
|
|
@@ -33,7 +38,11 @@ import { Local } from "@fast-china/utils";
|
|
|
33
38
|
Local.set("token", "value");
|
|
34
39
|
```
|
|
35
40
|
|
|
36
|
-
`configureStorage({ prefix: "admin:", crypto: true })` restores the old global prefix and Base64-obfuscation options. `crypto: true`
|
|
41
|
+
`configureStorage({ prefix: "admin:", crypto: true })` restores the old global prefix and Base64-obfuscation options. `Local` and `Session` `set/get` also accept a per-operation `{ crypto: boolean }`: `true` selects the Base64 codec, `false` selects the JSON codec, and omission uses the global codec. An operation override does not mutate global configuration. The current v3 envelope does not record its codec, so writes and reads of the same entry must use matching options; a mismatch throws a decoding error.
|
|
42
|
+
|
|
43
|
+
`crypto: true` and `base64StorageCodec` are reversible encoding rather than encryption and must not protect secrets. A custom `codec` may be supplied instead of global `crypto`.
|
|
44
|
+
|
|
45
|
+
`decodeBase64`, `decodeBase64Url`, `decodeLatin1Base64`, and `decodeSecureBase64` return the primitive-string `DecodedText` type. It is directly assignable to `string` and supports strict equality; JSON is returned only through an explicit `.parseJson<T = any>()` call, and the library never infers JSON from text content. The first text decode lazily installs a non-enumerable `String.prototype.parseJson`; a foreign property with the same name causes a `TypeError` instead of being overwritten. The generic type describes the expected shape but does not perform runtime validation.
|
|
37
46
|
|
|
38
47
|
`encodeSecureBase64` and `decodeSecureBase64` preserve the legacy dictionary payload. The random prefix prefers Web Crypto and falls back to `Math.random()` when unavailable; it does not provide a security property. Given the same default six-character prefix, valid legacy payloads remain byte-for-byte compatible. The old dictionary references an unavailable character for Base64 lengths 101–124, so the current implementation inserts a one-character fallback that the legacy removal flow can decode. The old custom-length argument always generated six random characters; the current API correctly generates `prefixLength` characters. Custom lengths must match during encoding and decoding; `0` disables both the prefix and dictionary insertion. The format remains reversible encoding rather than encryption.
|
|
39
48
|
|
|
@@ -55,16 +64,25 @@ The identifier is an installation-scoped value, not a hardware identifier, authe
|
|
|
55
64
|
|
|
56
65
|
## Logger
|
|
57
66
|
|
|
58
|
-
Logger scope belongs to each
|
|
67
|
+
Logger scope belongs to each entry rather than a logger or child instance. The default `logger` works without construction and has a minimum level of `debug`. Configure it once
|
|
68
|
+
at application startup when uni-app App-Plus needs split object output:
|
|
59
69
|
|
|
60
70
|
```ts
|
|
61
|
-
import { logger } from "@fast-china/utils";
|
|
71
|
+
import { configureLogger, logger } from "@fast-china/utils";
|
|
62
72
|
|
|
63
|
-
|
|
73
|
+
configureLogger({ uniAppPlusSplit: true });
|
|
74
|
+
logger.log("Launch", { code: 200, data: { id: 1 } });
|
|
75
|
+
logger.log("storage", "profile loaded", { userId: 1 });
|
|
64
76
|
logger.error("network", "request failed", error);
|
|
65
77
|
```
|
|
66
78
|
|
|
67
|
-
|
|
79
|
+
Log content is optional and may directly contain objects, arrays, `Error` instances, or other values. Non-string values are passed unchanged to
|
|
80
|
+
the Sink in normal runtimes. With App-Plus splitting enabled, the heading is emitted separately and each additional value is converted to readable
|
|
81
|
+
text because HBuilderX does not reliably display objects.
|
|
82
|
+
|
|
83
|
+
`configureLogger` replaces the complete configuration of the default `logger`; previously retained `logger` references immediately observe the new
|
|
84
|
+
configuration. Calling it without options restores all defaults. `createLogger` creates an isolated instance unaffected by global configuration.
|
|
85
|
+
Logger's `debug`, `log`, `warn`, and `error` methods call the matching Sink methods; the default Sink maps them to `console.debug`, `console.log`, `console.warn`, and `console.error`. Both support a minimum level, brand prefix, Sink, and optional App-Plus split output. Scope must be a non-empty string without surrounding whitespace.
|
|
68
86
|
|
|
69
87
|
## Clipboard
|
|
70
88
|
|
|
@@ -93,13 +111,21 @@ The TypeScript Crypto public API mirrors the public methods and algorithm casing
|
|
|
93
111
|
|
|
94
112
|
The Base64 v1 payload produced by `AESEncryptAuthenticated`, the `FAST-AES-256-GCM-V1` password payload, PBKDF2 password hashes, and PKCS#8/SPKI PEM keys interoperate with .NET in both directions. MD5 and HMAC output lowercase hexadecimal; SHA-1/256/384/512 output uppercase hexadecimal, matching .NET.
|
|
95
113
|
|
|
114
|
+
`AESDecrypt`, `AESDecryptAuthenticated`, `AESDecryptWithPassword`, and `RSADecryptOAEP` return the primitive-string `DecodedText` type (wrapped in a Promise for asynchronous APIs). Use the result directly as plaintext or call `.parseJson<T = any>()` explicitly:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
const plaintext = await AESDecryptWithPassword(payload, password);
|
|
118
|
+
const raw: string = plaintext;
|
|
119
|
+
const result = plaintext.parseJson<{ id: number }>();
|
|
120
|
+
```
|
|
121
|
+
|
|
96
122
|
Store passwords with `HashPasswordPBKDF2SHA256` and `VerifyPasswordPBKDF2SHA256`; the result is not decryptable. AES-GCM provides confidentiality and integrity, HMAC authenticates with a shared key, SHA-2 computes digests, and HKDF/PBKDF2 derive keys. MD5, SHA-1, AES-CBC, and AES-ECB do not provide modern password-storage or authenticated-encryption guarantees.
|
|
97
123
|
|
|
98
124
|
## Modules
|
|
99
125
|
|
|
100
126
|
- `array`: `chunk`, `removeNullishValues`, `unique`, `uniqueBy`, `groupBy`, `partition`, `difference`, `intersection`, `hasDuplicatesBy`, and `allEqualBy`.
|
|
101
127
|
- `async`: abort-aware `sleep`, timeout, retry, bounded concurrent mapping, debounce, and throttle primitives.
|
|
102
|
-
- `base64`: strict UTF-8 Base64/Base64URL byte and text
|
|
128
|
+
- `base64`: strict UTF-8 Base64/Base64URL byte functions and chainable text results plus the historical Latin-1 and dictionary-obfuscation functions.
|
|
103
129
|
- `color`: Hex parsing/formatting/mixing, explicit black/white mixing, luminance, and contrast helpers.
|
|
104
130
|
- `crypto`: random bytes, digests, HMAC, PBKDF2, HKDF, AES, RSA-OAEP/PSS, ECDSA, and ECDH.
|
|
105
131
|
- `date`: date validation and arithmetic, day ranges, relative formatting, and the seven historical date helpers as named functions.
|
|
@@ -121,6 +147,8 @@ Query and object helpers reject prototype-polluting keys. URL decoders are bound
|
|
|
121
147
|
|
|
122
148
|
Programming errors, invalid inputs, unsupported platform capabilities, and malformed protected data throw native errors unless a function explicitly documents a nullable result.
|
|
123
149
|
|
|
150
|
+
Since Fast.Utils 2.1.1, built-in validation and runtime failure messages are Chinese. Consumers must branch on native error types instead of matching message text.
|
|
151
|
+
|
|
124
152
|
`randomInt`, `randomString`, `generateUuidV4`, and `GenerateRandomBytes` all prefer Web Crypto and fall back to `Math.random()` when unavailable.
|
|
125
153
|
|
|
126
154
|
Fast.Utils 2.1.0 removes `secureRandomInt` and `secureRandomString`. This is a breaking change; consumers must migrate to `randomInt` and `randomString`, respectively.
|
package/docs/API.zh-CN.md
CHANGED
|
@@ -21,10 +21,15 @@ import { Local, Session } from "@fast-china/utils";
|
|
|
21
21
|
|
|
22
22
|
Local.set("profile", { name: "Ada" }, { ttlMs: 3_600_000 });
|
|
23
23
|
Session.set("draft", { step: 2 });
|
|
24
|
+
|
|
25
|
+
Local.set("private-profile", { name: "Ada" }, { crypto: true });
|
|
26
|
+
Local.get<{ name: string }>("private-profile", { crypto: true });
|
|
24
27
|
```
|
|
25
28
|
|
|
26
29
|
`Local` 和 `Session` 提供 `get`、`set`、`has`、`remove`、`removeByPrefix`、`keys`、`pruneExpired` 和仅清理当前命名空间的 `clear`。键缺失或过期时返回 `undefined`。TTL 非法、Prefix 为空、存储包络损坏、平台 Storage 不可用或重复配置发生冲突时抛出错误;浏览器配额与隐私策略错误直接向上传播。自定义选项必须在首次 Storage 操作前配置。
|
|
27
30
|
|
|
31
|
+
`get<Value = string>()` 在未传泛型时的静态返回类型为 `string | undefined`,可以直接读取字符串条目。Codec 在运行时仍通过 JSON 反序列化恢复原值,因此对象、数组或其他非字符串值不会被转换成字符串;需要准确类型提示时显式传入对应泛型。
|
|
32
|
+
|
|
28
33
|
uni-app 中,首次 Storage 操作或显式调用 `configureStorage` 会自动检测全局 `uni` 并使用其同步 Storage API。uni-app 没有独立 Session 后端,因此该模式调用 `Session` 会明确抛错。
|
|
29
34
|
|
|
30
35
|
```ts
|
|
@@ -33,7 +38,11 @@ import { Local } from "@fast-china/utils";
|
|
|
33
38
|
Local.set("token", "value");
|
|
34
39
|
```
|
|
35
40
|
|
|
36
|
-
`configureStorage({ prefix: "admin:", crypto: true })` 恢复了旧版全局前缀与 Base64 混淆选项。`
|
|
41
|
+
`configureStorage({ prefix: "admin:", crypto: true })` 恢复了旧版全局前缀与 Base64 混淆选项。`Local` 与 `Session` 的 `set/get` 也接受单次 `{ crypto: boolean }`:`true` 使用 Base64 Codec,`false` 使用 JSON Codec,省略时沿用全局 Codec。单次设置不会修改全局配置;当前 v3 包络不记录 Codec,读写同一条目时必须传入一致选项,错误配置会明确抛出解码错误。
|
|
42
|
+
|
|
43
|
+
`crypto: true` 和 `base64StorageCodec` 都只是可逆编码,不是加密,不能保护敏感数据。可以使用自定义 `codec` 替代全局 `crypto`。
|
|
44
|
+
|
|
45
|
+
`decodeBase64`、`decodeBase64Url`、`decodeLatin1Base64` 与 `decodeSecureBase64` 返回原始字符串类型 `DecodedText`,可以直接赋值给 `string` 或参与严格比较;显式调用 `.parseJson<T = any>()` 才返回 JSON 值,库不会根据文本内容自动推断 JSON。首次文本解码会按需安装不可枚举的 `String.prototype.parseJson`;若同名属性已被其他实现占用则抛出 `TypeError`,不会覆盖。泛型只描述期望类型,不执行运行时结构校验。
|
|
37
46
|
|
|
38
47
|
`encodeSecureBase64` 与 `decodeSecureBase64` 保留旧字典兼容载荷。随机前缀优先使用 Web Crypto,能力缺失时回退到 `Math.random()`;它不承担安全用途。给定相同的默认 6 字符前缀时,有效旧载荷保持逐字符兼容;旧字典在 Base64 长度 101–124 时会引用越界,当前实现使用单字符回退,旧删除字典流程仍可解码。旧自定义长度参数始终生成 6 个随机字符,当前 API 已按 `prefixLength` 正确生成。自定义 `prefixLength` 必须在编码和解码时保持一致;传入 `0` 会同时关闭随机前缀与字典插入。该格式仍是可逆编码,不等同于加密。
|
|
39
48
|
|
|
@@ -55,16 +64,24 @@ installationIdentity.clear();
|
|
|
55
64
|
|
|
56
65
|
## Logger
|
|
57
66
|
|
|
58
|
-
Logger
|
|
67
|
+
Logger 作用域属于每条日志,不保存在 Logger 或 Child 实例中。默认 `logger` 无需创建即可使用,最低输出级别为 `debug`;uni-app App-Plus
|
|
68
|
+
需要拆分对象输出时,在应用入口配置一次:
|
|
59
69
|
|
|
60
70
|
```ts
|
|
61
|
-
import { logger } from "@fast-china/utils";
|
|
71
|
+
import { configureLogger, logger } from "@fast-china/utils";
|
|
62
72
|
|
|
63
|
-
|
|
73
|
+
configureLogger({ uniAppPlusSplit: true });
|
|
74
|
+
logger.log("Launch", { code: 200, data: { id: 1 } });
|
|
75
|
+
logger.log("storage", "profile loaded", { userId: 1 });
|
|
64
76
|
logger.error("network", "request failed", error);
|
|
65
77
|
```
|
|
66
78
|
|
|
67
|
-
`
|
|
79
|
+
日志内容可省略,也可以直接传入对象、数组、`Error` 等任意值。普通环境会把非字符串值原样传给 Sink;启用 App-Plus
|
|
80
|
+
拆分输出后,为解决 HBuilderX 无法正确显示对象的问题,标题单独输出,附加值逐条转换为可读文本。
|
|
81
|
+
|
|
82
|
+
`configureLogger` 会替换默认 `logger` 的完整配置,已经保存的 `logger` 引用也会立即使用新配置;无参数调用会恢复默认值。
|
|
83
|
+
`createLogger` 用于创建不受全局配置影响的独立实例。Logger 的 `debug`、`log`、`warn`、`error` 分别调用 Sink 的同名方法,默认 Sink 对应 `console.debug`、`console.log`、`console.warn`、`console.error`。两者均支持最低级别、品牌前缀、Sink 和可选的 App-Plus 拆分输出。
|
|
84
|
+
作用域必须是无外围空白的非空字符串。
|
|
68
85
|
|
|
69
86
|
## 剪贴板
|
|
70
87
|
|
|
@@ -93,13 +110,21 @@ TypeScript Crypto 公共 API 与 .NET `CryptoUtil` 的公开方法及算法名
|
|
|
93
110
|
|
|
94
111
|
`AESEncryptAuthenticated` 的 Base64 v1 载荷、`AESEncryptWithPassword` 的 `FAST-AES-256-GCM-V1` 载荷、PBKDF2 密码哈希以及 PKCS#8/SPKI PEM 密钥均可与 .NET 双向使用。MD5 与 HMAC 输出小写十六进制;SHA-1/256/384/512 输出大写十六进制,与 .NET 保持一致。
|
|
95
112
|
|
|
113
|
+
`AESDecrypt`、`AESDecryptAuthenticated`、`AESDecryptWithPassword` 与 `RSADecryptOAEP` 返回原始字符串类型 `DecodedText`(异步入口返回其 Promise)。返回值可直接作为明文字符串使用,也可通过 `.parseJson<T = any>()` 显式解析 JSON:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const plaintext = await AESDecryptWithPassword(payload, password);
|
|
117
|
+
const raw: string = plaintext;
|
|
118
|
+
const result = plaintext.parseJson<{ id: number }>();
|
|
119
|
+
```
|
|
120
|
+
|
|
96
121
|
密码存储使用 `HashPasswordPBKDF2SHA256` 和 `VerifyPasswordPBKDF2SHA256`;该哈希不可解密。需要同时保证机密性和完整性的文本使用 AES-GCM 入口。HMAC 用于共享密钥认证,SHA-2 用于摘要,HKDF/PBKDF2 用于密钥派生。MD5、SHA-1、AES-CBC 和 AES-ECB 不提供现代密码存储或认证加密保证。
|
|
97
122
|
|
|
98
123
|
## 模块
|
|
99
124
|
|
|
100
125
|
- `array`:分块、压缩、去重、分组、分区、差集、交集和一致性判断。
|
|
101
126
|
- `async`:支持取消的 Sleep、超时、重试、受限并发映射、防抖和节流。
|
|
102
|
-
- `base64`:严格 UTF-8 Base64/Base64URL
|
|
127
|
+
- `base64`:严格 UTF-8 Base64/Base64URL 字节与链式文本结果,以及 Latin-1 和 SecureBase64 兼容函数。
|
|
103
128
|
- `color`:颜色解析、格式化、混合、明暗、亮度和对比度。
|
|
104
129
|
- `crypto`:随机字节、摘要、HMAC、PBKDF2、HKDF、AES、RSA-OAEP/PSS、ECDSA 和 ECDH。
|
|
105
130
|
- `date`:日期校验、加减、日范围、相对时间,以及七个历史日期功能的具名函数。
|
|
@@ -121,6 +146,8 @@ Query 与 Object API 拒绝原型污染键,URL 解码有最大深度,Storage
|
|
|
121
146
|
|
|
122
147
|
除明确说明返回空值的函数外,编程错误、非法输入、平台能力缺失和受保护数据损坏均抛出原生错误。
|
|
123
148
|
|
|
149
|
+
自 Fast.Utils 2.1.1 起,内置校验与运行时失败消息统一使用中文;调用方应依据原生错误类型分支,不应匹配消息文本。
|
|
150
|
+
|
|
124
151
|
`randomInt`、`randomString`、`generateUuidV4` 与 `GenerateRandomBytes` 默认都优先使用 Web Crypto,能力缺失时回退到 `Math.random()`。
|
|
125
152
|
|
|
126
153
|
Fast.Utils 2.1.0 已删除 `secureRandomInt` 与 `secureRandomString`,这是破坏性修改;调用方应分别改用 `randomInt` 与 `randomString`。
|
package/docs/RUNTIME_CONTRACT.md
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
- Framework boundary: Vue remains external to the package-manager build and is a required peer in `^3.3.0`.
|
|
8
8
|
- uni-app boundary: the first Storage operation, or an earlier `configureStorage({ prefix })` call, detects global `uni` and uses its synchronous Storage API.
|
|
9
9
|
- Browser storage: applications import `Local` and `Session` directly; `configureStorage()` is needed only to override defaults before the first operation.
|
|
10
|
-
-
|
|
10
|
+
- Storage operation overrides: `set/get({ crypto })` select JSON or Base64 for one operation without mutating global configuration. The v3 envelope does not identify its codec, so callers must use matching options for the same entry.
|
|
11
|
+
- Storage read typing: `get<Value = string>()` defaults to `string | undefined` when no generic is supplied; codecs still restore the original runtime JSON value.
|
|
12
|
+
- Stateful browser defaults: Storage, Identity, and default Logger configuration are page-global by design. Storage and Identity reject conflicting reconfiguration; `configureLogger` replaces the default Logger configuration while preserving the exported facade reference.
|
|
11
13
|
- Randomness: every random generation entry prefers Web Crypto and falls back to `Math.random()` when unavailable.
|
|
12
14
|
- Publishing: the repository root is the only package, `dist/` is the only build output, and `package.json#exports` is the complete public path whitelist.
|
|
13
15
|
|
|
@@ -17,7 +19,8 @@ Importing a module does not itself read `window`, browser Storage, or `uni`, so
|
|
|
17
19
|
|
|
18
20
|
- Stateless Array, Date, String, Number, Object, Base64, Color, DOM, Env, Async, and Crypto capabilities use named exports.
|
|
19
21
|
- The public API uses named functions instead of mutable aggregate utility objects.
|
|
20
|
-
-
|
|
22
|
+
- Base64 and Crypto text decoders return primitive strings typed as `DecodedText`. They can be used directly as strings; JSON parsing occurs only through an explicit `.parseJson<T = any>()` call. The first text decode lazily installs a non-enumerable `String.prototype.parseJson` and rejects a foreign same-name property instead of overwriting it. Storage codecs continue to parse JSON automatically.
|
|
23
|
+
- Stateful browser capabilities use cohesive package-owned objects: `Local`, `Session`, `installationIdentity`, and Logger instances. Logger exposes the matching `debug`, `log`, `warn`, and `error` levels, defaults to the `debug` minimum, and receives scope on each method rather than storing it in child instances. `createLogger` returns isolated instances, while `configureLogger` only changes the default `logger` facade.
|
|
21
24
|
- Internal adapters and client factories are implementation details and are not public export paths.
|
|
22
25
|
- Removing a named function, changing Storage/ciphertext formats, raising the browser syntax target, or changing the Vue peer range requires an explicit major-version Breaking Change.
|
|
23
26
|
|
|
@@ -28,10 +31,12 @@ Importing a module does not itself read `window`, browser Storage, or `uni`, so
|
|
|
28
31
|
- Vue 边界:Vue 不会打进包管理器使用的构建产物,是 `^3.3.0` 的必需 Peer。
|
|
29
32
|
- uni-app:首次 Storage 操作或更早的 `configureStorage({ prefix })` 调用会检测全局 `uni`,并使用其同步 Storage API。
|
|
30
33
|
- Storage:直接从包导入 `Local` 和 `Session` 即可;只有覆盖默认值时才需在首次操作前调用 `configureStorage()`。
|
|
31
|
-
-
|
|
34
|
+
- Storage 单次覆盖:`set/get({ crypto })` 只为当前操作选择 JSON 或 Base64,不修改全局配置。v3 包络不记录 Codec,调用方必须对同一条目使用匹配的读写选项。
|
|
35
|
+
- Storage 读取类型:`get<Value = string>()` 未传泛型时默认推断为 `string | undefined`,Codec 在运行时仍恢复原始 JSON 值。
|
|
36
|
+
- 状态:Storage、Identity 与默认 Logger 配置按浏览器页面全局共享。Storage 和 Identity 的冲突配置明确抛错;`configureLogger` 替换默认 Logger 的配置,同时保持导出的门面引用稳定。
|
|
32
37
|
- 随机数:所有随机生成入口都优先使用 Web Crypto,缺失时回退到 `Math.random()`。
|
|
33
38
|
- 发布:根目录是唯一 npm 包,`dist/` 是唯一构建输出,`exports` 是完整公共路径白名单。
|
|
34
39
|
|
|
35
40
|
模块导入本身不读取 `window`、浏览器 Storage 或 `uni`,不具备对应平台能力时只在调用相关 API 时明确失败。
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
无状态能力统一使用具名导出。Base64 与 Crypto 文本解码入口返回原始字符串类型 `DecodedText`,可以直接作为字符串使用;JSON 只在显式调用 `.parseJson<T = any>()` 时解析。首次文本解码会按需安装不可枚举的 `String.prototype.parseJson`,若同名属性已被其他实现占用则拒绝覆盖并抛错。Storage Codec 继续自动解析 JSON。有状态浏览器能力使用 `Local`、`Session`、`installationIdentity` 和 Logger 实例;Logger 提供与 Sink 同名的 `debug`、`log`、`warn`、`error` 级别,默认最低级别为 `debug`,作用域随每次调用传入,不创建 Child Logger。`createLogger` 返回隔离实例,`configureLogger` 只修改默认 `logger` 门面。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fast-china/utils",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Typed utilities for modern browsers, WebViews, Vue 3, and uni-app applications.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -72,20 +72,20 @@
|
|
|
72
72
|
"@eslint/markdown": "^8.0.3",
|
|
73
73
|
"@types/crypto-js": "^4.2.2",
|
|
74
74
|
"@types/node": "^24.13.3",
|
|
75
|
-
"eslint": "^10.
|
|
75
|
+
"eslint": "^10.9.1",
|
|
76
76
|
"eslint-config-flat-gitignore": "^2.3.0",
|
|
77
77
|
"eslint-config-prettier": "^10.1.8",
|
|
78
78
|
"eslint-plugin-import-x": "^4.17.1",
|
|
79
|
-
"eslint-plugin-jsonc": "^3.
|
|
80
|
-
"eslint-plugin-regexp": "^3.
|
|
81
|
-
"globals": "^17.
|
|
79
|
+
"eslint-plugin-jsonc": "^3.4.2",
|
|
80
|
+
"eslint-plugin-regexp": "^3.2.0",
|
|
81
|
+
"globals": "^17.11.0",
|
|
82
82
|
"prettier": "^3.9.6",
|
|
83
|
-
"publint": "^0.3.
|
|
83
|
+
"publint": "^0.3.24",
|
|
84
84
|
"tsdown": "^0.22.14",
|
|
85
|
-
"tsx": "^4.23.
|
|
85
|
+
"tsx": "^4.23.12",
|
|
86
86
|
"typescript": "^6.0.3",
|
|
87
|
-
"typescript-eslint": "^8.
|
|
88
|
-
"vue": "^3.5.
|
|
87
|
+
"typescript-eslint": "^8.68.0",
|
|
88
|
+
"vue": "^3.5.41"
|
|
89
89
|
},
|
|
90
90
|
"engines": {
|
|
91
91
|
"node": "^22.18.0 || ^24.18.0",
|