@h-ai/serv 1.0.0-beta.17 → 1.0.0-beta.19

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.
@@ -1,5 +1,5 @@
1
1
  import { mapHaiResult } from '../chunk-XKCLD42I.js';
2
- import { implement, servM } from '../chunk-FEG22LCX.js';
2
+ import { implement, servM } from '../chunk-PLGCHDZM.js';
3
3
  import { apiContract } from '@h-ai/api-contract';
4
4
  import { core, err, HaiCommonError, ok } from '@h-ai/core';
5
5
 
@@ -58,7 +58,9 @@ function createIamProcedures(deps) {
58
58
  ];
59
59
  const operation = operations.find((value) => policy.data.reauthentication.operations.includes(value)) ?? operations[0] ?? "iam.profile.update";
60
60
  return sensitive(context, operation, () => iam.user.updateCurrentUser(token, input));
61
- }).route("users.list").permission("iam.users.read").handle(({ input }) => iam.user.listUsers(input)).route("users.get").permission("iam.users.read").handle(({ input }) => iam.user.getUser(input.id, { include: ["roles"] })).route("users.create").permission("iam.users.write").handle(({ input, context }) => sensitive(context, "iam.users.create", () => createUser(iam, input))).route("users.invite").permission("iam.users.write").handle(({ input, context }) => sensitive(context, "iam.users.invite", () => createUser(iam, input))).route("users.update").permission("iam.users.write").handle(({ input, context }) => sensitive(context, "iam.users.update", () => updateUser(iam, input))).route("users.delete").permission("iam.users.write").handle(({ input, context }) => {
61
+ }).route("users.list").permission("iam.users.read").handle(({ input }) => iam.user.listUsers(input)).route("users.get").permission("iam.users.read").handle(({ input }) => iam.user.getUser(input.id, {
62
+ include: ["roles"]
63
+ })).route("users.create").permission("iam.users.write").handle(({ input, context }) => sensitive(context, "iam.users.create", () => createUser(iam, input))).route("users.invite").permission("iam.users.write").handle(({ input, context }) => sensitive(context, "iam.users.invite", () => createUser(iam, input))).route("users.update").permission("iam.users.write").handle(({ input, context }) => sensitive(context, "iam.users.update", () => updateUser(iam, input))).route("users.delete").permission("iam.users.write").handle(({ input, context }) => {
62
64
  if (context.session.userId === input.id)
63
65
  return err(HaiCommonError.FORBIDDEN, servM("serv_iamCannotDeleteCurrentUser", { locale: context.locale }));
64
66
  return sensitive(context, "iam.users.delete", () => iam.user.deleteUser(input.id));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/features/serv-feature-iam.ts"],"names":[],"mappings":"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","file":"iam.js","sourcesContent":["/**\n * @h-ai/serv — IAM 默认 procedures\n *\n * 基于 `@h-ai/iam` 提供开箱即用的 IAM procedures 实现:认证、用户管理、角色权限 CRUD。\n * @module features/serv-feature-iam\n */\n\nimport type {\n IamAdminCreateUserInput,\n IamAdminUpdateUserInput,\n IamCurrentUser,\n IamInviteUserInput,\n} from '@h-ai/api-contract'\nimport type { HaiResult } from '@h-ai/core'\nimport type { IamFunctions, TokenPair, User } from '@h-ai/iam'\nimport type { ServContext } from '../serv-context.js'\nimport { apiContract } from '@h-ai/api-contract'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\nimport { implement } from '../serv-router.js'\nimport { mapHaiResult } from './serv-feature-helpers.js'\n\nconst iamContract = apiContract.iam\n\nconst iamFeatureLogger = core.logger.child({ module: 'serv', scope: 'feature-iam' })\n\n/**\n * IAM 默认 procedures 依赖。\n *\n * @example\n * ```ts\n * import { iam } from '@h-ai/iam'\n * import { createIamProcedures } from '@h-ai/serv/features/iam'\n *\n * // 宿主已初始化 IAM 及共享依赖。\n * const procedures = createIamProcedures({ iam })\n * // logout 撤销请求持有的 opaque access token;过期 access 也可撤销 refresh,\n * // 不接收 userId,不要求有效 access 的 auth guard,失败仍检查 HaiResult。\n * ```\n */\nexport interface IamProcedureDeps {\n readonly iam: IamFunctions\n}\n\n/**\n * 创建默认 IAM procedures;按操作声明认证/权限。logout 只撤销请求持有的完整 opaque token,不要求该 access 尚有效,也不能按 userId 撤销。\n *\n * @example\n * ```ts\n * import { iam } from '@h-ai/iam'\n * import { createIamProcedures } from '@h-ai/serv/features/iam'\n *\n * // 宿主已初始化 IAM 及共享依赖。\n * const procedures = createIamProcedures({ iam })\n * // logout 撤销请求持有的 opaque access token;过期 access 也可撤销 refresh,\n * // 不接收 userId,不要求有效 access 的 auth guard,失败仍检查 HaiResult。\n * ```\n */\nexport function createIamProcedures(deps: IamProcedureDeps) {\n const { iam } = deps\n\n // 权限守卫先执行;额外鉴别只绑定已授权操作,不提升原有权限。\n async function sensitive<T>(context: ServContext, operation: string, run: () => Promise<HaiResult<T>>): Promise<HaiResult<T>> {\n const policy = iam.publicConfig?.reauthentication\n if (policy?.enabled) {\n const checked = await iam.auth.checkSensitiveOperation(context.accessToken ?? '', operation, context.request.headers.get('x-hai-reauthentication') ?? undefined)\n if (!checked.success)\n return checked\n }\n return run()\n }\n\n return implement(iamContract)\n .context<ServContext>()\n\n .route('auth.reauthenticate')\n .auth()\n .handle(({ input, context }) => iam.auth.reauthenticate(context.accessToken ?? '', input.password, input.operation))\n\n .route('auth.login', ({ input, context }) => iam.auth.login(input, context))\n .route('auth.createCaptcha', ({ input }) => iam.auth.createCaptcha(input))\n .route('auth.loginWithOtp', ({ input, context }) => iam.auth.loginWithOtp(input, context))\n .route('auth.loginWithLdap', ({ input, context }) => iam.auth.loginWithLdap(input, context))\n .route('auth.publicConfig', () => iam.getPublicConfig())\n .route('auth.renewPassword', ({ input }) => iam.user.renewPassword(input))\n .route('auth.requestPasswordReset', ({ input }) => iam.user.requestPasswordReset(input.identifier, input))\n .route('auth.confirmPasswordReset', ({ input }) => iam.user.confirmPasswordReset(input.token, input.newPassword))\n\n .route('auth.logout', ({ input, context }) => {\n // 仅撤销调用方持有的完整随机令牌;不要求 access 仍有效,也不接受 userId 撤销。\n const token = input.accessToken ?? context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.auth.logout(token)\n })\n\n .route('auth.closeSession', ({ input, context }) => {\n // 页面离开只撤销持有的会话,不清 Cookie,避免迟到响应覆盖下一文档的新登录。\n const token = input.accessToken ?? context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.auth.logout(token)\n })\n\n .route('auth.currentUser')\n .auth()\n .handle(async ({ context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n\n return mapHaiResult<User, IamCurrentUser>(\n await iam.user.getCurrentUser(token),\n user => ({\n ...user,\n roles: context.session.roles,\n permissions: context.session.permissions,\n }),\n )\n })\n\n .route('auth.refresh', async ({ input }) => {\n return mapHaiResult(await iam.session.refresh(input.refreshToken), tokens => ({ tokens }))\n })\n .route('auth.sendOtp', ({ input }) => iam.auth.sendOtp(input.identifier, input))\n .route('auth.register', ({ input, context }) => iam.auth.registerAndLogin(input, context))\n\n .route('auth.changePassword')\n .auth()\n .handle(({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.user.changeCurrentUserPassword(token, input.oldPassword, input.newPassword)\n })\n\n .route('auth.updateCurrentUser')\n .auth()\n .handle(async ({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n const policy = await iam.getPublicConfig()\n if (!policy.success)\n return policy\n // 标准资料入口与独立资料/头像入口共用策略;混合更新优先使用资料用途。\n const operations = [\n ...(Object.keys(input).some(key => key !== 'avatarUrl') ? ['iam.profile.update'] : []),\n ...(input.avatarUrl !== undefined ? ['iam.profile.avatar'] : []),\n ]\n const operation = operations.find(value => policy.data.reauthentication.operations.includes(value)) ?? operations[0] ?? 'iam.profile.update'\n return sensitive(context, operation, () => iam.user.updateCurrentUser(token, input))\n })\n\n .route('users.list')\n .permission('iam.users.read')\n .handle(({ input }) => iam.user.listUsers(input))\n\n .route('users.get')\n .permission('iam.users.read')\n .handle(({ input }) => iam.user.getUser(input.id, { include: ['roles'] }))\n\n .route('users.create')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.create', () => createUser(iam, input)))\n\n .route('users.invite')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.invite', () => createUser(iam, input)))\n\n .route('users.update')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.update', () => updateUser(iam, input)))\n\n .route('users.delete')\n .permission('iam.users.write')\n .handle(({ input, context }) => {\n // 防止自删:避免管理员误删自己的账号导致授权锁死。\n if (context.session.userId === input.id)\n return err(HaiCommonError.FORBIDDEN, servM('serv_iamCannotDeleteCurrentUser', { locale: context.locale }))\n return sensitive(context, 'iam.users.delete', () => iam.user.deleteUser(input.id))\n })\n\n .route('users.resetPassword')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.resetPassword', () => iam.user.adminResetPassword(input.id, input.newPassword)))\n\n .route('roles.list')\n .permission('iam.roles.read')\n .handle(() => iam.authz.getAllRoles())\n\n .route('roles.get')\n .permission('iam.roles.read')\n .handle(({ input }) => iam.authz.getRole(input.id))\n\n .route('roles.create')\n .permission('iam.roles.write')\n .handle(({ input, context }) => sensitive(context, 'iam.roles.create', () => iam.authz.createRole(input)))\n\n .route('roles.update')\n .permission('iam.roles.write')\n .handle(({ input, context }) => {\n const { id, ...data } = input\n return sensitive(context, 'iam.roles.update', () => iam.authz.updateRole(id, data))\n })\n\n .route('roles.delete')\n .permission('iam.roles.write')\n .handle(({ input, context }) => sensitive(context, 'iam.roles.delete', () => iam.authz.deleteRole(input.id)))\n\n .route('permissions.list')\n .permission('iam.permissions.read')\n .handle(({ input }) => iam.authz.getAllPermissions(input))\n\n .route('permissions.get')\n .permission('iam.permissions.read')\n .handle(({ input }) => iam.authz.getPermission(input.id))\n\n .route('permissions.create')\n .permission('iam.permissions.write')\n .handle(({ input, context }) => sensitive(context, 'iam.permissions.create', () => iam.authz.createPermission(input)))\n\n .route('permissions.setSensitiveOperations')\n .permission('iam.permissions.write')\n .handle(({ input, context }) => sensitive(context, 'iam.permissions.sensitiveOperations', () => iam.authz.setPermissionSensitiveOperations(input.id, input.sensitiveOperations)))\n\n .route('permissions.delete')\n .permission('iam.permissions.write')\n .handle(({ input, context }) => sensitive(context, 'iam.permissions.delete', () => iam.authz.deletePermission(input.id)))\n\n .build()\n}\n\nasync function createUser(iam: IamFunctions, input: IamAdminCreateUserInput | IamInviteUserInput): Promise<HaiResult<User>> {\n const invited = !('password' in input)\n const registerResult = 'password' in input ? await iam.user.createUser(input) : await iam.user.inviteUser(input)\n if (!registerResult.success)\n return registerResult\n\n const userId = registerResult.data.user.id\n\n // 任何后续步骤失败都需要回滚刚创建的用户,以避免重复用户名/邮箱错误阅存。\n async function rollback(reason: string): Promise<void> {\n if (invited) {\n // 邀请可能已经送达,不能回滚外部已发布凭据;保留账号供管理员补齐配置。\n iamFeatureLogger.error('Invited account requires configuration repair', { userId, reason })\n return\n }\n const deleteResult = await iam.user.deleteUser(userId)\n if (!deleteResult.success)\n iamFeatureLogger.error('createUser rollback failed, orphan user may exist', { userId, reason, error: deleteResult.error })\n }\n\n if (input.roleIds !== undefined) {\n const rolesResult = await iam.authz.syncRoles(userId, input.roleIds)\n if (!rolesResult.success) {\n await rollback('syncRoles failed')\n return rolesResult\n }\n }\n\n return ok(registerResult.data.user)\n}\n\nasync function updateUser(iam: IamFunctions, input: IamAdminUpdateUserInput): Promise<HaiResult<User>> {\n const { id, roleIds, ...data } = input\n const updateResult = await iam.user.updateUser(id, data)\n if (!updateResult.success)\n return updateResult\n\n if (roleIds) {\n const rolesResult = await iam.authz.syncRoles(id, roleIds)\n if (!rolesResult.success)\n return rolesResult\n }\n\n return updateResult\n}\n\n// re-export 类型以便 IDE 跳转\nexport type { TokenPair }\n"]}
1
+ {"version":3,"sources":["../../src/features/serv-feature-iam.ts"],"names":[],"mappings":"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","file":"iam.js","sourcesContent":["/**\n * @h-ai/serv — IAM 默认 procedures\n *\n * 基于 `@h-ai/iam` 提供开箱即用的 IAM procedures 实现:认证、用户管理、角色权限 CRUD。\n * @module features/serv-feature-iam\n */\n\nimport type {\n IamAdminCreateUserInput,\n IamAdminUpdateUserInput,\n IamCurrentUser,\n IamInviteUserInput,\n} from '@h-ai/api-contract'\nimport type { HaiResult } from '@h-ai/core'\nimport type { IamFunctions, TokenPair, User } from '@h-ai/iam'\nimport type { ServContext } from '../serv-context.js'\nimport { apiContract } from '@h-ai/api-contract'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\nimport { implement } from '../serv-router.js'\nimport { mapHaiResult } from './serv-feature-helpers.js'\n\nconst iamContract = apiContract.iam\n\nconst iamFeatureLogger = core.logger.child({ module: 'serv', scope: 'feature-iam' })\n\n/**\n * IAM 默认 procedures 依赖。\n *\n * @example\n * ```ts\n * import { iam } from '@h-ai/iam'\n * import { createIamProcedures } from '@h-ai/serv/features/iam'\n *\n * // 宿主已初始化 IAM 及共享依赖。\n * const procedures = createIamProcedures({ iam })\n * // logout 撤销请求持有的 opaque access token;过期 access 也可撤销 refresh,\n * // 不接收 userId,不要求有效 access 的 auth guard,失败仍检查 HaiResult。\n * ```\n */\nexport interface IamProcedureDeps {\n readonly iam: IamFunctions\n}\n\n/**\n * 创建默认 IAM procedures;按操作声明认证/权限。logout 只撤销请求持有的完整 opaque token,不要求该 access 尚有效,也不能按 userId 撤销。\n *\n * @example\n * ```ts\n * import { iam } from '@h-ai/iam'\n * import { createIamProcedures } from '@h-ai/serv/features/iam'\n *\n * // 宿主已初始化 IAM 及共享依赖。\n * const procedures = createIamProcedures({ iam })\n * // logout 撤销请求持有的 opaque access token;过期 access 也可撤销 refresh,\n * // 不接收 userId,不要求有效 access 的 auth guard,失败仍检查 HaiResult。\n * ```\n */\nexport function createIamProcedures(deps: IamProcedureDeps) {\n const { iam } = deps\n\n // 权限守卫先执行;额外鉴别只绑定已授权操作,不提升原有权限。\n async function sensitive<T>(context: ServContext, operation: string, run: () => Promise<HaiResult<T>>): Promise<HaiResult<T>> {\n const policy = iam.publicConfig?.reauthentication\n if (policy?.enabled) {\n const checked = await iam.auth.checkSensitiveOperation(context.accessToken ?? '', operation, context.request.headers.get('x-hai-reauthentication') ?? undefined)\n if (!checked.success)\n return checked\n }\n return run()\n }\n\n return implement(iamContract)\n .context<ServContext>()\n\n .route('auth.reauthenticate')\n .auth()\n .handle(({ input, context }) => iam.auth.reauthenticate(context.accessToken ?? '', input.password, input.operation))\n\n .route('auth.login', ({ input, context }) => iam.auth.login(input, context))\n .route('auth.createCaptcha', ({ input }) => iam.auth.createCaptcha(input))\n .route('auth.loginWithOtp', ({ input, context }) => iam.auth.loginWithOtp(input, context))\n .route('auth.loginWithLdap', ({ input, context }) => iam.auth.loginWithLdap(input, context))\n .route('auth.publicConfig', () => iam.getPublicConfig())\n .route('auth.renewPassword', ({ input }) => iam.user.renewPassword(input))\n .route('auth.requestPasswordReset', ({ input }) => iam.user.requestPasswordReset(input.identifier, input))\n .route('auth.confirmPasswordReset', ({ input }) => iam.user.confirmPasswordReset(input.token, input.newPassword))\n\n .route('auth.logout', ({ input, context }) => {\n // 仅撤销调用方持有的完整随机令牌;不要求 access 仍有效,也不接受 userId 撤销。\n const token = input.accessToken ?? context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.auth.logout(token)\n })\n\n .route('auth.closeSession', ({ input, context }) => {\n // 页面离开只撤销持有的会话,不清 Cookie,避免迟到响应覆盖下一文档的新登录。\n const token = input.accessToken ?? context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.auth.logout(token)\n })\n\n .route('auth.currentUser')\n .auth()\n .handle(async ({ context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n\n return mapHaiResult<User, IamCurrentUser>(\n await iam.user.getCurrentUser(token),\n user => ({\n ...user,\n roles: context.session.roles,\n permissions: context.session.permissions,\n }),\n )\n })\n\n .route('auth.refresh', async ({ input }) => {\n return mapHaiResult(await iam.session.refresh(input.refreshToken), tokens => ({ tokens }))\n })\n .route('auth.sendOtp', ({ input }) => iam.auth.sendOtp(input.identifier, input))\n .route('auth.register', ({ input, context }) => iam.auth.registerAndLogin(input, context))\n\n .route('auth.changePassword')\n .auth()\n .handle(({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.user.changeCurrentUserPassword(token, input.oldPassword, input.newPassword)\n })\n\n .route('auth.updateCurrentUser')\n .auth()\n .handle(async ({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n const policy = await iam.getPublicConfig()\n if (!policy.success)\n return policy\n // 标准资料入口与独立资料/头像入口共用策略;混合更新优先使用资料用途。\n const operations = [\n ...(Object.keys(input).some(key => key !== 'avatarUrl')\n ? ['iam.profile.update']\n : []),\n ...(input.avatarUrl !== undefined\n ? ['iam.profile.avatar']\n : []),\n ]\n const operation = operations.find(value => policy.data.reauthentication.operations.includes(value)) ?? operations[0] ?? 'iam.profile.update'\n return sensitive(context, operation, () => iam.user.updateCurrentUser(token, input))\n })\n\n .route('users.list')\n .permission('iam.users.read')\n .handle(({ input }) => iam.user.listUsers(input))\n\n .route('users.get')\n .permission('iam.users.read')\n .handle(({ input }) => iam.user.getUser(input.id, {\n include: ['roles'],\n }))\n\n .route('users.create')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.create', () => createUser(iam, input)))\n\n .route('users.invite')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.invite', () => createUser(iam, input)))\n\n .route('users.update')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.update', () => updateUser(iam, input)))\n\n .route('users.delete')\n .permission('iam.users.write')\n .handle(({ input, context }) => {\n // 防止自删:避免管理员误删自己的账号导致授权锁死。\n if (context.session.userId === input.id)\n return err(HaiCommonError.FORBIDDEN, servM('serv_iamCannotDeleteCurrentUser', { locale: context.locale }))\n return sensitive(context, 'iam.users.delete', () => iam.user.deleteUser(input.id))\n })\n\n .route('users.resetPassword')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.resetPassword', () => iam.user.adminResetPassword(input.id, input.newPassword)))\n\n .route('roles.list')\n .permission('iam.roles.read')\n .handle(() => iam.authz.getAllRoles())\n\n .route('roles.get')\n .permission('iam.roles.read')\n .handle(({ input }) => iam.authz.getRole(input.id))\n\n .route('roles.create')\n .permission('iam.roles.write')\n .handle(({ input, context }) => sensitive(context, 'iam.roles.create', () => iam.authz.createRole(input)))\n\n .route('roles.update')\n .permission('iam.roles.write')\n .handle(({ input, context }) => {\n const { id, ...data } = input\n return sensitive(context, 'iam.roles.update', () => iam.authz.updateRole(id, data))\n })\n\n .route('roles.delete')\n .permission('iam.roles.write')\n .handle(({ input, context }) => sensitive(context, 'iam.roles.delete', () => iam.authz.deleteRole(input.id)))\n\n .route('permissions.list')\n .permission('iam.permissions.read')\n .handle(({ input }) => iam.authz.getAllPermissions(input))\n\n .route('permissions.get')\n .permission('iam.permissions.read')\n .handle(({ input }) => iam.authz.getPermission(input.id))\n\n .route('permissions.create')\n .permission('iam.permissions.write')\n .handle(({ input, context }) => sensitive(context, 'iam.permissions.create', () => iam.authz.createPermission(input)))\n\n .route('permissions.setSensitiveOperations')\n .permission('iam.permissions.write')\n .handle(({ input, context }) => sensitive(context, 'iam.permissions.sensitiveOperations', () => iam.authz.setPermissionSensitiveOperations(input.id, input.sensitiveOperations)))\n\n .route('permissions.delete')\n .permission('iam.permissions.write')\n .handle(({ input, context }) => sensitive(context, 'iam.permissions.delete', () => iam.authz.deletePermission(input.id)))\n\n .build()\n}\n\nasync function createUser(iam: IamFunctions, input: IamAdminCreateUserInput | IamInviteUserInput): Promise<HaiResult<User>> {\n const invited = !('password' in input)\n const registerResult = 'password' in input\n ? await iam.user.createUser(input)\n : await iam.user.inviteUser(input)\n if (!registerResult.success)\n return registerResult\n\n const userId = registerResult.data.user.id\n\n // 任何后续步骤失败都需要回滚刚创建的用户,以避免重复用户名/邮箱错误阅存。\n async function rollback(reason: string): Promise<void> {\n if (invited) {\n // 邀请可能已经送达,不能回滚外部已发布凭据;保留账号供管理员补齐配置。\n iamFeatureLogger.error('Invited account requires configuration repair', { userId, reason })\n return\n }\n const deleteResult = await iam.user.deleteUser(userId)\n if (!deleteResult.success)\n iamFeatureLogger.error('createUser rollback failed, orphan user may exist', { userId, reason, error: deleteResult.error })\n }\n\n if (input.roleIds !== undefined) {\n const rolesResult = await iam.authz.syncRoles(userId, input.roleIds)\n if (!rolesResult.success) {\n await rollback('syncRoles failed')\n return rolesResult\n }\n }\n\n return ok(registerResult.data.user)\n}\n\nasync function updateUser(iam: IamFunctions, input: IamAdminUpdateUserInput): Promise<HaiResult<User>> {\n const { id, roleIds, ...data } = input\n const updateResult = await iam.user.updateUser(id, data)\n if (!updateResult.success)\n return updateResult\n\n if (roleIds) {\n const rolesResult = await iam.authz.syncRoles(id, roleIds)\n if (!rolesResult.success)\n return rolesResult\n }\n\n return updateResult\n}\n\n// re-export 类型以便 IDE 跳转\nexport type { TokenPair }\n"]}
@@ -2,8 +2,8 @@ import * as _orpc_server from '@orpc/server';
2
2
  import * as zod_v4_core from 'zod/v4/core';
3
3
  import * as zod from 'zod';
4
4
  import { StorageFunctions } from '@h-ai/storage';
5
- import { S as ServContext } from '../serv-context-BsNvQ-bb.js';
6
- import { A as AuthenticatedServContext } from '../serv-router-BnkxPvS6.js';
5
+ import { ServContext } from '../index.js';
6
+ import { AuthenticatedServContext } from '../index.js';
7
7
  import '@h-ai/core';
8
8
 
9
9
  /**
@@ -250,4 +250,5 @@ declare function createStorageProcedures(deps: StorageProcedureDeps): {
250
250
  };
251
251
  };
252
252
 
253
- export { type StorageAccessRequest, type StorageProcedureDeps, createStorageProcedures };
253
+ export { createStorageProcedures };
254
+ export type { StorageAccessRequest, StorageProcedureDeps };
@@ -1,6 +1,6 @@
1
- import { validateInputOrFail } from '../chunk-IQZQTAEW.js';
1
+ import { validateInputOrFail } from '../chunk-CZ6VA2EN.js';
2
2
  import { mapHaiResult } from '../chunk-XKCLD42I.js';
3
- import { implement, servM } from '../chunk-FEG22LCX.js';
3
+ import { implement, servM } from '../chunk-PLGCHDZM.js';
4
4
  import { apiContract, StorageDeleteSummarySchema } from '@h-ai/api-contract';
5
5
  import { err, HaiCommonError, ok } from '@h-ai/core';
6
6
  import { getStorageKeyIssue, StorageDeleteKeysSchema, STORAGE_KEY_MAX_BYTES } from '@h-ai/storage/schemas';
@@ -11,7 +11,9 @@ function buildStorageKeyValidationFailure(field, locale, messageKey, params) {
11
11
  return err(
12
12
  HaiCommonError.VALIDATION_ERROR,
13
13
  servM("serv_validationFailed", { locale }),
14
- [{ field, message }]
14
+ [
15
+ { field, message }
16
+ ]
15
17
  );
16
18
  }
17
19
  function validateStorageKeyOrFail(key, locale, field = "key") {
@@ -51,7 +53,11 @@ function createStorageProcedures(deps) {
51
53
  const keyResult = validateStorageKeyOrFail(input.key, context.locale);
52
54
  if (!keyResult.success)
53
55
  return keyResult;
54
- const access = await authorize({ context, operation: "download", keys: [keyResult.data] });
56
+ const access = await authorize({
57
+ context,
58
+ operation: "download",
59
+ keys: [keyResult.data]
60
+ });
55
61
  if (!access.success)
56
62
  return access;
57
63
  const expiresIn = input.expiresIn ?? 900;
@@ -64,7 +70,11 @@ function createStorageProcedures(deps) {
64
70
  const keyResult = validateStorageKeyOrFail(input.key, context.locale);
65
71
  if (!keyResult.success)
66
72
  return keyResult;
67
- const access = await authorize({ context, operation: "upload", keys: [keyResult.data] });
73
+ const access = await authorize({
74
+ context,
75
+ operation: "upload",
76
+ keys: [keyResult.data]
77
+ });
68
78
  if (!access.success)
69
79
  return access;
70
80
  const expiresIn = input.expiresIn ?? 900;
@@ -82,7 +92,11 @@ function createStorageProcedures(deps) {
82
92
  if (!prefixResult.success)
83
93
  return prefixResult;
84
94
  }
85
- const access = await authorize({ context, operation: "list", keys: [prefix] });
95
+ const access = await authorize({
96
+ context,
97
+ operation: "list",
98
+ keys: [prefix]
99
+ });
86
100
  if (!access.success)
87
101
  return access;
88
102
  return storage.dir.list(input);
@@ -90,7 +104,11 @@ function createStorageProcedures(deps) {
90
104
  const keyResult = validateStorageKeyOrFail(input.key, context.locale);
91
105
  if (!keyResult.success)
92
106
  return keyResult;
93
- const access = await authorize({ context, operation: "metadata", keys: [keyResult.data] });
107
+ const access = await authorize({
108
+ context,
109
+ operation: "metadata",
110
+ keys: [keyResult.data]
111
+ });
94
112
  if (!access.success)
95
113
  return access;
96
114
  return storage.file.head(keyResult.data);
@@ -98,7 +116,11 @@ function createStorageProcedures(deps) {
98
116
  const keyResult = validateStorageKeyOrFail(input.key, context.locale);
99
117
  if (!keyResult.success)
100
118
  return keyResult;
101
- const access = await authorize({ context, operation: "delete", keys: [keyResult.data] });
119
+ const access = await authorize({
120
+ context,
121
+ operation: "delete",
122
+ keys: [keyResult.data]
123
+ });
102
124
  if (!access.success)
103
125
  return access;
104
126
  return storage.file.delete(keyResult.data);
@@ -113,7 +135,10 @@ function createStorageProcedures(deps) {
113
135
  if (result.success)
114
136
  return result;
115
137
  const progress = StorageDeleteSummarySchema.safeParse(result.error.ext?.deleteMany);
116
- return err({ ...result.error, publicExt: progress.success ? { deleteMany: progress.data } : void 0 });
138
+ return err({
139
+ ...result.error,
140
+ publicExt: progress.success ? { deleteMany: progress.data } : void 0
141
+ });
117
142
  }).build();
118
143
  }
119
144
  function toPresignedResult(key, expiresIn, result) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/features/serv-feature-storage.ts"],"names":[],"mappings":"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","file":"storage.js","sourcesContent":["/**\n * @h-ai/serv — Storage 默认 procedures\n *\n * 基于 `@h-ai/storage` 提供开箱即用的 Storage procedures 实现:预签名 URL、列表、元数据、删除。\n *\n * 所有操作先验证路径,再调用应用提供的资源授权策略;未提供策略时拒绝访问。\n * 策略必须从已认证 session 确定用户/租户范围,不能信任客户端提供的前缀。\n * @module features/serv-feature-storage\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { StorageFunctions } from '@h-ai/storage'\nimport type { ServContext } from '../serv-context.js'\nimport type { ServMessageKey } from '../serv-i18n.js'\nimport type { AuthenticatedServContext } from '../serv-router.js'\nimport { apiContract, StorageDeleteSummarySchema } from '@h-ai/api-contract'\nimport { err, HaiCommonError, ok } from '@h-ai/core'\nimport { getStorageKeyIssue, STORAGE_KEY_MAX_BYTES, StorageDeleteKeysSchema } from '@h-ai/storage/schemas'\nimport { servM } from '../serv-i18n.js'\nimport { implement } from '../serv-router.js'\nimport { validateInputOrFail } from '../serv-validation.js'\nimport { mapHaiResult } from './serv-feature-helpers.js'\n\nconst storageContract = apiContract.storage\n\nfunction buildStorageKeyValidationFailure(\n field: string,\n locale: string,\n messageKey: ServMessageKey,\n params?: Record<string, string | number>,\n): HaiResult<never> {\n const message = servM(messageKey, { locale, params })\n return err(\n HaiCommonError.VALIDATION_ERROR,\n servM('serv_validationFailed', { locale }),\n [{ field, message }],\n )\n}\n\n/**\n * 对业务层再次校验存储 key 是否安全。\n *\n * 与公开 Schema 共用规则,保留业务入口防线;后续仍必须执行授权和 Provider 防逃逸。\n */\nfunction validateStorageKeyOrFail(key: string, locale: string, field: string = 'key'): HaiResult<string> {\n const issue = getStorageKeyIssue(key)\n if (issue === 'empty')\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyRequired')\n if (issue === 'tooLong') {\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyTooLong', {\n max: STORAGE_KEY_MAX_BYTES,\n })\n }\n if (issue === 'characters')\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyIllegalChars')\n if (issue === 'segments')\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyDotSegments')\n return ok(key)\n}\n\nfunction validateStorageKeysOrFail(keys: string[], locale: string): HaiResult<string[]> {\n const parsed = validateInputOrFail(StorageDeleteKeysSchema, keys, locale)\n if (!parsed.success)\n return parsed\n for (const [index, key] of keys.entries()) {\n const keyResult = validateStorageKeyOrFail(key, locale, `keys.${index}`)\n if (!keyResult.success)\n return keyResult\n }\n return ok(keys)\n}\n\n/**\n * 存储资源授权请求;批量操作只有全部资源获准后才会调用存储。\n *\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n * import { createStorageProcedures } from '@h-ai/serv/features/storage'\n *\n * // 宿主已初始化 Storage;用户根目录来自可信 session,含尾斜杠形成边界。\n * const procedures = createStorageProcedures({ storage, authorize: ({ context, keys }) => {\n * const root = `users/${encodeURIComponent(context.session.userId)}/`\n * return keys.length > 0 && keys.every(key => key.startsWith(root))\n * } })\n * // 空列表前缀与相邻用户名前缀均不能匹配此根目录。\n * ```\n */\nexport interface StorageAccessRequest {\n /** 操作类别;策略可分别授予读取、写入或删除权限。 */\n readonly operation: 'download' | 'upload' | 'list' | 'metadata' | 'delete' | 'deleteMany'\n /** 已通过认证的可信请求上下文。 */\n readonly context: AuthenticatedServContext\n /** 单项/批量 key;list 操作仅包含请求的 prefix,未提供 prefix 时为空字符串。 */\n readonly keys: readonly string[]\n}\n\n/**\n * Storage 默认 procedures 依赖。\n *\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n * import { createStorageProcedures } from '@h-ai/serv/features/storage'\n *\n * // 宿主已初始化 Storage;用户根目录来自可信 session,含尾斜杠形成边界。\n * const procedures = createStorageProcedures({ storage, authorize: ({ context, keys }) => {\n * const root = `users/${encodeURIComponent(context.session.userId)}/`\n * return keys.length > 0 && keys.every(key => key.startsWith(root))\n * } })\n * // 空列表前缀与相邻用户名前缀均不能匹配此根目录。\n * ```\n */\nexport interface StorageProcedureDeps {\n readonly storage: StorageFunctions\n /** 必需的资源授权策略;返回 true 才允许执行,缺失策略时默认拒绝。 */\n readonly authorize: (request: StorageAccessRequest) => boolean | Promise<boolean>\n}\n\n/**\n * 内部预签名结果投影,包含短期访问 URL 和可选失效时间。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * function safeUrl(value: PresignedResponse) {\n * return { url: value.url, key: value.key, expiresAt: value.expiresAt }\n * }\n * // URL 是授权后的短期凭据;只返回给已授权调用方,不记录到日志。\n * ```\n */\ninterface PresignedResponse {\n readonly url: string\n readonly key: string\n readonly expiresAt?: Date\n}\n\n/**\n * 创建具有认证、路径校验和资源授权的 Storage procedures。\n *\n * @param deps - 存储服务及必需的资源授权策略。\n * @returns 可挂载至 serv.createApp 的存储 router;拒绝访问返回 FORBIDDEN。\n *\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n * import { createStorageProcedures } from '@h-ai/serv/features/storage'\n *\n * // 宿主已初始化 Storage;用户根目录来自可信 session,含尾斜杠形成边界。\n * const procedures = createStorageProcedures({ storage, authorize: ({ context, keys }) => {\n * const root = `users/${encodeURIComponent(context.session.userId)}/`\n * return keys.length > 0 && keys.every(key => key.startsWith(root))\n * } })\n * // 空列表前缀与相邻用户名前缀均不能匹配此根目录。\n * ```\n */\nexport function createStorageProcedures(deps: StorageProcedureDeps) {\n const { storage } = deps\n\n async function authorize(request: StorageAccessRequest): Promise<HaiResult<void>> {\n if (typeof deps.authorize !== 'function' || await deps.authorize(request) !== true)\n return err(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale: request.context.locale }))\n return ok(undefined)\n }\n\n return implement(storageContract)\n .context<ServContext>()\n\n .route('presignedUrls.createDownload')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({ context, operation: 'download', keys: [keyResult.data] })\n if (!access.success)\n return access\n const expiresIn = input.expiresIn ?? 900\n return toPresignedResult(\n keyResult.data,\n expiresIn,\n await storage.presign.getUrl(keyResult.data, { expiresIn }),\n )\n })\n\n .route('presignedUrls.createUpload')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({ context, operation: 'upload', keys: [keyResult.data] })\n if (!access.success)\n return access\n const expiresIn = input.expiresIn ?? 900\n const contentType = input.contentType ?? 'application/octet-stream'\n const { key: _, ...options } = input\n return toPresignedResult(\n keyResult.data,\n expiresIn,\n await storage.presign.putUrl(keyResult.data, { ...options, expiresIn, contentType }),\n )\n })\n\n .route('files.list')\n .auth()\n .handle(async ({ input, context }) => {\n const prefix = input.prefix ?? ''\n if (prefix) {\n const prefixResult = validateStorageKeyOrFail(prefix, context.locale, 'prefix')\n if (!prefixResult.success)\n return prefixResult\n }\n const access = await authorize({ context, operation: 'list', keys: [prefix] })\n if (!access.success)\n return access\n return storage.dir.list(input)\n })\n\n .route('files.getMetadata')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({ context, operation: 'metadata', keys: [keyResult.data] })\n if (!access.success)\n return access\n return storage.file.head(keyResult.data)\n })\n\n .route('files.delete')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({ context, operation: 'delete', keys: [keyResult.data] })\n if (!access.success)\n return access\n return storage.file.delete(keyResult.data)\n })\n\n .route('files.deleteMany')\n .auth()\n .handle(async ({ input, context }) => {\n const keysResult = validateStorageKeysOrFail(input.keys, context.locale)\n if (!keysResult.success)\n return keysResult\n const access = await authorize({ context, operation: 'deleteMany', keys: keysResult.data })\n if (!access.success)\n return access\n const result = await storage.file.deleteMany(keysResult.data)\n if (result.success)\n return result\n const progress = StorageDeleteSummarySchema.safeParse(result.error.ext?.deleteMany)\n return err({ ...result.error, publicExt: progress.success ? { deleteMany: progress.data } : undefined })\n })\n\n .build()\n}\n\nfunction toPresignedResult(\n key: string,\n expiresIn: number | undefined,\n result: HaiResult<string>,\n): HaiResult<PresignedResponse> {\n return mapHaiResult(result, url => ({\n url,\n key,\n expiresAt: expiresIn ? new Date(Date.now() + expiresIn * 1000) : undefined,\n }))\n}\n"]}
1
+ {"version":3,"sources":["../../src/features/serv-feature-storage.ts"],"names":[],"mappings":"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","file":"storage.js","sourcesContent":["/**\n * @h-ai/serv — Storage 默认 procedures\n *\n * 基于 `@h-ai/storage` 提供开箱即用的 Storage procedures 实现:预签名 URL、列表、元数据、删除。\n *\n * 所有操作先验证路径,再调用应用提供的资源授权策略;未提供策略时拒绝访问。\n * 策略必须从已认证 session 确定用户/租户范围,不能信任客户端提供的前缀。\n * @module features/serv-feature-storage\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { StorageFunctions } from '@h-ai/storage'\nimport type { ServContext } from '../serv-context.js'\nimport type { ServMessageKey } from '../serv-i18n.js'\nimport type { AuthenticatedServContext } from '../serv-router.js'\nimport { apiContract, StorageDeleteSummarySchema } from '@h-ai/api-contract'\nimport { err, HaiCommonError, ok } from '@h-ai/core'\nimport { getStorageKeyIssue, STORAGE_KEY_MAX_BYTES, StorageDeleteKeysSchema } from '@h-ai/storage/schemas'\nimport { servM } from '../serv-i18n.js'\nimport { implement } from '../serv-router.js'\nimport { validateInputOrFail } from '../serv-validation.js'\nimport { mapHaiResult } from './serv-feature-helpers.js'\n\nconst storageContract = apiContract.storage\n\nfunction buildStorageKeyValidationFailure(\n field: string,\n locale: string,\n messageKey: ServMessageKey,\n params?: Record<string, string | number>,\n): HaiResult<never> {\n const message = servM(messageKey, { locale, params })\n return err(\n HaiCommonError.VALIDATION_ERROR,\n servM('serv_validationFailed', { locale }),\n [\n { field, message },\n ],\n )\n}\n\n/**\n * 对业务层再次校验存储 key 是否安全。\n *\n * 与公开 Schema 共用规则,保留业务入口防线;后续仍必须执行授权和 Provider 防逃逸。\n */\nfunction validateStorageKeyOrFail(key: string, locale: string, field: string = 'key'): HaiResult<string> {\n const issue = getStorageKeyIssue(key)\n if (issue === 'empty')\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyRequired')\n if (issue === 'tooLong') {\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyTooLong', {\n max: STORAGE_KEY_MAX_BYTES,\n })\n }\n if (issue === 'characters')\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyIllegalChars')\n if (issue === 'segments')\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyDotSegments')\n return ok(key)\n}\n\nfunction validateStorageKeysOrFail(keys: string[], locale: string): HaiResult<string[]> {\n const parsed = validateInputOrFail(StorageDeleteKeysSchema, keys, locale)\n if (!parsed.success)\n return parsed\n for (const [index, key] of keys.entries()) {\n const keyResult = validateStorageKeyOrFail(key, locale, `keys.${index}`)\n if (!keyResult.success)\n return keyResult\n }\n return ok(keys)\n}\n\n/**\n * 存储资源授权请求;批量操作只有全部资源获准后才会调用存储。\n *\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n * import { createStorageProcedures } from '@h-ai/serv/features/storage'\n *\n * // 宿主已初始化 Storage;用户根目录来自可信 session,含尾斜杠形成边界。\n * const procedures = createStorageProcedures({ storage, authorize: ({ context, keys }) => {\n * const root = `users/${encodeURIComponent(context.session.userId)}/`\n * return keys.length > 0 && keys.every(key => key.startsWith(root))\n * } })\n * // 空列表前缀与相邻用户名前缀均不能匹配此根目录。\n * ```\n */\nexport interface StorageAccessRequest {\n /** 操作类别;策略可分别授予读取、写入或删除权限。 */\n readonly operation: 'download' | 'upload' | 'list' | 'metadata' | 'delete' | 'deleteMany'\n /** 已通过认证的可信请求上下文。 */\n readonly context: AuthenticatedServContext\n /** 单项/批量 key;list 操作仅包含请求的 prefix,未提供 prefix 时为空字符串。 */\n readonly keys: readonly string[]\n}\n\n/**\n * Storage 默认 procedures 依赖。\n *\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n * import { createStorageProcedures } from '@h-ai/serv/features/storage'\n *\n * // 宿主已初始化 Storage;用户根目录来自可信 session,含尾斜杠形成边界。\n * const procedures = createStorageProcedures({ storage, authorize: ({ context, keys }) => {\n * const root = `users/${encodeURIComponent(context.session.userId)}/`\n * return keys.length > 0 && keys.every(key => key.startsWith(root))\n * } })\n * // 空列表前缀与相邻用户名前缀均不能匹配此根目录。\n * ```\n */\nexport interface StorageProcedureDeps {\n readonly storage: StorageFunctions\n /** 必需的资源授权策略;返回 true 才允许执行,缺失策略时默认拒绝。 */\n readonly authorize: (request: StorageAccessRequest) => boolean | Promise<boolean>\n}\n\n/**\n * 内部预签名结果投影,包含短期访问 URL 和可选失效时间。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * function safeUrl(value: PresignedResponse) {\n * return { url: value.url, key: value.key, expiresAt: value.expiresAt }\n * }\n * // URL 是授权后的短期凭据;只返回给已授权调用方,不记录到日志。\n * ```\n */\ninterface PresignedResponse {\n readonly url: string\n readonly key: string\n readonly expiresAt?: Date\n}\n\n/**\n * 创建具有认证、路径校验和资源授权的 Storage procedures。\n *\n * @param deps - 存储服务及必需的资源授权策略。\n * @returns 可挂载至 serv.createApp 的存储 router;拒绝访问返回 FORBIDDEN。\n *\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n * import { createStorageProcedures } from '@h-ai/serv/features/storage'\n *\n * // 宿主已初始化 Storage;用户根目录来自可信 session,含尾斜杠形成边界。\n * const procedures = createStorageProcedures({ storage, authorize: ({ context, keys }) => {\n * const root = `users/${encodeURIComponent(context.session.userId)}/`\n * return keys.length > 0 && keys.every(key => key.startsWith(root))\n * } })\n * // 空列表前缀与相邻用户名前缀均不能匹配此根目录。\n * ```\n */\nexport function createStorageProcedures(deps: StorageProcedureDeps) {\n const { storage } = deps\n\n async function authorize(request: StorageAccessRequest): Promise<HaiResult<void>> {\n if (typeof deps.authorize !== 'function' || await deps.authorize(request) !== true)\n return err(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale: request.context.locale }))\n return ok(undefined)\n }\n\n return implement(storageContract)\n .context<ServContext>()\n\n .route('presignedUrls.createDownload')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({\n context,\n operation: 'download',\n keys: [keyResult.data],\n })\n if (!access.success)\n return access\n const expiresIn = input.expiresIn ?? 900\n return toPresignedResult(\n keyResult.data,\n expiresIn,\n await storage.presign.getUrl(keyResult.data, { expiresIn }),\n )\n })\n\n .route('presignedUrls.createUpload')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({\n context,\n operation: 'upload',\n keys: [keyResult.data],\n })\n if (!access.success)\n return access\n const expiresIn = input.expiresIn ?? 900\n const contentType = input.contentType ?? 'application/octet-stream'\n const { key: _, ...options } = input\n return toPresignedResult(\n keyResult.data,\n expiresIn,\n await storage.presign.putUrl(keyResult.data, { ...options, expiresIn, contentType }),\n )\n })\n\n .route('files.list')\n .auth()\n .handle(async ({ input, context }) => {\n const prefix = input.prefix ?? ''\n if (prefix) {\n const prefixResult = validateStorageKeyOrFail(prefix, context.locale, 'prefix')\n if (!prefixResult.success)\n return prefixResult\n }\n const access = await authorize({\n context,\n operation: 'list',\n keys: [prefix],\n })\n if (!access.success)\n return access\n return storage.dir.list(input)\n })\n\n .route('files.getMetadata')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({\n context,\n operation: 'metadata',\n keys: [keyResult.data],\n })\n if (!access.success)\n return access\n return storage.file.head(keyResult.data)\n })\n\n .route('files.delete')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({\n context,\n operation: 'delete',\n keys: [keyResult.data],\n })\n if (!access.success)\n return access\n return storage.file.delete(keyResult.data)\n })\n\n .route('files.deleteMany')\n .auth()\n .handle(async ({ input, context }) => {\n const keysResult = validateStorageKeysOrFail(input.keys, context.locale)\n if (!keysResult.success)\n return keysResult\n const access = await authorize({ context, operation: 'deleteMany', keys: keysResult.data })\n if (!access.success)\n return access\n const result = await storage.file.deleteMany(keysResult.data)\n if (result.success)\n return result\n const progress = StorageDeleteSummarySchema.safeParse(result.error.ext?.deleteMany)\n return err({\n ...result.error,\n publicExt: progress.success\n ? { deleteMany: progress.data }\n : undefined,\n })\n })\n\n .build()\n}\n\nfunction toPresignedResult(\n key: string,\n expiresIn: number | undefined,\n result: HaiResult<string>,\n): HaiResult<PresignedResponse> {\n return mapHaiResult(result, url => ({\n url,\n key,\n expiresAt: expiresIn\n ? new Date(Date.now() + expiresIn * 1000)\n : undefined,\n }))\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,17 +1,17 @@
1
1
  import * as _h_ai_core from '@h-ai/core';
2
2
  import { HaiResult, TrustedProxies, RequestAccessConfigInput, ValidationFormError } from '@h-ai/core';
3
+ import { ProcedureHandler, ErrorMap, Meta, ProcedureHandlerOptions, ORPCErrorConstructorMap, Router, ContractRouter } from '@orpc/server';
3
4
  import { AIFunctions, AudioWsStartMessage, AudioFormat } from '@h-ai/ai';
4
5
  import { AnyContractRouter, OpenAPI } from '@orpc/contract';
5
- import { ProcedureHandler, ErrorMap, Meta, ProcedureHandlerOptions, ORPCErrorConstructorMap, Router } from '@orpc/server';
6
- import { a as ServSession, S as ServContext, b as ServIam, R as RefreshCookieConfig, C as CreateServContext, c as ServRequestMetadata, p as parseRequestContext, d as buildAuthContextFactory } from './serv-context-BsNvQ-bb.js';
7
- export { e as CreateServContextInput, N as NativeRefreshTokenTransportConfig, f as RefreshTokenPair } from './serv-context-BsNvQ-bb.js';
6
+ import { a as ServSession, S as ServContext, b as ServIam, R as RefreshCookieConfig, C as CreateServContext, c as ServRequestMetadata, d as CreateServContextInput } from './serv-context-DSsS_ksL.js';
7
+ export { N as NativeRefreshTokenTransportConfig, e as RefreshTokenPair } from './serv-context-DSsS_ksL.js';
8
8
  import { MiddlewareHandler } from 'hono';
9
9
  import { z, ZodType } from 'zod';
10
10
  import { TransportServerKeyRing, CryptoFunctions, TransportKeyStore } from '@h-ai/crypto';
11
11
  import { ServerType } from '@hono/node-server';
12
12
  import { AddressInfo } from 'node:net';
13
- import { i as implement } from './serv-router-BnkxPvS6.js';
14
- export { A as AuthenticatedServContext, S as ServImplementer, a as ServRouteHandler, b as ServRoutePath, c as ServRouterBuilder } from './serv-router-BnkxPvS6.js';
13
+ import { S as ServImplementer } from './serv-router-DWjYC0zD.js';
14
+ export { A as AuthenticatedServContext, a as ServRouteHandler, b as ServRoutePath, c as ServRouterBuilder } from './serv-router-DWjYC0zD.js';
15
15
  import { StorageFunctions } from '@h-ai/storage';
16
16
 
17
17
  /**
@@ -472,7 +472,7 @@ declare const ServConfigSchema: z.ZodPipe<z.ZodObject<{
472
472
  port: number;
473
473
  } | undefined;
474
474
  http?: {
475
- trustedProxies: string[] | "auto";
475
+ trustedProxies: "auto" | string[];
476
476
  access: false | {
477
477
  file: string;
478
478
  } | {
@@ -487,7 +487,7 @@ declare const ServConfigSchema: z.ZodPipe<z.ZodObject<{
487
487
  }[];
488
488
  id: string;
489
489
  paths: string[];
490
- methods: ("GET" | "HEAD" | "OPTIONS" | "POST" | "PUT" | "PATCH" | "DELETE" | "CONNECT" | "TRACE")[];
490
+ methods: ("GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "CONNECT" | "TRACE")[];
491
491
  encryption?: "required" | "disabled" | undefined;
492
492
  }[];
493
493
  global?: {
@@ -932,8 +932,8 @@ interface ServListenOptions {
932
932
  * 优雅关闭回调(释放业务模块资源)。
933
933
  *
934
934
  * 传入后 `serv.listen` 自动监听 `SIGINT` / `SIGTERM`,按以下顺序关闭:
935
- * 1. 停止接受新 HTTP 连接
936
- * 2. 调用此回调释放业务资源
935
+ * 1. 停止接受新 HTTP 连接,关闭 Audio WebSocket(未配合者最多等待一秒)
936
+ * 2. 等待监听句柄关闭,再调用此回调释放业务资源
937
937
  *
938
938
  * 应用层无需手动注册 `process.once('SIGINT', ...)` 或维护 `shuttingDown` 标志。
939
939
  */
@@ -961,7 +961,7 @@ interface ServNodeServer {
961
961
  readonly host: string;
962
962
  readonly port: number;
963
963
  };
964
- /** 优雅关闭服务器并等待已有连接断开。 */
964
+ /** 停止接入或取消待完成绑定;关闭语音连接最多等待一秒。并发关闭复用结果,真实监听失败仍会拒绝。 */
965
965
  readonly close: () => Promise<void>;
966
966
  }
967
967
  /**
@@ -1037,6 +1037,23 @@ interface ServCorsConfig {
1037
1037
  */
1038
1038
  declare function cors(config?: ServCorsConfig): ServMiddleware;
1039
1039
 
1040
+ /**
1041
+ * 获取 Serv 模块的 i18n 消息。
1042
+ *
1043
+ * 单次调用可通过 `options.locale` 指定该次调用使用的语言,
1044
+ * 用于在请求处理过程中根据 `Accept-Language` 头本地化错误消息,
1045
+ * 而不污染全局 locale(多并发请求安全)。
1046
+ *
1047
+ * @example
1048
+ * ```ts
1049
+ * import { serv } from '@h-ai/serv'
1050
+ *
1051
+ * const message = serv.m('serv_validationStringMin', { locale: 'en-US', params: { min: 3 } })
1052
+ * // 单次 locale 不修改并发请求的全局语言。
1053
+ * ```
1054
+ */
1055
+ declare const servM: (key: "serv_transportPlaintextRequired" | "$schema" | "serv_validationFailed" | "serv_validationRequired" | "serv_validationInvalid" | "serv_validationInvalidType" | "serv_validationUnexpectedFields" | "serv_validationStringMin" | "serv_validationStringMax" | "serv_validationNumberMin" | "serv_validationNumberMax" | "serv_validationArrayMin" | "serv_validationArrayMax" | "serv_validationTooSmall" | "serv_validationTooBig" | "serv_validationEmail" | "serv_validationUrl" | "serv_validationUuid" | "serv_validationEnum" | "serv_errorUnauthorized" | "serv_errorForbidden" | "serv_errorInternal" | "serv_storageKeyRequired" | "serv_storageKeyTooLong" | "serv_storageKeyIllegalChars" | "serv_storageKeyDotSegments" | "serv_iamCannotDeleteCurrentUser" | "serv_transportInvalidPayload" | "serv_transportUnknownClient" | "serv_transportClientIdRequired" | "serv_transportClientKeyNotFound" | "serv_transportDecryptFailed" | "serv_transportEncryptFailed" | "serv_transportKeyExchangeFailed" | "serv_transportBusy" | "serv_feedbackUnavailable" | "serv_invalidApiPrefix", options?: _h_ai_core.MessageOptions) => string;
1056
+
1040
1057
  /**
1041
1058
  * @h-ai/serv — OpenAPI 文档
1042
1059
  *
@@ -1317,26 +1334,28 @@ declare function resolveRequestLocale(headers: Headers | Record<string, string |
1317
1334
  declare function validateInputOrFail<TOutput>(schema: ZodType<TOutput>, input: unknown, locale?: string): HaiResult<TOutput>;
1318
1335
 
1319
1336
  /**
1320
- * hai-framework API service runtime 统一入口(扁平 API)。
1337
+ * @h-ai/serv — 公开类型聚合
1321
1338
  *
1339
+ * 将各模块对外暴露的类型集中在此重导出,方便消费方按需 `import type`。
1340
+ * @module serv-types
1341
+ */
1342
+
1343
+ /**
1344
+ * Serv 公开命名空间;显式函数签名避免消费者声明引用内部共享构建块。
1322
1345
  * @example
1323
1346
  * ```ts
1324
- * import type { ServContext } from '@h-ai/serv'
1325
- * import { apiContract } from '@h-ai/api-contract'
1347
+ * import type { ServFunctions } from '@h-ai/serv'
1326
1348
  * import { serv } from '@h-ai/serv'
1327
1349
  *
1328
- * const contract = apiContract.create({})
1329
- * const procedures = serv.implement(contract).context<ServContext>().build()
1330
- * const app = serv.createApp({ contract, procedures })
1331
- *
1332
- * const response = await app.request('/health')
1333
- * if (!response.ok) throw new Error('Health request failed')
1350
+ * const runtime: ServFunctions = serv
1351
+ * const context = runtime.parseRequestContext({ request: new Request('https://example.com') })
1352
+ * void context.requestId
1334
1353
  * ```
1335
1354
  */
1336
- declare const serv: {
1355
+ interface ServFunctions {
1337
1356
  createApp: typeof createApp;
1338
- parseRequestContext: typeof parseRequestContext;
1339
- buildAuthContextFactory: typeof buildAuthContextFactory;
1357
+ parseRequestContext: (input: CreateServContextInput) => ServContext;
1358
+ buildAuthContextFactory: (verifyToken: (accessToken: string) => Promise<HaiResult<ServSession>>) => CreateServContext;
1340
1359
  resolveRequestLocale: typeof resolveRequestLocale;
1341
1360
  listen: typeof listen;
1342
1361
  toFetch: typeof toFetch;
@@ -1344,11 +1363,41 @@ declare const serv: {
1344
1363
  storageAssets: typeof storageAssets;
1345
1364
  createRuntimeSecurityPolicy: typeof createRuntimeSecurityPolicy;
1346
1365
  generateSpec: typeof generateSpec;
1347
- implement: typeof implement;
1366
+ implement: <TContract extends ContractRouter<Meta>>(contract: TContract) => ServImplementer<TContract>;
1348
1367
  validateInputOrFail: typeof validateInputOrFail;
1349
1368
  WILDCARD_PERMISSION: string;
1350
1369
  WILDCARD_ROLE: string;
1351
- m: (key: "serv_transportPlaintextRequired" | "$schema" | "serv_validationFailed" | "serv_validationRequired" | "serv_validationInvalid" | "serv_validationInvalidType" | "serv_validationUnexpectedFields" | "serv_validationStringMin" | "serv_validationStringMax" | "serv_validationNumberMin" | "serv_validationNumberMax" | "serv_validationArrayMin" | "serv_validationArrayMax" | "serv_validationTooSmall" | "serv_validationTooBig" | "serv_validationEmail" | "serv_validationUrl" | "serv_validationUuid" | "serv_validationEnum" | "serv_errorUnauthorized" | "serv_errorForbidden" | "serv_errorInternal" | "serv_storageKeyRequired" | "serv_storageKeyTooLong" | "serv_storageKeyIllegalChars" | "serv_storageKeyDotSegments" | "serv_iamCannotDeleteCurrentUser" | "serv_transportInvalidPayload" | "serv_transportUnknownClient" | "serv_transportClientIdRequired" | "serv_transportClientKeyNotFound" | "serv_transportDecryptFailed" | "serv_transportEncryptFailed" | "serv_transportKeyExchangeFailed" | "serv_transportBusy" | "serv_feedbackUnavailable" | "serv_invalidApiPrefix", options?: _h_ai_core.MessageOptions) => string;
1352
- };
1370
+ m: typeof servM;
1371
+ }
1372
+
1373
+ /**
1374
+ * @h-ai/serv — API service runtime 统一入口
1375
+ *
1376
+ * 将 createApp、parseRequestContext、buildAuthContextFactory、listen、toFetch、openapi、pipeline 工具集合为扁平
1377
+ * 的 `serv` 命名空间,应用层通过这个模块访问所有 runtime 能力而无需直接导入内部文件。
1378
+ *
1379
+ * @module serv-main
1380
+ *
1381
+ */
1382
+
1383
+ /**
1384
+ * hai-framework API service runtime 统一入口(扁平 API)。
1385
+ *
1386
+ * @example
1387
+ * ```ts
1388
+ * import type { ServContext } from '@h-ai/serv'
1389
+ * import { apiContract } from '@h-ai/api-contract'
1390
+ * import { serv } from '@h-ai/serv'
1391
+ *
1392
+ * const contract = apiContract.create({})
1393
+ * const procedures = serv.implement(contract).context<ServContext>().build()
1394
+ * const app = serv.createApp({ contract, procedures })
1395
+ *
1396
+ * const response = await app.request('/health')
1397
+ * if (!response.ok) throw new Error('Health request failed')
1398
+ * ```
1399
+ */
1400
+ declare const serv: ServFunctions;
1353
1401
 
1354
- export { type AudioTicketGrant, type AudioTicketVerification, type AuthorizedAudioRequest, type CreateServAppOptions, CreateServContext, type GenerateOpenAPISpecOptions, RefreshCookieConfig, type ServAudioConfig, type ServConfig, type ServConfigInput, ServConfigSchema, ServContext, type ServCorsConfig, type ServCorsRuntimeConfig, type ServCorsRuntimeConfigInput, type ServDocsHttpConfig, type ServFetchHandler, type ServHealthHttpConfig, type ServHttpApp, type ServHttpConfig, type ServHttpConfigInput, ServIam, type ServListenOptions, type ServMiddleware, type ServMiddlewareFactory, type ServMiddlewareMount, type ServNodeServer, type ServOpenAPIHttpConfig, type ServProcedureHandler, type ServProcedureOptions, ServRequestMetadata, type ServRuntimeSecurityPolicy, type ServRuntimeSecurityPolicyInput, type ServServerConfig, type ServServerConfigInput, ServSession, type ServStorageAssetsConfig, type ServTransportConfig, type ServTransportRuntimeConfig, type ServTransportRuntimeConfigInput, type ServValidationFailureBody, serv };
1402
+ export { CreateServContext, CreateServContextInput, RefreshCookieConfig, ServConfigSchema, ServContext, ServIam, ServImplementer, ServRequestMetadata, ServSession, serv };
1403
+ export type { AudioTicketGrant, AudioTicketVerification, AuthorizedAudioRequest, CreateServAppOptions, GenerateOpenAPISpecOptions, ServAudioConfig, ServConfig, ServConfigInput, ServCorsConfig, ServCorsRuntimeConfig, ServCorsRuntimeConfigInput, ServDocsHttpConfig, ServFetchHandler, ServFunctions, ServHealthHttpConfig, ServHttpApp, ServHttpConfig, ServHttpConfigInput, ServListenOptions, ServMiddleware, ServMiddlewareFactory, ServMiddlewareMount, ServNodeServer, ServOpenAPIHttpConfig, ServProcedureHandler, ServProcedureOptions, ServRuntimeSecurityPolicy, ServRuntimeSecurityPolicyInput, ServServerConfig, ServServerConfigInput, ServStorageAssetsConfig, ServTransportConfig, ServTransportRuntimeConfig, ServTransportRuntimeConfigInput, ServValidationFailureBody };