@cmtlyt/lingshu-toolkit 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/665.js +1 -1
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -1
- package/dist/shared/lock-data/__test__/_helpers/memory-adapters.d.ts +95 -0
- package/dist/shared/lock-data/__test__/_helpers/memory-adapters.js +1 -0
- package/dist/shared/lock-data/__test__/index.test-d.d.ts +1 -0
- package/dist/shared/lock-data/__test__/integration/entry.test-d.d.ts +1 -0
- package/dist/shared/lock-data/__test__/playground.d.ts +1 -0
- package/dist/shared/lock-data/__test__/playground.js +1 -0
- package/dist/shared/lock-data/adapters/authority.d.ts +40 -0
- package/dist/shared/lock-data/adapters/authority.js +1 -0
- package/dist/shared/lock-data/adapters/channel.d.ts +39 -0
- package/dist/shared/lock-data/adapters/channel.js +1 -0
- package/dist/shared/lock-data/adapters/index.d.ts +58 -0
- package/dist/shared/lock-data/adapters/index.js +1 -0
- package/dist/shared/lock-data/adapters/logger.d.ts +56 -0
- package/dist/shared/lock-data/adapters/logger.js +1 -0
- package/dist/shared/lock-data/adapters/session-store.d.ts +37 -0
- package/dist/shared/lock-data/adapters/session-store.js +1 -0
- package/dist/shared/lock-data/authority/epoch.d.ts +135 -0
- package/dist/shared/lock-data/authority/epoch.js +1 -0
- package/dist/shared/lock-data/authority/extract.d.ts +107 -0
- package/dist/shared/lock-data/authority/extract.js +1 -0
- package/dist/shared/lock-data/authority/index.d.ts +182 -0
- package/dist/shared/lock-data/authority/index.js +1 -0
- package/dist/shared/lock-data/authority/serialize.d.ts +35 -0
- package/dist/shared/lock-data/authority/serialize.js +1 -0
- package/dist/shared/lock-data/constants.d.ts +46 -0
- package/dist/shared/lock-data/constants.js +1 -0
- package/dist/shared/lock-data/core/actions-helpers.d.ts +163 -0
- package/dist/shared/lock-data/core/actions-helpers.js +1 -0
- package/dist/shared/lock-data/core/actions.d.ts +72 -0
- package/dist/shared/lock-data/core/actions.js +1 -0
- package/dist/shared/lock-data/core/draft.d.ts +64 -0
- package/dist/shared/lock-data/core/draft.js +1 -0
- package/dist/shared/lock-data/core/entry.d.ts +133 -0
- package/dist/shared/lock-data/core/entry.js +1 -0
- package/dist/shared/lock-data/core/fanout.d.ts +42 -0
- package/dist/shared/lock-data/core/fanout.js +1 -0
- package/dist/shared/lock-data/core/readonly-view.d.ts +49 -0
- package/dist/shared/lock-data/core/readonly-view.js +1 -0
- package/dist/shared/lock-data/core/registry.d.ts +282 -0
- package/dist/shared/lock-data/core/registry.js +1 -0
- package/dist/shared/lock-data/core/signal.d.ts +33 -0
- package/dist/shared/lock-data/core/signal.js +1 -0
- package/dist/shared/lock-data/drivers/broadcast-protocol.d.ts +71 -0
- package/dist/shared/lock-data/drivers/broadcast-protocol.js +1 -0
- package/dist/shared/lock-data/drivers/broadcast-state.d.ts +125 -0
- package/dist/shared/lock-data/drivers/broadcast-state.js +1 -0
- package/dist/shared/lock-data/drivers/broadcast.d.ts +36 -0
- package/dist/shared/lock-data/drivers/broadcast.js +1 -0
- package/dist/shared/lock-data/drivers/custom.d.ts +27 -0
- package/dist/shared/lock-data/drivers/custom.js +1 -0
- package/dist/shared/lock-data/drivers/index.d.ts +59 -0
- package/dist/shared/lock-data/drivers/index.js +1 -0
- package/dist/shared/lock-data/drivers/local.d.ts +86 -0
- package/dist/shared/lock-data/drivers/local.js +1 -0
- package/dist/shared/lock-data/drivers/storage-protocol.d.ts +67 -0
- package/dist/shared/lock-data/drivers/storage-protocol.js +1 -0
- package/dist/shared/lock-data/drivers/storage-state.d.ts +103 -0
- package/dist/shared/lock-data/drivers/storage-state.js +1 -0
- package/dist/shared/lock-data/drivers/storage.d.ts +71 -0
- package/dist/shared/lock-data/drivers/storage.js +1 -0
- package/dist/shared/lock-data/drivers/types.d.ts +73 -0
- package/dist/shared/lock-data/drivers/types.js +0 -0
- package/dist/shared/lock-data/drivers/web-locks.d.ts +123 -0
- package/dist/shared/lock-data/drivers/web-locks.js +1 -0
- package/dist/shared/lock-data/errors/index.d.ts +12 -0
- package/dist/shared/lock-data/errors/index.js +1 -0
- package/dist/shared/lock-data/errors/invalid-options-error.d.ts +11 -0
- package/dist/shared/lock-data/errors/invalid-options-error.js +1 -0
- package/dist/shared/lock-data/errors/lock-aborted-error.d.ts +10 -0
- package/dist/shared/lock-data/errors/lock-aborted-error.js +1 -0
- package/dist/shared/lock-data/errors/lock-disposed-error.d.ts +14 -0
- package/dist/shared/lock-data/errors/lock-disposed-error.js +1 -0
- package/dist/shared/lock-data/errors/lock-revoked-error.d.ts +10 -0
- package/dist/shared/lock-data/errors/lock-revoked-error.js +1 -0
- package/dist/shared/lock-data/errors/lock-timeout-error.d.ts +9 -0
- package/dist/shared/lock-data/errors/lock-timeout-error.js +1 -0
- package/dist/shared/lock-data/errors/readonly-mutation-error.d.ts +11 -0
- package/dist/shared/lock-data/errors/readonly-mutation-error.js +1 -0
- package/dist/shared/lock-data/index.d.ts +57 -0
- package/dist/shared/lock-data/index.js +1 -0
- package/dist/shared/lock-data/types.d.ts +347 -0
- package/dist/shared/lock-data/types.js +0 -0
- package/dist/shared/lock-data/utils/json-safe.d.ts +69 -0
- package/dist/shared/lock-data/utils/json-safe.js +1 -0
- package/dist/shared/throw-error/index.d.ts +10 -3
- package/dist/shared/throw-error/index.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lock-data 错误类型的 barrel 导出
|
|
3
|
+
*
|
|
4
|
+
* 每个错误类拆分为独立文件,以遵循 biome 的 `noExcessiveClassesPerFile` 规则;
|
|
5
|
+
* 此文件仅做聚合导出,方便内部模块统一 import
|
|
6
|
+
*/
|
|
7
|
+
export { InvalidOptionsError } from './invalid-options-error';
|
|
8
|
+
export { LockAbortedError } from './lock-aborted-error';
|
|
9
|
+
export { LockDisposedError } from './lock-disposed-error';
|
|
10
|
+
export { LockRevokedError } from './lock-revoked-error';
|
|
11
|
+
export { LockTimeoutError } from './lock-timeout-error';
|
|
12
|
+
export { ReadonlyMutationError } from './readonly-mutation-error';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{InvalidOptionsError}from"./invalid-options-error.js";export{LockAbortedError}from"./lock-aborted-error.js";export{LockDisposedError}from"./lock-disposed-error.js";export{LockRevokedError}from"./lock-revoked-error.js";export{LockTimeoutError}from"./lock-timeout-error.js";export{ReadonlyMutationError}from"./readonly-mutation-error.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `options` 不合法时抛出(如 `timeout < 0`、未知的 `syncMode` 等)
|
|
3
|
+
*
|
|
4
|
+
* 归类为 TypeError 的原因:这是调用方传参问题,而非运行时故障,
|
|
5
|
+
* 与 JavaScript 原生对非法 API 用法抛 TypeError 的惯例对齐
|
|
6
|
+
* 对应 RFC.md「错误类型」章节
|
|
7
|
+
*/
|
|
8
|
+
declare class InvalidOptionsError extends TypeError {
|
|
9
|
+
constructor(message?: string);
|
|
10
|
+
}
|
|
11
|
+
export { InvalidOptionsError };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class r extends TypeError{constructor(r){super(r),this.name="InvalidOptionsError"}}export{r as InvalidOptionsError};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ActionCallOptions.signal` 在 acquiring / holding 阶段 abort 时抛出
|
|
3
|
+
*
|
|
4
|
+
* 与 `LockDisposedError` 区分:本错误仅影响当前调用,actions 实例仍可继续使用
|
|
5
|
+
* 对应 RFC.md「错误类型」章节
|
|
6
|
+
*/
|
|
7
|
+
declare class LockAbortedError extends Error {
|
|
8
|
+
constructor(message?: string);
|
|
9
|
+
}
|
|
10
|
+
export { LockAbortedError };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class r extends Error{constructor(r){super(r),this.name="LockAbortedError"}}export{r as LockAbortedError};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 调用方在 actions 已 `dispose()` 后继续使用时抛出
|
|
3
|
+
*
|
|
4
|
+
* 同时覆盖:
|
|
5
|
+
* - `options.signal.aborted` 后任意调用
|
|
6
|
+
* - `options.getValue` Promise reject 后共享同一 Entry 的任意调用
|
|
7
|
+
* (此时错误 `cause` 字段携带原始 reject 原因)
|
|
8
|
+
*
|
|
9
|
+
* 对应 RFC.md「错误类型」章节
|
|
10
|
+
*/
|
|
11
|
+
declare class LockDisposedError extends Error {
|
|
12
|
+
constructor(message?: string, options?: ErrorOptions);
|
|
13
|
+
}
|
|
14
|
+
export { LockDisposedError };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class r extends Error{constructor(r,o){super(r,o),this.name="LockDisposedError"}}export{r as LockDisposedError};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class r extends Error{constructor(r){super(r),this.name="LockRevokedError"}}export{r as LockRevokedError};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class r extends Error{constructor(r){super(r),this.name="LockTimeoutError"}}export{r as LockTimeoutError};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 直接修改 `readonly` 视图时抛出
|
|
3
|
+
*
|
|
4
|
+
* 归类为 TypeError 的原因:与原生对 frozen 对象写入时的规范语义对齐,
|
|
5
|
+
* 便于业务代码用同一种 catch 分支处理"写入被阻止"类错误
|
|
6
|
+
* 对应 RFC.md「错误类型」章节
|
|
7
|
+
*/
|
|
8
|
+
declare class ReadonlyMutationError extends TypeError {
|
|
9
|
+
constructor(message?: string);
|
|
10
|
+
}
|
|
11
|
+
export { ReadonlyMutationError };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class r extends TypeError{constructor(r){super(r),this.name="ReadonlyMutationError"}}export{r as ReadonlyMutationError};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { LockDataOptions, LockDataReturn, LockDataValueShape } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 创建一个带锁的数据容器(**单参数 API + getValue 必传 + 条件类型自动推断返回值**)
|
|
4
|
+
*
|
|
5
|
+
* 单签名 + 严格条件类型契约(对应 RFC.md「§3 核心语义 / §API 签名」+ 决策 #33 §A):
|
|
6
|
+
* - `getValue: () => T | Promise<T>` 必传 —— 数据来源唯一入口
|
|
7
|
+
* - `T` 必须满足 `LockDataValueShape<T> = T extends readonly unknown[] ? never : T`,
|
|
8
|
+
* 即 **类型层禁止顶层数组**(`lockData<string[]>` 在类型层即被排除为 `never`)
|
|
9
|
+
* - **返回值类型由 `LockDataReturn<T, O>` 条件类型从入参 `O` 直接推断**,无需调用方断言:
|
|
10
|
+
* 1. `O extends { syncMode: 'storage-authority' }` → 必须配 `id: string`,否则编译期 `never`(最严格)
|
|
11
|
+
* 2. 否则若 `ReturnType<O['getValue']> extends Promise<unknown>` → `Promise<LockDataTuple<T>>`
|
|
12
|
+
* 3. 否则 → `LockDataTuple<T>`
|
|
13
|
+
* - 同步抛错 → 同步抛 `LockDisposedError`(Entry 不构造)
|
|
14
|
+
* - 异步 reject → 返回的 Promise reject `LockDisposedError`,`cause` 字段携带原因
|
|
15
|
+
* - 运行时双重 fail-fast:`Array.isArray(awaited)` 拒绝顶层数组(抛 `InvalidOptionsError`),
|
|
16
|
+
* 非 JSON-safe 抛 `InvalidOptionsError`
|
|
17
|
+
*
|
|
18
|
+
* 返回元组:
|
|
19
|
+
* - 第一个元素:深只读视图(`ReadonlyView<T>`,wrapper Proxy 实现),业务只能读取不能直接写入
|
|
20
|
+
* - 第二个元素:actions(`update` / `replace` / `snapshot` / `dispose` 等),通过事务 API 修改数据
|
|
21
|
+
*
|
|
22
|
+
* ### 同步初始化(直接得元组,无需 await / 断言)
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* const [view, actions] = lockData<{ count: number }>({
|
|
26
|
+
* getValue: () => ({ count: 0 }),
|
|
27
|
+
* });
|
|
28
|
+
* view.count; // 0
|
|
29
|
+
* await actions.update((draft) => { draft.count = 1; });
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* ### 异步初始化(getValue 返回 Promise → 类型自动收窄为 Promise<LockDataTuple<T>>)
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* const [view, actions] = await lockData<User>({
|
|
36
|
+
* getValue: () => fetch('/api/user').then((r) => r.json()),
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* ### 跨 Tab 同步(syncMode='storage-authority' 必须配 id,否则编译期报错)
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* const [view, actions] = await lockData<{ count: number }>({
|
|
44
|
+
* id: 'shared-counter',
|
|
45
|
+
* syncMode: 'storage-authority',
|
|
46
|
+
* getValue: () => ({ count: 0 }),
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
type LockDataInfer<O> = O extends {
|
|
51
|
+
getValue: () => infer R;
|
|
52
|
+
} ? Awaited<R> extends infer T extends object ? T : never : never;
|
|
53
|
+
type LockDataResolveReturn<O extends object> = LockDataValueShape<LockDataInfer<O>> extends infer T extends object ? LockDataReturn<T, O> : never;
|
|
54
|
+
declare function lockData<const O extends LockDataOptions<unknown>>(options: O): LockDataResolveReturn<O>;
|
|
55
|
+
export { NEVER_TIMEOUT } from './constants';
|
|
56
|
+
export { InvalidOptionsError, LockAbortedError, LockDisposedError, LockRevokedError, LockTimeoutError, ReadonlyMutationError, } from './errors';
|
|
57
|
+
export { lockData };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{lockData as r}from"./core/entry.js";function o(o){let t=r(o);return t instanceof Promise,t}export{NEVER_TIMEOUT}from"./constants.js";export{InvalidOptionsError,LockAbortedError,LockDisposedError,LockRevokedError,LockTimeoutError,ReadonlyMutationError}from"./errors/index.js";export{o as lockData};
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lock-data 模块的公开类型定义
|
|
3
|
+
*
|
|
4
|
+
* 仅包含 API 表面(options / actions / listeners / adapters 等),
|
|
5
|
+
* 内部实现使用的类型(Entry / InstanceRegistry / DriverHandle 等)放在对应模块内部。
|
|
6
|
+
*
|
|
7
|
+
* 对应 RFC.md「附录 A:完整接口索引」章节。
|
|
8
|
+
*/
|
|
9
|
+
import type { NEVER_TIMEOUT } from './constants';
|
|
10
|
+
/**
|
|
11
|
+
* 超时参数支持的形态:
|
|
12
|
+
* - number:毫秒数(0 或负数非法,由参数校验层拦截)
|
|
13
|
+
* - NEVER_TIMEOUT:永不超时(业务自控)
|
|
14
|
+
*/
|
|
15
|
+
type TimeoutValue = number | typeof NEVER_TIMEOUT;
|
|
16
|
+
/** 跨进程同步模式;本期仅 `'none'` 与 `'storage-authority'` */
|
|
17
|
+
type SyncMode = 'none' | 'storage-authority';
|
|
18
|
+
/**
|
|
19
|
+
* 锁驱动选择模式
|
|
20
|
+
*
|
|
21
|
+
* 对应 RFC.md「能力检测与降级」;`adapters.getLock` 存在时本字段被忽略
|
|
22
|
+
*
|
|
23
|
+
* - `'auto'`(默认):按能力降级链 web-locks → broadcast → storage
|
|
24
|
+
* - `'web-locks'` / `'broadcast'` / `'storage'`:强制使用对应 driver;能力不可用时抛错
|
|
25
|
+
*/
|
|
26
|
+
type LockMode = 'auto' | 'web-locks' | 'broadcast' | 'storage';
|
|
27
|
+
/**
|
|
28
|
+
* 持久化策略:
|
|
29
|
+
* - `'session'`(默认):所有 Tab 关闭后重置;协作期的天然语义
|
|
30
|
+
* - `'persistent'`:跨浏览器重启保留;适合用户草稿 / 偏好
|
|
31
|
+
*/
|
|
32
|
+
type Persistence = 'session' | 'persistent';
|
|
33
|
+
/** `actions` 内部状态机的公开子集;用于 listeners.onLockStateChange 事件 */
|
|
34
|
+
type LockPhase = 'idle' | 'acquiring' | 'holding' | 'committing' | 'released' | 'revoked' | 'disposed';
|
|
35
|
+
/** revoke 触发原因 */
|
|
36
|
+
type RevokeReason = 'force' | 'timeout' | 'dispose';
|
|
37
|
+
/** `listeners.onSync` 的触发来源 */
|
|
38
|
+
type SyncSource = 'pull-on-acquire' | 'storage-event' | 'pageshow' | 'visibilitychange';
|
|
39
|
+
/** `listeners.onCommit` 的触发来源 */
|
|
40
|
+
type CommitSource = 'update' | 'replace';
|
|
41
|
+
/**
|
|
42
|
+
* Draft 执行期间记录的最小路径变更
|
|
43
|
+
*
|
|
44
|
+
* 同时服务审计(listeners.onCommit)与回滚(revoke / abort 时反向应用)
|
|
45
|
+
*
|
|
46
|
+
* **JSON-only 契约**:lock-data 的 draft 仅支持 JSON 安全类型(plain object / array /
|
|
47
|
+
* string / number(不含 NaN/Infinity)/ boolean / null),故 mutation op 仅有
|
|
48
|
+
* 普通对象属性的 `set` / `delete` 两种。Set / Map / Date / class 实例 等非 JSON
|
|
49
|
+
* 类型在 `createDraftSession` 入口与每次写入处会被显式拒绝(抛 `TypeError`),
|
|
50
|
+
* 详见 `core/draft.ts` 文件顶部「JSON-only 契约」说明。
|
|
51
|
+
*
|
|
52
|
+
* op 语义:
|
|
53
|
+
* - `'set'`:属性写入 / 新增,`path` 指向被修改的属性,`value` 为新值
|
|
54
|
+
* - `'delete'`:属性删除,`path` 指向被删除的属性,`value` 不携带
|
|
55
|
+
*/
|
|
56
|
+
type LockDataMutationOp = 'set' | 'delete';
|
|
57
|
+
interface LockDataMutation {
|
|
58
|
+
readonly path: readonly PropertyKey[];
|
|
59
|
+
readonly op: LockDataMutationOp;
|
|
60
|
+
readonly value?: unknown;
|
|
61
|
+
}
|
|
62
|
+
/** 状态流转事件 */
|
|
63
|
+
interface LockStateChangeEvent {
|
|
64
|
+
readonly phase: LockPhase;
|
|
65
|
+
readonly token: string;
|
|
66
|
+
}
|
|
67
|
+
/** revoke 事件 */
|
|
68
|
+
interface RevokeEvent {
|
|
69
|
+
readonly reason: RevokeReason;
|
|
70
|
+
readonly token: string;
|
|
71
|
+
}
|
|
72
|
+
/** commit 事件(仅 commit 成功时触发) */
|
|
73
|
+
interface CommitEvent<T> {
|
|
74
|
+
readonly source: CommitSource;
|
|
75
|
+
readonly token: string;
|
|
76
|
+
readonly rev: number;
|
|
77
|
+
readonly mutations: readonly LockDataMutation[];
|
|
78
|
+
readonly snapshot: T;
|
|
79
|
+
}
|
|
80
|
+
/** sync 事件(仅 syncMode 非 none 时触发) */
|
|
81
|
+
interface SyncEvent<T> {
|
|
82
|
+
readonly source: SyncSource;
|
|
83
|
+
readonly rev: number;
|
|
84
|
+
readonly snapshot: T;
|
|
85
|
+
}
|
|
86
|
+
/** 全部事件监听器;均为可选 */
|
|
87
|
+
interface LockDataListeners<T> {
|
|
88
|
+
onLockStateChange?: (event: LockStateChangeEvent) => void;
|
|
89
|
+
onRevoked?: (event: RevokeEvent) => void;
|
|
90
|
+
onCommit?: (event: CommitEvent<T>) => void;
|
|
91
|
+
onSync?: (event: SyncEvent<T>) => void;
|
|
92
|
+
}
|
|
93
|
+
/** 每次 action 调用级别的覆盖项 */
|
|
94
|
+
interface ActionCallOptions {
|
|
95
|
+
/** 覆盖 options.timeout 中的抢锁部分 */
|
|
96
|
+
acquireTimeout?: TimeoutValue;
|
|
97
|
+
/** 覆盖 options.timeout 中的持锁部分 */
|
|
98
|
+
holdTimeout?: TimeoutValue;
|
|
99
|
+
/** 强制抢占当前持有者 */
|
|
100
|
+
force?: boolean;
|
|
101
|
+
/** 仅影响本次调用的取消信号 */
|
|
102
|
+
signal?: AbortSignal;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 驱动工厂上下文;用户注入自定义 `adapters.getLock` 时 RFC 约定的参数
|
|
106
|
+
*
|
|
107
|
+
* 命名字段与 RFC 「自定义锁驱动」示例对齐,不使用简写
|
|
108
|
+
*/
|
|
109
|
+
interface LockDriverContext {
|
|
110
|
+
readonly name: string;
|
|
111
|
+
readonly token: string;
|
|
112
|
+
readonly force: boolean;
|
|
113
|
+
readonly acquireTimeout: TimeoutValue;
|
|
114
|
+
readonly holdTimeout: TimeoutValue;
|
|
115
|
+
readonly signal: AbortSignal;
|
|
116
|
+
}
|
|
117
|
+
/** 锁驱动句柄;由 `adapters.getLock` 返回 */
|
|
118
|
+
interface LockDriverHandle {
|
|
119
|
+
/**
|
|
120
|
+
* 释放锁
|
|
121
|
+
*
|
|
122
|
+
* 返回值兼容 Promises/A+ 规范的最小 thenable(仅需实现 `.then`),
|
|
123
|
+
* 实现侧通过 `Promise.resolve(...).catch(...)` 正规化后挂错误处理
|
|
124
|
+
*/
|
|
125
|
+
release: () => void | PromiseLike<void>;
|
|
126
|
+
onRevokedByDriver: (callback: (reason: 'force' | 'timeout') => void) => void;
|
|
127
|
+
}
|
|
128
|
+
/** `AuthorityAdapter` 工厂上下文 */
|
|
129
|
+
interface AuthorityAdapterContext {
|
|
130
|
+
readonly id: string;
|
|
131
|
+
}
|
|
132
|
+
/** `ChannelAdapter` 工厂上下文 */
|
|
133
|
+
interface ChannelAdapterContext {
|
|
134
|
+
readonly id: string;
|
|
135
|
+
readonly channel: 'session' | 'custom';
|
|
136
|
+
}
|
|
137
|
+
/** `SessionStoreAdapter` 工厂上下文 */
|
|
138
|
+
interface SessionStoreAdapterContext {
|
|
139
|
+
readonly id: string;
|
|
140
|
+
}
|
|
141
|
+
interface AuthorityAdapter {
|
|
142
|
+
read: () => string | null;
|
|
143
|
+
write: (raw: string) => void;
|
|
144
|
+
remove: () => void;
|
|
145
|
+
subscribe: (onExternalUpdate: (newValue: string | null) => void) => () => void;
|
|
146
|
+
}
|
|
147
|
+
interface ChannelAdapter {
|
|
148
|
+
postMessage: (message: unknown) => void;
|
|
149
|
+
subscribe: (onMessage: (message: unknown) => void) => () => void;
|
|
150
|
+
close: () => void;
|
|
151
|
+
}
|
|
152
|
+
interface SessionStoreAdapter {
|
|
153
|
+
read: () => string | null;
|
|
154
|
+
write: (value: string) => void;
|
|
155
|
+
}
|
|
156
|
+
interface LoggerAdapter {
|
|
157
|
+
warn: (message: string, ...extras: unknown[]) => void;
|
|
158
|
+
error: (message: string, ...extras: unknown[]) => void;
|
|
159
|
+
debug?: (message: string, ...extras: unknown[]) => void;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 顶层数组类型禁止
|
|
163
|
+
*
|
|
164
|
+
* wrapper Proxy 方案下,顶层数组会触发 `Object.keys(view)` / `JSON.stringify(view)` 的
|
|
165
|
+
* length invariant TypeError、`Array.isArray(view)` 永远返回 `false` 等不可调和的不变量冲突
|
|
166
|
+
*
|
|
167
|
+
* 编译期把 `T extends unknown[]` 排除为 `never`,运行时由 `core/entry.ts` 的 `Array.isArray(awaited)`
|
|
168
|
+
* 双重 fail-fast 拒绝,保证用户面错误信息明确(`InvalidOptionsError`)
|
|
169
|
+
*
|
|
170
|
+
* 对应 RFC.md「顶层数组禁止」章节
|
|
171
|
+
*/
|
|
172
|
+
type LockDataValueShape<T> = T extends readonly unknown[] ? never : T;
|
|
173
|
+
/**
|
|
174
|
+
* 所有环境依赖的统一注入入口
|
|
175
|
+
*
|
|
176
|
+
* 设计决策:采用工厂函数(`getXxx(ctx) => Adapter`)而非直接传实例,
|
|
177
|
+
* 理由是锁 / 权威副本 / 通道这些依赖与 id 强绑定,工厂形态允许上层
|
|
178
|
+
* 在首次创建 Entry 时按 id 组装;无 id 作用域的依赖(logger)直接传实例
|
|
179
|
+
*
|
|
180
|
+
* 注:本期不再提供 `clone` 适配器,所有快照派生使用 `JSON.parse(JSON.stringify(...))` 固化语义;
|
|
181
|
+
* `getValue` resolve 后 + `actions.replace(next)` 入参由 `assertJsonSafe` fail-fast 校验,
|
|
182
|
+
* 保证 `entry.dataRef.current` 永远只含 JSON 安全值
|
|
183
|
+
*/
|
|
184
|
+
interface LockDataAdapters<_T> {
|
|
185
|
+
getLock?: (ctx: LockDriverContext) => Promise<LockDriverHandle> | LockDriverHandle;
|
|
186
|
+
getAuthority?: (ctx: AuthorityAdapterContext) => AuthorityAdapter | null;
|
|
187
|
+
getChannel?: (ctx: ChannelAdapterContext) => ChannelAdapter | null;
|
|
188
|
+
getSessionStore?: (ctx: SessionStoreAdapterContext) => SessionStoreAdapter | null;
|
|
189
|
+
logger?: LoggerAdapter;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* `lockData` 的顶层配置(**单参数 API**)
|
|
193
|
+
*
|
|
194
|
+
* - `getValue` 必传:数据来源唯一入口,返回值禁止为顶层数组(`LockDataValueShape<T>` 类型层排除)
|
|
195
|
+
* - 同步 `getValue()` → `lockData()` 同步返回元组
|
|
196
|
+
* - 异步 `getValue()` → `lockData()` 返回 Promise<元组>,resolve 后才把元组交付给调用方
|
|
197
|
+
*
|
|
198
|
+
* 对应 RFC.md「§3 核心语义」「§API 签名」章节
|
|
199
|
+
*/
|
|
200
|
+
interface LockDataOptions<T> {
|
|
201
|
+
/**
|
|
202
|
+
* 锁 id;未传时视为"纯本地只读锁",不参与跨模块 / 跨 Tab 共享
|
|
203
|
+
*/
|
|
204
|
+
id?: string;
|
|
205
|
+
/**
|
|
206
|
+
* 数据初始化器(**必传**)
|
|
207
|
+
*
|
|
208
|
+
* - 返回值在类型层被 `LockDataValueShape<T>` 限制:禁止顶层数组(`T extends readonly unknown[]` 排除为 `never`)
|
|
209
|
+
* - 返回 `T` → `lockData` 同步返回元组
|
|
210
|
+
* - 返回 `Promise<T>` → `lockData` 返回 Promise<元组>
|
|
211
|
+
* - 同步 `getValue()` 抛错 → `lockData()` 调用栈直接抛 `LockDisposedError`(Entry 不构造)
|
|
212
|
+
* - 异步 `getValue()` reject → `lockData()` 返回的 Promise reject `LockDisposedError`,`cause` 字段携带原因
|
|
213
|
+
* - 运行时 `Array.isArray(awaited)` 双重 fail-fast 拒绝顶层数组(抛 `InvalidOptionsError`)
|
|
214
|
+
* - resolve 后由 `assertJsonSafe` 校验 JSON 安全(非 JSON-safe 抛 `InvalidOptionsError`)
|
|
215
|
+
*/
|
|
216
|
+
getValue: () => LockDataValueShape<T> | Promise<LockDataValueShape<T>>;
|
|
217
|
+
/** 默认抢锁 + 持锁超时,可被 `ActionCallOptions` 覆盖 */
|
|
218
|
+
timeout?: TimeoutValue;
|
|
219
|
+
/**
|
|
220
|
+
* 锁驱动选择;默认 `'auto'`
|
|
221
|
+
*
|
|
222
|
+
* `adapters.getLock` 存在时本字段被忽略(用户自定义 driver 优先级最高)
|
|
223
|
+
*/
|
|
224
|
+
mode?: LockMode;
|
|
225
|
+
/** 跨进程同步模式;默认 `'none'` */
|
|
226
|
+
syncMode?: SyncMode;
|
|
227
|
+
/** 持久化策略;默认 `'session'`(仅在 syncMode 非 none 时生效) */
|
|
228
|
+
persistence?: Persistence;
|
|
229
|
+
/** session-probe 等待窗口;默认 100ms */
|
|
230
|
+
sessionProbeTimeout?: number;
|
|
231
|
+
/** 实例级生命周期控制;abort 等价于 dispose() */
|
|
232
|
+
signal?: AbortSignal;
|
|
233
|
+
/** 全部事件监听器 */
|
|
234
|
+
listeners?: LockDataListeners<T>;
|
|
235
|
+
/** 所有环境依赖的注入入口 */
|
|
236
|
+
adapters?: LockDataAdapters<T>;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* actions 对象的公开 API
|
|
240
|
+
*
|
|
241
|
+
* 每个方法的语义详见 RFC.md「Actions 实现要点」章节
|
|
242
|
+
*/
|
|
243
|
+
interface LockDataActions<T extends object> {
|
|
244
|
+
/** 当前是否持有锁(释放 / revoke 后为 false) */
|
|
245
|
+
readonly isHolding: boolean;
|
|
246
|
+
/** 事务式写入;recipe 失败或被 revoke 时自动回滚 */
|
|
247
|
+
update: (recipe: (draft: T) => void | Promise<void>, callOptions?: ActionCallOptions) => Promise<void>;
|
|
248
|
+
/** 整体替换;等价于一次隐式 update 事务;入参由 `assertJsonSafe` fail-fast 校验 */
|
|
249
|
+
replace: (next: T, callOptions?: ActionCallOptions) => Promise<void>;
|
|
250
|
+
/**
|
|
251
|
+
* 不抢锁的快照读取
|
|
252
|
+
*
|
|
253
|
+
* 返回 `JSON.parse(JSON.stringify(entry.dataRef.current))` 产出的全新对象,
|
|
254
|
+
* 与内部 `dataRef.current` 完全隔离;调用方对返回值的任何 mutate 都不会影响内部状态
|
|
255
|
+
*
|
|
256
|
+
* 对应 RFC.md「actions.snapshot()」章节
|
|
257
|
+
*/
|
|
258
|
+
snapshot: () => T;
|
|
259
|
+
/** 手动抢锁;配合连续 update + release 实现多步事务 */
|
|
260
|
+
getLock: (callOptions?: ActionCallOptions) => Promise<void>;
|
|
261
|
+
/**
|
|
262
|
+
* 只还锁,不销毁实例;actions 仍可继续使用
|
|
263
|
+
*
|
|
264
|
+
* 与 `dispose` 的区别:release 对应 getLock,dispose 对应 lockData
|
|
265
|
+
*/
|
|
266
|
+
release: () => void;
|
|
267
|
+
/** 还锁 + 销毁实例;调用后本 actions 进入 disposed 终态 */
|
|
268
|
+
dispose: () => Promise<void>;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* lockData 返回值
|
|
272
|
+
*
|
|
273
|
+
* 同步初始化时为元组;异步初始化(getValue 返回 Promise 或 syncMode 非 none)时为 Promise<元组>
|
|
274
|
+
*
|
|
275
|
+
* 内部视角类型(第一个元素为裸 T),用于 `core/entry.ts` 及其调用链的实现;
|
|
276
|
+
* 对外公开契约请使用 `LockDataTuple<T>`(第一个元素为 `ReadonlyView<T>`)
|
|
277
|
+
*/
|
|
278
|
+
type LockDataResult<T extends object> = readonly [T, LockDataActions<T>];
|
|
279
|
+
/**
|
|
280
|
+
* 深只读视图
|
|
281
|
+
*
|
|
282
|
+
* - 函数类型透传不递归(避免破坏 this / 参数类型)
|
|
283
|
+
* - 对象类型递归加 readonly,所有属性只读
|
|
284
|
+
* - 其他类型(primitive / symbol)原样透传
|
|
285
|
+
*
|
|
286
|
+
* 运行时由 `core/readonly-view.ts::createReadonlyView` 返回深只读 Proxy 保证;
|
|
287
|
+
* 用户通过 `lockData()` 返回的 tuple 第一个元素拿到的即为 `ReadonlyView<T>` 实例
|
|
288
|
+
*
|
|
289
|
+
* 对应 RFC.md「ReadonlyView\<T\>」章节
|
|
290
|
+
*
|
|
291
|
+
* 注:`(...args: any[]) => any` 是 RFC 合同语义(匹配任意函数签名的透传),
|
|
292
|
+
* 无法用 `unknown[]` / `unknown` 替代(否则 `keyof` 分发到函数类型会得出 `never`)
|
|
293
|
+
*/
|
|
294
|
+
type ReadonlyView<T> = T extends (...args: never[]) => unknown ? T : T extends object ? {
|
|
295
|
+
readonly [K in keyof T]: ReadonlyView<T[K]>;
|
|
296
|
+
} : T;
|
|
297
|
+
/**
|
|
298
|
+
* lockData 公开契约的返回元组类型
|
|
299
|
+
*
|
|
300
|
+
* 与内部 `LockDataResult<T>` 的区别:第一个元素类型为 `ReadonlyView<T>`(深只读代理),
|
|
301
|
+
* 明确向用户传达"读句柄只读、写操作必须经 `actions.update/replace`"的设计意图
|
|
302
|
+
*
|
|
303
|
+
* 对应 RFC.md「签名」章节
|
|
304
|
+
*/
|
|
305
|
+
type LockDataTuple<T extends object> = readonly [ReadonlyView<T>, LockDataActions<T>];
|
|
306
|
+
/**
|
|
307
|
+
* lockData 返回值类型推断(**单签名 + 条件类型自动推断**)
|
|
308
|
+
*
|
|
309
|
+
* 设计动机:避免 `LockDataTuple<T> | Promise<LockDataTuple<T>>` 联合类型在调用点强迫
|
|
310
|
+
* 用户用 `as` 断言或 `instanceof Promise` 分支判断;类型层直接镜像运行时判定优先级,
|
|
311
|
+
* 让 `lockData({ getValue: () => ({}) })` 直接得到元组、`lockData({ getValue: () => Promise.resolve({}) })`
|
|
312
|
+
* 直接得到 Promise,无需 `await` / 断言
|
|
313
|
+
*
|
|
314
|
+
* 判定优先级(与 `core/entry.ts` 运行时分支严格对齐):
|
|
315
|
+
* 1. `O extends { syncMode: 'storage-authority' }`
|
|
316
|
+
* a. 若 `O extends { id: string }` → `Promise<LockDataTuple<T>>`
|
|
317
|
+
* b. 若 `O` 缺 `id` 字段 → `never`(**最严格类型层校验**:`syncMode='storage-authority'`
|
|
318
|
+
* 要求必须传 `id`,否则 authority 无法绑定作用域;编译期 fail-fast 让这种非法组合
|
|
319
|
+
* 在调用点直接类型报错,避免运行时静默 fallback 到 `'none'`)
|
|
320
|
+
* 2. 否则若 `ReturnType<O['getValue']> extends Promise<unknown>` → `Promise<LockDataTuple<T>>`
|
|
321
|
+
* 3. 否则 → `LockDataTuple<T>`
|
|
322
|
+
*
|
|
323
|
+
* 注:判定 1.b 用 `LockDataReturnNeverWithoutId` 做条件分支,避免 `O extends { id: string }`
|
|
324
|
+
* 在 `O` 是宽泛类型(如 `LockDataOptions<T>`)时被推断为 `true` 而绕过校验;
|
|
325
|
+
* 必须严格匹配 `O` 类型字面量中的 `id` 字段
|
|
326
|
+
*
|
|
327
|
+
* 注:第二个泛型参数 `O` 的约束放宽为 `object` 而非 `LockDataOptions<X>`,
|
|
328
|
+
* 是 TypeScript 条件类型推断的**标准协变兜底用法**:
|
|
329
|
+
* - `LockDataOptions<T>` 在 `T` 上是双向不变(`listeners.onCommit` 等回调字段使 `T` 既出现在
|
|
330
|
+
* 协变位置又出现在逆变位置)
|
|
331
|
+
* - 调用 `lockData<const O extends LockDataOptions<LockDataValueShape<LockDataInfer<O>>>>(options: O)`
|
|
332
|
+
* 后,把 `O` 传给 `LockDataReturn<T, O>` 时若约束写成 `LockDataOptions<X>`(无论 `X` 是
|
|
333
|
+
* `T` / `any` / `unknown`),都会因双向不变而拒绝(参见 tsc TS2344)
|
|
334
|
+
* - 条件分支只关心 `O` 是否包含 `syncMode` / `id` / `getValue` 字段,不需要 `O` 是
|
|
335
|
+
* `LockDataOptions` 的子类型;约束放到 `object` 即可(公开签名 `lockData` 的 `O` 约束
|
|
336
|
+
* 已保证调用方传入合法 `LockDataOptions<X>`,类型层条件分支无需重复约束)
|
|
337
|
+
*
|
|
338
|
+
* 对应 RFC.md「§签名」章节 + 决策 #33 §A
|
|
339
|
+
*/
|
|
340
|
+
type LockDataReturn<T extends object, O extends object> = O extends {
|
|
341
|
+
syncMode: 'storage-authority';
|
|
342
|
+
} ? O extends {
|
|
343
|
+
id: string;
|
|
344
|
+
} ? Promise<LockDataTuple<T>> : never : O extends {
|
|
345
|
+
getValue: () => infer R;
|
|
346
|
+
} ? R extends Promise<unknown> ? Promise<LockDataTuple<T>> : LockDataTuple<T> : LockDataTuple<T>;
|
|
347
|
+
export type { ActionCallOptions, AuthorityAdapter, AuthorityAdapterContext, ChannelAdapter, ChannelAdapterContext, CommitEvent, CommitSource, LockDataActions, LockDataAdapters, LockDataListeners, LockDataMutation, LockDataMutationOp, LockDataOptions, LockDataResult, LockDataReturn, LockDataTuple, LockDataValueShape, LockDriverContext, LockDriverHandle, LockMode, LockPhase, LockStateChangeEvent, LoggerAdapter, Persistence, ReadonlyView, RevokeEvent, RevokeReason, SessionStoreAdapter, SessionStoreAdapterContext, SyncEvent, SyncMode, SyncSource, TimeoutValue, };
|
|
File without changes
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lock-data 模块的 JSON 安全校验工具
|
|
3
|
+
*
|
|
4
|
+
* 设计动机:wrapper 方案下所有进入 `entry.dataRef.current` 的值都必须是 JSON 安全的,
|
|
5
|
+
* 否则 `JSON.parse(JSON.stringify(...))` 会静默丢失 Set / Map / Date / class instance / undefined 等
|
|
6
|
+
*
|
|
7
|
+
* 边界一致:
|
|
8
|
+
* - getValue resolve 后入口走 `assertJsonSafe(awaited, ...)` fail-fast
|
|
9
|
+
* - actions.replace(next) 入口走 `assertJsonSafe(next, ...)` fail-fast
|
|
10
|
+
* - draft.ts 的写入路径同样复用本模块的 assertJsonSafe(共享同一份语义)
|
|
11
|
+
*
|
|
12
|
+
* 顶层数组在类型层(`LockDataValueShape<T>`)已被排除为 `never`;运行时通过
|
|
13
|
+
* `assertNotTopLevelArray` 双重 fail-fast 拦截类型擦除路径下的误用
|
|
14
|
+
*
|
|
15
|
+
* 对应 fixes/api-getvalue-only-redesign.md §14.4 / RFC.md「JSON 安全契约」章节
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* 描述非 JSON 值的具体类型,用于错误信息
|
|
19
|
+
*
|
|
20
|
+
* 例:`Set` / `Map` / `Date` / `class instance (Foo)` / `function` / `bigint` / `NaN`
|
|
21
|
+
*/
|
|
22
|
+
declare function describeNonJsonValue(value: unknown): string;
|
|
23
|
+
/**
|
|
24
|
+
* 校验值是否为 JSON 安全类型;遇到非法值 / 循环引用立即抛 `TypeError`
|
|
25
|
+
*
|
|
26
|
+
* 允许:string / number(不含 NaN/Infinity)/ boolean / null / plain object / array
|
|
27
|
+
* 禁止:undefined / bigint / symbol / function / Set / Map / Date / RegExp / class 实例 /
|
|
28
|
+
* TypedArray / WeakMap / WeakSet / 循环引用 等
|
|
29
|
+
*
|
|
30
|
+
* `seen` 仅跟踪当前路径上访问过的容器(递归回溯时 `delete`),保证「同一兄弟节点的相同
|
|
31
|
+
* 引用」不会被误判为环
|
|
32
|
+
*
|
|
33
|
+
* @param subject 错误信息中的来源描述(如 `'getValue() result'` / `'actions.replace(next)'` / `'draft'`)
|
|
34
|
+
*/
|
|
35
|
+
declare function assertJsonSafe(value: unknown, path: readonly PropertyKey[], seen: WeakSet<object>, subject: string): void;
|
|
36
|
+
/**
|
|
37
|
+
* 顶层数组运行时拒绝(fail-fast)
|
|
38
|
+
*
|
|
39
|
+
* 类型层已通过 `LockDataValueShape<T> = T extends readonly unknown[] ? never : T` 排除,
|
|
40
|
+
* 本函数仅作类型擦除路径下的最后一道防线 —— 抛 `InvalidOptionsError`
|
|
41
|
+
*
|
|
42
|
+
* @param value 待检查的值
|
|
43
|
+
* @param subject 错误信息中的来源描述(如 `'getValue() result'` / `'actions.replace(next)'`)
|
|
44
|
+
*/
|
|
45
|
+
declare function assertNotTopLevelArray(value: unknown, subject: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* JSON 拷贝隔离:把任意 JSON 安全的值深拷贝为全新引用
|
|
48
|
+
*
|
|
49
|
+
* 调用方必须保证 input 已通过 `assertJsonSafe` —— 本函数不做校验,直接 `JSON.parse(JSON.stringify(...))`
|
|
50
|
+
*
|
|
51
|
+
* 用途:
|
|
52
|
+
* - `entry.dataRef.current = cloneByJson(getValue 返回值)`:getValue 与内部状态隔离
|
|
53
|
+
* - `entry.dataRef.current = cloneByJson(committedNext)`:commit 与 draft 隔离
|
|
54
|
+
* - `actions.snapshot() = cloneByJson(entry.dataRef.current)`:调用方与内部状态隔离
|
|
55
|
+
* - `commitEvent.snapshot = cloneByJson(entry.dataRef.current)`:listener 与内部状态隔离
|
|
56
|
+
*/
|
|
57
|
+
declare function cloneByJson<T>(value: T): T;
|
|
58
|
+
/**
|
|
59
|
+
* 入口便捷封装:组合 `assertNotTopLevelArray` + `assertJsonSafe`
|
|
60
|
+
*
|
|
61
|
+
* 用于 getValue / actions.replace 等"外部入参"边界的统一 fail-fast:
|
|
62
|
+
* 1. 顶层数组判定(双重 fail-fast 的运行时一道)
|
|
63
|
+
* 2. 整体 JSON 安全校验
|
|
64
|
+
*
|
|
65
|
+
* 同步链路抛 `InvalidOptionsError`(顶层数组)/ `TypeError`(其他非 JSON-safe),
|
|
66
|
+
* 调用方拿到错误后应原样向上抛
|
|
67
|
+
*/
|
|
68
|
+
declare function assertJsonSafeInput(value: unknown, subject: string): void;
|
|
69
|
+
export { assertJsonSafe, assertJsonSafeInput, assertNotTopLevelArray, cloneByJson, describeNonJsonValue };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{throwError as e}from"../../throw-error/index.js";import{ERROR_FN_NAME as t}from"../constants.js";import{InvalidOptionsError as r}from"../errors/index.js";function n(e){if(0===e.length)return"<root>";let t="";for(let r=0;r<e.length;r++){let n=e[r];if("number"==typeof n||"string"==typeof n&&/^\d+$/u.test(n)){t+=`[${String(n)}]`;continue}t+=0===r?String(n):`.${String(n)}`}return t}function o(e){if(void 0===e)return"undefined";if("number"==typeof e){if(Number.isNaN(e))return"NaN";if(!Number.isFinite(e))return e>0?"Infinity":"-Infinity"}let t=typeof e;if("object"!==t)return t;if(null===e)return"null";let r=Object.prototype.toString.call(e).slice(8,-1);if("Object"!==r)return r;let n=e.constructor;return n&&n!==Object&&"string"==typeof n.name&&n.name.length>0?`class instance (${n.name})`:"non-plain object"}function s(r,i,l,a){let u;if(null===r)return;void 0===r&&e(t,`${a} only supports JSON-safe values, got "undefined" at "${n(i)}" (use "null" instead)`,TypeError);let f=typeof r;if("string"===f||"boolean"===f)return;if("number"===f){Number.isFinite(r)||e(t,`${a} only supports JSON-safe values, got "${o(r)}" at "${n(i)}"`,TypeError);return}if("object"!==f&&e(t,`${a} only supports JSON-safe values, got "${o(r)}" at "${n(i)}"`,TypeError),l.has(r)&&e(t,`${a} detected cyclic reference at "${n(i)}"`,TypeError),Array.isArray(r)){l.add(r);for(let e=0;e<r.length;e++)s(r[e],[...i,e],l,a);l.delete(r);return}(u=Object.getPrototypeOf(r))!==Object.prototype&&null!==u&&e(t,`${a} only supports JSON-safe values (plain object / array / string / number / boolean / null), got "${o(r)}" at "${n(i)}"`,TypeError),l.add(r),Object.getOwnPropertySymbols(r).length>0&&e(t,`${a} only supports JSON-safe values, got symbol-keyed property at "${n(i)}"`,TypeError);let p=Object.keys(r);for(let e=0;e<p.length;e++){let t=p[e];s(r[t],[...i,t],l,a)}l.delete(r)}function i(n,o){Array.isArray(n)&&e(t,`${o} must not return an array; lockData rejects top-level arrays (wrap in object {} instead)`,r)}function l(e){return JSON.parse(JSON.stringify(e))}function a(e,t){i(e,t),s(e,[],new WeakSet,t)}export{s as assertJsonSafe,a as assertJsonSafeInput,i as assertNotTopLevelArray,l as cloneByJson,o as describeNonJsonValue};
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
interface ErrorOptions {
|
|
2
|
+
/** 原始错误(ES2022 Error.cause),便于错误链追踪 */
|
|
3
|
+
cause?: unknown;
|
|
4
|
+
}
|
|
5
|
+
declare function createError(fnName: string, message: string, ErrorClass?: ErrorConstructor, options?: ErrorOptions): Error;
|
|
6
|
+
declare function createError(fnName: string, message: string, options?: ErrorOptions): Error;
|
|
7
|
+
declare function throwError(fnName: string, message: string, ErrorClass?: ErrorConstructor, options?: ErrorOptions): never;
|
|
8
|
+
declare function throwError(fnName: string, message: string, options?: ErrorOptions): never;
|
|
9
|
+
declare function throwType(fnName: string, message: string, options?: ErrorOptions): never;
|
|
10
|
+
export type { ErrorOptions };
|
|
4
11
|
export { createError, throwError, throwType };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function r(o,t,
|
|
1
|
+
function r(o,t,e=Error,n){let c="function"==typeof e,u=c?e:Error,i=c?n:e,E=`[@cmtlyt/lingshu-toolkit#${o}]: ${t}`;return i&&"cause"in i?new u(E,{cause:i.cause}):new u(E)}function o(t,e,n=Error,c){throw r(t,e,n,c)}function t(r,t,e){o(r,t,TypeError,e)}export{r as createError,o as throwError,t as throwType};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmtlyt/lingshu-toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/shared.js",
|
|
6
6
|
"module": "./dist/shared.js",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@biomejs/biome": "2.4.11",
|
|
44
|
-
"@cmtlyt/unplugin-shadcn-registry-generate": "^0.1.
|
|
44
|
+
"@cmtlyt/unplugin-shadcn-registry-generate": "^0.1.6",
|
|
45
45
|
"@commitlint/cli": "^20.5.0",
|
|
46
46
|
"@commitlint/config-conventional": "^20.5.0",
|
|
47
47
|
"@rslib/core": "^0.21.0",
|