@h-ai/iam 0.1.0-alpha.53 → 0.1.0-alpha.54

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/README.md CHANGED
@@ -160,6 +160,8 @@ verifyToken(accessToken)
160
160
 
161
161
  #### 使用方式
162
162
 
163
+ `authz.createRole({ ..., permissionIds })` 和 `authz.updateRole(id, { ..., permissionIds })` 在同一事务中保存角色资料和完整权限集合;任一步失败均回滚。`permissionIds: []` 清空权限,省略则保持不变;重复 ID 去重,未知权限失败。并发更新以最后提交的完整集合为准,不合并两个集合。自管事务提交后同步现有用户会话;使用外部事务时,调用方仍须在提交后同步会话。Admin Console 直接使用此接口,不再拆分更新或补偿删除角色。
164
+
163
165
  ```ts
164
166
  // 密码登录
165
167
  const result = await iam.auth.login({ identifier: 'admin', password: 'Password123' })
@@ -546,3 +548,5 @@ pnpm test
546
548
  ## 许可证
547
549
 
548
550
  Apache-2.0
551
+
552
+ 角色列表通过 `iam.authz.getRoleUserCounts(roleIds)` 一次聚合当前页角色的真实成员数,返回 `HaiResult<Map<string, number>>`。无关联为 0;查询失败显示加载错误,不生成零统计。
package/dist/index.d.ts CHANGED
@@ -483,7 +483,9 @@ interface AuthzOperations {
483
483
  * @param role - 角色数据(code、name、description、isSystem)
484
484
  * @returns 成功返回创建的角色(含 id 和时间戳)
485
485
  */
486
- createRole: (role: Omit<Role, 'id' | 'createdAt' | 'updatedAt'>, tx?: DmlWithTxOperations) => Promise<HaiResult<Role>>;
486
+ createRole: (role: Omit<Role, 'id' | 'createdAt' | 'updatedAt'> & {
487
+ permissionIds?: string[];
488
+ }, tx?: DmlWithTxOperations) => Promise<HaiResult<Role>>;
487
489
  /**
488
490
  * 获取角色
489
491
  *
@@ -505,14 +507,18 @@ interface AuthzOperations {
505
507
  * @returns 成功返回分页角色列表
506
508
  */
507
509
  getAllRoles: (options?: PaginationOptionsInput) => Promise<HaiResult<PaginatedResult<Role>>>;
510
+ /** 批量统计角色成员数;成功时无关联返回 0,查询失败返回错误 */
511
+ getRoleUserCounts: (roleIds: string[]) => Promise<HaiResult<Map<string, number>>>;
508
512
  /**
509
513
  * 更新角色
510
514
  *
511
515
  * @param roleId - 角色 ID
512
- * @param data - 要更新的字段(name、description 等)
516
+ * @param data - 要更新的字段;permissionIds 在同一事务内全量替换,未传则保持权限不变
513
517
  * @returns 成功返回更新后的角色;角色不存在返回 ROLE_NOT_FOUND
514
518
  */
515
- updateRole: (roleId: string, data: Partial<Omit<Role, 'id' | 'createdAt' | 'updatedAt'>>, tx?: DmlWithTxOperations) => Promise<HaiResult<Role>>;
519
+ updateRole: (roleId: string, data: Partial<Omit<Role, 'id' | 'createdAt' | 'updatedAt'>> & {
520
+ permissionIds?: string[];
521
+ }, tx?: DmlWithTxOperations) => Promise<HaiResult<Role>>;
516
522
  /**
517
523
  * 删除角色
518
524
  *
package/dist/index.js CHANGED
@@ -2540,6 +2540,21 @@ async function createDbUserRoleRepository(roleRepository) {
2540
2540
  }
2541
2541
  return ok(result.data.map((r) => r.user_id));
2542
2542
  },
2543
+ async getUserCounts(roleIds) {
2544
+ const ids = [...new Set(roleIds)];
2545
+ const counts = new Map(ids.map((id) => [id, 0]));
2546
+ if (ids.length === 0)
2547
+ return ok(counts);
2548
+ const result = await reldb.sql.query(
2549
+ `SELECT role_id, COUNT(DISTINCT user_id) AS user_count FROM ${USER_ROLE_TABLE} WHERE role_id IN (${ids.map(() => "?").join(", ")}) GROUP BY role_id`,
2550
+ ids
2551
+ );
2552
+ if (!result.success)
2553
+ return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_queryRoleFailed", { params: { message: result.error.message } }), result.error);
2554
+ for (const row of result.data)
2555
+ counts.set(row.role_id, Number(row.user_count));
2556
+ return ok(counts);
2557
+ },
2543
2558
  async getRolesForUsers(userIds) {
2544
2559
  const result = /* @__PURE__ */ new Map();
2545
2560
  if (userIds.length === 0) {
@@ -2665,6 +2680,9 @@ async function createDbRoleRepository() {
2665
2680
  return repo;
2666
2681
  }
2667
2682
  var DbRoleRepository = class extends BaseReldbCrudRepository {
2683
+ async touch(roleId, tx) {
2684
+ return tx.execute(`UPDATE ${TABLE_NAME4} SET updated_at = ? WHERE id = ?`, [(/* @__PURE__ */ new Date()).toISOString(), roleId]);
2685
+ }
2668
2686
  constructor() {
2669
2687
  super(reldb, {
2670
2688
  table: TABLE_NAME4,
@@ -2853,6 +2871,25 @@ function createRbacManager(config) {
2853
2871
  }
2854
2872
  }
2855
2873
  }
2874
+ async function replaceRolePermissions(roleId, permissionIds, tx) {
2875
+ const uniqueIds = [...new Set(permissionIds)];
2876
+ for (const id of uniqueIds) {
2877
+ const permission = await permissionRepository.findById(id, tx);
2878
+ if (!permission.success)
2879
+ return permission;
2880
+ if (!permission.data)
2881
+ return err(HaiIamError.PERMISSION_NOT_FOUND, iamM("iam_permissionNotExist"));
2882
+ }
2883
+ const removed = await rolePermissionRepository.removeByRoleId(roleId, tx);
2884
+ if (!removed.success)
2885
+ return removed;
2886
+ for (const id of uniqueIds) {
2887
+ const assigned = await rolePermissionRepository.assign(roleId, id, tx);
2888
+ if (!assigned.success)
2889
+ return assigned;
2890
+ }
2891
+ return ok(void 0);
2892
+ }
2856
2893
  return {
2857
2894
  async checkPermission(userId, permission) {
2858
2895
  if (!rbacConfig.enabled) {
@@ -2978,6 +3015,7 @@ function createRbacManager(config) {
2978
3015
  },
2979
3016
  // ─── 角色管理 ───
2980
3017
  async createRole(role, tx) {
3018
+ const { permissionIds, ...metadata } = role;
2981
3019
  const ownTx = !tx;
2982
3020
  if (!tx) {
2983
3021
  const txResult = await reldb.tx.begin();
@@ -2987,7 +3025,7 @@ function createRbacManager(config) {
2987
3025
  tx = txResult.data;
2988
3026
  }
2989
3027
  try {
2990
- const createResult = await roleRepository.create(role, tx);
3028
+ const createResult = await roleRepository.create(metadata, tx);
2991
3029
  if (!createResult.success) {
2992
3030
  if (ownTx)
2993
3031
  await tx.rollback();
@@ -3008,6 +3046,14 @@ function createRbacManager(config) {
3008
3046
  await tx.rollback();
3009
3047
  return err(HaiIamError.ROLE_NOT_FOUND, iamM("iam_roleNotExist"));
3010
3048
  }
3049
+ if (permissionIds !== void 0) {
3050
+ const replaced = await replaceRolePermissions(createdResult.data.id, permissionIds, tx);
3051
+ if (!replaced.success) {
3052
+ if (ownTx)
3053
+ await tx.rollback();
3054
+ return replaced;
3055
+ }
3056
+ }
3011
3057
  if (ownTx) {
3012
3058
  const commitResult = await tx.commit();
3013
3059
  if (!commitResult.success) {
@@ -3058,7 +3104,11 @@ function createRbacManager(config) {
3058
3104
  }
3059
3105
  return ok(result.data);
3060
3106
  },
3107
+ getRoleUserCounts(roleIds) {
3108
+ return userRoleRepository.getUserCounts(roleIds);
3109
+ },
3061
3110
  async updateRole(roleId, data, tx) {
3111
+ const { permissionIds, ...metadata } = data;
3062
3112
  const ownTx = !tx;
3063
3113
  if (!tx) {
3064
3114
  const txResult = await reldb.tx.begin();
@@ -3068,7 +3118,7 @@ function createRbacManager(config) {
3068
3118
  tx = txResult.data;
3069
3119
  }
3070
3120
  try {
3071
- const updateResult = await roleRepository.updateById(roleId, data, tx);
3121
+ const updateResult = Object.values(metadata).some((value) => value !== void 0) ? await roleRepository.updateById(roleId, metadata, tx) : await roleRepository.touch(roleId, tx);
3072
3122
  if (!updateResult.success) {
3073
3123
  if (ownTx)
3074
3124
  await tx.rollback();
@@ -3079,6 +3129,14 @@ function createRbacManager(config) {
3079
3129
  await tx.rollback();
3080
3130
  return err(HaiIamError.ROLE_NOT_FOUND, iamM("iam_roleNotExist"));
3081
3131
  }
3132
+ if (permissionIds !== void 0) {
3133
+ const replaced = await replaceRolePermissions(roleId, permissionIds, tx);
3134
+ if (!replaced.success) {
3135
+ if (ownTx)
3136
+ await tx.rollback();
3137
+ return replaced;
3138
+ }
3139
+ }
3082
3140
  const updatedResult = await roleRepository.findById(roleId, tx);
3083
3141
  if (!updatedResult.success) {
3084
3142
  if (ownTx)