@southwind-ai/shared 0.1.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/index.ts +19 -0
- package/package.json +20 -0
- package/src/audit.ts +258 -0
- package/src/correlation.ts +11 -0
- package/src/data-governance.ts +89 -0
- package/src/database.ts +67 -0
- package/src/feature.ts +104 -0
- package/src/license-catalog.ts +33 -0
- package/src/license-legacy.ts +153 -0
- package/src/license.ts +267 -0
- package/src/migration.ts +39 -0
- package/src/module-configuration.ts +50 -0
- package/src/notification.ts +46 -0
- package/src/password.ts +4 -0
- package/src/platform-settings.ts +5 -0
- package/src/primitives.ts +47 -0
- package/src/profile.ts +13 -0
- package/src/rbac.ts +117 -0
- package/src/ruoyi.ts +98 -0
- package/src/scheduler.ts +108 -0
- package/src/search-provider.ts +24 -0
- package/src/workflow.ts +307 -0
package/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * from "./src/audit.js";
|
|
2
|
+
export * from "./src/database.js";
|
|
3
|
+
export * from "./src/data-governance.js";
|
|
4
|
+
export * from "./src/feature.js";
|
|
5
|
+
export * from "./src/license.js";
|
|
6
|
+
export * from "./src/license-catalog.js";
|
|
7
|
+
export * from "./src/migration.js";
|
|
8
|
+
export * from "./src/module-configuration.js";
|
|
9
|
+
export * from "./src/correlation.js";
|
|
10
|
+
export * from "./src/notification.js";
|
|
11
|
+
export * from "./src/password.js";
|
|
12
|
+
export * from "./src/platform-settings.js";
|
|
13
|
+
export * from "./src/profile.js";
|
|
14
|
+
export * from "./src/primitives.js";
|
|
15
|
+
export * from "./src/rbac.js";
|
|
16
|
+
export * from "./src/ruoyi.js";
|
|
17
|
+
export * from "./src/scheduler.js";
|
|
18
|
+
export * from "./src/search-provider.js";
|
|
19
|
+
export * from "./src/workflow.js";
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@southwind-ai/shared",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "UNLICENSED",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"bun": ">=1.3.0"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.ts",
|
|
11
|
+
"src/**/*.ts",
|
|
12
|
+
"!src/**/*.test.ts"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": "./index.ts"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/audit.ts
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import type { EntityId, ISODateTime } from "./primitives.js";
|
|
2
|
+
|
|
3
|
+
export interface AuditActionDefinition {
|
|
4
|
+
key: AuditActionType;
|
|
5
|
+
label: string;
|
|
6
|
+
description: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const AUDIT_ACTION_DEFINITIONS = [
|
|
10
|
+
{ key: "auth.login", label: "账号登录", description: "用户登录平台。" },
|
|
11
|
+
{ key: "auth.logout", label: "账号退出", description: "用户退出平台。" },
|
|
12
|
+
{
|
|
13
|
+
key: "auth.password.update",
|
|
14
|
+
label: "密码更新",
|
|
15
|
+
description: "用户或管理员更新账号密码。",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
key: "auth.session.revoke",
|
|
19
|
+
label: "会话撤销",
|
|
20
|
+
description: "管理员强制结束登录会话。",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
key: "user.profile.update",
|
|
24
|
+
label: "个人资料更新",
|
|
25
|
+
description: "用户更新自己的个人资料。",
|
|
26
|
+
},
|
|
27
|
+
{ key: "crud.create", label: "数据新增", description: "新增业务记录。" },
|
|
28
|
+
{ key: "crud.update", label: "数据更新", description: "修改业务记录。" },
|
|
29
|
+
{ key: "crud.delete", label: "数据删除", description: "删除业务记录。" },
|
|
30
|
+
{ key: "crud.restore", label: "数据恢复", description: "恢复已删除记录。" },
|
|
31
|
+
{ key: "data.import", label: "数据导入", description: "同步导入数据。" },
|
|
32
|
+
{ key: "data.export", label: "数据导出", description: "同步导出数据。" },
|
|
33
|
+
{
|
|
34
|
+
key: "data.search",
|
|
35
|
+
label: "数据搜索",
|
|
36
|
+
description: "用户主动提交搜索条件并查询业务数据。",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
key: "data.plaintext.read",
|
|
40
|
+
label: "敏感数据明文查看",
|
|
41
|
+
description: "单条查看经服务端授权的敏感字段明文。",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
key: "data.plaintext.batch-read",
|
|
45
|
+
label: "敏感数据批量明文查询",
|
|
46
|
+
description: "批量查询经服务端授权且受数据范围约束的敏感字段明文。",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: "data.governed-export.request",
|
|
50
|
+
label: "敏感数据导出申请",
|
|
51
|
+
description: "提交受策略、范围和可选审批约束的敏感数据导出申请。",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
key: "data.governed-export.generate",
|
|
55
|
+
label: "敏感数据导出生成",
|
|
56
|
+
description: "后台生成带服务端水印的受治理导出文件。",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
key: "data.governed-export.download",
|
|
60
|
+
label: "敏感数据导出下载",
|
|
61
|
+
description: "下载已完成且仍满足授权边界的受治理导出文件。",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: "data.governed-export.cancel",
|
|
65
|
+
label: "敏感数据导出取消",
|
|
66
|
+
description: "取消本人尚未完成的受治理导出任务。",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
key: "search.query",
|
|
70
|
+
label: "全局检索查询",
|
|
71
|
+
description: "提交全局检索;只记录摘要、长度和 Provider,不记录查询词。",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
key: "search.result.access",
|
|
75
|
+
label: "检索结果访问",
|
|
76
|
+
description: "访问已脱敏的全局检索结果。",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
key: "data.bulk-import.create",
|
|
80
|
+
label: "创建批量导入",
|
|
81
|
+
description: "创建后台批量导入任务。",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
key: "data.bulk-export.create",
|
|
85
|
+
label: "创建批量导出",
|
|
86
|
+
description: "创建后台批量导出任务。",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
key: "data.bulk-artifact.download",
|
|
90
|
+
label: "下载批量任务文件",
|
|
91
|
+
description: "下载批量任务结果或失败明细。",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
key: "file.upload.create",
|
|
95
|
+
label: "创建文件上传",
|
|
96
|
+
description: "创建受控文件上传会话。",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
key: "file.upload.complete",
|
|
100
|
+
label: "完成文件上传",
|
|
101
|
+
description: "校验并完成文件上传。",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
key: "file.upload.abort",
|
|
105
|
+
label: "终止文件上传",
|
|
106
|
+
description: "终止上传并清理已提交分片。",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: "file.download",
|
|
110
|
+
label: "文件下载",
|
|
111
|
+
description: "通过授权边界下载文件。",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
key: "config.update",
|
|
115
|
+
label: "全局配置更新",
|
|
116
|
+
description: "更新平台全局配置。",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
key: "config.rollback",
|
|
120
|
+
label: "配置版本回滚",
|
|
121
|
+
description: "基于历史版本创建并应用新的配置版本。",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
key: "license.verify",
|
|
125
|
+
label: "License 校验",
|
|
126
|
+
description: "校验 License 有效性。",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
key: "license.update",
|
|
130
|
+
label: "License 更新",
|
|
131
|
+
description: "安装或更新 License。",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
key: "feature.update",
|
|
135
|
+
label: "功能开关更新",
|
|
136
|
+
description: "更新功能开关状态、可见性或 License 适用范围。",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
key: "menu.structure.update",
|
|
140
|
+
label: "菜单结构更新",
|
|
141
|
+
description: "调整菜单父级、顺序或可见性。",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
key: "notification.publish",
|
|
145
|
+
label: "通知发布",
|
|
146
|
+
description: "发布站内通知。",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
key: "notification.read",
|
|
150
|
+
label: "通知已读",
|
|
151
|
+
description: "用户阅读站内通知。",
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
key: "notification.archive",
|
|
155
|
+
label: "通知归档",
|
|
156
|
+
description: "归档已发布通知。",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
key: "workflow.create",
|
|
160
|
+
label: "创建审批任务",
|
|
161
|
+
description: "创建最小审批任务。",
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
key: "workflow.claim",
|
|
165
|
+
label: "领取审批任务",
|
|
166
|
+
description: "候选角色领取审批任务。",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
key: "workflow.transfer",
|
|
170
|
+
label: "转交审批任务",
|
|
171
|
+
description: "当前领取人或管理员转交审批任务。",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
key: "workflow.decide",
|
|
175
|
+
label: "提交审批决定",
|
|
176
|
+
description: "当前领取人批准或驳回审批任务。",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
key: "workflow.cancel",
|
|
180
|
+
label: "取消审批任务",
|
|
181
|
+
description: "发起人或管理员取消审批任务。",
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
key: "workflow.timeout",
|
|
185
|
+
label: "审批任务超时",
|
|
186
|
+
description: "后台服务将到期审批任务置为超时。",
|
|
187
|
+
},
|
|
188
|
+
{ key: "task.trigger", label: "任务触发", description: "人工触发后台任务。" },
|
|
189
|
+
{ key: "task.read", label: "任务查看", description: "查看任务状态或历史。" },
|
|
190
|
+
{ key: "task.execute", label: "任务执行", description: "后台任务实际执行。" },
|
|
191
|
+
{ key: "task.recover", label: "任务恢复", description: "人工恢复异常任务。" },
|
|
192
|
+
{
|
|
193
|
+
key: "agent.tool.execute",
|
|
194
|
+
label: "Agent 工具执行",
|
|
195
|
+
description: "Agent 调用受控平台工具。",
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
key: "security.denied",
|
|
199
|
+
label: "安全拒绝",
|
|
200
|
+
description: "权限或安全策略拒绝请求。",
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
key: "system.error",
|
|
204
|
+
label: "系统错误",
|
|
205
|
+
description: "平台记录系统级异常。",
|
|
206
|
+
},
|
|
207
|
+
] as const;
|
|
208
|
+
|
|
209
|
+
export type BuiltInAuditActionType =
|
|
210
|
+
(typeof AUDIT_ACTION_DEFINITIONS)[number]["key"];
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 审计日志持久化保持字符串协议;业务动作是否有效由 Module Composition
|
|
214
|
+
* 对 AuditActionDefinition 目录进行 fail-fast 校验。
|
|
215
|
+
*/
|
|
216
|
+
export type AuditActionType = BuiltInAuditActionType | (string & {});
|
|
217
|
+
|
|
218
|
+
export type AuditActionRegistration = AuditActionType | AuditActionDefinition;
|
|
219
|
+
|
|
220
|
+
export function isAuditActionDefinition(
|
|
221
|
+
registration: AuditActionRegistration,
|
|
222
|
+
): registration is AuditActionDefinition {
|
|
223
|
+
return typeof registration !== "string";
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export type AuditOutcome = "attempted" | "success" | "failure" | "denied";
|
|
227
|
+
|
|
228
|
+
export interface AuditActor {
|
|
229
|
+
id: EntityId;
|
|
230
|
+
type: "user" | "service" | "system";
|
|
231
|
+
name: string;
|
|
232
|
+
roleCodes?: string[];
|
|
233
|
+
ip?: string;
|
|
234
|
+
userAgent?: string;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface AuditTarget {
|
|
238
|
+
type: string;
|
|
239
|
+
id?: EntityId;
|
|
240
|
+
label?: string;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export interface AuditEvent {
|
|
244
|
+
id: EntityId;
|
|
245
|
+
action: AuditActionType;
|
|
246
|
+
outcome: AuditOutcome;
|
|
247
|
+
actor: AuditActor;
|
|
248
|
+
target?: AuditTarget;
|
|
249
|
+
module?: string;
|
|
250
|
+
requestId?: string;
|
|
251
|
+
message?: string;
|
|
252
|
+
metadata?: Record<string, unknown>;
|
|
253
|
+
occurredAt: ISODateTime;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export interface AuditLogWriter {
|
|
257
|
+
write(event: AuditEvent): Promise<void>;
|
|
258
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const CORRELATION_ID_PATTERN = /^[A-Za-z0-9._-]{1,128}$/;
|
|
2
|
+
|
|
3
|
+
export function correlationId(
|
|
4
|
+
value: string | null | undefined,
|
|
5
|
+
factory: () => string = () => crypto.randomUUID(),
|
|
6
|
+
): string {
|
|
7
|
+
const candidate = value?.trim();
|
|
8
|
+
return candidate && CORRELATION_ID_PATTERN.test(candidate)
|
|
9
|
+
? candidate
|
|
10
|
+
: factory();
|
|
11
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { FeatureKey } from "./feature.js";
|
|
2
|
+
|
|
3
|
+
export type FieldClassification =
|
|
4
|
+
| "public"
|
|
5
|
+
| "internal"
|
|
6
|
+
| "personal"
|
|
7
|
+
| "sensitive"
|
|
8
|
+
| "secret";
|
|
9
|
+
|
|
10
|
+
export type FieldMaskStrategy =
|
|
11
|
+
| { kind: "redact"; replacement?: string }
|
|
12
|
+
| { kind: "partial"; prefix: number; suffix: number }
|
|
13
|
+
| { kind: "omit" };
|
|
14
|
+
|
|
15
|
+
export interface GovernedFieldPolicy {
|
|
16
|
+
fieldKey: string;
|
|
17
|
+
classification: FieldClassification;
|
|
18
|
+
mask: FieldMaskStrategy;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RoleDisplayStrategy {
|
|
22
|
+
/** 普通读取对已治理字段只能返回掩码结果。 */
|
|
23
|
+
read: "masked";
|
|
24
|
+
/** 明文仍须由 Server 有效权限签发一次性访问上下文。 */
|
|
25
|
+
reveal: "plaintext";
|
|
26
|
+
unauthorized: "hidden";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DataGovernancePolicyDefinition {
|
|
30
|
+
key: string;
|
|
31
|
+
resourceType: string;
|
|
32
|
+
version: string;
|
|
33
|
+
featureKey: FeatureKey;
|
|
34
|
+
readPermissionKey: string;
|
|
35
|
+
revealPermissionKey: string;
|
|
36
|
+
exportPermissionKey: string;
|
|
37
|
+
display: RoleDisplayStrategy;
|
|
38
|
+
fields: GovernedFieldPolicy[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type MaskResult<T> =
|
|
42
|
+
| {
|
|
43
|
+
kind: "plaintext";
|
|
44
|
+
classification: FieldClassification;
|
|
45
|
+
value: T;
|
|
46
|
+
grantId: string;
|
|
47
|
+
}
|
|
48
|
+
| {
|
|
49
|
+
kind: "masked";
|
|
50
|
+
classification: FieldClassification;
|
|
51
|
+
display: string;
|
|
52
|
+
}
|
|
53
|
+
| {
|
|
54
|
+
kind: "hidden";
|
|
55
|
+
classification: FieldClassification;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type MaskedValue = Extract<MaskResult<never>, { kind: "masked" }>;
|
|
59
|
+
|
|
60
|
+
export function maskGovernedValue(
|
|
61
|
+
value: unknown,
|
|
62
|
+
policy: GovernedFieldPolicy,
|
|
63
|
+
): MaskedValue | Extract<MaskResult<never>, { kind: "hidden" }> {
|
|
64
|
+
if (policy.mask.kind === "omit") {
|
|
65
|
+
return { kind: "hidden", classification: policy.classification };
|
|
66
|
+
}
|
|
67
|
+
const text = value === undefined || value === null ? "" : String(value);
|
|
68
|
+
if (policy.mask.kind === "redact") {
|
|
69
|
+
return {
|
|
70
|
+
kind: "masked",
|
|
71
|
+
classification: policy.classification,
|
|
72
|
+
display: text ? policy.mask.replacement || "[已脱敏]" : "",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const prefix = Math.max(0, policy.mask.prefix);
|
|
76
|
+
const suffix = Math.max(0, policy.mask.suffix);
|
|
77
|
+
if (!text || text.length <= prefix + suffix) {
|
|
78
|
+
return {
|
|
79
|
+
kind: "masked",
|
|
80
|
+
classification: policy.classification,
|
|
81
|
+
display: text ? "*".repeat(text.length) : "",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
kind: "masked",
|
|
86
|
+
classification: policy.classification,
|
|
87
|
+
display: `${text.slice(0, prefix)}${"*".repeat(text.length - prefix - suffix)}${suffix ? text.slice(-suffix) : ""}`,
|
|
88
|
+
};
|
|
89
|
+
}
|
package/src/database.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { FieldMapping, PageRequest } from "./primitives.js";
|
|
2
|
+
|
|
3
|
+
export type DatabaseDialect = "postgresql" | "mysql" | "dm" | "kingbase";
|
|
4
|
+
|
|
5
|
+
export type IdentifierStrategy =
|
|
6
|
+
| "identity"
|
|
7
|
+
| "serial"
|
|
8
|
+
| "sequence"
|
|
9
|
+
| "auto-increment";
|
|
10
|
+
export type JsonColumnStrategy = "jsonb" | "json" | "text";
|
|
11
|
+
|
|
12
|
+
export interface DialectCapabilities {
|
|
13
|
+
dialect: DatabaseDialect;
|
|
14
|
+
identifierStrategy: IdentifierStrategy;
|
|
15
|
+
jsonColumnStrategy: JsonColumnStrategy;
|
|
16
|
+
supportsReturning: boolean;
|
|
17
|
+
supportsRecursiveCte: boolean;
|
|
18
|
+
supportsFullTextSearch: boolean;
|
|
19
|
+
reservedWords: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PaginationSqlParts {
|
|
23
|
+
limit: number;
|
|
24
|
+
offset: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ColumnTypeMapping {
|
|
28
|
+
logicalType:
|
|
29
|
+
| "id"
|
|
30
|
+
| "string"
|
|
31
|
+
| "text"
|
|
32
|
+
| "integer"
|
|
33
|
+
| "boolean"
|
|
34
|
+
| "datetime"
|
|
35
|
+
| "json";
|
|
36
|
+
physicalType: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DatabaseCompatibilityAdapter {
|
|
40
|
+
dialect: DatabaseDialect;
|
|
41
|
+
capabilities: DialectCapabilities;
|
|
42
|
+
mapColumnType(
|
|
43
|
+
logicalType: ColumnTypeMapping["logicalType"],
|
|
44
|
+
): ColumnTypeMapping;
|
|
45
|
+
mapIdentifier(name: string): string;
|
|
46
|
+
mapJsonPath(column: string, path: string[]): string;
|
|
47
|
+
mapPagination(request: PageRequest): PaginationSqlParts;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface LegacyTableMapping {
|
|
51
|
+
sourceTable: string;
|
|
52
|
+
targetTable: string;
|
|
53
|
+
fields: FieldMapping[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface MigrationStatement {
|
|
57
|
+
sql: string;
|
|
58
|
+
params?: unknown[];
|
|
59
|
+
description?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface MigrationExecutionContext {
|
|
63
|
+
dialect: DatabaseDialect;
|
|
64
|
+
dryRun: boolean;
|
|
65
|
+
adapter: DatabaseCompatibilityAdapter;
|
|
66
|
+
execute(statement: MigrationStatement): Promise<void>;
|
|
67
|
+
}
|
package/src/feature.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { EntitlementKey, LicenseVersionKey } from "./license.js";
|
|
2
|
+
|
|
3
|
+
export type FeatureVisibility = "visible" | "hidden";
|
|
4
|
+
export type FeatureStage = "stable" | "beta" | "internal";
|
|
5
|
+
|
|
6
|
+
declare const featureKeyBrand: unique symbol;
|
|
7
|
+
|
|
8
|
+
export type FeatureKey = string & { readonly [featureKeyBrand]: "FeatureKey" };
|
|
9
|
+
|
|
10
|
+
export function featureKey(value: string): FeatureKey {
|
|
11
|
+
return value as FeatureKey;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface FeatureFlagDefinition {
|
|
15
|
+
key: FeatureKey;
|
|
16
|
+
label: string;
|
|
17
|
+
module: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
visible: FeatureVisibility;
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
stage: FeatureStage;
|
|
22
|
+
requiredEntitlement?: EntitlementKey;
|
|
23
|
+
/** 由业务应用维护;License 签发平台不配置此映射。 */
|
|
24
|
+
allowedLicenseVersions?: LicenseVersionKey[];
|
|
25
|
+
dependencies?: FeatureKey[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isLicenseVersionAllowed(
|
|
29
|
+
flag: FeatureFlagDefinition,
|
|
30
|
+
versionKey: LicenseVersionKey | undefined,
|
|
31
|
+
): boolean {
|
|
32
|
+
return (
|
|
33
|
+
!flag.allowedLicenseVersions ||
|
|
34
|
+
Boolean(versionKey && flag.allowedLicenseVersions.includes(versionKey))
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface FeatureFlagState {
|
|
39
|
+
key: FeatureKey;
|
|
40
|
+
enabled: boolean;
|
|
41
|
+
visible: FeatureVisibility;
|
|
42
|
+
entitlementGranted: boolean;
|
|
43
|
+
reason?:
|
|
44
|
+
| "disabled"
|
|
45
|
+
| "hidden"
|
|
46
|
+
| "entitlement-not-granted"
|
|
47
|
+
| "dependency-missing";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function resolveFeatureState(
|
|
51
|
+
flag: FeatureFlagDefinition,
|
|
52
|
+
enabledDependencies: ReadonlySet<string>,
|
|
53
|
+
entitlementGranted: boolean,
|
|
54
|
+
): FeatureFlagState {
|
|
55
|
+
if (flag.requiredEntitlement && !entitlementGranted) {
|
|
56
|
+
return {
|
|
57
|
+
key: flag.key,
|
|
58
|
+
enabled: false,
|
|
59
|
+
visible: flag.visible,
|
|
60
|
+
entitlementGranted,
|
|
61
|
+
reason: "entitlement-not-granted",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!flag.enabled) {
|
|
66
|
+
return {
|
|
67
|
+
key: flag.key,
|
|
68
|
+
enabled: false,
|
|
69
|
+
visible: flag.visible,
|
|
70
|
+
entitlementGranted,
|
|
71
|
+
reason: "disabled",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (flag.visible === "hidden") {
|
|
76
|
+
return {
|
|
77
|
+
key: flag.key,
|
|
78
|
+
enabled: false,
|
|
79
|
+
visible: flag.visible,
|
|
80
|
+
entitlementGranted,
|
|
81
|
+
reason: "hidden",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const missingDependency = flag.dependencies?.some(
|
|
86
|
+
(key) => !enabledDependencies.has(key),
|
|
87
|
+
);
|
|
88
|
+
if (missingDependency) {
|
|
89
|
+
return {
|
|
90
|
+
key: flag.key,
|
|
91
|
+
enabled: false,
|
|
92
|
+
visible: flag.visible,
|
|
93
|
+
entitlementGranted,
|
|
94
|
+
reason: "dependency-missing",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
key: flag.key,
|
|
100
|
+
enabled: true,
|
|
101
|
+
visible: flag.visible,
|
|
102
|
+
entitlementGranted,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { licenseVersionKey, type LicenseVersionKey } from "./license.js";
|
|
2
|
+
|
|
3
|
+
export interface LicenseCatalogDefinition {
|
|
4
|
+
key: LicenseVersionKey;
|
|
5
|
+
label: string;
|
|
6
|
+
description: string;
|
|
7
|
+
order: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const LICENSE_CATALOG = [
|
|
11
|
+
{
|
|
12
|
+
key: licenseVersionKey("free-trial"),
|
|
13
|
+
label: "免费试用版",
|
|
14
|
+
description: "用于产品体验和非生产验证的基础版本。",
|
|
15
|
+
order: 10,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
key: licenseVersionKey("standard"),
|
|
19
|
+
label: "标准版",
|
|
20
|
+
description: "面向常规企业业务场景的标准能力版本。",
|
|
21
|
+
order: 20,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
key: licenseVersionKey("advanced"),
|
|
25
|
+
label: "高级版",
|
|
26
|
+
description: "包含高级治理、自动化和大批量处理能力。",
|
|
27
|
+
order: 30,
|
|
28
|
+
},
|
|
29
|
+
] as const satisfies readonly LicenseCatalogDefinition[];
|
|
30
|
+
|
|
31
|
+
export function licenseCatalogKeys(): ReadonlySet<LicenseVersionKey> {
|
|
32
|
+
return new Set(LICENSE_CATALOG.map(({ key }) => key));
|
|
33
|
+
}
|