@fast-china/utils 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +32 -3
  3. package/README.zh.md +32 -3
  4. package/dist/array/index.mjs +1 -1
  5. package/dist/array/index.mjs.map +1 -1
  6. package/dist/async/index.mjs +21 -19
  7. package/dist/async/index.mjs.map +1 -1
  8. package/dist/base64/index.d.mts +10 -9
  9. package/dist/base64/index.mjs +15 -15
  10. package/dist/base64/index.mjs.map +1 -1
  11. package/dist/color/index.mjs +4 -4
  12. package/dist/color/index.mjs.map +1 -1
  13. package/dist/crypto/index.d.mts +9 -8
  14. package/dist/crypto/index.mjs +37 -37
  15. package/dist/crypto/index.mjs.map +1 -1
  16. package/dist/date/index.mjs +3 -3
  17. package/dist/date/index.mjs.map +1 -1
  18. package/dist/dom/style.mjs +3 -3
  19. package/dist/dom/style.mjs.map +1 -1
  20. package/dist/identity/index.mjs +4 -4
  21. package/dist/identity/index.mjs.map +1 -1
  22. package/dist/index.d.mts +4 -3
  23. package/dist/index.global.min.js +2 -2
  24. package/dist/index.global.min.js.map +1 -1
  25. package/dist/index.mjs +2 -2
  26. package/dist/internal/text.d.mts +17 -0
  27. package/dist/internal/text.mjs +41 -3
  28. package/dist/internal/text.mjs.map +1 -1
  29. package/dist/logger/index.d.mts +26 -22
  30. package/dist/logger/index.mjs +54 -25
  31. package/dist/logger/index.mjs.map +1 -1
  32. package/dist/number/index.mjs +12 -12
  33. package/dist/number/index.mjs.map +1 -1
  34. package/dist/object/index.mjs +1 -1
  35. package/dist/object/index.mjs.map +1 -1
  36. package/dist/storage/index.d.mts +14 -5
  37. package/dist/storage/index.mjs +39 -27
  38. package/dist/storage/index.mjs.map +1 -1
  39. package/dist/string/index.mjs +13 -13
  40. package/dist/string/index.mjs.map +1 -1
  41. package/dist/vue/emits.mjs +2 -2
  42. package/dist/vue/emits.mjs.map +1 -1
  43. package/dist/vue/func.mjs +1 -1
  44. package/dist/vue/func.mjs.map +1 -1
  45. package/dist/vue/install.mjs +12 -12
  46. package/dist/vue/install.mjs.map +1 -1
  47. package/dist/vue/props.d.mts +1 -1
  48. package/dist/vue/props.mjs.map +1 -1
  49. package/dist/vue/render.mjs +1 -1
  50. package/dist/vue/render.mjs.map +1 -1
  51. package/docs/API.md +34 -6
  52. package/docs/API.zh-CN.md +33 -6
  53. package/docs/RUNTIME_CONTRACT.md +9 -4
  54. package/package.json +9 -9
@@ -27,8 +27,16 @@ interface StorageConfiguration {
27
27
  /** 所有物理键使用的非空命名空间前缀; */
28
28
  prefix?: string;
29
29
  }
30
+ /** 单次 Storage 读取配置。 */
31
+ interface StorageReadOptions {
32
+ /**
33
+ * 仅覆盖本次读取使用的 Codec;`true` 使用 Base64 混淆,`false` 使用 JSON,省略时使用全局配置。
34
+ * 必须与写入该条目时使用的单次设置一致。
35
+ */
36
+ crypto?: boolean;
37
+ }
30
38
  /** 单次 Storage 写入配置。 */
31
- interface StorageWriteOptions {
39
+ interface StorageWriteOptions extends StorageReadOptions {
32
40
  /** 从写入时刻开始的有效毫秒数;必须是大于 0 的有限数,省略时永久有效。 */
33
41
  ttlMs?: number;
34
42
  }
@@ -44,10 +52,11 @@ interface StorageArea {
44
52
  /**
45
53
  * 获取并解码业务值;已过期记录会在读取时删除。
46
54
  * @param key - 不含全局前缀的非空业务键。
47
- * @returns 解码后的值;键缺失或过期时返回 `undefined`。
55
+ * @param options - 可选的单次 Base64 混淆开关;必须与写入时一致。
56
+ * @returns 解码后的值;未传泛型时静态类型默认为 `string`,键缺失或过期时返回 `undefined`。
48
57
  * @throws 当键非法、包络损坏、Codec 解码失败或后端不可用时抛出错误。
49
58
  */
50
- get: <Value = unknown>(key: string) => Value | undefined;
59
+ get: <Value = string>(key: string, options?: StorageReadOptions) => Value | undefined;
51
60
  /**
52
61
  * 判断一个可成功读取且未过期的业务键是否存在。
53
62
  * @param key - 不含全局前缀的非空业务键。
@@ -79,7 +88,7 @@ interface StorageArea {
79
88
  * 编码并写入业务值,可附加惰性清理的 TTL。
80
89
  * @param key - 不含全局前缀的非空业务键。
81
90
  * @param value - 必须受当前 Codec 支持的业务值。
82
- * @param options - 可选的单次写入 TTL
91
+ * @param options - 可选的单次写入 TTL 与 Base64 混淆开关。
83
92
  * @throws 当键、TTL、业务值或后端写入无效时抛出错误。
84
93
  */
85
94
  set: <Value>(key: string, value: Value, options?: StorageWriteOptions) => void;
@@ -103,5 +112,5 @@ declare function configureStorage(options?: StorageConfiguration): void;
103
112
  /** 返回全局 Storage 是否已经由应用入口配置。 */
104
113
  declare function isStorageConfigured(): boolean;
105
114
  //#endregion
106
- export { Local, Session, StorageArea, StorageCodec, StorageConfiguration, StorageWriteOptions, base64StorageCodec, configureStorage, isStorageConfigured };
115
+ export { Local, Session, StorageArea, StorageCodec, StorageConfiguration, StorageReadOptions, StorageWriteOptions, base64StorageCodec, configureStorage, isStorageConfigured };
107
116
  //# sourceMappingURL=index.d.mts.map
@@ -9,9 +9,9 @@ import { decodeSecureBase64, encodeSecureBase64 } from "../base64/index.mjs";
9
9
  const getGlobalUniStorage = () => {
10
10
  const value = Reflect.get(globalThis, "uni");
11
11
  if (value === void 0) return void 0;
12
- if (typeof value !== "object" && typeof value !== "function" || value === null) throw new TypeError("The global uni object does not provide synchronous Storage APIs.");
12
+ if (typeof value !== "object" && typeof value !== "function" || value === null) throw new TypeError("全局 uni 对象未提供同步 Storage API。");
13
13
  const storage = value;
14
- 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.");
14
+ if (typeof storage.getStorageSync !== "function" || typeof storage.getStorageInfoSync !== "function" || typeof storage.removeStorageSync !== "function" || typeof storage.setStorageSync !== "function") throw new TypeError("全局 uni 对象未提供同步 Storage API。");
15
15
  return storage;
16
16
  };
17
17
  /** 默认 JSON Codec;显式拒绝会被 JSON.stringify 静默丢弃的顶层值。 */
@@ -19,16 +19,16 @@ const jsonCodec = {
19
19
  decode: (value) => JSON.parse(value),
20
20
  encode: (value) => {
21
21
  const encoded = JSON.stringify(value);
22
- if (typeof encoded !== "string") throw new TypeError("The storage value is not JSON-serializable.");
22
+ if (typeof encoded !== "string") throw new TypeError("存储值无法序列化为 JSON");
23
23
  return encoded;
24
24
  }
25
25
  };
26
26
  /** Base64 混淆 Codec;只隐藏明文外观,不提供加密、完整性或认证。 */
27
27
  const base64StorageCodec = {
28
- decode: (value) => JSON.parse(decodeSecureBase64(value)),
28
+ decode: (value) => decodeSecureBase64(value).parseJson(),
29
29
  encode: (value) => {
30
30
  const encoded = JSON.stringify(value);
31
- if (typeof encoded !== "string") throw new TypeError("The storage value is not JSON-serializable.");
31
+ if (typeof encoded !== "string") throw new TypeError("存储值无法序列化为 JSON");
32
32
  return encodeSecureBase64(encoded);
33
33
  }
34
34
  };
@@ -48,7 +48,7 @@ const isRecord = (value) => typeof value === "object" && value !== null && !Arra
48
48
  * @throws `TypeError` 当值不是非空字符串。
49
49
  */
50
50
  const assertKey = (key) => {
51
- if (typeof key !== "string" || key.length === 0) throw new TypeError("Storage keys must be non-empty strings.");
51
+ if (typeof key !== "string" || key.length === 0) throw new TypeError("Storage 键必须是非空字符串。");
52
52
  };
53
53
  /**
54
54
  * 创建浏览器 Storage 后端。
@@ -60,7 +60,7 @@ const assertKey = (key) => {
60
60
  */
61
61
  const createWebStorageBackend = (kind) => {
62
62
  const storage = kind === "local" ? globalThis.localStorage : globalThis.sessionStorage;
63
- if (storage === void 0) throw new Error(`${kind}Storage is unavailable in the current runtime.`);
63
+ if (storage === void 0) throw new Error(`当前运行环境不支持 ${kind}Storage。`);
64
64
  return {
65
65
  getItem: (key) => storage.getItem(key),
66
66
  keys: () => {
@@ -109,17 +109,17 @@ const createUniStorageBackend = (storage) => ({
109
109
  * @throws `TypeError` 当原始值不是字符串、JSON 损坏、版本不支持或字段类型非法。
110
110
  */
111
111
  const parseStoredEnvelope = (rawValue, key) => {
112
- if (typeof rawValue !== "string") throw new TypeError(`Storage entry "${key}" is not a string.`);
112
+ if (typeof rawValue !== "string") throw new TypeError(`Storage 条目“${key}”不是字符串。`);
113
113
  try {
114
114
  const parsed = JSON.parse(rawValue);
115
- 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.");
115
+ if (!isRecord(parsed) || parsed["version"] !== 3 || typeof parsed["data"] !== "string" || !(parsed["expiresAt"] === null || typeof parsed["expiresAt"] === "number" && Number.isFinite(parsed["expiresAt"]))) throw new TypeError("不支持该存储包络。");
116
116
  return {
117
117
  data: parsed["data"],
118
118
  expiresAt: parsed["expiresAt"],
119
119
  version: 3
120
120
  };
121
121
  } catch (cause) {
122
- throw new TypeError(`Storage entry "${key}" is corrupted or unsupported.`, { cause });
122
+ throw new TypeError(`Storage 条目“${key}”已损坏或不受支持。`, { cause });
123
123
  }
124
124
  };
125
125
  /**
@@ -140,6 +140,18 @@ const createStorageArea = (backendFactory, prefix, codec, now) => {
140
140
  */
141
141
  const toStorageKey = (key) => `${prefix}${key}`;
142
142
  /**
143
+ * 解析本次读写实际使用的 Codec。
144
+ *
145
+ * @param crypto - 单次 Base64 混淆开关;省略时沿用 Area 全局 Codec。
146
+ * @returns 本次操作使用的全局、JSON 或 Base64 Codec。
147
+ * @throws `TypeError` 当 JavaScript 调用方传入非布尔值。
148
+ */
149
+ const resolveOperationCodec = (crypto) => {
150
+ if (crypto === void 0) return codec;
151
+ if (typeof crypto !== "boolean") throw new TypeError("Storage 单次 `crypto` 选项必须是布尔值。");
152
+ return crypto ? base64StorageCodec : jsonCodec;
153
+ };
154
+ /**
143
155
  * 枚举当前命名空间中的业务键。
144
156
  *
145
157
  * @param backend - 本次操作使用的后端。
@@ -148,7 +160,7 @@ const createStorageArea = (backendFactory, prefix, codec, now) => {
148
160
  */
149
161
  const listBusinessKeys = (backend) => {
150
162
  const keys = backend.keys();
151
- if (!Array.isArray(keys) || !keys.every((key) => typeof key === "string")) throw new TypeError("Storage backend keys must be strings.");
163
+ if (!Array.isArray(keys) || !keys.every((key) => typeof key === "string")) throw new TypeError("Storage 后端返回的键必须是字符串。");
152
164
  return [...new Set(keys.filter((key) => key.startsWith(prefix)).map((key) => key.slice(prefix.length)))].sort();
153
165
  };
154
166
  /**
@@ -168,7 +180,7 @@ const createStorageArea = (backendFactory, prefix, codec, now) => {
168
180
  const envelope = parseStoredEnvelope(rawValue, storageKey);
169
181
  if (envelope.expiresAt === null) return envelope;
170
182
  const timestamp = now();
171
- if (!Number.isFinite(timestamp)) throw new RangeError("Storage clock must return a finite timestamp.");
183
+ if (!Number.isFinite(timestamp)) throw new RangeError("Storage 时钟必须返回有限时间戳。");
172
184
  if (timestamp < envelope.expiresAt) return envelope;
173
185
  backend.removeItem(storageKey);
174
186
  };
@@ -178,13 +190,13 @@ const createStorageArea = (backendFactory, prefix, codec, now) => {
178
190
  const backend = backendFactory();
179
191
  for (const key of listBusinessKeys(backend)) backend.removeItem(toStorageKey(key));
180
192
  },
181
- get(key) {
193
+ get(key, options = {}) {
182
194
  const envelope = readStoredEnvelope(backendFactory(), key);
183
195
  if (envelope === void 0) return void 0;
184
196
  try {
185
- return codec.decode(envelope.data);
197
+ return resolveOperationCodec(options.crypto).decode(envelope.data);
186
198
  } catch (cause) {
187
- throw new TypeError(`Storage entry "${toStorageKey(key)}" could not be decoded.`, { cause });
199
+ throw new TypeError(`无法解码 Storage 条目“${toStorageKey(key)}”。`, { cause });
188
200
  }
189
201
  },
190
202
  has: (key) => readStoredEnvelope(backendFactory(), key) !== void 0,
@@ -209,20 +221,20 @@ const createStorageArea = (backendFactory, prefix, codec, now) => {
209
221
  },
210
222
  set(key, value, options = {}) {
211
223
  assertKey(key);
212
- if (value === void 0) throw new TypeError("Top-level undefined cannot be stored; remove the key instead.");
224
+ if (value === void 0) throw new TypeError("不能存储顶层 `undefined`,请改为移除对应的键。");
213
225
  let expiresAt = null;
214
226
  if (options.ttlMs !== void 0) {
215
- if (!Number.isFinite(options.ttlMs) || options.ttlMs <= 0) throw new RangeError("ttlMs must be a positive finite number.");
227
+ if (!Number.isFinite(options.ttlMs) || options.ttlMs <= 0) throw new RangeError("`ttlMs` 必须是大于 0 的有限数。");
216
228
  const timestamp = now();
217
- if (!Number.isFinite(timestamp) || !Number.isFinite(timestamp + options.ttlMs)) throw new RangeError("Storage expiry exceeds the supported timestamp range.");
229
+ if (!Number.isFinite(timestamp) || !Number.isFinite(timestamp + options.ttlMs)) throw new RangeError("Storage 过期时间超出支持的时间戳范围。");
218
230
  expiresAt = timestamp + options.ttlMs;
219
231
  }
220
232
  let data;
221
233
  try {
222
- data = codec.encode(value);
223
- if (typeof data !== "string") throw new TypeError("Storage codecs must return strings.");
234
+ data = resolveOperationCodec(options.crypto).encode(value);
235
+ if (typeof data !== "string") throw new TypeError("Storage Codec 必须返回字符串。");
224
236
  } catch (cause) {
225
- throw new TypeError("The storage value could not be encoded.", { cause });
237
+ throw new TypeError("无法编码存储值。", { cause });
226
238
  }
227
239
  backendFactory().setItem(toStorageKey(key), JSON.stringify({
228
240
  data,
@@ -239,7 +251,7 @@ const createStorageArea = (backendFactory, prefix, codec, now) => {
239
251
  */
240
252
  const requireStorageConfiguration = () => {
241
253
  if (activeConfiguration === void 0) configureStorage();
242
- if (activeConfiguration === void 0) throw new Error("Storage configuration could not be initialized.");
254
+ if (activeConfiguration === void 0) throw new Error("无法初始化 Storage 配置。");
243
255
  return activeConfiguration;
244
256
  };
245
257
  /**
@@ -258,7 +270,7 @@ const createStorageAreaProxy = (select, name) => {
258
270
  */
259
271
  const getArea = () => {
260
272
  const area = select(requireStorageConfiguration());
261
- if (area === void 0) throw new Error(`${name} is unavailable in uni-app.`);
273
+ if (area === void 0) throw new Error(`uni-app 中不支持 ${name}。`);
262
274
  return area;
263
275
  };
264
276
  return {
@@ -268,7 +280,7 @@ const createStorageAreaProxy = (select, name) => {
268
280
  clear: () => {
269
281
  getArea().clear();
270
282
  },
271
- get: (key) => getArea().get(key),
283
+ get: (key, options) => getArea().get(key, options),
272
284
  has: (key) => getArea().has(key),
273
285
  keys: () => getArea().keys(),
274
286
  pruneExpired: () => getArea().pruneExpired(),
@@ -298,13 +310,13 @@ const Session = createStorageAreaProxy((configuration) => configuration.session,
298
310
  */
299
311
  function configureStorage(options = {}) {
300
312
  const prefix = options.prefix ?? "fast__";
301
- if (typeof prefix !== "string" || prefix.length === 0) throw new TypeError("Storage prefix must be a non-empty string.");
302
- if (options.codec !== void 0 && options.crypto === true) throw new TypeError("Storage codec and crypto options cannot be used together.");
313
+ if (typeof prefix !== "string" || prefix.length === 0) throw new TypeError("Storage 前缀必须是非空字符串。");
314
+ if (options.codec !== void 0 && options.crypto === true) throw new TypeError("Storage Codec 和加密选项不能同时使用。");
303
315
  const codec = options.codec ?? (options.crypto === true ? base64StorageCodec : jsonCodec);
304
316
  const now = options.now ?? Date.now;
305
317
  if (activeConfiguration !== void 0) {
306
318
  if (activeConfiguration.prefix === prefix && activeConfiguration.codec === codec && activeConfiguration.now === now) return;
307
- throw new Error("Storage has already been configured with different options.");
319
+ throw new Error("Storage 已使用其他选项完成配置。");
308
320
  }
309
321
  const uni = getGlobalUniStorage();
310
322
  const configuration = {
@@ -1 +1 @@
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/**\n * 读取并校验当前运行时的全局 uni-app 同步 Storage。\n *\n * @returns 检测到 uni-app 时返回同步 Storage;普通浏览器环境返回 `undefined`。\n * @throws `TypeError` 当全局 `uni` 存在但缺少本库需要的同步 Storage 方法。\n */\nconst getGlobalUniStorage = (): UniStorageLike | undefined => {\n\tconst value: unknown = Reflect.get(globalThis, \"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\" ? globalThis.localStorage : globalThis.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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"}
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 StorageReadOptions {\n\t/**\n\t * 仅覆盖本次读取使用的 Codec;`true` 使用 Base64 混淆,`false` 使用 JSON,省略时使用全局配置。\n\t * 必须与写入该条目时使用的单次设置一致。\n\t */\n\tcrypto?: boolean;\n}\n\n/** 单次 Storage 写入配置。 */\nexport interface StorageWriteOptions extends StorageReadOptions {\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 * @param options - 可选的单次 Base64 混淆开关;必须与写入时一致。\n\t * @returns 解码后的值;未传泛型时静态类型默认为 `string`,键缺失或过期时返回 `undefined`。\n\t * @throws 当键非法、包络损坏、Codec 解码失败或后端不可用时抛出错误。\n\t */\n\tget: <Value = string>(key: string, options?: StorageReadOptions) => 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 与 Base64 混淆开关。\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/**\n * 读取并校验当前运行时的全局 uni-app 同步 Storage。\n *\n * @returns 检测到 uni-app 时返回同步 Storage;普通浏览器环境返回 `undefined`。\n * @throws `TypeError` 当全局 `uni` 存在但缺少本库需要的同步 Storage 方法。\n */\nconst getGlobalUniStorage = (): UniStorageLike | undefined => {\n\tconst value: unknown = Reflect.get(globalThis, \"uni\");\n\tif (value === undefined) return undefined;\n\tif ((typeof value !== \"object\" && typeof value !== \"function\") || value === null) {\n\t\tthrow new TypeError(\"全局 uni 对象未提供同步 Storage API。\");\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(\"全局 uni 对象未提供同步 Storage API。\");\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(\"存储值无法序列化为 JSON。\");\n\t\treturn encoded;\n\t},\n};\n\n/** Base64 混淆 Codec;只隐藏明文外观,不提供加密、完整性或认证。 */\nexport const base64StorageCodec: StorageCodec = {\n\tdecode: (value): unknown => decodeSecureBase64(value).parseJson(),\n\tencode: (value): string => {\n\t\tconst encoded: unknown = JSON.stringify(value);\n\t\tif (typeof encoded !== \"string\") throw new TypeError(\"存储值无法序列化为 JSON。\");\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 键必须是非空字符串。\");\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\" ? globalThis.localStorage : globalThis.sessionStorage;\n\tif (storage === undefined) throw new Error(`当前运行环境不支持 ${kind}Storage。`);\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 条目“${key}”不是字符串。`);\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(\"不支持该存储包络。\");\n\t\t}\n\t\treturn { data: parsed[\"data\"], expiresAt: parsed[\"expiresAt\"], version: 3 };\n\t} catch (cause) {\n\t\tthrow new TypeError(`Storage 条目“${key}”已损坏或不受支持。`, { 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 * 解析本次读写实际使用的 Codec。\n\t *\n\t * @param crypto - 单次 Base64 混淆开关;省略时沿用 Area 全局 Codec。\n\t * @returns 本次操作使用的全局、JSON 或 Base64 Codec。\n\t * @throws `TypeError` 当 JavaScript 调用方传入非布尔值。\n\t */\n\tconst resolveOperationCodec = (crypto: boolean | undefined): StorageCodec => {\n\t\tif (crypto === undefined) return codec;\n\t\tif (typeof crypto !== \"boolean\") throw new TypeError(\"Storage 单次 `crypto` 选项必须是布尔值。\");\n\t\treturn crypto ? base64StorageCodec : jsonCodec;\n\t};\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 后端返回的键必须是字符串。\");\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 时钟必须返回有限时间戳。\");\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, options: StorageReadOptions = {}): 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 resolveOperationCodec(options.crypto).decode(envelope.data) as Value;\n\t\t\t} catch (cause) {\n\t\t\t\tthrow new TypeError(`无法解码 Storage 条目“${toStorageKey(key)}”。`, { 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(\"不能存储顶层 `undefined`,请改为移除对应的键。\");\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` 必须是大于 0 的有限数。\");\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 过期时间超出支持的时间戳范围。\");\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 = resolveOperationCodec(options.crypto).encode(value);\n\t\t\t\tif (typeof data !== \"string\") throw new TypeError(\"Storage Codec 必须返回字符串。\");\n\t\t\t} catch (cause) {\n\t\t\t\tthrow new TypeError(\"无法编码存储值。\", { 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 配置。\");\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(`uni-app 中不支持 ${name}。`);\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, options?: StorageReadOptions): Value | undefined => getArea().get<Value>(key, options),\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 前缀必须是非空字符串。\");\n\t}\n\tif (options.codec !== undefined && options.crypto === true) {\n\t\tthrow new TypeError(\"Storage 的 Codec 和加密选项不能同时使用。\");\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 已使用其他选项完成配置。\");\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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"}
@@ -37,7 +37,7 @@ const createUuidV4FromBytes = (bytes) => {
37
37
  */
38
38
  const splitGraphemes = (value, locale) => {
39
39
  const Segmenter = globalThis.Intl?.Segmenter;
40
- if (typeof Segmenter !== "function") throw new Error("Intl.Segmenter is unavailable in the current runtime.");
40
+ if (typeof Segmenter !== "function") throw new Error("当前运行环境不支持 Intl.Segmenter");
41
41
  const segmenter = new Segmenter(locale ?? defaultStringLocale, { granularity: "grapheme" });
42
42
  return Array.from(segmenter.segment(value), ({ segment }) => segment);
43
43
  };
@@ -50,7 +50,7 @@ const splitGraphemes = (value, locale) => {
50
50
  * @throws `URIError` 当任一层包含非法百分号序列;深度非法时抛出 `RangeError`。
51
51
  */
52
52
  function decodeURIComponentRepeatedly(value, maxDepth = 10) {
53
- if (!Number.isSafeInteger(maxDepth) || maxDepth < 0) throw new RangeError("maxDepth must be a non-negative safe integer.");
53
+ if (!Number.isSafeInteger(maxDepth) || maxDepth < 0) throw new RangeError("`maxDepth` 必须是非负安全整数。");
54
54
  let decoded = value;
55
55
  for (let index = 0; index < maxDepth; index += 1) {
56
56
  const next = decodeURIComponent(decoded);
@@ -186,7 +186,7 @@ function kebabCase(value, locale) {
186
186
  * `Intl.Segmenter` 时抛出 `Error`。
187
187
  */
188
188
  function truncateGraphemes(value, maxLength, suffix = "…", locale) {
189
- if (!Number.isSafeInteger(maxLength) || maxLength < 0) throw new RangeError("maxLength must be a non-negative safe integer.");
189
+ if (!Number.isSafeInteger(maxLength) || maxLength < 0) throw new RangeError("`maxLength` 必须是非负安全整数。");
190
190
  const segments = splitGraphemes(value, locale);
191
191
  return segments.length > maxLength ? segments.slice(0, maxLength).join("") + suffix : value;
192
192
  }
@@ -202,14 +202,14 @@ function truncateGraphemes(value, maxLength, suffix = "…", locale) {
202
202
  async function copy(value) {
203
203
  const uni = Reflect.get(globalThis, "uni");
204
204
  if (uni !== void 0) {
205
- if (typeof uni !== "object" && typeof uni !== "function" || uni === null) throw new TypeError("The global uni object does not provide setClipboardData.");
205
+ if (typeof uni !== "object" && typeof uni !== "function" || uni === null) throw new TypeError("全局 uni 对象未提供 `setClipboardData`。");
206
206
  const setClipboardData = Reflect.get(uni, "setClipboardData");
207
- if (typeof setClipboardData !== "function") throw new TypeError("The global uni object does not provide setClipboardData.");
207
+ if (typeof setClipboardData !== "function") throw new TypeError("全局 uni 对象未提供 `setClipboardData`。");
208
208
  await new Promise((resolve, reject) => {
209
209
  Reflect.apply(setClipboardData, uni, [{
210
210
  data: value,
211
211
  fail: (error) => {
212
- reject(error instanceof Error ? error : new Error("Failed to copy text to the clipboard.", { cause: error }));
212
+ reject(error instanceof Error ? error : new Error("文本复制到剪贴板失败。", { cause: error }));
213
213
  },
214
214
  success: resolve
215
215
  }]);
@@ -222,7 +222,7 @@ async function copy(value) {
222
222
  return;
223
223
  }
224
224
  const document = globalThis.document;
225
- if (typeof document?.createElement !== "function" || document.body === null || typeof document.execCommand !== "function") throw new Error("Clipboard access is unavailable in the current runtime.");
225
+ if (typeof document?.createElement !== "function" || document.body === null || typeof document.execCommand !== "function") throw new Error("当前运行环境不支持访问剪贴板。");
226
226
  const textarea = document.createElement("textarea");
227
227
  textarea.value = value;
228
228
  textarea.style.left = "-999999px";
@@ -230,7 +230,7 @@ async function copy(value) {
230
230
  textarea.style.position = "fixed";
231
231
  textarea.style.top = "-999999px";
232
232
  document.body.appendChild(textarea);
233
- let copied = false;
233
+ let copied;
234
234
  try {
235
235
  textarea.focus();
236
236
  textarea.select();
@@ -238,7 +238,7 @@ async function copy(value) {
238
238
  } finally {
239
239
  textarea.remove();
240
240
  }
241
- if (!copied) throw new Error("Failed to copy text to the clipboard.");
241
+ if (!copied) throw new Error("文本复制到剪贴板失败。");
242
242
  }
243
243
  /**
244
244
  * 生成随机字符串。
@@ -250,11 +250,11 @@ async function copy(value) {
250
250
  * @throws `RangeError` 当长度或字母表非法。
251
251
  */
252
252
  function randomString(length, alphabet = defaultRandomAlphabet) {
253
- if (!Number.isSafeInteger(length) || length < 0 || length > maximumRandomStringLength) throw new RangeError(`length must be a safe integer from 0 through ${maximumRandomStringLength}.`);
253
+ if (!Number.isSafeInteger(length) || length < 0 || length > maximumRandomStringLength) throw new RangeError(`\`length\` 必须是 0 ${maximumRandomStringLength} 之间的安全整数。`);
254
254
  const characters = Array.from(alphabet);
255
- if (characters.length === 0) throw new RangeError("alphabet cannot be empty.");
256
- if (new Set(characters).size !== characters.length) throw new RangeError("alphabet cannot contain duplicate characters.");
257
- if (characters.length > 4294967296) throw new RangeError("alphabet cannot contain more than 2^32 characters.");
255
+ if (characters.length === 0) throw new RangeError("`alphabet` 不能为空。");
256
+ if (new Set(characters).size !== characters.length) throw new RangeError("`alphabet` 不能包含重复字符。");
257
+ if (characters.length > 4294967296) throw new RangeError("`alphabet` 不能包含超过 2^32 个字符。");
258
258
  if (length === 0) return "";
259
259
  const acceptanceLimit = Math.floor(uint32Range / characters.length) * characters.length;
260
260
  const result = [];
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/string/index.ts"],"sourcesContent":["const defaultRandomAlphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\nconst defaultStringLocale = \"en-US\";\nconst maximumRandomStringLength = 1_000_000;\nconst maximumRandomValuesPerBatch = 16_384;\nconst uint32Range = 0x1_0000_0000;\nconst uuidV4Pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;\n\n/** 查询字符串解析结果;重复键保留为数组,不存在的键读取为 `undefined`。 */\nexport type ParsedQueryParameters = Record<string, string | string[] | undefined>;\n\n/** 大小写与字素分割可接受的显式语言;省略时固定使用 `en-US` 以保持输出稳定。 */\nexport type StringLocale = string | readonly string[] | undefined;\n\n/** 使用 Web Crypto 填充随机值,能力缺失时回退到 `Math.random()`。 */\nconst fillRandomValues = (values: Uint8Array<ArrayBuffer> | Uint32Array<ArrayBuffer>): void => {\n\tconst crypto = globalThis.crypto;\n\tif (typeof crypto?.getRandomValues === \"function\") {\n\t\tcrypto.getRandomValues(values);\n\t\treturn;\n\t}\n\tconst range = values.BYTES_PER_ELEMENT === Uint8Array.BYTES_PER_ELEMENT ? 0x100 : uint32Range;\n\tfor (let index = 0; index < values.length; index += 1) values[index] = Math.floor(Math.random() * range);\n};\n\n/**\n * 从随机字节创建 UUID v4。\n *\n * @param bytes - 长度至少为 16 的随机字节;Version 与 Variant 位会被原地修改。\n * @returns 小写、带连字符的 RFC 4122 UUID v4。\n */\nconst createUuidV4FromBytes = (bytes: Uint8Array): string => {\n\tbytes[6] = ((bytes[6] ?? 0) & 15) | 64;\n\tbytes[8] = ((bytes[8] ?? 0) & 63) | 128;\n\tconst hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n};\n\n/**\n * 按用户可见字素切分文本。\n *\n * @param value - 待切分字符串。\n * @param locale - Segmenter 使用的显式语言;省略时使用固定默认值。\n * @returns 保留组合 Emoji、变音符号和连接序列的字素数组。\n * @throws `Error` 当平台缺少 `Intl.Segmenter`。\n */\nconst splitGraphemes = (value: string, locale: StringLocale): string[] => {\n\tconst Segmenter = globalThis.Intl?.Segmenter;\n\tif (typeof Segmenter !== \"function\") {\n\t\tthrow new Error(\"Intl.Segmenter is unavailable in the current runtime.\");\n\t}\n\tconst segmenter = new Segmenter(locale ?? defaultStringLocale, { granularity: \"grapheme\" });\n\treturn Array.from(segmenter.segment(value), ({ segment }) => segment);\n};\n\n/**\n * 重复执行 URI 组件解码,直到值稳定或达到深度上限。\n *\n * @param value - 不包含 URI 路径语义的编码组件。\n * @param maxDepth - 最大解码次数,默认 `10`。\n * @returns 解码稳定或达到上限后的组件文本。\n * @throws `URIError` 当任一层包含非法百分号序列;深度非法时抛出 `RangeError`。\n */\nexport function decodeURIComponentRepeatedly(value: string, maxDepth = 10): string {\n\tif (!Number.isSafeInteger(maxDepth) || maxDepth < 0) throw new RangeError(\"maxDepth must be a non-negative safe integer.\");\n\tlet decoded = value;\n\tfor (let index = 0; index < maxDepth; index += 1) {\n\t\tconst next = decodeURIComponent(decoded);\n\t\tif (next === decoded) break;\n\t\tdecoded = next;\n\t}\n\treturn decoded;\n}\n\n/**\n * 解析带 `://` 的绝对 URL、`?query` 或纯查询字符串。\n *\n * @remarks 纯查询字符串值中的未编码 `?` 会作为值内容保留;片段标识及其后内容被忽略。\n * @param input - 完整 URL、带前导问号或不带前导问号的查询文本。\n * @returns 重复键对应字符串数组,空值保留为空字符串。\n */\nexport function parseQueryString(input: string): ParsedQueryParameters {\n\tconst fragmentStart = input.indexOf(\"#\");\n\tconst withoutFragment = fragmentStart < 0 ? input : input.slice(0, fragmentStart);\n\tconst isAbsoluteUrl = /^[a-z][a-z\\d+.-]*:\\/\\//iu.test(withoutFragment);\n\tconst queryStart = withoutFragment.indexOf(\"?\");\n\tif (isAbsoluteUrl && queryStart < 0) return {};\n\tconst query = isAbsoluteUrl ? withoutFragment.slice(queryStart + 1) : withoutFragment.replace(/^\\?/u, \"\");\n\tconst result: ParsedQueryParameters = {};\n\tfor (const [key, value] of new URLSearchParams(query)) {\n\t\tconst existing = Object.hasOwn(result, key) ? result[key] : undefined;\n\t\tif (existing === undefined) {\n\t\t\t// defineProperty 让 `__proto__` 成为普通自有键,不触发 Object.prototype Setter。\n\t\t\tObject.defineProperty(result, key, { configurable: true, enumerable: true, value, writable: true });\n\t\t} else if (Array.isArray(existing)) existing.push(value);\n\t\telse Object.defineProperty(result, key, { configurable: true, enumerable: true, value: [existing, value], writable: true });\n\t}\n\treturn result;\n}\n\n/**\n * 判断文本是否为任意合法 JSON 值,包括标量与 `null`。\n *\n * @param value - 待解析文本;纯空白不视为 JSON。\n * @returns `JSON.parse` 能完整解析时返回 `true`。\n */\nexport function isValidJson(value: string): boolean {\n\tif (value.trim().length === 0) return false;\n\ttry {\n\t\tJSON.parse(value);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * 按大小写边界、连字符、下划线与空白切分单词。\n *\n * @example `XMLHttp_request` 返回 `[\"XML\", \"Http\", \"request\"]`。\n * @param value - 待拆分文本。\n * @returns 删除空项、保持输入顺序的单词数组。\n */\nexport function splitWords(value: string): string[] {\n\treturn value\n\t\t.trim()\n\t\t.replace(/(\\p{Ll}|\\p{N})(\\p{Lu})/gu, \"$1 $2\")\n\t\t.replace(/(\\p{Lu})(\\p{Lu}\\p{Ll})/gu, \"$1 $2\")\n\t\t.split(/[\\s_-]+/u)\n\t\t.filter((part) => part.length > 0);\n}\n\n/**\n * 将首个 Unicode 码点转为大写。\n *\n * @param value - 输入文本;空字符串保持为空。\n * @param locale - 显式语言,默认固定为 `en-US`。\n * @returns 首个 Unicode 码点转换后的文本。\n */\nexport function upperFirst(value: string, locale?: StringLocale): string {\n\tconst characters = Array.from(value);\n\tconst first = characters.shift();\n\treturn first === undefined ? \"\" : first.toLocaleUpperCase(locale ?? defaultStringLocale) + characters.join(\"\");\n}\n\n/**\n * 将首个 Unicode 码点转为小写。\n *\n * @param value - 输入文本;空字符串保持为空。\n * @param locale - 显式语言,默认固定为 `en-US`。\n * @returns 首个 Unicode 码点转换后的文本。\n */\nexport function lowerFirst(value: string, locale?: StringLocale): string {\n\tconst characters = Array.from(value);\n\tconst first = characters.shift();\n\treturn first === undefined ? \"\" : first.toLocaleLowerCase(locale ?? defaultStringLocale) + characters.join(\"\");\n}\n\n/**\n * 将文本转换为 camelCase。\n *\n * @param value - 由大小写、连字符、下划线或空白分隔的文本。\n * @param locale - 大小写转换使用的语言,默认固定为 `en-US`。\n * @returns camelCase 文本。\n */\nexport function camelCase(value: string, locale?: StringLocale): string {\n\treturn splitWords(value)\n\t\t.map((part, index) => {\n\t\t\tconst normalized = part.toLocaleLowerCase(locale ?? defaultStringLocale);\n\t\t\treturn index === 0 ? normalized : upperFirst(normalized, locale);\n\t\t})\n\t\t.join(\"\");\n}\n\n/**\n * 将文本转换为 PascalCase。\n *\n * @param value - 参数语义与 {@link camelCase} 一致。\n * @param locale - 大小写转换使用的显式语言。\n * @returns PascalCase 文本。\n */\nexport function pascalCase(value: string, locale?: StringLocale): string {\n\treturn upperFirst(camelCase(value, locale), locale);\n}\n\n/**\n * 将文本转换为 kebab-case。\n *\n * @param value - 参数语义与 {@link camelCase} 一致。\n * @param locale - 大小写转换使用的显式语言。\n * @returns kebab-case 文本。\n */\nexport function kebabCase(value: string, locale?: StringLocale): string {\n\treturn splitWords(value)\n\t\t.map((part) => part.toLocaleLowerCase(locale ?? defaultStringLocale))\n\t\t.join(\"-\");\n}\n\n/**\n * 按 Unicode 字素簇截断文本,避免拆开 emoji、组合音标或代理对。\n *\n * @param value - 输入文本。\n * @param maxLength - 保留的最大字素簇数量。\n * @param suffix - 被截断时追加的文本,默认单字符省略号 `…`;不计入上限。\n * @param locale - 字素分割语言,默认固定为 `en-US`。\n * @returns 未超限时返回原字符串,否则返回截断内容与后缀。\n * @throws `RangeError` 当 `maxLength` 不是非负安全整数或 Locale 无效;缺少\n * `Intl.Segmenter` 时抛出 `Error`。\n */\nexport function truncateGraphemes(value: string, maxLength: number, suffix = \"…\", locale?: StringLocale): string {\n\tif (!Number.isSafeInteger(maxLength) || maxLength < 0) throw new RangeError(\"maxLength must be a non-negative safe integer.\");\n\tconst segments = splitGraphemes(value, locale);\n\treturn segments.length > maxLength ? segments.slice(0, maxLength).join(\"\") + suffix : value;\n}\n\n/**\n * 把文本复制到系统剪贴板。\n *\n * @remarks uni-app 使用 `setClipboardData`;浏览器优先使用 Clipboard API,并在该 API 不可用时\n * 回退到 `document.execCommand(\"copy\")`。平台拒绝访问剪贴板时不会静默忽略错误。\n * @param value - 要复制的文本。\n * @returns 复制完成后兑现的 Promise。\n * @throws `Error` 当运行时没有可用的剪贴板能力或复制失败。\n */\nexport async function copy(value: string): Promise<void> {\n\tconst uni: unknown = Reflect.get(globalThis, \"uni\");\n\tif (uni !== undefined) {\n\t\tif ((typeof uni !== \"object\" && typeof uni !== \"function\") || uni === null) {\n\t\t\tthrow new TypeError(\"The global uni object does not provide setClipboardData.\");\n\t\t}\n\t\tconst setClipboardData: unknown = Reflect.get(uni, \"setClipboardData\");\n\t\tif (typeof setClipboardData !== \"function\") throw new TypeError(\"The global uni object does not provide setClipboardData.\");\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tReflect.apply(setClipboardData, uni, [\n\t\t\t\t{\n\t\t\t\t\tdata: value,\n\t\t\t\t\tfail: (error: unknown): void => {\n\t\t\t\t\t\treject(error instanceof Error ? error : new Error(\"Failed to copy text to the clipboard.\", { cause: error }));\n\t\t\t\t\t},\n\t\t\t\t\tsuccess: resolve,\n\t\t\t\t},\n\t\t\t]);\n\t\t});\n\t\treturn;\n\t}\n\n\tconst clipboard = globalThis.navigator?.clipboard;\n\tif (globalThis.isSecureContext === true && typeof clipboard?.writeText === \"function\") {\n\t\tawait clipboard.writeText(value);\n\t\treturn;\n\t}\n\n\tconst document = globalThis.document;\n\tif (typeof document?.createElement !== \"function\" || document.body === null || typeof document.execCommand !== \"function\") {\n\t\tthrow new Error(\"Clipboard access is unavailable in the current runtime.\");\n\t}\n\tconst textarea = document.createElement(\"textarea\");\n\ttextarea.value = value;\n\ttextarea.style.left = \"-999999px\";\n\ttextarea.style.opacity = \"0\";\n\ttextarea.style.position = \"fixed\";\n\ttextarea.style.top = \"-999999px\";\n\tdocument.body.appendChild(textarea);\n\tlet copied = false;\n\ttry {\n\t\ttextarea.focus();\n\t\ttextarea.select();\n\t\tcopied = document.execCommand(\"copy\");\n\t} finally {\n\t\ttextarea.remove();\n\t}\n\tif (!copied) throw new Error(\"Failed to copy text to the clipboard.\");\n}\n\n/**\n * 生成随机字符串。\n *\n * @remarks 优先使用 Web Crypto;平台缺少安全随机能力时回退到 `Math.random()`。\n * @param length - 字符数量,必须是 0 至 1,000,000 的安全整数。\n * @param alphabet - 不得为空、包含重复字符或超过 2^32 个 Unicode 码点。\n * @returns 由 `alphabet` 中 Unicode 码点组成的随机文本。\n * @throws `RangeError` 当长度或字母表非法。\n */\nexport function randomString(length: number, alphabet: string = defaultRandomAlphabet): string {\n\tif (!Number.isSafeInteger(length) || length < 0 || length > maximumRandomStringLength) {\n\t\tthrow new RangeError(`length must be a safe integer from 0 through ${maximumRandomStringLength}.`);\n\t}\n\tconst characters = Array.from(alphabet);\n\tif (characters.length === 0) throw new RangeError(\"alphabet cannot be empty.\");\n\tif (new Set(characters).size !== characters.length) throw new RangeError(\"alphabet cannot contain duplicate characters.\");\n\tif (characters.length > 0x1_0000_0000) throw new RangeError(\"alphabet cannot contain more than 2^32 characters.\");\n\tif (length === 0) return \"\";\n\n\t// 丢弃不能平均映射到字母表的尾部区间,避免 `%` 造成前部字符概率偏高。\n\tconst acceptanceLimit = Math.floor(uint32Range / characters.length) * characters.length;\n\tconst result: string[] = [];\n\twhile (result.length < length) {\n\t\tconst remaining = length - result.length;\n\t\t// 分批填充可控制临时内存,并避开 Web Crypto 单次随机数组大小限制。\n\t\tconst samples = new Uint32Array(Math.min(remaining, maximumRandomValuesPerBatch));\n\t\tfillRandomValues(samples);\n\t\tfor (const sample of samples) {\n\t\t\tif (sample >= acceptanceLimit) continue;\n\t\t\tconst character = characters[sample % characters.length];\n\t\t\tif (character === undefined) continue;\n\t\t\tresult.push(character);\n\t\t\tif (result.length === length) break;\n\t\t}\n\t}\n\treturn result.join(\"\");\n}\n\n/**\n * 生成 RFC 4122 version 4 UUID。\n *\n * @remarks 优先使用 Web Crypto;平台缺少安全随机能力时回退到 `Math.random()`。\n * 该 UUID 适合普通唯一标识,不应作为安全令牌或秘密。\n * @returns 小写、带连字符的 UUID v4。\n */\nexport function generateUuidV4(): string {\n\tconst crypto = globalThis.crypto;\n\tif (typeof crypto?.randomUUID === \"function\") return crypto.randomUUID();\n\tconst bytes = new Uint8Array(16);\n\tfillRandomValues(bytes);\n\treturn createUuidV4FromBytes(bytes);\n}\n\n/**\n * 判断字符串是否为 RFC 4122 version 4 UUID。\n *\n * @param value - 待验证文本;十六进制字母大小写均可。\n * @returns 版本位与 Variant 位均正确时返回 `true`。\n */\nexport function isUuidV4(value: string): boolean {\n\treturn uuidV4Pattern.test(value);\n}\n\n/**\n * 转义 HTML 文本上下文中的五个特殊字符。\n *\n * @remarks 这不是 HTML 清洗器,不能让不可信文本安全进入 URL、CSS、脚本或属性名上下文。\n * @param value - 将作为 HTML 文本节点内容的字符串。\n * @returns 转义 `&`、`<`、`>`、双引号与单引号后的文本。\n */\nexport function escapeHtml(value: string): string {\n\treturn value.replace(/[&<>\"']/gu, (character) => {\n\t\tswitch (character) {\n\t\t\tcase \"&\":\n\t\t\t\treturn \"&amp;\";\n\t\t\tcase \"<\":\n\t\t\t\treturn \"&lt;\";\n\t\t\tcase \">\":\n\t\t\t\treturn \"&gt;\";\n\t\t\tcase '\"':\n\t\t\t\treturn \"&quot;\";\n\t\t\tdefault:\n\t\t\t\treturn \"&#39;\";\n\t\t}\n\t});\n}\n\n/**\n * 把连续 Unicode 空白折叠为单个空格并删除两端空白。\n *\n * @param value - 输入文本。\n * @returns 规范化后的文本;全空白输入返回空字符串。\n */\nexport function normalizeWhitespace(value: string): string {\n\treturn value.trim().replace(/\\s+/gu, \" \");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/string/index.ts"],"sourcesContent":["const defaultRandomAlphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\nconst defaultStringLocale = \"en-US\";\nconst maximumRandomStringLength = 1_000_000;\nconst maximumRandomValuesPerBatch = 16_384;\nconst uint32Range = 0x1_0000_0000;\nconst uuidV4Pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;\n\n/** 查询字符串解析结果;重复键保留为数组,不存在的键读取为 `undefined`。 */\nexport type ParsedQueryParameters = Record<string, string | string[] | undefined>;\n\n/** 大小写与字素分割可接受的显式语言;省略时固定使用 `en-US` 以保持输出稳定。 */\nexport type StringLocale = string | readonly string[] | undefined;\n\n/** 使用 Web Crypto 填充随机值,能力缺失时回退到 `Math.random()`。 */\nconst fillRandomValues = (values: Uint8Array<ArrayBuffer> | Uint32Array<ArrayBuffer>): void => {\n\tconst crypto = globalThis.crypto;\n\tif (typeof crypto?.getRandomValues === \"function\") {\n\t\tcrypto.getRandomValues(values);\n\t\treturn;\n\t}\n\tconst range = values.BYTES_PER_ELEMENT === Uint8Array.BYTES_PER_ELEMENT ? 0x100 : uint32Range;\n\tfor (let index = 0; index < values.length; index += 1) values[index] = Math.floor(Math.random() * range);\n};\n\n/**\n * 从随机字节创建 UUID v4。\n *\n * @param bytes - 长度至少为 16 的随机字节;Version 与 Variant 位会被原地修改。\n * @returns 小写、带连字符的 RFC 4122 UUID v4。\n */\nconst createUuidV4FromBytes = (bytes: Uint8Array): string => {\n\tbytes[6] = ((bytes[6] ?? 0) & 15) | 64;\n\tbytes[8] = ((bytes[8] ?? 0) & 63) | 128;\n\tconst hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n};\n\n/**\n * 按用户可见字素切分文本。\n *\n * @param value - 待切分字符串。\n * @param locale - Segmenter 使用的显式语言;省略时使用固定默认值。\n * @returns 保留组合 Emoji、变音符号和连接序列的字素数组。\n * @throws `Error` 当平台缺少 `Intl.Segmenter`。\n */\nconst splitGraphemes = (value: string, locale: StringLocale): string[] => {\n\tconst Segmenter = globalThis.Intl?.Segmenter;\n\tif (typeof Segmenter !== \"function\") {\n\t\tthrow new Error(\"当前运行环境不支持 Intl.Segmenter。\");\n\t}\n\tconst segmenter = new Segmenter(locale ?? defaultStringLocale, { granularity: \"grapheme\" });\n\treturn Array.from(segmenter.segment(value), ({ segment }) => segment);\n};\n\n/**\n * 重复执行 URI 组件解码,直到值稳定或达到深度上限。\n *\n * @param value - 不包含 URI 路径语义的编码组件。\n * @param maxDepth - 最大解码次数,默认 `10`。\n * @returns 解码稳定或达到上限后的组件文本。\n * @throws `URIError` 当任一层包含非法百分号序列;深度非法时抛出 `RangeError`。\n */\nexport function decodeURIComponentRepeatedly(value: string, maxDepth = 10): string {\n\tif (!Number.isSafeInteger(maxDepth) || maxDepth < 0) throw new RangeError(\"`maxDepth` 必须是非负安全整数。\");\n\tlet decoded = value;\n\tfor (let index = 0; index < maxDepth; index += 1) {\n\t\tconst next = decodeURIComponent(decoded);\n\t\tif (next === decoded) break;\n\t\tdecoded = next;\n\t}\n\treturn decoded;\n}\n\n/**\n * 解析带 `://` 的绝对 URL、`?query` 或纯查询字符串。\n *\n * @remarks 纯查询字符串值中的未编码 `?` 会作为值内容保留;片段标识及其后内容被忽略。\n * @param input - 完整 URL、带前导问号或不带前导问号的查询文本。\n * @returns 重复键对应字符串数组,空值保留为空字符串。\n */\nexport function parseQueryString(input: string): ParsedQueryParameters {\n\tconst fragmentStart = input.indexOf(\"#\");\n\tconst withoutFragment = fragmentStart < 0 ? input : input.slice(0, fragmentStart);\n\tconst isAbsoluteUrl = /^[a-z][a-z\\d+.-]*:\\/\\//iu.test(withoutFragment);\n\tconst queryStart = withoutFragment.indexOf(\"?\");\n\tif (isAbsoluteUrl && queryStart < 0) return {};\n\tconst query = isAbsoluteUrl ? withoutFragment.slice(queryStart + 1) : withoutFragment.replace(/^\\?/u, \"\");\n\tconst result: ParsedQueryParameters = {};\n\tfor (const [key, value] of new URLSearchParams(query)) {\n\t\tconst existing = Object.hasOwn(result, key) ? result[key] : undefined;\n\t\tif (existing === undefined) {\n\t\t\t// defineProperty 让 `__proto__` 成为普通自有键,不触发 Object.prototype Setter。\n\t\t\tObject.defineProperty(result, key, { configurable: true, enumerable: true, value, writable: true });\n\t\t} else if (Array.isArray(existing)) existing.push(value);\n\t\telse Object.defineProperty(result, key, { configurable: true, enumerable: true, value: [existing, value], writable: true });\n\t}\n\treturn result;\n}\n\n/**\n * 判断文本是否为任意合法 JSON 值,包括标量与 `null`。\n *\n * @param value - 待解析文本;纯空白不视为 JSON。\n * @returns `JSON.parse` 能完整解析时返回 `true`。\n */\nexport function isValidJson(value: string): boolean {\n\tif (value.trim().length === 0) return false;\n\ttry {\n\t\tJSON.parse(value);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * 按大小写边界、连字符、下划线与空白切分单词。\n *\n * @example `XMLHttp_request` 返回 `[\"XML\", \"Http\", \"request\"]`。\n * @param value - 待拆分文本。\n * @returns 删除空项、保持输入顺序的单词数组。\n */\nexport function splitWords(value: string): string[] {\n\treturn value\n\t\t.trim()\n\t\t.replace(/(\\p{Ll}|\\p{N})(\\p{Lu})/gu, \"$1 $2\")\n\t\t.replace(/(\\p{Lu})(\\p{Lu}\\p{Ll})/gu, \"$1 $2\")\n\t\t.split(/[\\s_-]+/u)\n\t\t.filter((part) => part.length > 0);\n}\n\n/**\n * 将首个 Unicode 码点转为大写。\n *\n * @param value - 输入文本;空字符串保持为空。\n * @param locale - 显式语言,默认固定为 `en-US`。\n * @returns 首个 Unicode 码点转换后的文本。\n */\nexport function upperFirst(value: string, locale?: StringLocale): string {\n\tconst characters = Array.from(value);\n\tconst first = characters.shift();\n\treturn first === undefined ? \"\" : first.toLocaleUpperCase(locale ?? defaultStringLocale) + characters.join(\"\");\n}\n\n/**\n * 将首个 Unicode 码点转为小写。\n *\n * @param value - 输入文本;空字符串保持为空。\n * @param locale - 显式语言,默认固定为 `en-US`。\n * @returns 首个 Unicode 码点转换后的文本。\n */\nexport function lowerFirst(value: string, locale?: StringLocale): string {\n\tconst characters = Array.from(value);\n\tconst first = characters.shift();\n\treturn first === undefined ? \"\" : first.toLocaleLowerCase(locale ?? defaultStringLocale) + characters.join(\"\");\n}\n\n/**\n * 将文本转换为 camelCase。\n *\n * @param value - 由大小写、连字符、下划线或空白分隔的文本。\n * @param locale - 大小写转换使用的语言,默认固定为 `en-US`。\n * @returns camelCase 文本。\n */\nexport function camelCase(value: string, locale?: StringLocale): string {\n\treturn splitWords(value)\n\t\t.map((part, index) => {\n\t\t\tconst normalized = part.toLocaleLowerCase(locale ?? defaultStringLocale);\n\t\t\treturn index === 0 ? normalized : upperFirst(normalized, locale);\n\t\t})\n\t\t.join(\"\");\n}\n\n/**\n * 将文本转换为 PascalCase。\n *\n * @param value - 参数语义与 {@link camelCase} 一致。\n * @param locale - 大小写转换使用的显式语言。\n * @returns PascalCase 文本。\n */\nexport function pascalCase(value: string, locale?: StringLocale): string {\n\treturn upperFirst(camelCase(value, locale), locale);\n}\n\n/**\n * 将文本转换为 kebab-case。\n *\n * @param value - 参数语义与 {@link camelCase} 一致。\n * @param locale - 大小写转换使用的显式语言。\n * @returns kebab-case 文本。\n */\nexport function kebabCase(value: string, locale?: StringLocale): string {\n\treturn splitWords(value)\n\t\t.map((part) => part.toLocaleLowerCase(locale ?? defaultStringLocale))\n\t\t.join(\"-\");\n}\n\n/**\n * 按 Unicode 字素簇截断文本,避免拆开 emoji、组合音标或代理对。\n *\n * @param value - 输入文本。\n * @param maxLength - 保留的最大字素簇数量。\n * @param suffix - 被截断时追加的文本,默认单字符省略号 `…`;不计入上限。\n * @param locale - 字素分割语言,默认固定为 `en-US`。\n * @returns 未超限时返回原字符串,否则返回截断内容与后缀。\n * @throws `RangeError` 当 `maxLength` 不是非负安全整数或 Locale 无效;缺少\n * `Intl.Segmenter` 时抛出 `Error`。\n */\nexport function truncateGraphemes(value: string, maxLength: number, suffix = \"…\", locale?: StringLocale): string {\n\tif (!Number.isSafeInteger(maxLength) || maxLength < 0) throw new RangeError(\"`maxLength` 必须是非负安全整数。\");\n\tconst segments = splitGraphemes(value, locale);\n\treturn segments.length > maxLength ? segments.slice(0, maxLength).join(\"\") + suffix : value;\n}\n\n/**\n * 把文本复制到系统剪贴板。\n *\n * @remarks uni-app 使用 `setClipboardData`;浏览器优先使用 Clipboard API,并在该 API 不可用时\n * 回退到 `document.execCommand(\"copy\")`。平台拒绝访问剪贴板时不会静默忽略错误。\n * @param value - 要复制的文本。\n * @returns 复制完成后兑现的 Promise。\n * @throws `Error` 当运行时没有可用的剪贴板能力或复制失败。\n */\nexport async function copy(value: string): Promise<void> {\n\tconst uni: unknown = Reflect.get(globalThis, \"uni\");\n\tif (uni !== undefined) {\n\t\tif ((typeof uni !== \"object\" && typeof uni !== \"function\") || uni === null) {\n\t\t\tthrow new TypeError(\"全局 uni 对象未提供 `setClipboardData`。\");\n\t\t}\n\t\tconst setClipboardData: unknown = Reflect.get(uni, \"setClipboardData\");\n\t\tif (typeof setClipboardData !== \"function\") throw new TypeError(\"全局 uni 对象未提供 `setClipboardData`。\");\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tReflect.apply(setClipboardData, uni, [\n\t\t\t\t{\n\t\t\t\t\tdata: value,\n\t\t\t\t\tfail: (error: unknown): void => {\n\t\t\t\t\t\treject(error instanceof Error ? error : new Error(\"文本复制到剪贴板失败。\", { cause: error }));\n\t\t\t\t\t},\n\t\t\t\t\tsuccess: resolve,\n\t\t\t\t},\n\t\t\t]);\n\t\t});\n\t\treturn;\n\t}\n\n\tconst clipboard = globalThis.navigator?.clipboard;\n\tif (globalThis.isSecureContext === true && typeof clipboard?.writeText === \"function\") {\n\t\tawait clipboard.writeText(value);\n\t\treturn;\n\t}\n\n\tconst document = globalThis.document;\n\tif (typeof document?.createElement !== \"function\" || document.body === null || typeof document.execCommand !== \"function\") {\n\t\tthrow new Error(\"当前运行环境不支持访问剪贴板。\");\n\t}\n\tconst textarea = document.createElement(\"textarea\");\n\ttextarea.value = value;\n\ttextarea.style.left = \"-999999px\";\n\ttextarea.style.opacity = \"0\";\n\ttextarea.style.position = \"fixed\";\n\ttextarea.style.top = \"-999999px\";\n\tdocument.body.appendChild(textarea);\n\tlet copied: boolean;\n\ttry {\n\t\ttextarea.focus();\n\t\ttextarea.select();\n\t\tcopied = document.execCommand(\"copy\");\n\t} finally {\n\t\ttextarea.remove();\n\t}\n\tif (!copied) throw new Error(\"文本复制到剪贴板失败。\");\n}\n\n/**\n * 生成随机字符串。\n *\n * @remarks 优先使用 Web Crypto;平台缺少安全随机能力时回退到 `Math.random()`。\n * @param length - 字符数量,必须是 0 至 1,000,000 的安全整数。\n * @param alphabet - 不得为空、包含重复字符或超过 2^32 个 Unicode 码点。\n * @returns 由 `alphabet` 中 Unicode 码点组成的随机文本。\n * @throws `RangeError` 当长度或字母表非法。\n */\nexport function randomString(length: number, alphabet: string = defaultRandomAlphabet): string {\n\tif (!Number.isSafeInteger(length) || length < 0 || length > maximumRandomStringLength) {\n\t\tthrow new RangeError(`\\`length\\` 必须是 0 到 ${maximumRandomStringLength} 之间的安全整数。`);\n\t}\n\tconst characters = Array.from(alphabet);\n\tif (characters.length === 0) throw new RangeError(\"`alphabet` 不能为空。\");\n\tif (new Set(characters).size !== characters.length) throw new RangeError(\"`alphabet` 不能包含重复字符。\");\n\tif (characters.length > 0x1_0000_0000) throw new RangeError(\"`alphabet` 不能包含超过 2^32 个字符。\");\n\tif (length === 0) return \"\";\n\n\t// 丢弃不能平均映射到字母表的尾部区间,避免 `%` 造成前部字符概率偏高。\n\tconst acceptanceLimit = Math.floor(uint32Range / characters.length) * characters.length;\n\tconst result: string[] = [];\n\twhile (result.length < length) {\n\t\tconst remaining = length - result.length;\n\t\t// 分批填充可控制临时内存,并避开 Web Crypto 单次随机数组大小限制。\n\t\tconst samples = new Uint32Array(Math.min(remaining, maximumRandomValuesPerBatch));\n\t\tfillRandomValues(samples);\n\t\tfor (const sample of samples) {\n\t\t\tif (sample >= acceptanceLimit) continue;\n\t\t\tconst character = characters[sample % characters.length];\n\t\t\tif (character === undefined) continue;\n\t\t\tresult.push(character);\n\t\t\tif (result.length === length) break;\n\t\t}\n\t}\n\treturn result.join(\"\");\n}\n\n/**\n * 生成 RFC 4122 version 4 UUID。\n *\n * @remarks 优先使用 Web Crypto;平台缺少安全随机能力时回退到 `Math.random()`。\n * 该 UUID 适合普通唯一标识,不应作为安全令牌或秘密。\n * @returns 小写、带连字符的 UUID v4。\n */\nexport function generateUuidV4(): string {\n\tconst crypto = globalThis.crypto;\n\tif (typeof crypto?.randomUUID === \"function\") return crypto.randomUUID();\n\tconst bytes = new Uint8Array(16);\n\tfillRandomValues(bytes);\n\treturn createUuidV4FromBytes(bytes);\n}\n\n/**\n * 判断字符串是否为 RFC 4122 version 4 UUID。\n *\n * @param value - 待验证文本;十六进制字母大小写均可。\n * @returns 版本位与 Variant 位均正确时返回 `true`。\n */\nexport function isUuidV4(value: string): boolean {\n\treturn uuidV4Pattern.test(value);\n}\n\n/**\n * 转义 HTML 文本上下文中的五个特殊字符。\n *\n * @remarks 这不是 HTML 清洗器,不能让不可信文本安全进入 URL、CSS、脚本或属性名上下文。\n * @param value - 将作为 HTML 文本节点内容的字符串。\n * @returns 转义 `&`、`<`、`>`、双引号与单引号后的文本。\n */\nexport function escapeHtml(value: string): string {\n\treturn value.replace(/[&<>\"']/gu, (character) => {\n\t\tswitch (character) {\n\t\t\tcase \"&\":\n\t\t\t\treturn \"&amp;\";\n\t\t\tcase \"<\":\n\t\t\t\treturn \"&lt;\";\n\t\t\tcase \">\":\n\t\t\t\treturn \"&gt;\";\n\t\t\tcase '\"':\n\t\t\t\treturn \"&quot;\";\n\t\t\tdefault:\n\t\t\t\treturn \"&#39;\";\n\t\t}\n\t});\n}\n\n/**\n * 把连续 Unicode 空白折叠为单个空格并删除两端空白。\n *\n * @param value - 输入文本。\n * @returns 规范化后的文本;全空白输入返回空字符串。\n */\nexport function normalizeWhitespace(value: string): string {\n\treturn value.trim().replace(/\\s+/gu, \" \");\n}\n"],"mappings":"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"}
@@ -26,9 +26,9 @@ function useEmits(emits, emit, ignoredEvents = []) {
26
26
  const handlerNames = /* @__PURE__ */ new Set();
27
27
  for (const eventName of Object.keys(emits)) {
28
28
  if (ignored.has(eventName)) continue;
29
- if (eventName.length === 0 || /\s/u.test(eventName)) throw new TypeError(`Invalid Vue event name: "${eventName}".`);
29
+ if (eventName.length === 0 || /\s/u.test(eventName)) throw new TypeError(`无效的 Vue 事件名称:“${eventName}”。`);
30
30
  const handlerName = toHandlerName(eventName);
31
- if (handlerNames.has(handlerName)) throw new TypeError(`Vue events map to the same handler property: "${handlerName}".`);
31
+ if (handlerNames.has(handlerName)) throw new TypeError(`多个 Vue 事件映射到同一处理器属性:“${handlerName}”。`);
32
32
  handlerNames.add(handlerName);
33
33
  Object.defineProperty(handlers, handlerName, {
34
34
  enumerable: true,
@@ -1 +1 @@
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"}
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(`无效的 Vue 事件名称:“${eventName}”。`);\n\t\t\tconst handlerName = toHandlerName(eventName);\n\t\t\tif (handlerNames.has(handlerName)) {\n\t\t\t\tthrow new TypeError(`多个 Vue 事件映射到同一处理器属性:“${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,iBAAiB,UAAU,GAAG;GACvG,MAAM,cAAc,cAAc,SAAS;GAC3C,IAAI,aAAa,IAAI,WAAW,GAC/B,MAAM,IAAI,UAAU,wBAAwB,YAAY,GAAG;GAE5D,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"}