@fast-china/utils 1.0.38 → 2.0.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 +48 -0
- package/CONTRIBUTING.md +87 -0
- package/README.md +109 -50
- package/README.zh.md +109 -50
- package/SECURITY.md +46 -0
- package/dist/array/index.d.mts +87 -0
- package/dist/array/index.mjs +161 -0
- package/dist/array/index.mjs.map +1 -0
- package/dist/async/index.d.mts +146 -0
- package/dist/async/index.mjs +336 -0
- package/dist/async/index.mjs.map +1 -0
- package/dist/base64/index.d.mts +105 -0
- package/dist/base64/index.mjs +427 -0
- package/dist/base64/index.mjs.map +1 -0
- package/dist/color/index.d.mts +90 -0
- package/dist/color/index.mjs +178 -0
- package/dist/color/index.mjs.map +1 -0
- package/dist/crypto/index.d.mts +326 -0
- package/dist/crypto/index.mjs +843 -0
- package/dist/crypto/index.mjs.map +1 -0
- package/dist/date/index.d.mts +191 -0
- package/dist/date/index.mjs +383 -0
- package/dist/date/index.mjs.map +1 -0
- package/dist/dom/style.d.mts +30 -0
- package/dist/dom/style.mjs +74 -0
- package/dist/dom/style.mjs.map +1 -0
- package/dist/env/index.d.mts +63 -0
- package/dist/env/index.mjs +97 -0
- package/dist/env/index.mjs.map +1 -0
- package/dist/identity/index.d.mts +78 -0
- package/dist/identity/index.mjs +87 -0
- package/dist/identity/index.mjs.map +1 -0
- package/dist/index.d.mts +24 -0
- package/dist/index.global.min.js +3 -2
- package/dist/index.global.min.js.map +1 -1
- package/dist/index.mjs +23 -0
- package/dist/internal/text.mjs +36 -0
- package/dist/internal/text.mjs.map +1 -0
- package/dist/logger/index.d.mts +87 -0
- package/dist/logger/index.mjs +124 -0
- package/dist/logger/index.mjs.map +1 -0
- package/dist/number/index.d.mts +89 -0
- package/dist/number/index.mjs +215 -0
- package/dist/number/index.mjs.map +1 -0
- package/dist/object/index.d.mts +76 -0
- package/dist/object/index.mjs +134 -0
- package/dist/object/index.mjs.map +1 -0
- package/dist/storage/index.d.mts +107 -0
- package/dist/storage/index.mjs +327 -0
- package/dist/storage/index.mjs.map +1 -0
- package/dist/string/index.d.mts +130 -0
- package/dist/string/index.mjs +275 -0
- package/dist/string/index.mjs.map +1 -0
- package/dist/vue/emits.d.mts +24 -0
- package/dist/vue/emits.mjs +47 -0
- package/dist/vue/emits.mjs.map +1 -0
- package/dist/vue/expose.d.mts +12 -0
- package/dist/vue/expose.mjs +16 -0
- package/dist/vue/expose.mjs.map +1 -0
- package/dist/vue/func.d.mts +14 -0
- package/dist/vue/func.mjs +16 -0
- package/dist/vue/func.mjs.map +1 -0
- package/dist/vue/index.d.mts +9 -0
- package/dist/vue/install.d.mts +51 -0
- package/dist/vue/install.mjs +123 -0
- package/dist/vue/install.mjs.map +1 -0
- package/dist/vue/props.d.mts +23 -0
- package/dist/vue/props.mjs +41 -0
- package/dist/vue/props.mjs.map +1 -0
- package/dist/vue/render.d.mts +12 -0
- package/dist/vue/render.mjs +17 -0
- package/dist/vue/render.mjs.map +1 -0
- package/dist/vue/slots.d.mts +19 -0
- package/dist/vue/slots.mjs +13 -0
- package/dist/vue/slots.mjs.map +1 -0
- package/dist/vue/with.d.mts +12 -0
- package/dist/vue/with.mjs +15 -0
- package/dist/vue/with.mjs.map +1 -0
- package/docs/API.md +112 -0
- package/docs/API.zh-CN.md +112 -0
- package/docs/DEVELOPMENT_RELEASE.zh-CN.md +65 -0
- package/docs/RUNTIME_CONTRACT.md +37 -0
- package/package.json +63 -73
- package/dist/index.global.js +0 -14500
- package/dist/index.global.js.map +0 -1
- package/es/array/index.d.ts +0 -19
- package/es/array/index.mjs +0 -2
- package/es/array/index.mjs.map +0 -1
- package/es/base64/index.d.ts +0 -21
- package/es/base64/index.mjs +0 -2
- package/es/base64/index.mjs.map +0 -1
- package/es/click/index.d.ts +0 -33
- package/es/click/index.mjs +0 -2
- package/es/click/index.mjs.map +0 -1
- package/es/color/index.d.ts +0 -33
- package/es/color/index.mjs +0 -2
- package/es/color/index.mjs.map +0 -1
- package/es/console/index.d.ts +0 -31
- package/es/console/index.mjs +0 -2
- package/es/console/index.mjs.map +0 -1
- package/es/crypto/index.d.ts +0 -46
- package/es/crypto/index.mjs +0 -2
- package/es/crypto/index.mjs.map +0 -1
- package/es/date/index.d.ts +0 -44
- package/es/date/index.mjs +0 -2
- package/es/date/index.mjs.map +0 -1
- package/es/dom/index.d.ts +0 -1
- package/es/dom/index.mjs +0 -2
- package/es/dom/index.mjs.map +0 -1
- package/es/dom/style.d.ts +0 -12
- package/es/dom/style.mjs +0 -2
- package/es/dom/style.mjs.map +0 -1
- package/es/env/index.d.ts +0 -25
- package/es/env/index.mjs +0 -2
- package/es/env/index.mjs.map +0 -1
- package/es/error/index.d.ts +0 -3
- package/es/error/index.mjs +0 -2
- package/es/error/index.mjs.map +0 -1
- package/es/identity/index.d.ts +0 -14
- package/es/identity/index.mjs +0 -2
- package/es/identity/index.mjs.map +0 -1
- package/es/index.d.ts +0 -15
- package/es/index.es.d.ts +0 -2
- package/es/index.mjs +0 -2
- package/es/index.mjs.map +0 -1
- package/es/object/index.d.ts +0 -13
- package/es/object/index.mjs +0 -2
- package/es/object/index.mjs.map +0 -1
- package/es/storage/index.d.ts +0 -96
- package/es/storage/index.mjs +0 -2
- package/es/storage/index.mjs.map +0 -1
- package/es/string/index.d.ts +0 -114
- package/es/string/index.mjs +0 -2
- package/es/string/index.mjs.map +0 -1
- package/es/vue/emits.d.ts +0 -8
- package/es/vue/emits.mjs +0 -2
- package/es/vue/emits.mjs.map +0 -1
- package/es/vue/expose.d.ts +0 -4
- package/es/vue/expose.mjs +0 -2
- package/es/vue/expose.mjs.map +0 -1
- package/es/vue/func.d.ts +0 -6
- package/es/vue/func.mjs +0 -2
- package/es/vue/func.mjs.map +0 -1
- package/es/vue/index.d.ts +0 -8
- package/es/vue/index.mjs +0 -2
- package/es/vue/index.mjs.map +0 -1
- package/es/vue/install.d.ts +0 -5
- package/es/vue/install.mjs +0 -2
- package/es/vue/install.mjs.map +0 -1
- package/es/vue/props.d.ts +0 -9
- package/es/vue/props.mjs +0 -2
- package/es/vue/props.mjs.map +0 -1
- package/es/vue/slots.d.ts +0 -11
- package/es/vue/slots.mjs +0 -2
- package/es/vue/slots.mjs.map +0 -1
- package/es/vue/useRender.d.ts +0 -6
- package/es/vue/useRender.mjs +0 -2
- package/es/vue/useRender.mjs.map +0 -1
- package/es/vue/with.d.ts +0 -5
- package/es/vue/with.mjs +0 -2
- package/es/vue/with.mjs.map +0 -1
- package/lib/array/index.d.ts +0 -19
- package/lib/array/index.js +0 -2
- package/lib/array/index.js.map +0 -1
- package/lib/base64/index.d.ts +0 -21
- package/lib/base64/index.js +0 -2
- package/lib/base64/index.js.map +0 -1
- package/lib/click/index.d.ts +0 -33
- package/lib/click/index.js +0 -2
- package/lib/click/index.js.map +0 -1
- package/lib/color/index.d.ts +0 -33
- package/lib/color/index.js +0 -2
- package/lib/color/index.js.map +0 -1
- package/lib/console/index.d.ts +0 -31
- package/lib/console/index.js +0 -2
- package/lib/console/index.js.map +0 -1
- package/lib/crypto/index.d.ts +0 -46
- package/lib/crypto/index.js +0 -2
- package/lib/crypto/index.js.map +0 -1
- package/lib/date/index.d.ts +0 -44
- package/lib/date/index.js +0 -2
- package/lib/date/index.js.map +0 -1
- package/lib/dom/index.d.ts +0 -1
- package/lib/dom/index.js +0 -2
- package/lib/dom/index.js.map +0 -1
- package/lib/dom/style.d.ts +0 -12
- package/lib/dom/style.js +0 -2
- package/lib/dom/style.js.map +0 -1
- package/lib/env/index.d.ts +0 -25
- package/lib/env/index.js +0 -2
- package/lib/env/index.js.map +0 -1
- package/lib/error/index.d.ts +0 -3
- package/lib/error/index.js +0 -2
- package/lib/error/index.js.map +0 -1
- package/lib/identity/index.d.ts +0 -14
- package/lib/identity/index.js +0 -2
- package/lib/identity/index.js.map +0 -1
- package/lib/index.d.ts +0 -15
- package/lib/index.es.d.ts +0 -2
- package/lib/index.js +0 -2
- package/lib/index.js.map +0 -1
- package/lib/object/index.d.ts +0 -13
- package/lib/object/index.js +0 -2
- package/lib/object/index.js.map +0 -1
- package/lib/storage/index.d.ts +0 -96
- package/lib/storage/index.js +0 -2
- package/lib/storage/index.js.map +0 -1
- package/lib/string/index.d.ts +0 -114
- package/lib/string/index.js +0 -2
- package/lib/string/index.js.map +0 -1
- package/lib/vue/emits.d.ts +0 -8
- package/lib/vue/emits.js +0 -2
- package/lib/vue/emits.js.map +0 -1
- package/lib/vue/expose.d.ts +0 -4
- package/lib/vue/expose.js +0 -2
- package/lib/vue/expose.js.map +0 -1
- package/lib/vue/func.d.ts +0 -6
- package/lib/vue/func.js +0 -2
- package/lib/vue/func.js.map +0 -1
- package/lib/vue/index.d.ts +0 -8
- package/lib/vue/index.js +0 -2
- package/lib/vue/index.js.map +0 -1
- package/lib/vue/install.d.ts +0 -5
- package/lib/vue/install.js +0 -2
- package/lib/vue/install.js.map +0 -1
- package/lib/vue/props.d.ts +0 -9
- package/lib/vue/props.js +0 -2
- package/lib/vue/props.js.map +0 -1
- package/lib/vue/slots.d.ts +0 -11
- package/lib/vue/slots.js +0 -2
- package/lib/vue/slots.js.map +0 -1
- package/lib/vue/useRender.d.ts +0 -6
- package/lib/vue/useRender.js +0 -2
- package/lib/vue/useRender.js.map +0 -1
- package/lib/vue/with.d.ts +0 -5
- package/lib/vue/with.js +0 -2
- package/lib/vue/with.js.map +0 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//#region src/object/index.d.ts
|
|
2
|
+
/** URL 查询参数支持的单值类型。 */
|
|
3
|
+
type QueryPrimitive = bigint | boolean | number | string | null | undefined;
|
|
4
|
+
/** URL 查询参数值;数组使用重复键表示。 */
|
|
5
|
+
type QueryValue = QueryPrimitive | readonly QueryPrimitive[];
|
|
6
|
+
/** {@link toQueryString} 的序列化选项。 */
|
|
7
|
+
interface QueryStringOptions {
|
|
8
|
+
/** 返回非空结果时是否添加 `?`;默认 `false`。 */
|
|
9
|
+
prefixQuestionMark?: boolean;
|
|
10
|
+
/** 是否按键的 UTF-16 码元顺序稳定排序;默认保留对象枚举顺序。 */
|
|
11
|
+
sort?: boolean;
|
|
12
|
+
/** 空格编码方式;默认遵循表单编码并输出 `+`。 */
|
|
13
|
+
space?: "percent" | "plus";
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 判断值是否是普通对象。
|
|
17
|
+
*
|
|
18
|
+
* @param value - 任意待检查值。
|
|
19
|
+
* @returns 原型为 `Object.prototype` 或 `null` 时返回 `true`。
|
|
20
|
+
*/
|
|
21
|
+
declare function isPlainObject(value: unknown): value is Record<PropertyKey, unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* 安全判断对象是否拥有自己的属性。
|
|
24
|
+
*
|
|
25
|
+
* @remarks 不调用可能被对象覆盖的 `hasOwnProperty`。
|
|
26
|
+
* @param value - 待检查对象。
|
|
27
|
+
* @param key - 字符串、数字或 Symbol 属性键。
|
|
28
|
+
* @returns 属性为对象自有属性时返回 `true`,并收窄键类型。
|
|
29
|
+
*/
|
|
30
|
+
declare function hasOwn<ObjectType extends object, Key extends PropertyKey>(value: ObjectType, key: Key): key is Key & keyof ObjectType;
|
|
31
|
+
/**
|
|
32
|
+
* 从对象中选择指定自有可枚举属性。
|
|
33
|
+
*
|
|
34
|
+
* @param source - 不会被修改的源对象。
|
|
35
|
+
* @param keys - 需要保留的键;不存在的键被忽略。
|
|
36
|
+
* @returns 新对象,保持 `keys` 的遍历顺序。
|
|
37
|
+
*/
|
|
38
|
+
declare function pick<Source extends object, Keys extends readonly (keyof Source)[]>(source: Source, keys: Keys): Pick<Source, Keys[number]>;
|
|
39
|
+
/**
|
|
40
|
+
* 浅复制对象并删除指定属性。
|
|
41
|
+
*
|
|
42
|
+
* @param source - 不会被修改的源对象。
|
|
43
|
+
* @param keys - 需要排除的键。
|
|
44
|
+
* @returns 包含其余自有可枚举字符串与 Symbol 属性的新对象。
|
|
45
|
+
*/
|
|
46
|
+
declare function omit<Source extends object, Keys extends readonly (keyof Source)[]>(source: Source, keys: Keys): Omit<Source, Keys[number]>;
|
|
47
|
+
/**
|
|
48
|
+
* 映射对象的自有可枚举属性值。
|
|
49
|
+
*
|
|
50
|
+
* @param source - 不会被修改的源对象。
|
|
51
|
+
* @param mapper - 接收值、键和源对象的映射函数。
|
|
52
|
+
* @returns 保留原键的新对象。
|
|
53
|
+
*/
|
|
54
|
+
declare function mapValues<Source extends object, Result>(source: Source, mapper: (value: Source[keyof Source], key: keyof Source, source: Source) => Result): { [Key in keyof Source]: Result; };
|
|
55
|
+
/**
|
|
56
|
+
* 对自有可枚举属性执行 SameValue 浅比较。
|
|
57
|
+
*
|
|
58
|
+
* @remarks 嵌套对象只比较引用;`NaN` 相等,`0` 与 `-0` 不相等。
|
|
59
|
+
* @param left - 第一对象。
|
|
60
|
+
* @param right - 第二对象。
|
|
61
|
+
* @returns 自有可枚举键集合与对应值均满足 SameValue 时返回 `true`。
|
|
62
|
+
*/
|
|
63
|
+
declare function shallowEqual(left: object, right: object): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* 将对象序列化为标准 URL 查询字符串。
|
|
66
|
+
*
|
|
67
|
+
* @remarks `null` 与 `undefined` 被跳过;数组使用重复键;返回值不会修改输入。
|
|
68
|
+
* @param value - 查询参数对象。
|
|
69
|
+
* @param options - 排序、空格和问号前缀选项。
|
|
70
|
+
* @returns URL 编码后的查询字符串;没有参数时始终返回空字符串。
|
|
71
|
+
* @throws `RangeError` 当参数包含 `NaN` 或无穷数字。
|
|
72
|
+
*/
|
|
73
|
+
declare function toQueryString(value: Readonly<Record<string, QueryValue>>, options?: QueryStringOptions): string;
|
|
74
|
+
//#endregion
|
|
75
|
+
export { QueryPrimitive, QueryStringOptions, QueryValue, hasOwn, isPlainObject, mapValues, omit, pick, shallowEqual, toQueryString };
|
|
76
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
//#region src/object/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* 判断 Query Value 是否为重复参数数组。
|
|
4
|
+
*
|
|
5
|
+
* @param value - 单值或数组形式的 Query Value。
|
|
6
|
+
* @returns 是只读原始值数组时返回 `true`。
|
|
7
|
+
*/
|
|
8
|
+
const isQueryPrimitiveArray = (value) => Array.isArray(value);
|
|
9
|
+
/**
|
|
10
|
+
* 安全写入结果对象的自有可枚举属性。
|
|
11
|
+
*
|
|
12
|
+
* @remarks 使用 `defineProperty` 避免 `__proto__` 触发 Setter,并显式拒绝三个原型污染键。
|
|
13
|
+
* @param target - 要写入的结果对象。
|
|
14
|
+
* @param key - 自有属性键。
|
|
15
|
+
* @param value - 属性值。
|
|
16
|
+
* @throws `TypeError` 当键为 `__proto__`、`prototype` 或 `constructor`。
|
|
17
|
+
*/
|
|
18
|
+
const defineEnumerableProperty = (target, key, value) => {
|
|
19
|
+
Object.defineProperty(target, key, {
|
|
20
|
+
configurable: true,
|
|
21
|
+
enumerable: true,
|
|
22
|
+
value,
|
|
23
|
+
writable: true
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* 判断值是否是普通对象。
|
|
28
|
+
*
|
|
29
|
+
* @param value - 任意待检查值。
|
|
30
|
+
* @returns 原型为 `Object.prototype` 或 `null` 时返回 `true`。
|
|
31
|
+
*/
|
|
32
|
+
function isPlainObject(value) {
|
|
33
|
+
if (typeof value !== "object" || value === null) return false;
|
|
34
|
+
const prototype = Object.getPrototypeOf(value);
|
|
35
|
+
return prototype === null || prototype === Object.prototype;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 安全判断对象是否拥有自己的属性。
|
|
39
|
+
*
|
|
40
|
+
* @remarks 不调用可能被对象覆盖的 `hasOwnProperty`。
|
|
41
|
+
* @param value - 待检查对象。
|
|
42
|
+
* @param key - 字符串、数字或 Symbol 属性键。
|
|
43
|
+
* @returns 属性为对象自有属性时返回 `true`,并收窄键类型。
|
|
44
|
+
*/
|
|
45
|
+
function hasOwn(value, key) {
|
|
46
|
+
return Object.hasOwn(value, key);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 从对象中选择指定自有可枚举属性。
|
|
50
|
+
*
|
|
51
|
+
* @param source - 不会被修改的源对象。
|
|
52
|
+
* @param keys - 需要保留的键;不存在的键被忽略。
|
|
53
|
+
* @returns 新对象,保持 `keys` 的遍历顺序。
|
|
54
|
+
*/
|
|
55
|
+
function pick(source, keys) {
|
|
56
|
+
const result = {};
|
|
57
|
+
for (const key of keys) if (Object.prototype.propertyIsEnumerable.call(source, key)) defineEnumerableProperty(result, key, source[key]);
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 浅复制对象并删除指定属性。
|
|
62
|
+
*
|
|
63
|
+
* @param source - 不会被修改的源对象。
|
|
64
|
+
* @param keys - 需要排除的键。
|
|
65
|
+
* @returns 包含其余自有可枚举字符串与 Symbol 属性的新对象。
|
|
66
|
+
*/
|
|
67
|
+
function omit(source, keys) {
|
|
68
|
+
const result = { ...source };
|
|
69
|
+
for (const key of keys) Reflect.deleteProperty(result, key);
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 映射对象的自有可枚举属性值。
|
|
74
|
+
*
|
|
75
|
+
* @param source - 不会被修改的源对象。
|
|
76
|
+
* @param mapper - 接收值、键和源对象的映射函数。
|
|
77
|
+
* @returns 保留原键的新对象。
|
|
78
|
+
*/
|
|
79
|
+
function mapValues(source, mapper) {
|
|
80
|
+
const result = {};
|
|
81
|
+
for (const key of Reflect.ownKeys(source)) if (Object.prototype.propertyIsEnumerable.call(source, key)) defineEnumerableProperty(result, key, mapper(source[key], key, source));
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 对自有可枚举属性执行 SameValue 浅比较。
|
|
86
|
+
*
|
|
87
|
+
* @remarks 嵌套对象只比较引用;`NaN` 相等,`0` 与 `-0` 不相等。
|
|
88
|
+
* @param left - 第一对象。
|
|
89
|
+
* @param right - 第二对象。
|
|
90
|
+
* @returns 自有可枚举键集合与对应值均满足 SameValue 时返回 `true`。
|
|
91
|
+
*/
|
|
92
|
+
function shallowEqual(left, right) {
|
|
93
|
+
if (Object.is(left, right)) return true;
|
|
94
|
+
const leftKeys = Reflect.ownKeys(left).filter((key) => Object.prototype.propertyIsEnumerable.call(left, key));
|
|
95
|
+
const rightKeys = Reflect.ownKeys(right).filter((key) => Object.prototype.propertyIsEnumerable.call(right, key));
|
|
96
|
+
if (leftKeys.length !== rightKeys.length) return false;
|
|
97
|
+
return leftKeys.every((key) => Object.hasOwn(right, key) && Object.is(Reflect.get(left, key), Reflect.get(right, key)));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 把 Query 原始值规范化为文本。
|
|
101
|
+
*
|
|
102
|
+
* @param value - 已排除空值的字符串、数字、布尔值或 BigInt。
|
|
103
|
+
* @returns 与 URLSearchParams 兼容的文本值。
|
|
104
|
+
* @throws `RangeError` 当数字不是有限值。
|
|
105
|
+
*/
|
|
106
|
+
const serializeQueryValue = (value) => {
|
|
107
|
+
if (typeof value === "number" && !Number.isFinite(value)) throw new RangeError("Query parameter numbers must be finite.");
|
|
108
|
+
return String(value);
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* 将对象序列化为标准 URL 查询字符串。
|
|
112
|
+
*
|
|
113
|
+
* @remarks `null` 与 `undefined` 被跳过;数组使用重复键;返回值不会修改输入。
|
|
114
|
+
* @param value - 查询参数对象。
|
|
115
|
+
* @param options - 排序、空格和问号前缀选项。
|
|
116
|
+
* @returns URL 编码后的查询字符串;没有参数时始终返回空字符串。
|
|
117
|
+
* @throws `RangeError` 当参数包含 `NaN` 或无穷数字。
|
|
118
|
+
*/
|
|
119
|
+
function toQueryString(value, options = {}) {
|
|
120
|
+
const entries = Object.entries(value);
|
|
121
|
+
if (options.sort) entries.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
122
|
+
const parameters = new URLSearchParams();
|
|
123
|
+
for (const [key, rawValue] of entries) {
|
|
124
|
+
const values = isQueryPrimitiveArray(rawValue) ? rawValue : [rawValue];
|
|
125
|
+
for (const item of values) if (item !== null && item !== void 0) parameters.append(key, serializeQueryValue(item));
|
|
126
|
+
}
|
|
127
|
+
let result = parameters.toString();
|
|
128
|
+
if (options.space === "percent") result = result.replace(/\+/gu, "%20");
|
|
129
|
+
return result && options.prefixQuestionMark ? `?${result}` : result;
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
export { hasOwn, isPlainObject, mapValues, omit, pick, shallowEqual, toQueryString };
|
|
133
|
+
|
|
134
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/object/index.ts"],"sourcesContent":["/** URL 查询参数支持的单值类型。 */\nexport type QueryPrimitive = bigint | boolean | number | string | null | undefined;\n\n/** URL 查询参数值;数组使用重复键表示。 */\nexport type QueryValue = QueryPrimitive | readonly QueryPrimitive[];\n\n/**\n * 判断 Query Value 是否为重复参数数组。\n *\n * @param value - 单值或数组形式的 Query Value。\n * @returns 是只读原始值数组时返回 `true`。\n */\nconst isQueryPrimitiveArray = (value: QueryValue): value is readonly QueryPrimitive[] => Array.isArray(value);\n\n/** {@link toQueryString} 的序列化选项。 */\nexport interface QueryStringOptions {\n\t/** 返回非空结果时是否添加 `?`;默认 `false`。 */\n\tprefixQuestionMark?: boolean;\n\t/** 是否按键的 UTF-16 码元顺序稳定排序;默认保留对象枚举顺序。 */\n\tsort?: boolean;\n\t/** 空格编码方式;默认遵循表单编码并输出 `+`。 */\n\tspace?: \"percent\" | \"plus\";\n}\n\n/**\n * 安全写入结果对象的自有可枚举属性。\n *\n * @remarks 使用 `defineProperty` 避免 `__proto__` 触发 Setter,并显式拒绝三个原型污染键。\n * @param target - 要写入的结果对象。\n * @param key - 自有属性键。\n * @param value - 属性值。\n * @throws `TypeError` 当键为 `__proto__`、`prototype` 或 `constructor`。\n */\nconst defineEnumerableProperty = (target: object, key: PropertyKey, value: unknown): void => {\n\tObject.defineProperty(target, key, { configurable: true, enumerable: true, value, writable: true });\n};\n\n/**\n * 判断值是否是普通对象。\n *\n * @param value - 任意待检查值。\n * @returns 原型为 `Object.prototype` 或 `null` 时返回 `true`。\n */\nexport function isPlainObject(value: unknown): value is Record<PropertyKey, unknown> {\n\tif (typeof value !== \"object\" || value === null) return false;\n\tconst prototype = Object.getPrototypeOf(value) as object | null;\n\treturn prototype === null || prototype === Object.prototype;\n}\n\n/**\n * 安全判断对象是否拥有自己的属性。\n *\n * @remarks 不调用可能被对象覆盖的 `hasOwnProperty`。\n * @param value - 待检查对象。\n * @param key - 字符串、数字或 Symbol 属性键。\n * @returns 属性为对象自有属性时返回 `true`,并收窄键类型。\n */\nexport function hasOwn<ObjectType extends object, Key extends PropertyKey>(value: ObjectType, key: Key): key is Key & keyof ObjectType {\n\treturn Object.hasOwn(value, key);\n}\n\n/**\n * 从对象中选择指定自有可枚举属性。\n *\n * @param source - 不会被修改的源对象。\n * @param keys - 需要保留的键;不存在的键被忽略。\n * @returns 新对象,保持 `keys` 的遍历顺序。\n */\nexport function pick<Source extends object, Keys extends readonly (keyof Source)[]>(source: Source, keys: Keys): Pick<Source, Keys[number]> {\n\tconst result = {} as Pick<Source, Keys[number]>;\n\tfor (const key of keys) {\n\t\tif (Object.prototype.propertyIsEnumerable.call(source, key)) defineEnumerableProperty(result, key, source[key]);\n\t}\n\treturn result;\n}\n\n/**\n * 浅复制对象并删除指定属性。\n *\n * @param source - 不会被修改的源对象。\n * @param keys - 需要排除的键。\n * @returns 包含其余自有可枚举字符串与 Symbol 属性的新对象。\n */\nexport function omit<Source extends object, Keys extends readonly (keyof Source)[]>(source: Source, keys: Keys): Omit<Source, Keys[number]> {\n\tconst result = { ...source };\n\tfor (const key of keys) Reflect.deleteProperty(result, key);\n\treturn result;\n}\n\n/**\n * 映射对象的自有可枚举属性值。\n *\n * @param source - 不会被修改的源对象。\n * @param mapper - 接收值、键和源对象的映射函数。\n * @returns 保留原键的新对象。\n */\nexport function mapValues<Source extends object, Result>(\n\tsource: Source,\n\tmapper: (value: Source[keyof Source], key: keyof Source, source: Source) => Result\n): { [Key in keyof Source]: Result } {\n\tconst result = {} as { [Key in keyof Source]: Result };\n\tfor (const key of Reflect.ownKeys(source) as (keyof Source)[]) {\n\t\tif (Object.prototype.propertyIsEnumerable.call(source, key)) defineEnumerableProperty(result, key, mapper(source[key], key, source));\n\t}\n\treturn result;\n}\n\n/**\n * 对自有可枚举属性执行 SameValue 浅比较。\n *\n * @remarks 嵌套对象只比较引用;`NaN` 相等,`0` 与 `-0` 不相等。\n * @param left - 第一对象。\n * @param right - 第二对象。\n * @returns 自有可枚举键集合与对应值均满足 SameValue 时返回 `true`。\n */\nexport function shallowEqual(left: object, right: object): boolean {\n\tif (Object.is(left, right)) return true;\n\tconst leftKeys = Reflect.ownKeys(left).filter((key) => Object.prototype.propertyIsEnumerable.call(left, key));\n\tconst rightKeys = Reflect.ownKeys(right).filter((key) => Object.prototype.propertyIsEnumerable.call(right, key));\n\tif (leftKeys.length !== rightKeys.length) return false;\n\treturn leftKeys.every((key) => Object.hasOwn(right, key) && Object.is(Reflect.get(left, key), Reflect.get(right, key)));\n}\n\n/**\n * 把 Query 原始值规范化为文本。\n *\n * @param value - 已排除空值的字符串、数字、布尔值或 BigInt。\n * @returns 与 URLSearchParams 兼容的文本值。\n * @throws `RangeError` 当数字不是有限值。\n */\nconst serializeQueryValue = (value: Exclude<QueryPrimitive, null | undefined>): string => {\n\tif (typeof value === \"number\" && !Number.isFinite(value)) {\n\t\tthrow new RangeError(\"Query parameter numbers must be finite.\");\n\t}\n\treturn String(value);\n};\n\n/**\n * 将对象序列化为标准 URL 查询字符串。\n *\n * @remarks `null` 与 `undefined` 被跳过;数组使用重复键;返回值不会修改输入。\n * @param value - 查询参数对象。\n * @param options - 排序、空格和问号前缀选项。\n * @returns URL 编码后的查询字符串;没有参数时始终返回空字符串。\n * @throws `RangeError` 当参数包含 `NaN` 或无穷数字。\n */\nexport function toQueryString(value: Readonly<Record<string, QueryValue>>, options: QueryStringOptions = {}): string {\n\tconst entries = Object.entries(value);\n\tif (options.sort) entries.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0));\n\tconst parameters = new URLSearchParams();\n\tfor (const [key, rawValue] of entries) {\n\t\tconst values = isQueryPrimitiveArray(rawValue) ? rawValue : [rawValue];\n\t\tfor (const item of values) {\n\t\t\tif (item !== null && item !== undefined) parameters.append(key, serializeQueryValue(item));\n\t\t}\n\t}\n\tlet result = parameters.toString();\n\tif (options.space === \"percent\") result = result.replace(/\\+/gu, \"%20\");\n\treturn result && options.prefixQuestionMark ? `?${result}` : result;\n}\n"],"mappings":";;;;;;;AAYA,MAAM,yBAAyB,UAA0D,MAAM,QAAQ,KAAK;;;;;;;;;;AAqB5G,MAAM,4BAA4B,QAAgB,KAAkB,UAAyB;CAC5F,OAAO,eAAe,QAAQ,KAAK;EAAE,cAAc;EAAM,YAAY;EAAM;EAAO,UAAU;CAAK,CAAC;AACnG;;;;;;;AAQA,SAAgB,cAAc,OAAuD;CACpF,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,MAAM,YAAY,OAAO,eAAe,KAAK;CAC7C,OAAO,cAAc,QAAQ,cAAc,OAAO;AACnD;;;;;;;;;AAUA,SAAgB,OAA2D,OAAmB,KAAyC;CACtI,OAAO,OAAO,OAAO,OAAO,GAAG;AAChC;;;;;;;;AASA,SAAgB,KAAoE,QAAgB,MAAwC;CAC3I,MAAM,SAAS,CAAC;CAChB,KAAK,MAAM,OAAO,MACjB,IAAI,OAAO,UAAU,qBAAqB,KAAK,QAAQ,GAAG,GAAG,yBAAyB,QAAQ,KAAK,OAAO,IAAI;CAE/G,OAAO;AACR;;;;;;;;AASA,SAAgB,KAAoE,QAAgB,MAAwC;CAC3I,MAAM,SAAS,EAAE,GAAG,OAAO;CAC3B,KAAK,MAAM,OAAO,MAAM,QAAQ,eAAe,QAAQ,GAAG;CAC1D,OAAO;AACR;;;;;;;;AASA,SAAgB,UACf,QACA,QACoC;CACpC,MAAM,SAAS,CAAC;CAChB,KAAK,MAAM,OAAO,QAAQ,QAAQ,MAAM,GACvC,IAAI,OAAO,UAAU,qBAAqB,KAAK,QAAQ,GAAG,GAAG,yBAAyB,QAAQ,KAAK,OAAO,OAAO,MAAM,KAAK,MAAM,CAAC;CAEpI,OAAO;AACR;;;;;;;;;AAUA,SAAgB,aAAa,MAAc,OAAwB;CAClE,IAAI,OAAO,GAAG,MAAM,KAAK,GAAG,OAAO;CACnC,MAAM,WAAW,QAAQ,QAAQ,IAAI,CAAC,CAAC,QAAQ,QAAQ,OAAO,UAAU,qBAAqB,KAAK,MAAM,GAAG,CAAC;CAC5G,MAAM,YAAY,QAAQ,QAAQ,KAAK,CAAC,CAAC,QAAQ,QAAQ,OAAO,UAAU,qBAAqB,KAAK,OAAO,GAAG,CAAC;CAC/G,IAAI,SAAS,WAAW,UAAU,QAAQ,OAAO;CACjD,OAAO,SAAS,OAAO,QAAQ,OAAO,OAAO,OAAO,GAAG,KAAK,OAAO,GAAG,QAAQ,IAAI,MAAM,GAAG,GAAG,QAAQ,IAAI,OAAO,GAAG,CAAC,CAAC;AACvH;;;;;;;;AASA,MAAM,uBAAuB,UAA6D;CACzF,IAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,GACtD,MAAM,IAAI,WAAW,yCAAyC;CAE/D,OAAO,OAAO,KAAK;AACpB;;;;;;;;;;AAWA,SAAgB,cAAc,OAA6C,UAA8B,CAAC,GAAW;CACpH,MAAM,UAAU,OAAO,QAAQ,KAAK;CACpC,IAAI,QAAQ,MAAM,QAAQ,MAAM,CAAC,OAAO,CAAC,WAAY,OAAO,QAAQ,KAAK,OAAO,QAAQ,IAAI,CAAE;CAC9F,MAAM,aAAa,IAAI,gBAAgB;CACvC,KAAK,MAAM,CAAC,KAAK,aAAa,SAAS;EACtC,MAAM,SAAS,sBAAsB,QAAQ,IAAI,WAAW,CAAC,QAAQ;EACrE,KAAK,MAAM,QAAQ,QAClB,IAAI,SAAS,QAAQ,SAAS,KAAA,GAAW,WAAW,OAAO,KAAK,oBAAoB,IAAI,CAAC;CAE3F;CACA,IAAI,SAAS,WAAW,SAAS;CACjC,IAAI,QAAQ,UAAU,WAAW,SAAS,OAAO,QAAQ,QAAQ,KAAK;CACtE,OAAO,UAAU,QAAQ,qBAAqB,IAAI,WAAW;AAC9D"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
//#region src/storage/index.d.ts
|
|
2
|
+
/** Storage 业务值编码器。 */
|
|
3
|
+
interface StorageCodec {
|
|
4
|
+
/**
|
|
5
|
+
* 把已编码文本恢复为业务值。
|
|
6
|
+
* @param value - 由同一 Codec 的 `encode` 生成并持久化的文本。
|
|
7
|
+
* @returns 解码后的业务值。
|
|
8
|
+
* @throws 当文本损坏、格式不受支持或无法反序列化时应抛出错误。
|
|
9
|
+
*/
|
|
10
|
+
decode: (value: string) => unknown;
|
|
11
|
+
/**
|
|
12
|
+
* 把业务值编码为可持久化字符串。
|
|
13
|
+
* @param value - 调用方传入的业务值。
|
|
14
|
+
* @returns 可由同一 Codec 的 `decode` 无损恢复的文本。
|
|
15
|
+
* @throws 当值不受支持或无法序列化时应抛出错误。
|
|
16
|
+
*/
|
|
17
|
+
encode: (value: unknown) => string;
|
|
18
|
+
}
|
|
19
|
+
/** 程序入口调用 {@link configureStorage} 时使用的全局配置。 */
|
|
20
|
+
interface StorageConfiguration {
|
|
21
|
+
/** 自定义值编码器;默认使用严格 JSON Codec,同一应用生命周期内必须保持同一引用。 */
|
|
22
|
+
codec?: StorageCodec;
|
|
23
|
+
/** 启用 Base64 可逆混淆;不提供加密、完整性或认证,不能与 `codec` 同时使用。 */
|
|
24
|
+
crypto?: boolean;
|
|
25
|
+
/** 返回 Unix 毫秒时间戳的时钟;默认使用 `Date.now`,主要用于 TTL 测试与受控时间源。 */
|
|
26
|
+
now?: () => number;
|
|
27
|
+
/** 所有物理键使用的非空命名空间前缀; */
|
|
28
|
+
prefix?: string;
|
|
29
|
+
}
|
|
30
|
+
/** 单次 Storage 写入配置。 */
|
|
31
|
+
interface StorageWriteOptions {
|
|
32
|
+
/** 从写入时刻开始的有效毫秒数;必须是大于 0 的有限数,省略时永久有效。 */
|
|
33
|
+
ttlMs?: number;
|
|
34
|
+
}
|
|
35
|
+
/** `Local` 与 `Session` 的统一操作接口。 */
|
|
36
|
+
interface StorageArea {
|
|
37
|
+
/** 当前全局 Storage 配置的物理键前缀;首次读取会激活默认配置。 */
|
|
38
|
+
readonly prefix: string;
|
|
39
|
+
/**
|
|
40
|
+
* 删除当前命名空间内的全部键,不影响同一后端中的其他应用键。
|
|
41
|
+
* @throws `Error` 当当前平台后端不可用。
|
|
42
|
+
*/
|
|
43
|
+
clear: () => void;
|
|
44
|
+
/**
|
|
45
|
+
* 获取并解码业务值;已过期记录会在读取时删除。
|
|
46
|
+
* @param key - 不含全局前缀的非空业务键。
|
|
47
|
+
* @returns 解码后的值;键缺失或过期时返回 `undefined`。
|
|
48
|
+
* @throws 当键非法、包络损坏、Codec 解码失败或后端不可用时抛出错误。
|
|
49
|
+
*/
|
|
50
|
+
get: <Value = unknown>(key: string) => Value | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* 判断一个可成功读取且未过期的业务键是否存在。
|
|
53
|
+
* @param key - 不含全局前缀的非空业务键。
|
|
54
|
+
* @returns 键存在且包络有效时返回 `true`。
|
|
55
|
+
*/
|
|
56
|
+
has: (key: string) => boolean;
|
|
57
|
+
/**
|
|
58
|
+
* 返回当前命名空间内的业务键快照。
|
|
59
|
+
* @returns 已移除全局前缀并按字典序排列的新数组;不会自动清理过期项。
|
|
60
|
+
*/
|
|
61
|
+
keys: () => string[];
|
|
62
|
+
/**
|
|
63
|
+
* 扫描当前命名空间并删除全部过期记录。
|
|
64
|
+
* @returns 本次实际删除的记录数量。
|
|
65
|
+
* @throws 当发现损坏包络或后端不可用时抛出错误。
|
|
66
|
+
*/
|
|
67
|
+
pruneExpired: () => number;
|
|
68
|
+
/**
|
|
69
|
+
* 删除单个业务键;键不存在时保持幂等。
|
|
70
|
+
* @param key - 不含全局前缀的非空业务键。
|
|
71
|
+
*/
|
|
72
|
+
remove: (key: string) => void;
|
|
73
|
+
/**
|
|
74
|
+
* 删除业务键以指定文本开头的全部条目,范围仍受全局命名空间限制。
|
|
75
|
+
* @param keyPrefix - 不含全局前缀的非空业务键前缀。
|
|
76
|
+
*/
|
|
77
|
+
removeByPrefix: (keyPrefix: string) => void;
|
|
78
|
+
/**
|
|
79
|
+
* 编码并写入业务值,可附加惰性清理的 TTL。
|
|
80
|
+
* @param key - 不含全局前缀的非空业务键。
|
|
81
|
+
* @param value - 必须受当前 Codec 支持的业务值。
|
|
82
|
+
* @param options - 可选的单次写入 TTL。
|
|
83
|
+
* @throws 当键、TTL、业务值或后端写入无效时抛出错误。
|
|
84
|
+
*/
|
|
85
|
+
set: <Value>(key: string, value: Value, options?: StorageWriteOptions) => void;
|
|
86
|
+
}
|
|
87
|
+
/** Base64 混淆 Codec;只隐藏明文外观,不提供加密、完整性或认证。 */
|
|
88
|
+
declare const base64StorageCodec: StorageCodec;
|
|
89
|
+
/** 浏览器 localStorage 或自动检测的 uni-app Storage 全局业务入口。 */
|
|
90
|
+
declare const Local: StorageArea;
|
|
91
|
+
/** 浏览器 sessionStorage 的全局业务入口;uni-app 不提供会话存储。 */
|
|
92
|
+
declare const Session: StorageArea;
|
|
93
|
+
/**
|
|
94
|
+
* 在首次 Storage 操作前可选配置 `Local` 与 `Session`。
|
|
95
|
+
*
|
|
96
|
+
* @remarks 不调用时在首次操作上使用 `fast__`、JSON Codec 与 `Date.now`。首次激活后只允许以完全相同的值和引用重复调用。若检测到
|
|
97
|
+
* 全局 `uni`,则自动使用其同步 Storage 且只启用 `Local`,否则使用浏览器 `localStorage` 与 `sessionStorage`。
|
|
98
|
+
* `crypto: true` 仅恢复旧版 Base64 混淆行为,不能保护敏感数据。
|
|
99
|
+
* @param options - 可选的全局键前缀、Codec、旧版混淆选项与时钟。
|
|
100
|
+
* @throws 配置非法、重复配置冲突或目标平台 Storage 不可用时抛出错误。
|
|
101
|
+
*/
|
|
102
|
+
declare function configureStorage(options?: StorageConfiguration): void;
|
|
103
|
+
/** 返回全局 Storage 是否已经由应用入口配置。 */
|
|
104
|
+
declare function isStorageConfigured(): boolean;
|
|
105
|
+
//#endregion
|
|
106
|
+
export { Local, Session, StorageArea, StorageCodec, StorageConfiguration, StorageWriteOptions, base64StorageCodec, configureStorage, isStorageConfigured };
|
|
107
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { decodeSecureBase64, encodeSecureBase64 } from "../base64/index.mjs";
|
|
2
|
+
//#region src/storage/index.ts
|
|
3
|
+
const runtimeStorageGlobals = globalThis;
|
|
4
|
+
/**
|
|
5
|
+
* 读取并校验当前运行时的全局 uni-app 同步 Storage。
|
|
6
|
+
*
|
|
7
|
+
* @returns 检测到 uni-app 时返回同步 Storage;普通浏览器环境返回 `undefined`。
|
|
8
|
+
* @throws `TypeError` 当全局 `uni` 存在但缺少本库需要的同步 Storage 方法。
|
|
9
|
+
*/
|
|
10
|
+
const getGlobalUniStorage = () => {
|
|
11
|
+
const value = runtimeStorageGlobals.uni;
|
|
12
|
+
if (value === void 0) return void 0;
|
|
13
|
+
if (typeof value !== "object" && typeof value !== "function" || value === null) throw new TypeError("The global uni object does not provide synchronous Storage APIs.");
|
|
14
|
+
const storage = value;
|
|
15
|
+
if (typeof storage.getStorageSync !== "function" || typeof storage.getStorageInfoSync !== "function" || typeof storage.removeStorageSync !== "function" || typeof storage.setStorageSync !== "function") throw new TypeError("The global uni object does not provide synchronous Storage APIs.");
|
|
16
|
+
return storage;
|
|
17
|
+
};
|
|
18
|
+
/** 默认 JSON Codec;显式拒绝会被 JSON.stringify 静默丢弃的顶层值。 */
|
|
19
|
+
const jsonCodec = {
|
|
20
|
+
decode: (value) => JSON.parse(value),
|
|
21
|
+
encode: (value) => {
|
|
22
|
+
const encoded = JSON.stringify(value);
|
|
23
|
+
if (typeof encoded !== "string") throw new TypeError("The storage value is not JSON-serializable.");
|
|
24
|
+
return encoded;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/** Base64 混淆 Codec;只隐藏明文外观,不提供加密、完整性或认证。 */
|
|
28
|
+
const base64StorageCodec = {
|
|
29
|
+
decode: (value) => JSON.parse(decodeSecureBase64(value)),
|
|
30
|
+
encode: (value) => {
|
|
31
|
+
const encoded = JSON.stringify(value);
|
|
32
|
+
if (typeof encoded !== "string") throw new TypeError("The storage value is not JSON-serializable.");
|
|
33
|
+
return encodeSecureBase64(encoded);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/** 页面级唯一配置;只允许幂等重复配置,避免模块加载顺序改变行为。 */
|
|
37
|
+
let activeConfiguration;
|
|
38
|
+
/**
|
|
39
|
+
* 判断未知值是否为非数组对象记录。
|
|
40
|
+
*
|
|
41
|
+
* @param value - JSON.parse 返回的未知值。
|
|
42
|
+
* @returns 值为非空、非数组对象时返回 `true`。
|
|
43
|
+
*/
|
|
44
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
45
|
+
/**
|
|
46
|
+
* 校验 Storage 业务键。
|
|
47
|
+
*
|
|
48
|
+
* @param key - 不含全局 Prefix 的业务键或业务键前缀。
|
|
49
|
+
* @throws `TypeError` 当值不是非空字符串。
|
|
50
|
+
*/
|
|
51
|
+
const assertKey = (key) => {
|
|
52
|
+
if (typeof key !== "string" || key.length === 0) throw new TypeError("Storage keys must be non-empty strings.");
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* 创建浏览器 Storage 后端。
|
|
56
|
+
*
|
|
57
|
+
* @remarks 平台对象在调用阶段读取,因此导入模块不会访问浏览器全局对象。
|
|
58
|
+
* @param kind - 选择 `localStorage` 或 `sessionStorage`。
|
|
59
|
+
* @returns 统一的内部同步后端。
|
|
60
|
+
* @throws `Error` 当所选 Storage 在当前环境不可用。
|
|
61
|
+
*/
|
|
62
|
+
const createWebStorageBackend = (kind) => {
|
|
63
|
+
const storage = kind === "local" ? runtimeStorageGlobals.localStorage : runtimeStorageGlobals.sessionStorage;
|
|
64
|
+
if (storage === void 0) throw new Error(`${kind}Storage is unavailable in the current runtime.`);
|
|
65
|
+
return {
|
|
66
|
+
getItem: (key) => storage.getItem(key),
|
|
67
|
+
keys: () => {
|
|
68
|
+
const keys = [];
|
|
69
|
+
for (let index = 0; index < storage.length; index += 1) {
|
|
70
|
+
const key = storage.key(index);
|
|
71
|
+
if (key !== null) keys.push(key);
|
|
72
|
+
}
|
|
73
|
+
return keys;
|
|
74
|
+
},
|
|
75
|
+
removeItem: (key) => {
|
|
76
|
+
storage.removeItem(key);
|
|
77
|
+
},
|
|
78
|
+
setItem: (key, value) => {
|
|
79
|
+
storage.setItem(key, value);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* 把 uni-app 同步 Storage 适配为内部后端。
|
|
85
|
+
*
|
|
86
|
+
* @remarks uni-app 以空字符串同时表示“键缺失”和“真实空值”,因此空字符串需要结合键清单消除歧义。
|
|
87
|
+
* @param storage - 已从全局 `uni` 读取并校验的同步 API。
|
|
88
|
+
* @returns 统一的内部同步后端。
|
|
89
|
+
*/
|
|
90
|
+
const createUniStorageBackend = (storage) => ({
|
|
91
|
+
getItem: (key) => {
|
|
92
|
+
const value = storage.getStorageSync(key);
|
|
93
|
+
if (value !== "") return value;
|
|
94
|
+
return storage.getStorageInfoSync().keys.includes(key) ? value : void 0;
|
|
95
|
+
},
|
|
96
|
+
keys: () => [...storage.getStorageInfoSync().keys],
|
|
97
|
+
removeItem: (key) => {
|
|
98
|
+
storage.removeStorageSync(key);
|
|
99
|
+
},
|
|
100
|
+
setItem: (key, value) => {
|
|
101
|
+
storage.setStorageSync(key, value);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
/**
|
|
105
|
+
* 解析并校验版本化 Storage 包络。
|
|
106
|
+
*
|
|
107
|
+
* @param rawValue - 后端返回的原始值。
|
|
108
|
+
* @param key - 用于错误定位的完整物理键。
|
|
109
|
+
* @returns 当前 v3 包络。
|
|
110
|
+
* @throws `TypeError` 当原始值不是字符串、JSON 损坏、版本不支持或字段类型非法。
|
|
111
|
+
*/
|
|
112
|
+
const parseStoredEnvelope = (rawValue, key) => {
|
|
113
|
+
if (typeof rawValue !== "string") throw new TypeError(`Storage entry "${key}" is not a string.`);
|
|
114
|
+
try {
|
|
115
|
+
const parsed = JSON.parse(rawValue);
|
|
116
|
+
if (!isRecord(parsed) || parsed["version"] !== 3 || typeof parsed["data"] !== "string" || !(parsed["expiresAt"] === null || typeof parsed["expiresAt"] === "number" && Number.isFinite(parsed["expiresAt"]))) throw new TypeError("Unsupported storage envelope.");
|
|
117
|
+
return {
|
|
118
|
+
data: parsed["data"],
|
|
119
|
+
expiresAt: parsed["expiresAt"],
|
|
120
|
+
version: 3
|
|
121
|
+
};
|
|
122
|
+
} catch (cause) {
|
|
123
|
+
throw new TypeError(`Storage entry "${key}" is corrupted or unsupported.`, { cause });
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* 创建绑定命名空间、Codec 与时钟的 Storage Area。
|
|
128
|
+
*
|
|
129
|
+
* @param backendFactory - 每次操作时解析平台后端的工厂,保证导入安全并反映平台可用性。
|
|
130
|
+
* @param prefix - 已校验的全局物理键前缀。
|
|
131
|
+
* @param codec - 业务值与包络文本之间的 Codec。
|
|
132
|
+
* @param now - TTL 计算使用的可注入时钟。
|
|
133
|
+
* @returns 完整的命名空间 Storage 操作集合。
|
|
134
|
+
*/
|
|
135
|
+
const createStorageArea = (backendFactory, prefix, codec, now) => {
|
|
136
|
+
/**
|
|
137
|
+
* 拼接物理键。
|
|
138
|
+
*
|
|
139
|
+
* @param key - 已校验业务键。
|
|
140
|
+
* @returns 带当前命名空间前缀的物理键。
|
|
141
|
+
*/
|
|
142
|
+
const toStorageKey = (key) => `${prefix}${key}`;
|
|
143
|
+
/**
|
|
144
|
+
* 枚举当前命名空间中的业务键。
|
|
145
|
+
*
|
|
146
|
+
* @param backend - 本次操作使用的后端。
|
|
147
|
+
* @returns 已移除物理前缀、去重并排序的业务键。
|
|
148
|
+
* @throws `TypeError` 当后端返回非字符串键。
|
|
149
|
+
*/
|
|
150
|
+
const listBusinessKeys = (backend) => {
|
|
151
|
+
const keys = backend.keys();
|
|
152
|
+
if (!Array.isArray(keys) || !keys.every((key) => typeof key === "string")) throw new TypeError("Storage backend keys must be strings.");
|
|
153
|
+
return [...new Set(keys.filter((key) => key.startsWith(prefix)).map((key) => key.slice(prefix.length)))].sort();
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* 读取并处理单个包络。
|
|
157
|
+
*
|
|
158
|
+
* @param backend - 本次操作使用的后端。
|
|
159
|
+
* @param key - 业务键。
|
|
160
|
+
* @returns 未过期包络;键缺失或已经过期时返回 `undefined`。
|
|
161
|
+
* @throws `TypeError` 当键或包络非法。
|
|
162
|
+
* @throws `RangeError` 当注入时钟返回非有限时间戳。
|
|
163
|
+
*/
|
|
164
|
+
const readStoredEnvelope = (backend, key) => {
|
|
165
|
+
assertKey(key);
|
|
166
|
+
const storageKey = toStorageKey(key);
|
|
167
|
+
const rawValue = backend.getItem(storageKey);
|
|
168
|
+
if (rawValue === null || rawValue === void 0) return void 0;
|
|
169
|
+
const envelope = parseStoredEnvelope(rawValue, storageKey);
|
|
170
|
+
if (envelope.expiresAt === null) return envelope;
|
|
171
|
+
const timestamp = now();
|
|
172
|
+
if (!Number.isFinite(timestamp)) throw new RangeError("Storage clock must return a finite timestamp.");
|
|
173
|
+
if (timestamp < envelope.expiresAt) return envelope;
|
|
174
|
+
backend.removeItem(storageKey);
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
prefix,
|
|
178
|
+
clear() {
|
|
179
|
+
const backend = backendFactory();
|
|
180
|
+
for (const key of listBusinessKeys(backend)) backend.removeItem(toStorageKey(key));
|
|
181
|
+
},
|
|
182
|
+
get(key) {
|
|
183
|
+
const envelope = readStoredEnvelope(backendFactory(), key);
|
|
184
|
+
if (envelope === void 0) return void 0;
|
|
185
|
+
try {
|
|
186
|
+
return codec.decode(envelope.data);
|
|
187
|
+
} catch (cause) {
|
|
188
|
+
throw new TypeError(`Storage entry "${toStorageKey(key)}" could not be decoded.`, { cause });
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
has: (key) => readStoredEnvelope(backendFactory(), key) !== void 0,
|
|
192
|
+
keys: () => listBusinessKeys(backendFactory()),
|
|
193
|
+
pruneExpired() {
|
|
194
|
+
const backend = backendFactory();
|
|
195
|
+
let removed = 0;
|
|
196
|
+
for (const key of listBusinessKeys(backend)) {
|
|
197
|
+
const before = backend.getItem(toStorageKey(key));
|
|
198
|
+
if (before !== null && before !== void 0 && readStoredEnvelope(backend, key) === void 0) removed += 1;
|
|
199
|
+
}
|
|
200
|
+
return removed;
|
|
201
|
+
},
|
|
202
|
+
remove(key) {
|
|
203
|
+
assertKey(key);
|
|
204
|
+
backendFactory().removeItem(toStorageKey(key));
|
|
205
|
+
},
|
|
206
|
+
removeByPrefix(keyPrefix) {
|
|
207
|
+
assertKey(keyPrefix);
|
|
208
|
+
const backend = backendFactory();
|
|
209
|
+
for (const key of listBusinessKeys(backend)) if (key.startsWith(keyPrefix)) backend.removeItem(toStorageKey(key));
|
|
210
|
+
},
|
|
211
|
+
set(key, value, options = {}) {
|
|
212
|
+
assertKey(key);
|
|
213
|
+
if (value === void 0) throw new TypeError("Top-level undefined cannot be stored; remove the key instead.");
|
|
214
|
+
let expiresAt = null;
|
|
215
|
+
if (options.ttlMs !== void 0) {
|
|
216
|
+
if (!Number.isFinite(options.ttlMs) || options.ttlMs <= 0) throw new RangeError("ttlMs must be a positive finite number.");
|
|
217
|
+
const timestamp = now();
|
|
218
|
+
if (!Number.isFinite(timestamp) || !Number.isFinite(timestamp + options.ttlMs)) throw new RangeError("Storage expiry exceeds the supported timestamp range.");
|
|
219
|
+
expiresAt = timestamp + options.ttlMs;
|
|
220
|
+
}
|
|
221
|
+
let data;
|
|
222
|
+
try {
|
|
223
|
+
data = codec.encode(value);
|
|
224
|
+
if (typeof data !== "string") throw new TypeError("Storage codecs must return strings.");
|
|
225
|
+
} catch (cause) {
|
|
226
|
+
throw new TypeError("The storage value could not be encoded.", { cause });
|
|
227
|
+
}
|
|
228
|
+
backendFactory().setItem(toStorageKey(key), JSON.stringify({
|
|
229
|
+
data,
|
|
230
|
+
expiresAt,
|
|
231
|
+
version: 3
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* 获取已激活的全局 Storage 配置。
|
|
238
|
+
*
|
|
239
|
+
* @returns 显式配置或首次 Storage 操作创建的默认配置。
|
|
240
|
+
*/
|
|
241
|
+
const requireStorageConfiguration = () => {
|
|
242
|
+
if (activeConfiguration === void 0) configureStorage();
|
|
243
|
+
if (activeConfiguration === void 0) throw new Error("Storage configuration could not be initialized.");
|
|
244
|
+
return activeConfiguration;
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* 创建稳定的公开 Storage 门面。
|
|
248
|
+
*
|
|
249
|
+
* @param select - 从激活配置选择 Local 或 Session 的函数。
|
|
250
|
+
* @param name - 用于不可用错误的公开门面名称。
|
|
251
|
+
* @returns 可安全导入、并在首次调用时解析默认或显式配置的稳定对象。
|
|
252
|
+
*/
|
|
253
|
+
const createStorageAreaProxy = (select, name) => {
|
|
254
|
+
/**
|
|
255
|
+
* 解析当前实际 Area。
|
|
256
|
+
*
|
|
257
|
+
* @returns 配置中的 Local 或 Session Area。
|
|
258
|
+
* @throws `Error` 当 uni-app 模式请求 Session。
|
|
259
|
+
*/
|
|
260
|
+
const getArea = () => {
|
|
261
|
+
const area = select(requireStorageConfiguration());
|
|
262
|
+
if (area === void 0) throw new Error(`${name} is unavailable in uni-app.`);
|
|
263
|
+
return area;
|
|
264
|
+
};
|
|
265
|
+
return {
|
|
266
|
+
get prefix() {
|
|
267
|
+
return getArea().prefix;
|
|
268
|
+
},
|
|
269
|
+
clear: () => {
|
|
270
|
+
getArea().clear();
|
|
271
|
+
},
|
|
272
|
+
get: (key) => getArea().get(key),
|
|
273
|
+
has: (key) => getArea().has(key),
|
|
274
|
+
keys: () => getArea().keys(),
|
|
275
|
+
pruneExpired: () => getArea().pruneExpired(),
|
|
276
|
+
remove: (key) => {
|
|
277
|
+
getArea().remove(key);
|
|
278
|
+
},
|
|
279
|
+
removeByPrefix: (keyPrefix) => {
|
|
280
|
+
getArea().removeByPrefix(keyPrefix);
|
|
281
|
+
},
|
|
282
|
+
set: (key, value, options) => {
|
|
283
|
+
getArea().set(key, value, options);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
};
|
|
287
|
+
/** 浏览器 localStorage 或自动检测的 uni-app Storage 全局业务入口。 */
|
|
288
|
+
const Local = createStorageAreaProxy((configuration) => configuration.local, "Local");
|
|
289
|
+
/** 浏览器 sessionStorage 的全局业务入口;uni-app 不提供会话存储。 */
|
|
290
|
+
const Session = createStorageAreaProxy((configuration) => configuration.session, "Session");
|
|
291
|
+
/**
|
|
292
|
+
* 在首次 Storage 操作前可选配置 `Local` 与 `Session`。
|
|
293
|
+
*
|
|
294
|
+
* @remarks 不调用时在首次操作上使用 `fast__`、JSON Codec 与 `Date.now`。首次激活后只允许以完全相同的值和引用重复调用。若检测到
|
|
295
|
+
* 全局 `uni`,则自动使用其同步 Storage 且只启用 `Local`,否则使用浏览器 `localStorage` 与 `sessionStorage`。
|
|
296
|
+
* `crypto: true` 仅恢复旧版 Base64 混淆行为,不能保护敏感数据。
|
|
297
|
+
* @param options - 可选的全局键前缀、Codec、旧版混淆选项与时钟。
|
|
298
|
+
* @throws 配置非法、重复配置冲突或目标平台 Storage 不可用时抛出错误。
|
|
299
|
+
*/
|
|
300
|
+
function configureStorage(options = {}) {
|
|
301
|
+
const prefix = options.prefix ?? "fast__";
|
|
302
|
+
if (typeof prefix !== "string" || prefix.length === 0) throw new TypeError("Storage prefix must be a non-empty string.");
|
|
303
|
+
if (options.codec !== void 0 && options.crypto === true) throw new TypeError("Storage codec and crypto options cannot be used together.");
|
|
304
|
+
const codec = options.codec ?? (options.crypto === true ? base64StorageCodec : jsonCodec);
|
|
305
|
+
const now = options.now ?? Date.now;
|
|
306
|
+
if (activeConfiguration !== void 0) {
|
|
307
|
+
if (activeConfiguration.prefix === prefix && activeConfiguration.codec === codec && activeConfiguration.now === now) return;
|
|
308
|
+
throw new Error("Storage has already been configured with different options.");
|
|
309
|
+
}
|
|
310
|
+
const uni = getGlobalUniStorage();
|
|
311
|
+
const configuration = {
|
|
312
|
+
codec,
|
|
313
|
+
local: createStorageArea(uni === void 0 ? () => createWebStorageBackend("local") : () => createUniStorageBackend(uni), prefix, codec, now),
|
|
314
|
+
now,
|
|
315
|
+
prefix
|
|
316
|
+
};
|
|
317
|
+
if (uni === void 0) configuration.session = createStorageArea(() => createWebStorageBackend("session"), prefix, codec, now);
|
|
318
|
+
activeConfiguration = configuration;
|
|
319
|
+
}
|
|
320
|
+
/** 返回全局 Storage 是否已经由应用入口配置。 */
|
|
321
|
+
function isStorageConfigured() {
|
|
322
|
+
return activeConfiguration !== void 0;
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
325
|
+
export { Local, Session, base64StorageCodec, configureStorage, isStorageConfigured };
|
|
326
|
+
|
|
327
|
+
//# sourceMappingURL=index.mjs.map
|