@fast-china/utils 2.0.0 → 2.0.3
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 +34 -0
- package/Fast.png +0 -0
- package/README.md +27 -29
- package/README.zh.md +27 -29
- package/dist/base64/index.mjs.map +1 -1
- package/dist/crypto/index.d.mts +266 -97
- package/dist/crypto/index.mjs +565 -263
- package/dist/crypto/index.mjs.map +1 -1
- package/dist/identity/index.d.mts +5 -5
- package/dist/identity/index.mjs +2 -2
- package/dist/identity/index.mjs.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.global.min.js +3 -0
- package/dist/index.global.min.js.map +1 -0
- package/dist/index.mjs +2 -2
- package/dist/storage/index.d.mts +11 -8
- package/dist/storage/index.mjs +21 -18
- package/dist/storage/index.mjs.map +1 -1
- package/dist/vue/emits.mjs.map +1 -1
- package/dist/vue/index.d.mts +2 -2
- package/dist/vue/install.d.mts +11 -29
- package/dist/vue/install.mjs +25 -25
- package/dist/vue/install.mjs.map +1 -1
- package/dist/vue/props.mjs.map +1 -1
- package/dist/vue/render.d.mts +2 -3
- package/dist/vue/render.mjs +3 -9
- package/dist/vue/render.mjs.map +1 -1
- package/docs/API.md +30 -14
- package/docs/API.zh-CN.md +30 -14
- package/docs/DEVELOPMENT_RELEASE.zh-CN.md +4 -4
- package/docs/RUNTIME_CONTRACT.md +10 -10
- package/package.json +9 -11
- package/dist/array/index.d.mts.map +0 -1
- package/dist/async/index.d.mts.map +0 -1
- package/dist/base64/index.d.mts.map +0 -1
- package/dist/color/index.d.mts.map +0 -1
- package/dist/crypto/index.d.mts.map +0 -1
- package/dist/date/index.d.mts.map +0 -1
- package/dist/dom/style.d.mts.map +0 -1
- package/dist/env/index.d.mts.map +0 -1
- package/dist/identity/index.d.mts.map +0 -1
- package/dist/logger/index.d.mts.map +0 -1
- package/dist/number/index.d.mts.map +0 -1
- package/dist/object/index.d.mts.map +0 -1
- package/dist/storage/index.d.mts.map +0 -1
- package/dist/string/index.d.mts.map +0 -1
- package/dist/vue/emits.d.mts.map +0 -1
- package/dist/vue/expose.d.mts.map +0 -1
- package/dist/vue/func.d.mts.map +0 -1
- package/dist/vue/install.d.mts.map +0 -1
- package/dist/vue/props.d.mts.map +0 -1
- package/dist/vue/render.d.mts.map +0 -1
- package/dist/vue/slots.d.mts.map +0 -1
- package/dist/vue/with.d.mts.map +0 -1
- package/src/array/index.ts +0 -173
- package/src/async/index.ts +0 -475
- package/src/base64/index.ts +0 -374
- package/src/color/index.ts +0 -208
- package/src/crypto/index.ts +0 -670
- package/src/date/index.ts +0 -451
- package/src/dom/index.ts +0 -6
- package/src/dom/style.ts +0 -92
- package/src/env/index.ts +0 -169
- package/src/identity/index.ts +0 -144
- package/src/index.ts +0 -20
- package/src/internal/text.ts +0 -46
- package/src/logger/index.ts +0 -219
- package/src/number/index.ts +0 -235
- package/src/object/index.ts +0 -160
- package/src/storage/index.ts +0 -524
- package/src/string/index.ts +0 -328
- package/src/vue/emits.ts +0 -71
- package/src/vue/expose.ts +0 -11
- package/src/vue/func.ts +0 -17
- package/src/vue/index.ts +0 -13
- package/src/vue/install.ts +0 -185
- package/src/vue/props.ts +0 -39
- package/src/vue/render.ts +0 -41
- package/src/vue/slots.ts +0 -23
- package/src/vue/with.ts +0 -10
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { debounce, mapConcurrent, retry, sleep, throttle, withTimeout } from "./
|
|
|
3
3
|
import { average, clamp, formatBytes, inRange, lerp, roundTo, secureRandomInt, sum } from "./number/index.mjs";
|
|
4
4
|
import { decodeBase64, decodeBase64Bytes, decodeBase64Url, decodeBase64UrlBytes, decodeLatin1Base64, decodeSecureBase64, encodeBase64, encodeBase64Bytes, encodeBase64Url, encodeBase64UrlBytes, encodeLatin1Base64, encodeSecureBase64 } from "./base64/index.mjs";
|
|
5
5
|
import { contrastRatio, formatHexColor, mixHexColorWithBlack, mixHexColorWithWhite, mixHexColors, parseHexColor, pickHigherContrastColor, relativeLuminance } from "./color/index.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { AESDecrypt, AESDecryptAuthenticated, AESDecryptWithPassword, AESEncrypt, AESEncryptAuthenticated, AESEncryptWithPassword, DeriveECDHKeySHA256, DeriveECDHSecret, ECDSASign, ECDSAVerify, FixedTimeEquals, GenerateECDHKeyPair, GenerateECDSAKeyPair, GenerateRSAKeyPair, GenerateRandomBytes, HKDFSHA256, HMACSHA256Encrypt, HMACSHA384Encrypt, HMACSHA512Encrypt, HashPasswordPBKDF2SHA256, MD5Encrypt, PBKDF2SHA256, RSADecryptOAEP, RSAEncryptOAEP, RSASignPSS, RSAVerifyPSS, SHA1Encrypt, SHA256Bytes, SHA256Encrypt, SHA384Bytes, SHA384Encrypt, SHA512Bytes, SHA512Encrypt, VerifyPasswordPBKDF2SHA256 } from "./crypto/index.mjs";
|
|
7
7
|
import { addDays, addMonths, addYears, createDateRangeShortcuts, createDateShortcuts, createOneMonthRangeFromToday, endOfDay, formatChineseRelativeTime, formatRelativeTime, getLocalDayBounds, getLocalTimeGreeting, getStartOfToday, isDateAfterNow, isFuture, isSameDay, isValidDate, isWithinInterval, startOfDay, toDate } from "./date/index.mjs";
|
|
8
8
|
import { addCssUnit, serializeStyle } from "./dom/style.mjs";
|
|
9
9
|
import { detectRuntime, hasWebCrypto, isBrowser, isMobileUserAgent, isNode, isTabletUserAgent, isUniApp, isWebWorker } from "./env/index.mjs";
|
|
@@ -20,4 +20,4 @@ import { definePropType, useProps } from "./vue/props.mjs";
|
|
|
20
20
|
import { useRender } from "./vue/render.mjs";
|
|
21
21
|
import { makeSlots } from "./vue/slots.mjs";
|
|
22
22
|
import { withDefineType } from "./vue/with.mjs";
|
|
23
|
-
export { Local, Session, addCssUnit, addDays, addMonths, addYears, allEqualBy,
|
|
23
|
+
export { AESDecrypt, AESDecryptAuthenticated, AESDecryptWithPassword, AESEncrypt, AESEncryptAuthenticated, AESEncryptWithPassword, DeriveECDHKeySHA256, DeriveECDHSecret, ECDSASign, ECDSAVerify, FixedTimeEquals, GenerateECDHKeyPair, GenerateECDSAKeyPair, GenerateRSAKeyPair, GenerateRandomBytes, HKDFSHA256, HMACSHA256Encrypt, HMACSHA384Encrypt, HMACSHA512Encrypt, HashPasswordPBKDF2SHA256, Local, MD5Encrypt, PBKDF2SHA256, RSADecryptOAEP, RSAEncryptOAEP, RSASignPSS, RSAVerifyPSS, SHA1Encrypt, SHA256Bytes, SHA256Encrypt, SHA384Bytes, SHA384Encrypt, SHA512Bytes, SHA512Encrypt, Session, VerifyPasswordPBKDF2SHA256, addCssUnit, addDays, addMonths, addYears, allEqualBy, average, base64StorageCodec, callOptionalFunction, camelCase, chunk, clamp, configureInstallationIdentity, configureStorage, contrastRatio, createDateRangeShortcuts, createDateShortcuts, createLogger, createOneMonthRangeFromToday, debounce, decodeBase64, decodeBase64Bytes, decodeBase64Url, decodeBase64UrlBytes, decodeLatin1Base64, decodeSecureBase64, decodeURIComponentRepeatedly, definePropType, detectRuntime, difference, encodeBase64, encodeBase64Bytes, encodeBase64Url, encodeBase64UrlBytes, encodeLatin1Base64, encodeSecureBase64, endOfDay, escapeHtml, formatBytes, formatChineseRelativeTime, formatHexColor, formatRelativeTime, generateUuidV4, getLocalDayBounds, getLocalTimeGreeting, getOrCreateInstallationId, getStartOfToday, groupBy, hasDuplicatesBy, hasOwn, hasWebCrypto, inRange, installationIdentity, intersection, isBrowser, isDateAfterNow, isFuture, isMobileUserAgent, isNode, isPlainObject, isSameDay, isStorageConfigured, isTabletUserAgent, isUniApp, isUuidV4, isValidDate, isValidJson, isWebWorker, isWithinInterval, kebabCase, lerp, logger, lowerFirst, makeSlots, mapConcurrent, mapValues, mixHexColorWithBlack, mixHexColorWithWhite, mixHexColors, normalizeWhitespace, omit, parseHexColor, parseQueryString, partition, pascalCase, pick, pickHigherContrastColor, relativeLuminance, removeNullishValues, retry, roundTo, secureRandomInt, secureRandomString, serializeStyle, shallowEqual, sleep, splitWords, startOfDay, sum, throttle, toDate, toQueryString, truncateGraphemes, unique, uniqueBy, upperFirst, useEmits, useExpose, useProps, useRender, withDefineType, withInstall, withInstallDirective, withNoopInstall, withTimeout };
|
package/dist/storage/index.d.mts
CHANGED
|
@@ -20,10 +20,12 @@ interface StorageCodec {
|
|
|
20
20
|
interface StorageConfiguration {
|
|
21
21
|
/** 自定义值编码器;默认使用严格 JSON Codec,同一应用生命周期内必须保持同一引用。 */
|
|
22
22
|
codec?: StorageCodec;
|
|
23
|
+
/** 启用 Base64 可逆混淆;不提供加密、完整性或认证,不能与 `codec` 同时使用。 */
|
|
24
|
+
crypto?: boolean;
|
|
23
25
|
/** 返回 Unix 毫秒时间戳的时钟;默认使用 `Date.now`,主要用于 TTL 测试与受控时间源。 */
|
|
24
26
|
now?: () => number;
|
|
25
|
-
/**
|
|
26
|
-
prefix
|
|
27
|
+
/** 所有物理键使用的非空命名空间前缀; */
|
|
28
|
+
prefix?: string;
|
|
27
29
|
}
|
|
28
30
|
/** 单次 Storage 写入配置。 */
|
|
29
31
|
interface StorageWriteOptions {
|
|
@@ -32,11 +34,11 @@ interface StorageWriteOptions {
|
|
|
32
34
|
}
|
|
33
35
|
/** `Local` 与 `Session` 的统一操作接口。 */
|
|
34
36
|
interface StorageArea {
|
|
35
|
-
/** 当前全局 Storage
|
|
37
|
+
/** 当前全局 Storage 配置的物理键前缀;首次读取会激活默认配置。 */
|
|
36
38
|
readonly prefix: string;
|
|
37
39
|
/**
|
|
38
40
|
* 删除当前命名空间内的全部键,不影响同一后端中的其他应用键。
|
|
39
|
-
* @throws `Error`
|
|
41
|
+
* @throws `Error` 当当前平台后端不可用。
|
|
40
42
|
*/
|
|
41
43
|
clear: () => void;
|
|
42
44
|
/**
|
|
@@ -89,14 +91,15 @@ declare const Local: StorageArea;
|
|
|
89
91
|
/** 浏览器 sessionStorage 的全局业务入口;uni-app 不提供会话存储。 */
|
|
90
92
|
declare const Session: StorageArea;
|
|
91
93
|
/**
|
|
92
|
-
*
|
|
94
|
+
* 在首次 Storage 操作前可选配置 `Local` 与 `Session`。
|
|
93
95
|
*
|
|
94
|
-
* @remarks
|
|
96
|
+
* @remarks 不调用时在首次操作上使用 `fast__`、JSON Codec 与 `Date.now`。首次激活后只允许以完全相同的值和引用重复调用。若检测到
|
|
95
97
|
* 全局 `uni`,则自动使用其同步 Storage 且只启用 `Local`,否则使用浏览器 `localStorage` 与 `sessionStorage`。
|
|
96
|
-
*
|
|
98
|
+
* `crypto: true` 仅恢复旧版 Base64 混淆行为,不能保护敏感数据。
|
|
99
|
+
* @param options - 可选的全局键前缀、Codec、旧版混淆选项与时钟。
|
|
97
100
|
* @throws 配置非法、重复配置冲突或目标平台 Storage 不可用时抛出错误。
|
|
98
101
|
*/
|
|
99
|
-
declare function configureStorage(options
|
|
102
|
+
declare function configureStorage(options?: StorageConfiguration): void;
|
|
100
103
|
/** 返回全局 Storage 是否已经由应用入口配置。 */
|
|
101
104
|
declare function isStorageConfigured(): boolean;
|
|
102
105
|
//#endregion
|
package/dist/storage/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { decodeSecureBase64, encodeSecureBase64 } from "../base64/index.mjs";
|
|
2
2
|
//#region src/storage/index.ts
|
|
3
3
|
const runtimeStorageGlobals = globalThis;
|
|
4
4
|
/**
|
|
@@ -26,11 +26,11 @@ const jsonCodec = {
|
|
|
26
26
|
};
|
|
27
27
|
/** Base64 混淆 Codec;只隐藏明文外观,不提供加密、完整性或认证。 */
|
|
28
28
|
const base64StorageCodec = {
|
|
29
|
-
decode: (value) => JSON.parse(
|
|
29
|
+
decode: (value) => JSON.parse(decodeSecureBase64(value)),
|
|
30
30
|
encode: (value) => {
|
|
31
31
|
const encoded = JSON.stringify(value);
|
|
32
32
|
if (typeof encoded !== "string") throw new TypeError("The storage value is not JSON-serializable.");
|
|
33
|
-
return
|
|
33
|
+
return encodeSecureBase64(encoded);
|
|
34
34
|
}
|
|
35
35
|
};
|
|
36
36
|
/** 页面级唯一配置;只允许幂等重复配置,避免模块加载顺序改变行为。 */
|
|
@@ -236,11 +236,11 @@ const createStorageArea = (backendFactory, prefix, codec, now) => {
|
|
|
236
236
|
/**
|
|
237
237
|
* 获取已激活的全局 Storage 配置。
|
|
238
238
|
*
|
|
239
|
-
* @returns
|
|
240
|
-
* @throws `Error` 当应用入口尚未配置 Storage。
|
|
239
|
+
* @returns 显式配置或首次 Storage 操作创建的默认配置。
|
|
241
240
|
*/
|
|
242
241
|
const requireStorageConfiguration = () => {
|
|
243
|
-
if (activeConfiguration === void 0)
|
|
242
|
+
if (activeConfiguration === void 0) configureStorage();
|
|
243
|
+
if (activeConfiguration === void 0) throw new Error("Storage configuration could not be initialized.");
|
|
244
244
|
return activeConfiguration;
|
|
245
245
|
};
|
|
246
246
|
/**
|
|
@@ -248,14 +248,14 @@ const requireStorageConfiguration = () => {
|
|
|
248
248
|
*
|
|
249
249
|
* @param select - 从激活配置选择 Local 或 Session 的函数。
|
|
250
250
|
* @param name - 用于不可用错误的公开门面名称。
|
|
251
|
-
* @returns
|
|
251
|
+
* @returns 可安全导入、并在首次调用时解析默认或显式配置的稳定对象。
|
|
252
252
|
*/
|
|
253
253
|
const createStorageAreaProxy = (select, name) => {
|
|
254
254
|
/**
|
|
255
255
|
* 解析当前实际 Area。
|
|
256
256
|
*
|
|
257
257
|
* @returns 配置中的 Local 或 Session Area。
|
|
258
|
-
* @throws `Error`
|
|
258
|
+
* @throws `Error` 当 uni-app 模式请求 Session。
|
|
259
259
|
*/
|
|
260
260
|
const getArea = () => {
|
|
261
261
|
const area = select(requireStorageConfiguration());
|
|
@@ -289,29 +289,32 @@ const Local = createStorageAreaProxy((configuration) => configuration.local, "Lo
|
|
|
289
289
|
/** 浏览器 sessionStorage 的全局业务入口;uni-app 不提供会话存储。 */
|
|
290
290
|
const Session = createStorageAreaProxy((configuration) => configuration.session, "Session");
|
|
291
291
|
/**
|
|
292
|
-
*
|
|
292
|
+
* 在首次 Storage 操作前可选配置 `Local` 与 `Session`。
|
|
293
293
|
*
|
|
294
|
-
* @remarks
|
|
294
|
+
* @remarks 不调用时在首次操作上使用 `fast__`、JSON Codec 与 `Date.now`。首次激活后只允许以完全相同的值和引用重复调用。若检测到
|
|
295
295
|
* 全局 `uni`,则自动使用其同步 Storage 且只启用 `Local`,否则使用浏览器 `localStorage` 与 `sessionStorage`。
|
|
296
|
-
*
|
|
296
|
+
* `crypto: true` 仅恢复旧版 Base64 混淆行为,不能保护敏感数据。
|
|
297
|
+
* @param options - 可选的全局键前缀、Codec、旧版混淆选项与时钟。
|
|
297
298
|
* @throws 配置非法、重复配置冲突或目标平台 Storage 不可用时抛出错误。
|
|
298
299
|
*/
|
|
299
|
-
function configureStorage(options) {
|
|
300
|
-
|
|
301
|
-
|
|
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);
|
|
302
305
|
const now = options.now ?? Date.now;
|
|
303
306
|
if (activeConfiguration !== void 0) {
|
|
304
|
-
if (activeConfiguration.prefix ===
|
|
307
|
+
if (activeConfiguration.prefix === prefix && activeConfiguration.codec === codec && activeConfiguration.now === now) return;
|
|
305
308
|
throw new Error("Storage has already been configured with different options.");
|
|
306
309
|
}
|
|
307
310
|
const uni = getGlobalUniStorage();
|
|
308
311
|
const configuration = {
|
|
309
312
|
codec,
|
|
310
|
-
local: createStorageArea(uni === void 0 ? () => createWebStorageBackend("local") : () => createUniStorageBackend(uni),
|
|
313
|
+
local: createStorageArea(uni === void 0 ? () => createWebStorageBackend("local") : () => createUniStorageBackend(uni), prefix, codec, now),
|
|
311
314
|
now,
|
|
312
|
-
prefix
|
|
315
|
+
prefix
|
|
313
316
|
};
|
|
314
|
-
if (uni === void 0) configuration.session = createStorageArea(() => createWebStorageBackend("session"),
|
|
317
|
+
if (uni === void 0) configuration.session = createStorageArea(() => createWebStorageBackend("session"), prefix, codec, now);
|
|
315
318
|
activeConfiguration = configuration;
|
|
316
319
|
}
|
|
317
320
|
/** 返回全局 Storage 是否已经由应用入口配置。 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/storage/index.ts"],"sourcesContent":["import { decodeBase64, encodeBase64 } from \"../base64/index.js\";\n\n/** uni-app 同步存储信息中本库实际读取的字段。 */\ninterface UniStorageInfo {\n\t/** 当前平台可见的物理键快照。 */\n\tkeys: readonly string[];\n}\n\n/** 全局 `uni` 必须提供的同步存储 API 最小结构。 */\ninterface UniStorageLike {\n\t/**\n\t * 同步读取一个物理键的原始值。\n\t * @param key - 已包含全局命名空间前缀的物理键。\n\t * @returns 平台保存的值;键缺失时应返回 `undefined`、`null` 或空字符串。\n\t */\n\tgetStorageSync: (key: string) => unknown;\n\t/**\n\t * 同步读取平台当前可见的全部物理键。\n\t * @returns 至少包含只读 `keys` 数组的快照对象。\n\t */\n\tgetStorageInfoSync: () => UniStorageInfo;\n\t/**\n\t * 同步删除一个物理键;键不存在时应保持幂等。\n\t * @param key - 已包含全局命名空间前缀的物理键。\n\t */\n\tremoveStorageSync: (key: string) => void;\n\t/**\n\t * 同步写入已经序列化的包络文本。\n\t * @param key - 已包含全局命名空间前缀的物理键。\n\t * @param value - JSON 包络字符串,不是未经编码的业务值。\n\t */\n\tsetStorageSync: (key: string, value: string) => void;\n}\n\n/** Storage 业务值编码器。 */\nexport interface StorageCodec {\n\t/**\n\t * 把已编码文本恢复为业务值。\n\t * @param value - 由同一 Codec 的 `encode` 生成并持久化的文本。\n\t * @returns 解码后的业务值。\n\t * @throws 当文本损坏、格式不受支持或无法反序列化时应抛出错误。\n\t */\n\tdecode: (value: string) => unknown;\n\t/**\n\t * 把业务值编码为可持久化字符串。\n\t * @param value - 调用方传入的业务值。\n\t * @returns 可由同一 Codec 的 `decode` 无损恢复的文本。\n\t * @throws 当值不受支持或无法序列化时应抛出错误。\n\t */\n\tencode: (value: unknown) => string;\n}\n\n/** 程序入口调用 {@link configureStorage} 时使用的全局配置。 */\nexport interface StorageConfiguration {\n\t/** 自定义值编码器;默认使用严格 JSON Codec,同一应用生命周期内必须保持同一引用。 */\n\tcodec?: StorageCodec;\n\t/** 返回 Unix 毫秒时间戳的时钟;默认使用 `Date.now`,主要用于 TTL 测试与受控时间源。 */\n\tnow?: () => number;\n\t/** 所有物理键使用的非空命名空间前缀;`clear` 与 `keys` 不会越过该范围。 */\n\tprefix: string;\n}\n\n/** 单次 Storage 写入配置。 */\nexport interface StorageWriteOptions {\n\t/** 从写入时刻开始的有效毫秒数;必须是大于 0 的有限数,省略时永久有效。 */\n\tttlMs?: number;\n}\n\n/** `Local` 与 `Session` 的统一操作接口。 */\nexport interface StorageArea {\n\t/** 当前全局 Storage 配置的物理键前缀;读取它会要求 Storage 已经完成配置。 */\n\treadonly prefix: string;\n\t/**\n\t * 删除当前命名空间内的全部键,不影响同一后端中的其他应用键。\n\t * @throws `Error` 当 Storage 尚未配置或后端不可用。\n\t */\n\tclear: () => void;\n\t/**\n\t * 获取并解码业务值;已过期记录会在读取时删除。\n\t * @param key - 不含全局前缀的非空业务键。\n\t * @returns 解码后的值;键缺失或过期时返回 `undefined`。\n\t * @throws 当键非法、包络损坏、Codec 解码失败或后端不可用时抛出错误。\n\t */\n\tget: <Value = unknown>(key: string) => Value | undefined;\n\t/**\n\t * 判断一个可成功读取且未过期的业务键是否存在。\n\t * @param key - 不含全局前缀的非空业务键。\n\t * @returns 键存在且包络有效时返回 `true`。\n\t */\n\thas: (key: string) => boolean;\n\t/**\n\t * 返回当前命名空间内的业务键快照。\n\t * @returns 已移除全局前缀并按字典序排列的新数组;不会自动清理过期项。\n\t */\n\tkeys: () => string[];\n\t/**\n\t * 扫描当前命名空间并删除全部过期记录。\n\t * @returns 本次实际删除的记录数量。\n\t * @throws 当发现损坏包络或后端不可用时抛出错误。\n\t */\n\tpruneExpired: () => number;\n\t/**\n\t * 删除单个业务键;键不存在时保持幂等。\n\t * @param key - 不含全局前缀的非空业务键。\n\t */\n\tremove: (key: string) => void;\n\t/**\n\t * 删除业务键以指定文本开头的全部条目,范围仍受全局命名空间限制。\n\t * @param keyPrefix - 不含全局前缀的非空业务键前缀。\n\t */\n\tremoveByPrefix: (keyPrefix: string) => void;\n\t/**\n\t * 编码并写入业务值,可附加惰性清理的 TTL。\n\t * @param key - 不含全局前缀的非空业务键。\n\t * @param value - 必须受当前 Codec 支持的业务值。\n\t * @param options - 可选的单次写入 TTL。\n\t * @throws 当键、TTL、业务值或后端写入无效时抛出错误。\n\t */\n\tset: <Value>(key: string, value: Value, options?: StorageWriteOptions) => void;\n}\n\n/** 浏览器 Storage 与 uni-app Storage 适配后的最小内部协议。 */\ninterface StorageBackend {\n\t/** 读取物理键原始值;缺失约定由上层统一规范为 `undefined`。 */\n\tgetItem: (key: string) => unknown;\n\t/** 枚举后端可见的全部物理键;返回值必须是不会随枚举过程变化的快照。 */\n\tkeys: () => readonly string[];\n\t/** 删除单个物理键;实现必须允许重复删除。 */\n\tremoveItem: (key: string) => void;\n\t/** 写入已经序列化的包络文本;配额和平台错误保持原样传播。 */\n\tsetItem: (key: string, value: string) => void;\n}\n\n/** 物理存储中的版本化包络;业务值始终先经 Codec 转为文本。 */\ninterface StoredEnvelope {\n\t/** Codec 编码后的业务文本;只有在包络结构校验通过后才能交给 Codec。 */\n\tdata: string;\n\t/** Unix 毫秒绝对过期时间戳;`null` 表示永久有效。 */\n\texpiresAt: number | null;\n\t/** 当前持久化协议版本;读取其他版本必须明确失败,不能猜测迁移。 */\n\tversion: 3;\n}\n\n/** 首次配置后冻结使用的解析结果和两个稳定门面。 */\ninterface ActiveStorageConfiguration {\n\t/** 首次配置后锁定的业务值 Codec 引用。 */\n\tcodec: StorageCodec;\n\t/** 已绑定 Local 后端、命名空间、Codec 与时钟的实际 Area。 */\n\tlocal: StorageArea;\n\t/** 首次配置后锁定的 TTL 时钟引用。 */\n\tnow: () => number;\n\t/** 所有 Area 共享的物理键命名空间前缀。 */\n\tprefix: string;\n\t/** 仅浏览器模式存在的 Session Area;uni-app 模式必须保持缺失。 */\n\tsession?: StorageArea;\n}\n\n/** 浏览器 Storage 在调用阶段延迟读取的平台全局对象最小视图。 */\ninterface RuntimeStorageGlobals {\n\t/** 可选 localStorage;缺失时 `Local` 操作明确失败。 */\n\tlocalStorage?: Storage;\n\t/** 可选 sessionStorage;缺失时 `Session` 操作明确失败。 */\n\tsessionStorage?: Storage;\n\t/** uni-app 运行时暴露的全局对象;只在调用 `configureStorage` 时读取和校验。 */\n\tuni?: unknown;\n}\n\nconst runtimeStorageGlobals = globalThis as unknown as RuntimeStorageGlobals;\n\n/**\n * 读取并校验当前运行时的全局 uni-app 同步 Storage。\n *\n * @returns 检测到 uni-app 时返回同步 Storage;普通浏览器环境返回 `undefined`。\n * @throws `TypeError` 当全局 `uni` 存在但缺少本库需要的同步 Storage 方法。\n */\nconst getGlobalUniStorage = (): UniStorageLike | undefined => {\n\tconst value = runtimeStorageGlobals.uni;\n\tif (value === undefined) return undefined;\n\tif ((typeof value !== \"object\" && typeof value !== \"function\") || value === null) {\n\t\tthrow new TypeError(\"The global uni object does not provide synchronous Storage APIs.\");\n\t}\n\tconst storage = value as Partial<UniStorageLike>;\n\tif (\n\t\ttypeof storage.getStorageSync !== \"function\" ||\n\t\ttypeof storage.getStorageInfoSync !== \"function\" ||\n\t\ttypeof storage.removeStorageSync !== \"function\" ||\n\t\ttypeof storage.setStorageSync !== \"function\"\n\t) {\n\t\tthrow new TypeError(\"The global uni object does not provide synchronous Storage APIs.\");\n\t}\n\treturn storage as UniStorageLike;\n};\n\n/** 默认 JSON Codec;显式拒绝会被 JSON.stringify 静默丢弃的顶层值。 */\nconst jsonCodec: StorageCodec = {\n\tdecode: (value): unknown => JSON.parse(value) as unknown,\n\tencode: (value): string => {\n\t\tconst encoded: unknown = JSON.stringify(value);\n\t\tif (typeof encoded !== \"string\") throw new TypeError(\"The storage value is not JSON-serializable.\");\n\t\treturn encoded;\n\t},\n};\n\n/** Base64 混淆 Codec;只隐藏明文外观,不提供加密、完整性或认证。 */\nexport const base64StorageCodec: StorageCodec = {\n\tdecode: (value): unknown => JSON.parse(decodeBase64(value)) as unknown,\n\tencode: (value): string => {\n\t\tconst encoded: unknown = JSON.stringify(value);\n\t\tif (typeof encoded !== \"string\") throw new TypeError(\"The storage value is not JSON-serializable.\");\n\t\treturn encodeBase64(encoded);\n\t},\n};\n\n/** 页面级唯一配置;只允许幂等重复配置,避免模块加载顺序改变行为。 */\nlet activeConfiguration: ActiveStorageConfiguration | undefined;\n\n/**\n * 判断未知值是否为非数组对象记录。\n *\n * @param value - JSON.parse 返回的未知值。\n * @returns 值为非空、非数组对象时返回 `true`。\n */\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === \"object\" && value !== null && !Array.isArray(value);\n\n/**\n * 校验 Storage 业务键。\n *\n * @param key - 不含全局 Prefix 的业务键或业务键前缀。\n * @throws `TypeError` 当值不是非空字符串。\n */\nconst assertKey = (key: string): void => {\n\tif (typeof key !== \"string\" || key.length === 0) throw new TypeError(\"Storage keys must be non-empty strings.\");\n};\n\n/**\n * 创建浏览器 Storage 后端。\n *\n * @remarks 平台对象在调用阶段读取,因此导入模块不会访问浏览器全局对象。\n * @param kind - 选择 `localStorage` 或 `sessionStorage`。\n * @returns 统一的内部同步后端。\n * @throws `Error` 当所选 Storage 在当前环境不可用。\n */\nconst createWebStorageBackend = (kind: \"local\" | \"session\"): StorageBackend => {\n\tconst storage = kind === \"local\" ? runtimeStorageGlobals.localStorage : runtimeStorageGlobals.sessionStorage;\n\tif (storage === undefined) throw new Error(`${kind}Storage is unavailable in the current runtime.`);\n\treturn {\n\t\tgetItem: (key): string | null => storage.getItem(key),\n\t\tkeys: (): string[] => {\n\t\t\tconst keys: string[] = [];\n\t\t\tfor (let index = 0; index < storage.length; index += 1) {\n\t\t\t\tconst key = storage.key(index);\n\t\t\t\tif (key !== null) keys.push(key);\n\t\t\t}\n\t\t\treturn keys;\n\t\t},\n\t\tremoveItem: (key): void => {\n\t\t\tstorage.removeItem(key);\n\t\t},\n\t\tsetItem: (key, value): void => {\n\t\t\tstorage.setItem(key, value);\n\t\t},\n\t};\n};\n\n/**\n * 把 uni-app 同步 Storage 适配为内部后端。\n *\n * @remarks uni-app 以空字符串同时表示“键缺失”和“真实空值”,因此空字符串需要结合键清单消除歧义。\n * @param storage - 已从全局 `uni` 读取并校验的同步 API。\n * @returns 统一的内部同步后端。\n */\nconst createUniStorageBackend = (storage: UniStorageLike): StorageBackend => ({\n\tgetItem: (key): unknown => {\n\t\tconst value = storage.getStorageSync(key);\n\t\tif (value !== \"\") return value;\n\t\treturn storage.getStorageInfoSync().keys.includes(key) ? value : undefined;\n\t},\n\tkeys: (): readonly string[] => [...storage.getStorageInfoSync().keys],\n\tremoveItem: (key): void => {\n\t\tstorage.removeStorageSync(key);\n\t},\n\tsetItem: (key, value): void => {\n\t\tstorage.setStorageSync(key, value);\n\t},\n});\n\n/**\n * 解析并校验版本化 Storage 包络。\n *\n * @param rawValue - 后端返回的原始值。\n * @param key - 用于错误定位的完整物理键。\n * @returns 当前 v3 包络。\n * @throws `TypeError` 当原始值不是字符串、JSON 损坏、版本不支持或字段类型非法。\n */\nconst parseStoredEnvelope = (rawValue: unknown, key: string): StoredEnvelope => {\n\tif (typeof rawValue !== \"string\") throw new TypeError(`Storage entry \"${key}\" is not a string.`);\n\ttry {\n\t\tconst parsed = JSON.parse(rawValue) as unknown;\n\t\tif (\n\t\t\t!isRecord(parsed) ||\n\t\t\tparsed[\"version\"] !== 3 ||\n\t\t\ttypeof parsed[\"data\"] !== \"string\" ||\n\t\t\t!(parsed[\"expiresAt\"] === null || (typeof parsed[\"expiresAt\"] === \"number\" && Number.isFinite(parsed[\"expiresAt\"])))\n\t\t) {\n\t\t\tthrow new TypeError(\"Unsupported storage envelope.\");\n\t\t}\n\t\treturn { data: parsed[\"data\"], expiresAt: parsed[\"expiresAt\"], version: 3 };\n\t} catch (cause) {\n\t\tthrow new TypeError(`Storage entry \"${key}\" is corrupted or unsupported.`, { cause });\n\t}\n};\n\n/**\n * 创建绑定命名空间、Codec 与时钟的 Storage Area。\n *\n * @param backendFactory - 每次操作时解析平台后端的工厂,保证导入安全并反映平台可用性。\n * @param prefix - 已校验的全局物理键前缀。\n * @param codec - 业务值与包络文本之间的 Codec。\n * @param now - TTL 计算使用的可注入时钟。\n * @returns 完整的命名空间 Storage 操作集合。\n */\nconst createStorageArea = (backendFactory: () => StorageBackend, prefix: string, codec: StorageCodec, now: () => number): StorageArea => {\n\t/**\n\t * 拼接物理键。\n\t *\n\t * @param key - 已校验业务键。\n\t * @returns 带当前命名空间前缀的物理键。\n\t */\n\tconst toStorageKey = (key: string): string => `${prefix}${key}`;\n\t/**\n\t * 枚举当前命名空间中的业务键。\n\t *\n\t * @param backend - 本次操作使用的后端。\n\t * @returns 已移除物理前缀、去重并排序的业务键。\n\t * @throws `TypeError` 当后端返回非字符串键。\n\t */\n\tconst listBusinessKeys = (backend: StorageBackend): string[] => {\n\t\tconst keys = backend.keys();\n\t\tif (!Array.isArray(keys) || !keys.every((key) => typeof key === \"string\")) {\n\t\t\tthrow new TypeError(\"Storage backend keys must be strings.\");\n\t\t}\n\t\treturn [...new Set(keys.filter((key) => key.startsWith(prefix)).map((key) => key.slice(prefix.length)))].sort();\n\t};\n\t/**\n\t * 读取并处理单个包络。\n\t *\n\t * @param backend - 本次操作使用的后端。\n\t * @param key - 业务键。\n\t * @returns 未过期包络;键缺失或已经过期时返回 `undefined`。\n\t * @throws `TypeError` 当键或包络非法。\n\t * @throws `RangeError` 当注入时钟返回非有限时间戳。\n\t */\n\tconst readStoredEnvelope = (backend: StorageBackend, key: string): StoredEnvelope | undefined => {\n\t\tassertKey(key);\n\t\tconst storageKey = toStorageKey(key);\n\t\tconst rawValue = backend.getItem(storageKey);\n\t\tif (rawValue === null || rawValue === undefined) return undefined;\n\t\tconst envelope = parseStoredEnvelope(rawValue, storageKey);\n\t\tif (envelope.expiresAt === null) return envelope;\n\t\tconst timestamp = now();\n\t\tif (!Number.isFinite(timestamp)) throw new RangeError(\"Storage clock must return a finite timestamp.\");\n\t\tif (timestamp < envelope.expiresAt) return envelope;\n\t\t// 过期项在读取时立即删除,后续 has/keys/pruneExpired 观察到一致状态。\n\t\tbackend.removeItem(storageKey);\n\t\treturn undefined;\n\t};\n\n\treturn {\n\t\tprefix,\n\t\tclear(): void {\n\t\t\tconst backend = backendFactory();\n\t\t\tfor (const key of listBusinessKeys(backend)) backend.removeItem(toStorageKey(key));\n\t\t},\n\t\tget<Value>(key: string): Value | undefined {\n\t\t\tconst envelope = readStoredEnvelope(backendFactory(), key);\n\t\t\tif (envelope === undefined) return undefined;\n\t\t\ttry {\n\t\t\t\treturn codec.decode(envelope.data) as Value;\n\t\t\t} catch (cause) {\n\t\t\t\tthrow new TypeError(`Storage entry \"${toStorageKey(key)}\" could not be decoded.`, { cause });\n\t\t\t}\n\t\t},\n\t\thas: (key): boolean => readStoredEnvelope(backendFactory(), key) !== undefined,\n\t\tkeys: (): string[] => listBusinessKeys(backendFactory()),\n\t\tpruneExpired(): number {\n\t\t\tconst backend = backendFactory();\n\t\t\tlet removed = 0;\n\t\t\tfor (const key of listBusinessKeys(backend)) {\n\t\t\t\t// read 同时处理删除;先读取一次用于区分“原本缺失”和“本轮因过期删除”。\n\t\t\t\tconst before = backend.getItem(toStorageKey(key));\n\t\t\t\tif (before !== null && before !== undefined && readStoredEnvelope(backend, key) === undefined) removed += 1;\n\t\t\t}\n\t\t\treturn removed;\n\t\t},\n\t\tremove(key: string): void {\n\t\t\tassertKey(key);\n\t\t\tbackendFactory().removeItem(toStorageKey(key));\n\t\t},\n\t\tremoveByPrefix(keyPrefix: string): void {\n\t\t\tassertKey(keyPrefix);\n\t\t\tconst backend = backendFactory();\n\t\t\tfor (const key of listBusinessKeys(backend)) if (key.startsWith(keyPrefix)) backend.removeItem(toStorageKey(key));\n\t\t},\n\t\tset<Value>(key: string, value: Value, options: StorageWriteOptions = {}): void {\n\t\t\tassertKey(key);\n\t\t\tif (value === undefined) throw new TypeError(\"Top-level undefined cannot be stored; remove the key instead.\");\n\t\t\tlet expiresAt: number | null = null;\n\t\t\tif (options.ttlMs !== undefined) {\n\t\t\t\tif (!Number.isFinite(options.ttlMs) || options.ttlMs <= 0) throw new RangeError(\"ttlMs must be a positive finite number.\");\n\t\t\t\tconst timestamp = now();\n\t\t\t\tif (!Number.isFinite(timestamp) || !Number.isFinite(timestamp + options.ttlMs)) {\n\t\t\t\t\tthrow new RangeError(\"Storage expiry exceeds the supported timestamp range.\");\n\t\t\t\t}\n\t\t\t\texpiresAt = timestamp + options.ttlMs;\n\t\t\t}\n\t\t\tlet data: string;\n\t\t\ttry {\n\t\t\t\tdata = codec.encode(value);\n\t\t\t\tif (typeof data !== \"string\") throw new TypeError(\"Storage codecs must return strings.\");\n\t\t\t} catch (cause) {\n\t\t\t\tthrow new TypeError(\"The storage value could not be encoded.\", { cause });\n\t\t\t}\n\t\t\tbackendFactory().setItem(toStorageKey(key), JSON.stringify({ data, expiresAt, version: 3 } satisfies StoredEnvelope));\n\t\t},\n\t};\n};\n\n/**\n * 获取已激活的全局 Storage 配置。\n *\n * @returns 首次 `configureStorage` 创建的内部配置。\n * @throws `Error` 当应用入口尚未配置 Storage。\n */\nconst requireStorageConfiguration = (): ActiveStorageConfiguration => {\n\tif (activeConfiguration === undefined) {\n\t\tthrow new Error(\"Storage is not configured. Call configureStorage() once from the application entry.\");\n\t}\n\treturn activeConfiguration;\n};\n\n/**\n * 创建稳定的公开 Storage 门面。\n *\n * @param select - 从激活配置选择 Local 或 Session 的函数。\n * @param name - 用于不可用错误的公开门面名称。\n * @returns 可在配置前导入、但只在调用时解析实际 Area 的稳定对象。\n */\nconst createStorageAreaProxy = (select: (configuration: ActiveStorageConfiguration) => StorageArea | undefined, name: string): StorageArea => {\n\t/**\n\t * 解析当前实际 Area。\n\t *\n\t * @returns 配置中的 Local 或 Session Area。\n\t * @throws `Error` 当尚未配置,或 uni-app 模式请求 Session。\n\t */\n\tconst getArea = (): StorageArea => {\n\t\tconst area = select(requireStorageConfiguration());\n\t\tif (area === undefined) throw new Error(`${name} is unavailable in uni-app.`);\n\t\treturn area;\n\t};\n\treturn {\n\t\tget prefix(): string {\n\t\t\treturn getArea().prefix;\n\t\t},\n\t\tclear: (): void => {\n\t\t\tgetArea().clear();\n\t\t},\n\t\tget: <Value>(key: string): Value | undefined => getArea().get<Value>(key),\n\t\thas: (key): boolean => getArea().has(key),\n\t\tkeys: (): string[] => getArea().keys(),\n\t\tpruneExpired: (): number => getArea().pruneExpired(),\n\t\tremove: (key): void => {\n\t\t\tgetArea().remove(key);\n\t\t},\n\t\tremoveByPrefix: (keyPrefix): void => {\n\t\t\tgetArea().removeByPrefix(keyPrefix);\n\t\t},\n\t\tset: <Value>(key: string, value: Value, options?: StorageWriteOptions): void => {\n\t\t\tgetArea().set(key, value, options);\n\t\t},\n\t};\n};\n\n/** 浏览器 localStorage 或自动检测的 uni-app Storage 全局业务入口。 */\nexport const Local: StorageArea = createStorageAreaProxy((configuration) => configuration.local, \"Local\");\n\n/** 浏览器 sessionStorage 的全局业务入口;uni-app 不提供会话存储。 */\nexport const Session: StorageArea = createStorageAreaProxy((configuration) => configuration.session, \"Session\");\n\n/**\n * 在应用入口一次性配置 `Local` 与 `Session`。\n *\n * @remarks 首次配置后只允许以完全相同的值和引用重复调用。模块导入不会访问平台对象;调用本函数时若检测到\n * 全局 `uni`,则自动使用其同步 Storage 且只启用 `Local`,否则使用浏览器 `localStorage` 与 `sessionStorage`。\n * @param options - 全局键前缀、Codec 与时钟。\n * @throws 配置非法、重复配置冲突或目标平台 Storage 不可用时抛出错误。\n */\nexport function configureStorage(options: StorageConfiguration): void {\n\tif (typeof options.prefix !== \"string\" || options.prefix.length === 0) {\n\t\tthrow new TypeError(\"Storage prefix must be a non-empty string.\");\n\t}\n\tconst codec = options.codec ?? jsonCodec;\n\tconst now = options.now ?? Date.now;\n\tif (activeConfiguration !== undefined) {\n\t\t// 相同配置允许多个入口模块幂等调用;任何引用或值变化都视为冲突。\n\t\tif (activeConfiguration.prefix === options.prefix && activeConfiguration.codec === codec && activeConfiguration.now === now) {\n\t\t\treturn;\n\t\t}\n\t\tthrow new Error(\"Storage has already been configured with different options.\");\n\t}\n\tconst uni = getGlobalUniStorage();\n\tconst localBackend =\n\t\tuni === undefined ? (): StorageBackend => createWebStorageBackend(\"local\") : (): StorageBackend => createUniStorageBackend(uni);\n\tconst local = createStorageArea(localBackend, options.prefix, codec, now);\n\tconst configuration: ActiveStorageConfiguration = { codec, local, now, prefix: options.prefix };\n\tif (uni === undefined) {\n\t\tconfiguration.session = createStorageArea(() => createWebStorageBackend(\"session\"), options.prefix, codec, now);\n\t}\n\tactiveConfiguration = configuration;\n}\n\n/** 返回全局 Storage 是否已经由应用入口配置。 */\nexport function isStorageConfigured(): boolean {\n\treturn activeConfiguration !== undefined;\n}\n"],"mappings":";;AAuKA,MAAM,wBAAwB;;;;;;;AAQ9B,MAAM,4BAAwD;CAC7D,MAAM,QAAQ,sBAAsB;CACpC,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;CAChC,IAAK,OAAO,UAAU,YAAY,OAAO,UAAU,cAAe,UAAU,MAC3E,MAAM,IAAI,UAAU,kEAAkE;CAEvF,MAAM,UAAU;CAChB,IACC,OAAO,QAAQ,mBAAmB,cAClC,OAAO,QAAQ,uBAAuB,cACtC,OAAO,QAAQ,sBAAsB,cACrC,OAAO,QAAQ,mBAAmB,YAElC,MAAM,IAAI,UAAU,kEAAkE;CAEvF,OAAO;AACR;;AAGA,MAAM,YAA0B;CAC/B,SAAS,UAAmB,KAAK,MAAM,KAAK;CAC5C,SAAS,UAAkB;EAC1B,MAAM,UAAmB,KAAK,UAAU,KAAK;EAC7C,IAAI,OAAO,YAAY,UAAU,MAAM,IAAI,UAAU,6CAA6C;EAClG,OAAO;CACR;AACD;;AAGA,MAAa,qBAAmC;CAC/C,SAAS,UAAmB,KAAK,MAAM,aAAa,KAAK,CAAC;CAC1D,SAAS,UAAkB;EAC1B,MAAM,UAAmB,KAAK,UAAU,KAAK;EAC7C,IAAI,OAAO,YAAY,UAAU,MAAM,IAAI,UAAU,6CAA6C;EAClG,OAAO,aAAa,OAAO;CAC5B;AACD;;AAGA,IAAI;;;;;;;AAQJ,MAAM,YAAY,UAAqD,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;;;;;;;AAQ1I,MAAM,aAAa,QAAsB;CACxC,IAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,GAAG,MAAM,IAAI,UAAU,yCAAyC;AAC/G;;;;;;;;;AAUA,MAAM,2BAA2B,SAA8C;CAC9E,MAAM,UAAU,SAAS,UAAU,sBAAsB,eAAe,sBAAsB;CAC9F,IAAI,YAAY,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,KAAK,+CAA+C;CAClG,OAAO;EACN,UAAU,QAAuB,QAAQ,QAAQ,GAAG;EACpD,YAAsB;GACrB,MAAM,OAAiB,CAAC;GACxB,KAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,SAAS,GAAG;IACvD,MAAM,MAAM,QAAQ,IAAI,KAAK;IAC7B,IAAI,QAAQ,MAAM,KAAK,KAAK,GAAG;GAChC;GACA,OAAO;EACR;EACA,aAAa,QAAc;GAC1B,QAAQ,WAAW,GAAG;EACvB;EACA,UAAU,KAAK,UAAgB;GAC9B,QAAQ,QAAQ,KAAK,KAAK;EAC3B;CACD;AACD;;;;;;;;AASA,MAAM,2BAA2B,aAA6C;CAC7E,UAAU,QAAiB;EAC1B,MAAM,QAAQ,QAAQ,eAAe,GAAG;EACxC,IAAI,UAAU,IAAI,OAAO;EACzB,OAAO,QAAQ,mBAAmB,CAAC,CAAC,KAAK,SAAS,GAAG,IAAI,QAAQ,KAAA;CAClE;CACA,YAA+B,CAAC,GAAG,QAAQ,mBAAmB,CAAC,CAAC,IAAI;CACpE,aAAa,QAAc;EAC1B,QAAQ,kBAAkB,GAAG;CAC9B;CACA,UAAU,KAAK,UAAgB;EAC9B,QAAQ,eAAe,KAAK,KAAK;CAClC;AACD;;;;;;;;;AAUA,MAAM,uBAAuB,UAAmB,QAAgC;CAC/E,IAAI,OAAO,aAAa,UAAU,MAAM,IAAI,UAAU,kBAAkB,IAAI,mBAAmB;CAC/F,IAAI;EACH,MAAM,SAAS,KAAK,MAAM,QAAQ;EAClC,IACC,CAAC,SAAS,MAAM,KAChB,OAAO,eAAe,KACtB,OAAO,OAAO,YAAY,YAC1B,EAAE,OAAO,iBAAiB,QAAS,OAAO,OAAO,iBAAiB,YAAY,OAAO,SAAS,OAAO,YAAY,IAEjH,MAAM,IAAI,UAAU,+BAA+B;EAEpD,OAAO;GAAE,MAAM,OAAO;GAAS,WAAW,OAAO;GAAc,SAAS;EAAE;CAC3E,SAAS,OAAO;EACf,MAAM,IAAI,UAAU,kBAAkB,IAAI,iCAAiC,EAAE,MAAM,CAAC;CACrF;AACD;;;;;;;;;;AAWA,MAAM,qBAAqB,gBAAsC,QAAgB,OAAqB,QAAmC;;;;;;;CAOxI,MAAM,gBAAgB,QAAwB,GAAG,SAAS;;;;;;;;CAQ1D,MAAM,oBAAoB,YAAsC;EAC/D,MAAM,OAAO,QAAQ,KAAK;EAC1B,IAAI,CAAC,MAAM,QAAQ,IAAI,KAAK,CAAC,KAAK,OAAO,QAAQ,OAAO,QAAQ,QAAQ,GACvE,MAAM,IAAI,UAAU,uCAAuC;EAE5D,OAAO,CAAC,GAAG,IAAI,IAAI,KAAK,QAAQ,QAAQ,IAAI,WAAW,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK;CAC/G;;;;;;;;;;CAUA,MAAM,sBAAsB,SAAyB,QAA4C;EAChG,UAAU,GAAG;EACb,MAAM,aAAa,aAAa,GAAG;EACnC,MAAM,WAAW,QAAQ,QAAQ,UAAU;EAC3C,IAAI,aAAa,QAAQ,aAAa,KAAA,GAAW,OAAO,KAAA;EACxD,MAAM,WAAW,oBAAoB,UAAU,UAAU;EACzD,IAAI,SAAS,cAAc,MAAM,OAAO;EACxC,MAAM,YAAY,IAAI;EACtB,IAAI,CAAC,OAAO,SAAS,SAAS,GAAG,MAAM,IAAI,WAAW,+CAA+C;EACrG,IAAI,YAAY,SAAS,WAAW,OAAO;EAE3C,QAAQ,WAAW,UAAU;CAE9B;CAEA,OAAO;EACN;EACA,QAAc;GACb,MAAM,UAAU,eAAe;GAC/B,KAAK,MAAM,OAAO,iBAAiB,OAAO,GAAG,QAAQ,WAAW,aAAa,GAAG,CAAC;EAClF;EACA,IAAW,KAAgC;GAC1C,MAAM,WAAW,mBAAmB,eAAe,GAAG,GAAG;GACzD,IAAI,aAAa,KAAA,GAAW,OAAO,KAAA;GACnC,IAAI;IACH,OAAO,MAAM,OAAO,SAAS,IAAI;GAClC,SAAS,OAAO;IACf,MAAM,IAAI,UAAU,kBAAkB,aAAa,GAAG,EAAE,0BAA0B,EAAE,MAAM,CAAC;GAC5F;EACD;EACA,MAAM,QAAiB,mBAAmB,eAAe,GAAG,GAAG,MAAM,KAAA;EACrE,YAAsB,iBAAiB,eAAe,CAAC;EACvD,eAAuB;GACtB,MAAM,UAAU,eAAe;GAC/B,IAAI,UAAU;GACd,KAAK,MAAM,OAAO,iBAAiB,OAAO,GAAG;IAE5C,MAAM,SAAS,QAAQ,QAAQ,aAAa,GAAG,CAAC;IAChD,IAAI,WAAW,QAAQ,WAAW,KAAA,KAAa,mBAAmB,SAAS,GAAG,MAAM,KAAA,GAAW,WAAW;GAC3G;GACA,OAAO;EACR;EACA,OAAO,KAAmB;GACzB,UAAU,GAAG;GACb,eAAe,CAAC,CAAC,WAAW,aAAa,GAAG,CAAC;EAC9C;EACA,eAAe,WAAyB;GACvC,UAAU,SAAS;GACnB,MAAM,UAAU,eAAe;GAC/B,KAAK,MAAM,OAAO,iBAAiB,OAAO,GAAG,IAAI,IAAI,WAAW,SAAS,GAAG,QAAQ,WAAW,aAAa,GAAG,CAAC;EACjH;EACA,IAAW,KAAa,OAAc,UAA+B,CAAC,GAAS;GAC9E,UAAU,GAAG;GACb,IAAI,UAAU,KAAA,GAAW,MAAM,IAAI,UAAU,+DAA+D;GAC5G,IAAI,YAA2B;GAC/B,IAAI,QAAQ,UAAU,KAAA,GAAW;IAChC,IAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,KAAK,QAAQ,SAAS,GAAG,MAAM,IAAI,WAAW,yCAAyC;IACzH,MAAM,YAAY,IAAI;IACtB,IAAI,CAAC,OAAO,SAAS,SAAS,KAAK,CAAC,OAAO,SAAS,YAAY,QAAQ,KAAK,GAC5E,MAAM,IAAI,WAAW,uDAAuD;IAE7E,YAAY,YAAY,QAAQ;GACjC;GACA,IAAI;GACJ,IAAI;IACH,OAAO,MAAM,OAAO,KAAK;IACzB,IAAI,OAAO,SAAS,UAAU,MAAM,IAAI,UAAU,qCAAqC;GACxF,SAAS,OAAO;IACf,MAAM,IAAI,UAAU,2CAA2C,EAAE,MAAM,CAAC;GACzE;GACA,eAAe,CAAC,CAAC,QAAQ,aAAa,GAAG,GAAG,KAAK,UAAU;IAAE;IAAM;IAAW,SAAS;GAAE,CAA0B,CAAC;EACrH;CACD;AACD;;;;;;;AAQA,MAAM,oCAAgE;CACrE,IAAI,wBAAwB,KAAA,GAC3B,MAAM,IAAI,MAAM,qFAAqF;CAEtG,OAAO;AACR;;;;;;;;AASA,MAAM,0BAA0B,QAAgF,SAA8B;;;;;;;CAO7I,MAAM,gBAA6B;EAClC,MAAM,OAAO,OAAO,4BAA4B,CAAC;EACjD,IAAI,SAAS,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,KAAK,4BAA4B;EAC5E,OAAO;CACR;CACA,OAAO;EACN,IAAI,SAAiB;GACpB,OAAO,QAAQ,CAAC,CAAC;EAClB;EACA,aAAmB;GAClB,QAAQ,CAAC,CAAC,MAAM;EACjB;EACA,MAAa,QAAmC,QAAQ,CAAC,CAAC,IAAW,GAAG;EACxE,MAAM,QAAiB,QAAQ,CAAC,CAAC,IAAI,GAAG;EACxC,YAAsB,QAAQ,CAAC,CAAC,KAAK;EACrC,oBAA4B,QAAQ,CAAC,CAAC,aAAa;EACnD,SAAS,QAAc;GACtB,QAAQ,CAAC,CAAC,OAAO,GAAG;EACrB;EACA,iBAAiB,cAAoB;GACpC,QAAQ,CAAC,CAAC,eAAe,SAAS;EACnC;EACA,MAAa,KAAa,OAAc,YAAwC;GAC/E,QAAQ,CAAC,CAAC,IAAI,KAAK,OAAO,OAAO;EAClC;CACD;AACD;;AAGA,MAAa,QAAqB,wBAAwB,kBAAkB,cAAc,OAAO,OAAO;;AAGxG,MAAa,UAAuB,wBAAwB,kBAAkB,cAAc,SAAS,SAAS;;;;;;;;;AAU9G,SAAgB,iBAAiB,SAAqC;CACrE,IAAI,OAAO,QAAQ,WAAW,YAAY,QAAQ,OAAO,WAAW,GACnE,MAAM,IAAI,UAAU,4CAA4C;CAEjE,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,MAAM,QAAQ,OAAO,KAAK;CAChC,IAAI,wBAAwB,KAAA,GAAW;EAEtC,IAAI,oBAAoB,WAAW,QAAQ,UAAU,oBAAoB,UAAU,SAAS,oBAAoB,QAAQ,KACvH;EAED,MAAM,IAAI,MAAM,6DAA6D;CAC9E;CACA,MAAM,MAAM,oBAAoB;CAIhC,MAAM,gBAA4C;EAAE;EAAO,OAD7C,kBADb,QAAQ,KAAA,UAAkC,wBAAwB,OAAO,UAA0B,wBAAwB,GAAG,GACjF,QAAQ,QAAQ,OAAO,GACN;EAAG;EAAK,QAAQ,QAAQ;CAAO;CAC9F,IAAI,QAAQ,KAAA,GACX,cAAc,UAAU,wBAAwB,wBAAwB,SAAS,GAAG,QAAQ,QAAQ,OAAO,GAAG;CAE/G,sBAAsB;AACvB;;AAGA,SAAgB,sBAA+B;CAC9C,OAAO,wBAAwB,KAAA;AAChC"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/storage/index.ts"],"sourcesContent":["import { decodeSecureBase64, encodeSecureBase64 } from \"../base64/index\";\n\n/** uni-app 同步存储信息中本库实际读取的字段。 */\ninterface UniStorageInfo {\n\t/** 当前平台可见的物理键快照。 */\n\tkeys: readonly string[];\n}\n\n/** 全局 `uni` 必须提供的同步存储 API 最小结构。 */\ninterface UniStorageLike {\n\t/**\n\t * 同步读取一个物理键的原始值。\n\t * @param key - 已包含全局命名空间前缀的物理键。\n\t * @returns 平台保存的值;键缺失时应返回 `undefined`、`null` 或空字符串。\n\t */\n\tgetStorageSync: (key: string) => unknown;\n\t/**\n\t * 同步读取平台当前可见的全部物理键。\n\t * @returns 至少包含只读 `keys` 数组的快照对象。\n\t */\n\tgetStorageInfoSync: () => UniStorageInfo;\n\t/**\n\t * 同步删除一个物理键;键不存在时应保持幂等。\n\t * @param key - 已包含全局命名空间前缀的物理键。\n\t */\n\tremoveStorageSync: (key: string) => void;\n\t/**\n\t * 同步写入已经序列化的包络文本。\n\t * @param key - 已包含全局命名空间前缀的物理键。\n\t * @param value - JSON 包络字符串,不是未经编码的业务值。\n\t */\n\tsetStorageSync: (key: string, value: string) => void;\n}\n\n/** Storage 业务值编码器。 */\nexport interface StorageCodec {\n\t/**\n\t * 把已编码文本恢复为业务值。\n\t * @param value - 由同一 Codec 的 `encode` 生成并持久化的文本。\n\t * @returns 解码后的业务值。\n\t * @throws 当文本损坏、格式不受支持或无法反序列化时应抛出错误。\n\t */\n\tdecode: (value: string) => unknown;\n\t/**\n\t * 把业务值编码为可持久化字符串。\n\t * @param value - 调用方传入的业务值。\n\t * @returns 可由同一 Codec 的 `decode` 无损恢复的文本。\n\t * @throws 当值不受支持或无法序列化时应抛出错误。\n\t */\n\tencode: (value: unknown) => string;\n}\n\n/** 程序入口调用 {@link configureStorage} 时使用的全局配置。 */\nexport interface StorageConfiguration {\n\t/** 自定义值编码器;默认使用严格 JSON Codec,同一应用生命周期内必须保持同一引用。 */\n\tcodec?: StorageCodec;\n\t/** 启用 Base64 可逆混淆;不提供加密、完整性或认证,不能与 `codec` 同时使用。 */\n\tcrypto?: boolean;\n\t/** 返回 Unix 毫秒时间戳的时钟;默认使用 `Date.now`,主要用于 TTL 测试与受控时间源。 */\n\tnow?: () => number;\n\t/** 所有物理键使用的非空命名空间前缀; */\n\tprefix?: string;\n}\n\n/** 单次 Storage 写入配置。 */\nexport interface StorageWriteOptions {\n\t/** 从写入时刻开始的有效毫秒数;必须是大于 0 的有限数,省略时永久有效。 */\n\tttlMs?: number;\n}\n\n/** `Local` 与 `Session` 的统一操作接口。 */\nexport interface StorageArea {\n\t/** 当前全局 Storage 配置的物理键前缀;首次读取会激活默认配置。 */\n\treadonly prefix: string;\n\t/**\n\t * 删除当前命名空间内的全部键,不影响同一后端中的其他应用键。\n\t * @throws `Error` 当当前平台后端不可用。\n\t */\n\tclear: () => void;\n\t/**\n\t * 获取并解码业务值;已过期记录会在读取时删除。\n\t * @param key - 不含全局前缀的非空业务键。\n\t * @returns 解码后的值;键缺失或过期时返回 `undefined`。\n\t * @throws 当键非法、包络损坏、Codec 解码失败或后端不可用时抛出错误。\n\t */\n\tget: <Value = unknown>(key: string) => Value | undefined;\n\t/**\n\t * 判断一个可成功读取且未过期的业务键是否存在。\n\t * @param key - 不含全局前缀的非空业务键。\n\t * @returns 键存在且包络有效时返回 `true`。\n\t */\n\thas: (key: string) => boolean;\n\t/**\n\t * 返回当前命名空间内的业务键快照。\n\t * @returns 已移除全局前缀并按字典序排列的新数组;不会自动清理过期项。\n\t */\n\tkeys: () => string[];\n\t/**\n\t * 扫描当前命名空间并删除全部过期记录。\n\t * @returns 本次实际删除的记录数量。\n\t * @throws 当发现损坏包络或后端不可用时抛出错误。\n\t */\n\tpruneExpired: () => number;\n\t/**\n\t * 删除单个业务键;键不存在时保持幂等。\n\t * @param key - 不含全局前缀的非空业务键。\n\t */\n\tremove: (key: string) => void;\n\t/**\n\t * 删除业务键以指定文本开头的全部条目,范围仍受全局命名空间限制。\n\t * @param keyPrefix - 不含全局前缀的非空业务键前缀。\n\t */\n\tremoveByPrefix: (keyPrefix: string) => void;\n\t/**\n\t * 编码并写入业务值,可附加惰性清理的 TTL。\n\t * @param key - 不含全局前缀的非空业务键。\n\t * @param value - 必须受当前 Codec 支持的业务值。\n\t * @param options - 可选的单次写入 TTL。\n\t * @throws 当键、TTL、业务值或后端写入无效时抛出错误。\n\t */\n\tset: <Value>(key: string, value: Value, options?: StorageWriteOptions) => void;\n}\n\n/** 浏览器 Storage 与 uni-app Storage 适配后的最小内部协议。 */\ninterface StorageBackend {\n\t/** 读取物理键原始值;缺失约定由上层统一规范为 `undefined`。 */\n\tgetItem: (key: string) => unknown;\n\t/** 枚举后端可见的全部物理键;返回值必须是不会随枚举过程变化的快照。 */\n\tkeys: () => readonly string[];\n\t/** 删除单个物理键;实现必须允许重复删除。 */\n\tremoveItem: (key: string) => void;\n\t/** 写入已经序列化的包络文本;配额和平台错误保持原样传播。 */\n\tsetItem: (key: string, value: string) => void;\n}\n\n/** 物理存储中的版本化包络;业务值始终先经 Codec 转为文本。 */\ninterface StoredEnvelope {\n\t/** Codec 编码后的业务文本;只有在包络结构校验通过后才能交给 Codec。 */\n\tdata: string;\n\t/** Unix 毫秒绝对过期时间戳;`null` 表示永久有效。 */\n\texpiresAt: number | null;\n\t/** 当前持久化协议版本;读取其他版本必须明确失败,不能猜测迁移。 */\n\tversion: 3;\n}\n\n/** 首次配置后冻结使用的解析结果和两个稳定门面。 */\ninterface ActiveStorageConfiguration {\n\t/** 首次配置后锁定的业务值 Codec 引用。 */\n\tcodec: StorageCodec;\n\t/** 已绑定 Local 后端、命名空间、Codec 与时钟的实际 Area。 */\n\tlocal: StorageArea;\n\t/** 首次配置后锁定的 TTL 时钟引用。 */\n\tnow: () => number;\n\t/** 所有 Area 共享的物理键命名空间前缀。 */\n\tprefix: string;\n\t/** 仅浏览器模式存在的 Session Area;uni-app 模式必须保持缺失。 */\n\tsession?: StorageArea;\n}\n\n/** 浏览器 Storage 在调用阶段延迟读取的平台全局对象最小视图。 */\ninterface RuntimeStorageGlobals {\n\t/** 可选 localStorage;缺失时 `Local` 操作明确失败。 */\n\tlocalStorage?: Storage;\n\t/** 可选 sessionStorage;缺失时 `Session` 操作明确失败。 */\n\tsessionStorage?: Storage;\n\t/** uni-app 运行时暴露的全局对象;只在显式配置或首次 Storage 操作时读取和校验。 */\n\tuni?: unknown;\n}\n\nconst runtimeStorageGlobals = globalThis as unknown as RuntimeStorageGlobals;\n\n/**\n * 读取并校验当前运行时的全局 uni-app 同步 Storage。\n *\n * @returns 检测到 uni-app 时返回同步 Storage;普通浏览器环境返回 `undefined`。\n * @throws `TypeError` 当全局 `uni` 存在但缺少本库需要的同步 Storage 方法。\n */\nconst getGlobalUniStorage = (): UniStorageLike | undefined => {\n\tconst value = runtimeStorageGlobals.uni;\n\tif (value === undefined) return undefined;\n\tif ((typeof value !== \"object\" && typeof value !== \"function\") || value === null) {\n\t\tthrow new TypeError(\"The global uni object does not provide synchronous Storage APIs.\");\n\t}\n\tconst storage = value as Partial<UniStorageLike>;\n\tif (\n\t\ttypeof storage.getStorageSync !== \"function\" ||\n\t\ttypeof storage.getStorageInfoSync !== \"function\" ||\n\t\ttypeof storage.removeStorageSync !== \"function\" ||\n\t\ttypeof storage.setStorageSync !== \"function\"\n\t) {\n\t\tthrow new TypeError(\"The global uni object does not provide synchronous Storage APIs.\");\n\t}\n\treturn storage as UniStorageLike;\n};\n\n/** 默认 JSON Codec;显式拒绝会被 JSON.stringify 静默丢弃的顶层值。 */\nconst jsonCodec: StorageCodec = {\n\tdecode: (value): unknown => JSON.parse(value) as unknown,\n\tencode: (value): string => {\n\t\tconst encoded: unknown = JSON.stringify(value);\n\t\tif (typeof encoded !== \"string\") throw new TypeError(\"The storage value is not JSON-serializable.\");\n\t\treturn encoded;\n\t},\n};\n\n/** Base64 混淆 Codec;只隐藏明文外观,不提供加密、完整性或认证。 */\nexport const base64StorageCodec: StorageCodec = {\n\tdecode: (value): unknown => JSON.parse(decodeSecureBase64(value)) as unknown,\n\tencode: (value): string => {\n\t\tconst encoded: unknown = JSON.stringify(value);\n\t\tif (typeof encoded !== \"string\") throw new TypeError(\"The storage value is not JSON-serializable.\");\n\t\treturn encodeSecureBase64(encoded);\n\t},\n};\n\n/** 页面级唯一配置;只允许幂等重复配置,避免模块加载顺序改变行为。 */\nlet activeConfiguration: ActiveStorageConfiguration | undefined;\n\n/**\n * 判断未知值是否为非数组对象记录。\n *\n * @param value - JSON.parse 返回的未知值。\n * @returns 值为非空、非数组对象时返回 `true`。\n */\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === \"object\" && value !== null && !Array.isArray(value);\n\n/**\n * 校验 Storage 业务键。\n *\n * @param key - 不含全局 Prefix 的业务键或业务键前缀。\n * @throws `TypeError` 当值不是非空字符串。\n */\nconst assertKey = (key: string): void => {\n\tif (typeof key !== \"string\" || key.length === 0) throw new TypeError(\"Storage keys must be non-empty strings.\");\n};\n\n/**\n * 创建浏览器 Storage 后端。\n *\n * @remarks 平台对象在调用阶段读取,因此导入模块不会访问浏览器全局对象。\n * @param kind - 选择 `localStorage` 或 `sessionStorage`。\n * @returns 统一的内部同步后端。\n * @throws `Error` 当所选 Storage 在当前环境不可用。\n */\nconst createWebStorageBackend = (kind: \"local\" | \"session\"): StorageBackend => {\n\tconst storage = kind === \"local\" ? runtimeStorageGlobals.localStorage : runtimeStorageGlobals.sessionStorage;\n\tif (storage === undefined) throw new Error(`${kind}Storage is unavailable in the current runtime.`);\n\treturn {\n\t\tgetItem: (key): string | null => storage.getItem(key),\n\t\tkeys: (): string[] => {\n\t\t\tconst keys: string[] = [];\n\t\t\tfor (let index = 0; index < storage.length; index += 1) {\n\t\t\t\tconst key = storage.key(index);\n\t\t\t\tif (key !== null) keys.push(key);\n\t\t\t}\n\t\t\treturn keys;\n\t\t},\n\t\tremoveItem: (key): void => {\n\t\t\tstorage.removeItem(key);\n\t\t},\n\t\tsetItem: (key, value): void => {\n\t\t\tstorage.setItem(key, value);\n\t\t},\n\t};\n};\n\n/**\n * 把 uni-app 同步 Storage 适配为内部后端。\n *\n * @remarks uni-app 以空字符串同时表示“键缺失”和“真实空值”,因此空字符串需要结合键清单消除歧义。\n * @param storage - 已从全局 `uni` 读取并校验的同步 API。\n * @returns 统一的内部同步后端。\n */\nconst createUniStorageBackend = (storage: UniStorageLike): StorageBackend => ({\n\tgetItem: (key): unknown => {\n\t\tconst value = storage.getStorageSync(key);\n\t\tif (value !== \"\") return value;\n\t\treturn storage.getStorageInfoSync().keys.includes(key) ? value : undefined;\n\t},\n\tkeys: (): readonly string[] => [...storage.getStorageInfoSync().keys],\n\tremoveItem: (key): void => {\n\t\tstorage.removeStorageSync(key);\n\t},\n\tsetItem: (key, value): void => {\n\t\tstorage.setStorageSync(key, value);\n\t},\n});\n\n/**\n * 解析并校验版本化 Storage 包络。\n *\n * @param rawValue - 后端返回的原始值。\n * @param key - 用于错误定位的完整物理键。\n * @returns 当前 v3 包络。\n * @throws `TypeError` 当原始值不是字符串、JSON 损坏、版本不支持或字段类型非法。\n */\nconst parseStoredEnvelope = (rawValue: unknown, key: string): StoredEnvelope => {\n\tif (typeof rawValue !== \"string\") throw new TypeError(`Storage entry \"${key}\" is not a string.`);\n\ttry {\n\t\tconst parsed = JSON.parse(rawValue) as unknown;\n\t\tif (\n\t\t\t!isRecord(parsed) ||\n\t\t\tparsed[\"version\"] !== 3 ||\n\t\t\ttypeof parsed[\"data\"] !== \"string\" ||\n\t\t\t!(parsed[\"expiresAt\"] === null || (typeof parsed[\"expiresAt\"] === \"number\" && Number.isFinite(parsed[\"expiresAt\"])))\n\t\t) {\n\t\t\tthrow new TypeError(\"Unsupported storage envelope.\");\n\t\t}\n\t\treturn { data: parsed[\"data\"], expiresAt: parsed[\"expiresAt\"], version: 3 };\n\t} catch (cause) {\n\t\tthrow new TypeError(`Storage entry \"${key}\" is corrupted or unsupported.`, { cause });\n\t}\n};\n\n/**\n * 创建绑定命名空间、Codec 与时钟的 Storage Area。\n *\n * @param backendFactory - 每次操作时解析平台后端的工厂,保证导入安全并反映平台可用性。\n * @param prefix - 已校验的全局物理键前缀。\n * @param codec - 业务值与包络文本之间的 Codec。\n * @param now - TTL 计算使用的可注入时钟。\n * @returns 完整的命名空间 Storage 操作集合。\n */\nconst createStorageArea = (backendFactory: () => StorageBackend, prefix: string, codec: StorageCodec, now: () => number): StorageArea => {\n\t/**\n\t * 拼接物理键。\n\t *\n\t * @param key - 已校验业务键。\n\t * @returns 带当前命名空间前缀的物理键。\n\t */\n\tconst toStorageKey = (key: string): string => `${prefix}${key}`;\n\t/**\n\t * 枚举当前命名空间中的业务键。\n\t *\n\t * @param backend - 本次操作使用的后端。\n\t * @returns 已移除物理前缀、去重并排序的业务键。\n\t * @throws `TypeError` 当后端返回非字符串键。\n\t */\n\tconst listBusinessKeys = (backend: StorageBackend): string[] => {\n\t\tconst keys = backend.keys();\n\t\tif (!Array.isArray(keys) || !keys.every((key) => typeof key === \"string\")) {\n\t\t\tthrow new TypeError(\"Storage backend keys must be strings.\");\n\t\t}\n\t\treturn [...new Set(keys.filter((key) => key.startsWith(prefix)).map((key) => key.slice(prefix.length)))].sort();\n\t};\n\t/**\n\t * 读取并处理单个包络。\n\t *\n\t * @param backend - 本次操作使用的后端。\n\t * @param key - 业务键。\n\t * @returns 未过期包络;键缺失或已经过期时返回 `undefined`。\n\t * @throws `TypeError` 当键或包络非法。\n\t * @throws `RangeError` 当注入时钟返回非有限时间戳。\n\t */\n\tconst readStoredEnvelope = (backend: StorageBackend, key: string): StoredEnvelope | undefined => {\n\t\tassertKey(key);\n\t\tconst storageKey = toStorageKey(key);\n\t\tconst rawValue = backend.getItem(storageKey);\n\t\tif (rawValue === null || rawValue === undefined) return undefined;\n\t\tconst envelope = parseStoredEnvelope(rawValue, storageKey);\n\t\tif (envelope.expiresAt === null) return envelope;\n\t\tconst timestamp = now();\n\t\tif (!Number.isFinite(timestamp)) throw new RangeError(\"Storage clock must return a finite timestamp.\");\n\t\tif (timestamp < envelope.expiresAt) return envelope;\n\t\t// 过期项在读取时立即删除,后续 has/keys/pruneExpired 观察到一致状态。\n\t\tbackend.removeItem(storageKey);\n\t\treturn undefined;\n\t};\n\n\treturn {\n\t\tprefix,\n\t\tclear(): void {\n\t\t\tconst backend = backendFactory();\n\t\t\tfor (const key of listBusinessKeys(backend)) backend.removeItem(toStorageKey(key));\n\t\t},\n\t\tget<Value>(key: string): Value | undefined {\n\t\t\tconst envelope = readStoredEnvelope(backendFactory(), key);\n\t\t\tif (envelope === undefined) return undefined;\n\t\t\ttry {\n\t\t\t\treturn codec.decode(envelope.data) as Value;\n\t\t\t} catch (cause) {\n\t\t\t\tthrow new TypeError(`Storage entry \"${toStorageKey(key)}\" could not be decoded.`, { cause });\n\t\t\t}\n\t\t},\n\t\thas: (key): boolean => readStoredEnvelope(backendFactory(), key) !== undefined,\n\t\tkeys: (): string[] => listBusinessKeys(backendFactory()),\n\t\tpruneExpired(): number {\n\t\t\tconst backend = backendFactory();\n\t\t\tlet removed = 0;\n\t\t\tfor (const key of listBusinessKeys(backend)) {\n\t\t\t\t// read 同时处理删除;先读取一次用于区分“原本缺失”和“本轮因过期删除”。\n\t\t\t\tconst before = backend.getItem(toStorageKey(key));\n\t\t\t\tif (before !== null && before !== undefined && readStoredEnvelope(backend, key) === undefined) removed += 1;\n\t\t\t}\n\t\t\treturn removed;\n\t\t},\n\t\tremove(key: string): void {\n\t\t\tassertKey(key);\n\t\t\tbackendFactory().removeItem(toStorageKey(key));\n\t\t},\n\t\tremoveByPrefix(keyPrefix: string): void {\n\t\t\tassertKey(keyPrefix);\n\t\t\tconst backend = backendFactory();\n\t\t\tfor (const key of listBusinessKeys(backend)) if (key.startsWith(keyPrefix)) backend.removeItem(toStorageKey(key));\n\t\t},\n\t\tset<Value>(key: string, value: Value, options: StorageWriteOptions = {}): void {\n\t\t\tassertKey(key);\n\t\t\tif (value === undefined) throw new TypeError(\"Top-level undefined cannot be stored; remove the key instead.\");\n\t\t\tlet expiresAt: number | null = null;\n\t\t\tif (options.ttlMs !== undefined) {\n\t\t\t\tif (!Number.isFinite(options.ttlMs) || options.ttlMs <= 0) throw new RangeError(\"ttlMs must be a positive finite number.\");\n\t\t\t\tconst timestamp = now();\n\t\t\t\tif (!Number.isFinite(timestamp) || !Number.isFinite(timestamp + options.ttlMs)) {\n\t\t\t\t\tthrow new RangeError(\"Storage expiry exceeds the supported timestamp range.\");\n\t\t\t\t}\n\t\t\t\texpiresAt = timestamp + options.ttlMs;\n\t\t\t}\n\t\t\tlet data: string;\n\t\t\ttry {\n\t\t\t\tdata = codec.encode(value);\n\t\t\t\tif (typeof data !== \"string\") throw new TypeError(\"Storage codecs must return strings.\");\n\t\t\t} catch (cause) {\n\t\t\t\tthrow new TypeError(\"The storage value could not be encoded.\", { cause });\n\t\t\t}\n\t\t\tbackendFactory().setItem(toStorageKey(key), JSON.stringify({ data, expiresAt, version: 3 } satisfies StoredEnvelope));\n\t\t},\n\t};\n};\n\n/**\n * 获取已激活的全局 Storage 配置。\n *\n * @returns 显式配置或首次 Storage 操作创建的默认配置。\n */\nconst requireStorageConfiguration = (): ActiveStorageConfiguration => {\n\tif (activeConfiguration === undefined) configureStorage();\n\tif (activeConfiguration === undefined) throw new Error(\"Storage configuration could not be initialized.\");\n\treturn activeConfiguration;\n};\n\n/**\n * 创建稳定的公开 Storage 门面。\n *\n * @param select - 从激活配置选择 Local 或 Session 的函数。\n * @param name - 用于不可用错误的公开门面名称。\n * @returns 可安全导入、并在首次调用时解析默认或显式配置的稳定对象。\n */\nconst createStorageAreaProxy = (select: (configuration: ActiveStorageConfiguration) => StorageArea | undefined, name: string): StorageArea => {\n\t/**\n\t * 解析当前实际 Area。\n\t *\n\t * @returns 配置中的 Local 或 Session Area。\n\t * @throws `Error` 当 uni-app 模式请求 Session。\n\t */\n\tconst getArea = (): StorageArea => {\n\t\tconst area = select(requireStorageConfiguration());\n\t\tif (area === undefined) throw new Error(`${name} is unavailable in uni-app.`);\n\t\treturn area;\n\t};\n\treturn {\n\t\tget prefix(): string {\n\t\t\treturn getArea().prefix;\n\t\t},\n\t\tclear: (): void => {\n\t\t\tgetArea().clear();\n\t\t},\n\t\tget: <Value>(key: string): Value | undefined => getArea().get<Value>(key),\n\t\thas: (key): boolean => getArea().has(key),\n\t\tkeys: (): string[] => getArea().keys(),\n\t\tpruneExpired: (): number => getArea().pruneExpired(),\n\t\tremove: (key): void => {\n\t\t\tgetArea().remove(key);\n\t\t},\n\t\tremoveByPrefix: (keyPrefix): void => {\n\t\t\tgetArea().removeByPrefix(keyPrefix);\n\t\t},\n\t\tset: <Value>(key: string, value: Value, options?: StorageWriteOptions): void => {\n\t\t\tgetArea().set(key, value, options);\n\t\t},\n\t};\n};\n\n/** 浏览器 localStorage 或自动检测的 uni-app Storage 全局业务入口。 */\nexport const Local: StorageArea = createStorageAreaProxy((configuration) => configuration.local, \"Local\");\n\n/** 浏览器 sessionStorage 的全局业务入口;uni-app 不提供会话存储。 */\nexport const Session: StorageArea = createStorageAreaProxy((configuration) => configuration.session, \"Session\");\n\n/**\n * 在首次 Storage 操作前可选配置 `Local` 与 `Session`。\n *\n * @remarks 不调用时在首次操作上使用 `fast__`、JSON Codec 与 `Date.now`。首次激活后只允许以完全相同的值和引用重复调用。若检测到\n * 全局 `uni`,则自动使用其同步 Storage 且只启用 `Local`,否则使用浏览器 `localStorage` 与 `sessionStorage`。\n * `crypto: true` 仅恢复旧版 Base64 混淆行为,不能保护敏感数据。\n * @param options - 可选的全局键前缀、Codec、旧版混淆选项与时钟。\n * @throws 配置非法、重复配置冲突或目标平台 Storage 不可用时抛出错误。\n */\nexport function configureStorage(options: StorageConfiguration = {}): void {\n\tconst prefix = options.prefix ?? \"fast__\";\n\tif (typeof prefix !== \"string\" || prefix.length === 0) {\n\t\tthrow new TypeError(\"Storage prefix must be a non-empty string.\");\n\t}\n\tif (options.codec !== undefined && options.crypto === true) {\n\t\tthrow new TypeError(\"Storage codec and crypto options cannot be used together.\");\n\t}\n\tconst codec = options.codec ?? (options.crypto === true ? base64StorageCodec : jsonCodec);\n\tconst now = options.now ?? Date.now;\n\tif (activeConfiguration !== undefined) {\n\t\t// 相同配置允许多个入口模块幂等调用;任何引用或值变化都视为冲突。\n\t\tif (activeConfiguration.prefix === prefix && activeConfiguration.codec === codec && activeConfiguration.now === now) {\n\t\t\treturn;\n\t\t}\n\t\tthrow new Error(\"Storage has already been configured with different options.\");\n\t}\n\tconst uni = getGlobalUniStorage();\n\tconst localBackend =\n\t\tuni === undefined ? (): StorageBackend => createWebStorageBackend(\"local\") : (): StorageBackend => createUniStorageBackend(uni);\n\tconst local = createStorageArea(localBackend, prefix, codec, now);\n\tconst configuration: ActiveStorageConfiguration = { codec, local, now, prefix };\n\tif (uni === undefined) {\n\t\tconfiguration.session = createStorageArea(() => createWebStorageBackend(\"session\"), prefix, codec, now);\n\t}\n\tactiveConfiguration = configuration;\n}\n\n/** 返回全局 Storage 是否已经由应用入口配置。 */\nexport function isStorageConfigured(): boolean {\n\treturn activeConfiguration !== undefined;\n}\n"],"mappings":"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"}
|
package/dist/vue/emits.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emits.mjs","names":[],"sources":["../../src/vue/emits.ts"],"sourcesContent":["import { computed } from \"vue\";\
|
|
1
|
+
{"version":3,"file":"emits.mjs","names":[],"sources":["../../src/vue/emits.ts"],"sourcesContent":["import { computed } from \"vue\";\nimport type { ComputedRef } from \"vue\";\n\n/** Vue Emits 对象中允许的校验器形状。 */\ntype EmitValidator = ((...arguments_: never[]) => unknown) | null;\n/** 事件名到可选参数校验器的内部映射。 */\ntype EmitsOptions = Record<string, EmitValidator>;\n/** 从校验器中提取事件参数;无校验器时保留未知参数。 */\ntype EventArguments<Validator> = Validator extends (...arguments_: infer Arguments) => unknown ? Arguments : unknown[];\n/** 在类型层递归把 kebab-case 事件名转换为 PascalCase。 */\ntype PascalEventName<Value extends string> = Value extends `${infer Head}-${infer Tail}`\n\t? `${Capitalize<Head>}${PascalEventName<Tail>}`\n\t: Capitalize<Value>;\n\n/** 把事件配置映射为 Vue `onXxx` 属性。 */\nexport type EmitHandlers<Emits extends EmitsOptions> = {\n\t[Name in keyof Emits as Name extends string ? `on${PascalEventName<Name>}` : never]: (...arguments_: EventArguments<Emits[Name]>) => void;\n};\n\n/**\n * 把事件名转换为 Vue Handler Prop 名称。\n *\n * @param eventName - Emits 对象中的原始事件名,可使用 kebab-case。\n * @returns `onPascalCase` 形式的属性名。\n * @throws `TypeError` 当事件名包含空片段或无法生成有效 Handler 名称。\n */\nconst toHandlerName = (eventName: string): string => {\n\treturn `on${eventName\n\t\t.split(\"-\")\n\t\t.map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n\t\t.join(\"\")}`;\n};\n\n/**\n * 构建响应式 Vue 事件处理器。\n *\n * @param emits - Vue emits 配置对象。\n * @param emit - `setup` 上下文提供的 emit 函数。\n * @param ignoredEvents - 不需要向子组件透传的事件名。\n * @returns 随配置重新计算的事件处理器对象。\n */\nexport function useEmits<Emits extends EmitsOptions>(\n\temits: Emits,\n\temit: (...arguments_: never[]) => unknown,\n\tignoredEvents: readonly (keyof Emits)[] = []\n): ComputedRef<Partial<EmitHandlers<Emits>>> {\n\tconst ignored = new Set<PropertyKey>(ignoredEvents);\n\tconst emitEvent = emit as unknown as (eventName: string, ...arguments_: unknown[]) => void;\n\treturn computed<Partial<EmitHandlers<Emits>>>(() => {\n\t\tconst handlers = {} as Partial<EmitHandlers<Emits>>;\n\t\tconst handlerNames = new Set<string>();\n\t\tfor (const eventName of Object.keys(emits)) {\n\t\t\tif (ignored.has(eventName)) continue;\n\t\t\tif (eventName.length === 0 || /\\s/u.test(eventName)) throw new TypeError(`Invalid Vue event name: \"${eventName}\".`);\n\t\t\tconst handlerName = toHandlerName(eventName);\n\t\t\tif (handlerNames.has(handlerName)) {\n\t\t\t\tthrow new TypeError(`Vue events map to the same handler property: \"${handlerName}\".`);\n\t\t\t}\n\t\t\thandlerNames.add(handlerName);\n\t\t\tObject.defineProperty(handlers, handlerName, {\n\t\t\t\tenumerable: true,\n\t\t\t\tvalue: (...arguments_: unknown[]): void => {\n\t\t\t\t\temitEvent(eventName, ...arguments_);\n\t\t\t\t},\n\t\t\t\twritable: true,\n\t\t\t});\n\t\t}\n\t\treturn handlers;\n\t});\n}\n"],"mappings":";;;;;;;;;AA0BA,MAAM,iBAAiB,cAA8B;CACpD,OAAO,KAAK,UACV,MAAM,GAAG,CAAC,CACV,KAAK,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAC3D,KAAK,EAAE;AACV;;;;;;;;;AAUA,SAAgB,SACf,OACA,MACA,gBAA0C,CAAC,GACC;CAC5C,MAAM,UAAU,IAAI,IAAiB,aAAa;CAClD,MAAM,YAAY;CAClB,OAAO,eAA6C;EACnD,MAAM,WAAW,CAAC;EAClB,MAAM,+BAAe,IAAI,IAAY;EACrC,KAAK,MAAM,aAAa,OAAO,KAAK,KAAK,GAAG;GAC3C,IAAI,QAAQ,IAAI,SAAS,GAAG;GAC5B,IAAI,UAAU,WAAW,KAAK,MAAM,KAAK,SAAS,GAAG,MAAM,IAAI,UAAU,4BAA4B,UAAU,GAAG;GAClH,MAAM,cAAc,cAAc,SAAS;GAC3C,IAAI,aAAa,IAAI,WAAW,GAC/B,MAAM,IAAI,UAAU,iDAAiD,YAAY,GAAG;GAErF,aAAa,IAAI,WAAW;GAC5B,OAAO,eAAe,UAAU,aAAa;IAC5C,YAAY;IACZ,QAAQ,GAAG,eAAgC;KAC1C,UAAU,WAAW,GAAG,UAAU;IACnC;IACA,UAAU;GACX,CAAC;EACF;EACA,OAAO;CACR,CAAC;AACF"}
|
package/dist/vue/index.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { EmitHandlers, useEmits } from "./emits.mjs";
|
|
2
2
|
import { useExpose } from "./expose.mjs";
|
|
3
3
|
import { AwaitableFunction, callOptionalFunction } from "./func.mjs";
|
|
4
|
-
import { Installable, TSXWithInstall, VueInstallValue,
|
|
4
|
+
import { Installable, TSXWithInstall, VueInstallValue, withInstall, withInstallDirective, withNoopInstall } from "./install.mjs";
|
|
5
5
|
import { definePropType, useProps } from "./props.mjs";
|
|
6
6
|
import { useRender } from "./render.mjs";
|
|
7
7
|
import { TypedSlots, TypedSlotsDeclaration, makeSlots } from "./slots.mjs";
|
|
8
8
|
import { withDefineType } from "./with.mjs";
|
|
9
|
-
export { AwaitableFunction, EmitHandlers, Installable, TSXWithInstall, TypedSlots, TypedSlotsDeclaration, VueInstallValue,
|
|
9
|
+
export { AwaitableFunction, EmitHandlers, Installable, TSXWithInstall, TypedSlots, TypedSlotsDeclaration, VueInstallValue, callOptionalFunction, definePropType, makeSlots, useEmits, useExpose, useProps, useRender, withDefineType, withInstall, withInstallDirective, withNoopInstall };
|
package/dist/vue/install.d.mts
CHANGED
|
@@ -1,37 +1,19 @@
|
|
|
1
|
+
import { App } from "vue";
|
|
1
2
|
//#region src/vue/install.d.ts
|
|
2
|
-
/** Vue 2.7 构造器与 Vue 3 App 共有的组件、指令注册能力。 */
|
|
3
|
-
interface VueRegistrationTarget {
|
|
4
|
-
/**
|
|
5
|
-
* 读取或注册全局组件。
|
|
6
|
-
*
|
|
7
|
-
* @param name - 全局组件名。
|
|
8
|
-
* @param component - 注册时传入的组件;省略时读取现有组件。
|
|
9
|
-
* @returns Vue 平台返回的现有组件、注册结果或目标自身。
|
|
10
|
-
*/
|
|
11
|
-
component: (name: string, component?: unknown) => unknown;
|
|
12
|
-
/**
|
|
13
|
-
* 读取或注册全局指令。
|
|
14
|
-
*
|
|
15
|
-
* @param name - 不带 `v-` 的全局指令名。
|
|
16
|
-
* @param directive - 注册时传入的指令;省略时读取现有指令。
|
|
17
|
-
* @returns Vue 平台返回的现有指令、注册结果或目标自身。
|
|
18
|
-
*/
|
|
19
|
-
directive: (name: string, directive?: unknown) => unknown;
|
|
20
|
-
}
|
|
21
3
|
/** Vue 组件对象、函数组件或指令对象可接受的最小结构类型。 */
|
|
22
4
|
type VueInstallValue = object | ((...arguments_: never[]) => unknown);
|
|
23
|
-
/** 为 Vue 组件或指令附加供 Vue
|
|
5
|
+
/** 为 Vue 组件或指令附加供 Vue 3 `app.use()` 调用的安装能力。 */
|
|
24
6
|
type Installable<Value> = Value & {
|
|
25
7
|
/**
|
|
26
|
-
* 把当前组件或指令安装到 Vue
|
|
27
|
-
* @param
|
|
8
|
+
* 把当前组件或指令安装到 Vue 3 App。
|
|
9
|
+
* @param app - Vue 3 App 实例。
|
|
28
10
|
*/
|
|
29
|
-
install: (
|
|
11
|
+
install: (app: App) => void;
|
|
30
12
|
};
|
|
31
13
|
/** TSX 组件安装类型;与 {@link Installable} 保持同一运行时契约。 */
|
|
32
14
|
type TSXWithInstall<Value> = Installable<Value>;
|
|
33
15
|
/**
|
|
34
|
-
* 为主组件附加 Vue
|
|
16
|
+
* 为主组件附加 Vue 3 `app.use()` 安装能力。
|
|
35
17
|
*
|
|
36
18
|
* @remarks 函数会直接为 `main` 定义附属组件属性和 `install`。所有组件名称、附属属性
|
|
37
19
|
* 冲突会在修改 `main` 前完成校验;安装到 App 时也会先预检全部全局名称,再统一注册。
|
|
@@ -39,21 +21,21 @@ type TSXWithInstall<Value> = Installable<Value>;
|
|
|
39
21
|
* @param extras - 同时注册并以可枚举属性挂到主组件的附属组件映射。
|
|
40
22
|
* @returns 原始 `main` 引用,并附加类型化的 `install` 与 `extras` 属性。
|
|
41
23
|
* @throws `TypeError` 当组件缺少合法名称、已有 `install`、附属键或名称发生冲突。
|
|
42
|
-
* @throws `Error`
|
|
24
|
+
* @throws `Error` 当 App 中同名位置已经注册其他组件。
|
|
43
25
|
*/
|
|
44
26
|
declare function withInstall<Main extends VueInstallValue, Extras extends Record<string, VueInstallValue> = Record<never, never>>(main: Main, extras?: Extras): Installable<Main> & Extras;
|
|
45
27
|
/**
|
|
46
28
|
* 为不需要单独注册的附属组件附加空安装函数。
|
|
47
29
|
*
|
|
48
30
|
* @remarks 适用于只能作为主组件附属属性使用、但仍需满足 Vue Plugin 类型的组件。
|
|
49
|
-
* 函数直接修改并返回传入组件,不会向 Vue
|
|
31
|
+
* 函数直接修改并返回传入组件,不会向 Vue 3 App 注册内容。
|
|
50
32
|
* @param component - 尚未定义或继承 `install` 属性的组件。
|
|
51
33
|
* @returns 原组件引用及无副作用的 `install` 方法。
|
|
52
34
|
* @throws `TypeError` 当组件自身或原型链已经存在 `install`。
|
|
53
35
|
*/
|
|
54
36
|
declare function withNoopInstall<Value extends VueInstallValue>(component: Value): TSXWithInstall<Value>;
|
|
55
37
|
/**
|
|
56
|
-
* 为 Vue
|
|
38
|
+
* 为 Vue 3 指令附加插件安装能力。
|
|
57
39
|
*
|
|
58
40
|
* @remarks 函数直接修改并返回指令。安装时重复注册同一引用保持幂等,不会覆盖同名的
|
|
59
41
|
* 其他指令。名称只传给 `directive()`,不得包含 `v-` 前缀。
|
|
@@ -61,9 +43,9 @@ declare function withNoopInstall<Value extends VueInstallValue>(component: Value
|
|
|
61
43
|
* @param name - 非空、无空白且不以 `v-` 开头的全局指令名。
|
|
62
44
|
* @returns 原指令引用及 Vue Plugin `install` 方法。
|
|
63
45
|
* @throws `TypeError` 当名称非法、指令已有 `install`,或安装目标无效。
|
|
64
|
-
* @throws `Error`
|
|
46
|
+
* @throws `Error` 当 App 中同名位置已经注册其他指令。
|
|
65
47
|
*/
|
|
66
48
|
declare function withInstallDirective<Value extends VueInstallValue>(directive: Value, name: string): Installable<Value>;
|
|
67
49
|
//#endregion
|
|
68
|
-
export { Installable, TSXWithInstall, VueInstallValue,
|
|
50
|
+
export { Installable, TSXWithInstall, VueInstallValue, withInstall, withInstallDirective, withNoopInstall };
|
|
69
51
|
//# sourceMappingURL=install.d.mts.map
|
package/dist/vue/install.mjs
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
//#region src/vue/install.ts
|
|
2
2
|
/**
|
|
3
|
-
* 校验 Vue 插件安装目标。
|
|
3
|
+
* 校验 Vue 3 插件安装目标。
|
|
4
4
|
*
|
|
5
|
-
* @param value - Vue
|
|
6
|
-
* @returns
|
|
7
|
-
* @throws `TypeError`
|
|
5
|
+
* @param value - Vue 3 App 实例。
|
|
6
|
+
* @returns 只包含组件和指令注册能力的 App。
|
|
7
|
+
* @throws `TypeError` 当目标不是对象或缺少 `component`、`directive` 方法。
|
|
8
8
|
*/
|
|
9
|
-
const
|
|
10
|
-
if (typeof value !== "object"
|
|
11
|
-
const
|
|
12
|
-
if (typeof
|
|
13
|
-
return
|
|
9
|
+
const assertApp = (value) => {
|
|
10
|
+
if (typeof value !== "object" || value === null) throw new TypeError("Vue plugin installation requires a Vue 3 App.");
|
|
11
|
+
const app = value;
|
|
12
|
+
if (typeof app.component !== "function" || typeof app.directive !== "function") throw new TypeError("Vue plugin installation requires component() and directive() registration methods.");
|
|
13
|
+
return app;
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
16
16
|
* 提取组件的全局注册名称。
|
|
17
17
|
*
|
|
18
18
|
* @param component - 待注册组件。
|
|
19
|
-
* @returns
|
|
20
|
-
* @throws `TypeError`
|
|
19
|
+
* @returns 经过校验的显式名称。
|
|
20
|
+
* @throws `TypeError` 当组件没有非空字符串名称,或名称包含空白。
|
|
21
21
|
*/
|
|
22
22
|
const getComponentName = (component) => {
|
|
23
23
|
const name = component.name;
|
|
@@ -28,14 +28,14 @@ const getComponentName = (component) => {
|
|
|
28
28
|
* 预检单个组件注册。
|
|
29
29
|
*
|
|
30
30
|
* @remarks 先读取同名组件并完成冲突判断,再返回延迟执行动作;调用方可以在所有组件预检通过后统一提交。
|
|
31
|
-
* @param
|
|
31
|
+
* @param app - 已校验的 Vue 3 App 注册目标。
|
|
32
32
|
* @param component - 待注册组件。
|
|
33
33
|
* @returns 已校验的组件引用、名称和目标中是否已经存在同一引用。
|
|
34
34
|
* @throws `Error` 当同名位置已由其他组件占用。
|
|
35
35
|
*/
|
|
36
|
-
const prepareComponentRegistration = (
|
|
36
|
+
const prepareComponentRegistration = (app, component) => {
|
|
37
37
|
const name = getComponentName(component);
|
|
38
|
-
const existing =
|
|
38
|
+
const existing = app.component(name);
|
|
39
39
|
if (existing !== void 0 && existing !== component) throw new Error(`Vue component name "${name}" is already registered by another component.`);
|
|
40
40
|
return {
|
|
41
41
|
component,
|
|
@@ -44,7 +44,7 @@ const prepareComponentRegistration = (target, component) => {
|
|
|
44
44
|
};
|
|
45
45
|
};
|
|
46
46
|
/**
|
|
47
|
-
* 为主组件附加 Vue
|
|
47
|
+
* 为主组件附加 Vue 3 `app.use()` 安装能力。
|
|
48
48
|
*
|
|
49
49
|
* @remarks 函数会直接为 `main` 定义附属组件属性和 `install`。所有组件名称、附属属性
|
|
50
50
|
* 冲突会在修改 `main` 前完成校验;安装到 App 时也会先预检全部全局名称,再统一注册。
|
|
@@ -52,7 +52,7 @@ const prepareComponentRegistration = (target, component) => {
|
|
|
52
52
|
* @param extras - 同时注册并以可枚举属性挂到主组件的附属组件映射。
|
|
53
53
|
* @returns 原始 `main` 引用,并附加类型化的 `install` 与 `extras` 属性。
|
|
54
54
|
* @throws `TypeError` 当组件缺少合法名称、已有 `install`、附属键或名称发生冲突。
|
|
55
|
-
* @throws `Error`
|
|
55
|
+
* @throws `Error` 当 App 中同名位置已经注册其他组件。
|
|
56
56
|
*/
|
|
57
57
|
function withInstall(main, extras) {
|
|
58
58
|
const componentNames = /* @__PURE__ */ new Set([getComponentName(main)]);
|
|
@@ -73,9 +73,9 @@ function withInstall(main, extras) {
|
|
|
73
73
|
writable: true
|
|
74
74
|
});
|
|
75
75
|
installable.install = (value) => {
|
|
76
|
-
const
|
|
77
|
-
const registrations = [main, ...extraEntries.map(([, component]) => component)].map((component) => prepareComponentRegistration(
|
|
78
|
-
for (const registration of registrations) if (!registration.registered)
|
|
76
|
+
const app = assertApp(value);
|
|
77
|
+
const registrations = [main, ...extraEntries.map(([, component]) => component)].map((component) => prepareComponentRegistration(app, component));
|
|
78
|
+
for (const registration of registrations) if (!registration.registered) app.component(registration.name, registration.component);
|
|
79
79
|
};
|
|
80
80
|
return installable;
|
|
81
81
|
}
|
|
@@ -83,7 +83,7 @@ function withInstall(main, extras) {
|
|
|
83
83
|
* 为不需要单独注册的附属组件附加空安装函数。
|
|
84
84
|
*
|
|
85
85
|
* @remarks 适用于只能作为主组件附属属性使用、但仍需满足 Vue Plugin 类型的组件。
|
|
86
|
-
* 函数直接修改并返回传入组件,不会向 Vue
|
|
86
|
+
* 函数直接修改并返回传入组件,不会向 Vue 3 App 注册内容。
|
|
87
87
|
* @param component - 尚未定义或继承 `install` 属性的组件。
|
|
88
88
|
* @returns 原组件引用及无副作用的 `install` 方法。
|
|
89
89
|
* @throws `TypeError` 当组件自身或原型链已经存在 `install`。
|
|
@@ -95,7 +95,7 @@ function withNoopInstall(component) {
|
|
|
95
95
|
return installable;
|
|
96
96
|
}
|
|
97
97
|
/**
|
|
98
|
-
* 为 Vue
|
|
98
|
+
* 为 Vue 3 指令附加插件安装能力。
|
|
99
99
|
*
|
|
100
100
|
* @remarks 函数直接修改并返回指令。安装时重复注册同一引用保持幂等,不会覆盖同名的
|
|
101
101
|
* 其他指令。名称只传给 `directive()`,不得包含 `v-` 前缀。
|
|
@@ -103,17 +103,17 @@ function withNoopInstall(component) {
|
|
|
103
103
|
* @param name - 非空、无空白且不以 `v-` 开头的全局指令名。
|
|
104
104
|
* @returns 原指令引用及 Vue Plugin `install` 方法。
|
|
105
105
|
* @throws `TypeError` 当名称非法、指令已有 `install`,或安装目标无效。
|
|
106
|
-
* @throws `Error`
|
|
106
|
+
* @throws `Error` 当 App 中同名位置已经注册其他指令。
|
|
107
107
|
*/
|
|
108
108
|
function withInstallDirective(directive, name) {
|
|
109
109
|
if (name.length === 0 || /\s/u.test(name) || name.startsWith("v-")) throw new TypeError("Installable Vue directives require a name without whitespace or a v- prefix.");
|
|
110
110
|
if ("install" in Object(directive)) throw new TypeError("The Vue directive already defines an install property.");
|
|
111
111
|
const installable = directive;
|
|
112
112
|
installable.install = (value) => {
|
|
113
|
-
const
|
|
114
|
-
const existing =
|
|
113
|
+
const app = assertApp(value);
|
|
114
|
+
const existing = app.directive(name);
|
|
115
115
|
if (existing !== void 0 && existing !== directive) throw new Error(`Vue directive name "${name}" is already registered by another directive.`);
|
|
116
|
-
if (existing !== directive)
|
|
116
|
+
if (existing !== directive) app.directive(name, directive);
|
|
117
117
|
};
|
|
118
118
|
return installable;
|
|
119
119
|
}
|