@h-ai/serv 1.0.0-beta.2 → 1.0.0-beta.21

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.
@@ -2,8 +2,8 @@ import * as _orpc_server from '@orpc/server';
2
2
  import * as zod from 'zod';
3
3
  import * as zod_v4_core from 'zod/v4/core';
4
4
  import { FeedbackFunctions, FeedbackContext } from '@h-ai/feedback';
5
- import { S as ServContext } from '../serv-context-DE1JH5bd.js';
6
- import { A as AuthenticatedServContext } from '../serv-router-BJk04-oS.js';
5
+ import { ServContext } from '../index.js';
6
+ import { AuthenticatedServContext } from '../index.js';
7
7
  import '@h-ai/core';
8
8
 
9
9
  /**
@@ -114,14 +114,14 @@ declare function createFeedbackProcedures(deps: FeedbackProcedureDeps): {
114
114
  handled: "handled";
115
115
  }>;
116
116
  collectionStatus: zod.ZodEnum<{
117
+ failed: "failed";
117
118
  collecting: "collecting";
118
119
  complete: "complete";
119
120
  partial: "partial";
120
- failed: "failed";
121
121
  }>;
122
122
  notificationStatus: zod.ZodEnum<{
123
- pending: "pending";
124
123
  failed: "failed";
124
+ pending: "pending";
125
125
  disabled: "disabled";
126
126
  sending: "sending";
127
127
  sent: "sent";
@@ -166,14 +166,14 @@ declare function createFeedbackProcedures(deps: FeedbackProcedureDeps): {
166
166
  handled: "handled";
167
167
  }>>;
168
168
  collectionStatus: zod.ZodOptional<zod.ZodEnum<{
169
+ failed: "failed";
169
170
  collecting: "collecting";
170
171
  complete: "complete";
171
172
  partial: "partial";
172
- failed: "failed";
173
173
  }>>;
174
174
  notificationStatus: zod.ZodOptional<zod.ZodEnum<{
175
- pending: "pending";
176
175
  failed: "failed";
176
+ pending: "pending";
177
177
  disabled: "disabled";
178
178
  sending: "sending";
179
179
  sent: "sent";
@@ -198,14 +198,14 @@ declare function createFeedbackProcedures(deps: FeedbackProcedureDeps): {
198
198
  handled: "handled";
199
199
  }>;
200
200
  collectionStatus: zod.ZodEnum<{
201
+ failed: "failed";
201
202
  collecting: "collecting";
202
203
  complete: "complete";
203
204
  partial: "partial";
204
- failed: "failed";
205
205
  }>;
206
206
  notificationStatus: zod.ZodEnum<{
207
- pending: "pending";
208
207
  failed: "failed";
208
+ pending: "pending";
209
209
  disabled: "disabled";
210
210
  sending: "sending";
211
211
  sent: "sent";
@@ -258,14 +258,14 @@ declare function createFeedbackProcedures(deps: FeedbackProcedureDeps): {
258
258
  handled: "handled";
259
259
  }>;
260
260
  collectionStatus: zod.ZodEnum<{
261
+ failed: "failed";
261
262
  collecting: "collecting";
262
263
  complete: "complete";
263
264
  partial: "partial";
264
- failed: "failed";
265
265
  }>;
266
266
  notificationStatus: zod.ZodEnum<{
267
- pending: "pending";
268
267
  failed: "failed";
268
+ pending: "pending";
269
269
  disabled: "disabled";
270
270
  sending: "sending";
271
271
  sent: "sent";
@@ -292,8 +292,8 @@ declare function createFeedbackProcedures(deps: FeedbackProcedureDeps): {
292
292
  checksum: zod.ZodString;
293
293
  feedbackId: zod.ZodString;
294
294
  status: zod.ZodEnum<{
295
- pending: "pending";
296
295
  failed: "failed";
296
+ pending: "pending";
297
297
  available: "available";
298
298
  }>;
299
299
  missingReason: zod.ZodOptional<zod.ZodString>;
@@ -324,8 +324,8 @@ declare function createFeedbackProcedures(deps: FeedbackProcedureDeps): {
324
324
  cleanup: "cleanup";
325
325
  }>;
326
326
  status: zod.ZodEnum<{
327
- pending: "pending";
328
327
  failed: "failed";
328
+ pending: "pending";
329
329
  running: "running";
330
330
  succeeded: "succeeded";
331
331
  }>;
@@ -436,14 +436,14 @@ declare function createFeedbackProcedures(deps: FeedbackProcedureDeps): {
436
436
  handled: "handled";
437
437
  }>;
438
438
  collectionStatus: zod.ZodEnum<{
439
+ failed: "failed";
439
440
  collecting: "collecting";
440
441
  complete: "complete";
441
442
  partial: "partial";
442
- failed: "failed";
443
443
  }>;
444
444
  notificationStatus: zod.ZodEnum<{
445
- pending: "pending";
446
445
  failed: "failed";
446
+ pending: "pending";
447
447
  disabled: "disabled";
448
448
  sending: "sending";
449
449
  sent: "sent";
@@ -519,8 +519,8 @@ declare function createFeedbackProcedures(deps: FeedbackProcedureDeps): {
519
519
  cleanup: "cleanup";
520
520
  }>;
521
521
  status: zod.ZodEnum<{
522
- pending: "pending";
523
522
  failed: "failed";
523
+ pending: "pending";
524
524
  running: "running";
525
525
  succeeded: "succeeded";
526
526
  }>;
@@ -628,14 +628,14 @@ declare function createFeedbackProcedures(deps: FeedbackProcedureDeps): {
628
628
  handled: "handled";
629
629
  }>;
630
630
  collectionStatus: zod.ZodEnum<{
631
+ failed: "failed";
631
632
  collecting: "collecting";
632
633
  complete: "complete";
633
634
  partial: "partial";
634
- failed: "failed";
635
635
  }>;
636
636
  notificationStatus: zod.ZodEnum<{
637
- pending: "pending";
638
637
  failed: "failed";
638
+ pending: "pending";
639
639
  disabled: "disabled";
640
640
  sending: "sending";
641
641
  sent: "sent";
@@ -684,8 +684,8 @@ declare function createFeedbackProcedures(deps: FeedbackProcedureDeps): {
684
684
  checksum: zod.ZodString;
685
685
  feedbackId: zod.ZodString;
686
686
  status: zod.ZodEnum<{
687
- pending: "pending";
688
687
  failed: "failed";
688
+ pending: "pending";
689
689
  available: "available";
690
690
  }>;
691
691
  missingReason: zod.ZodOptional<zod.ZodString>;
@@ -731,4 +731,5 @@ declare function createFeedbackProcedures(deps: FeedbackProcedureDeps): {
731
731
  };
732
732
  };
733
733
 
734
- export { type FeedbackProcedureDeps, createFeedbackProcedures };
734
+ export { createFeedbackProcedures };
735
+ export type { FeedbackProcedureDeps };
@@ -1,4 +1,4 @@
1
- import { implement, servM } from '../chunk-I3F7RN4I.js';
1
+ import { implement, servM } from '../chunk-PLGCHDZM.js';
2
2
  import { apiContract } from '@h-ai/api-contract';
3
3
  import { err, HaiCommonError, ok, core } from '@h-ai/core';
4
4
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/features/serv-feature-feedback.ts"],"names":[],"mappings":"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","file":"feedback.js","sourcesContent":["/** @h-ai/serv — 按需挂载反馈领域,不接收客户端身份或对象存储路径。 */\nimport type { FeedbackContext, FeedbackFunctions } from '@h-ai/feedback'\nimport type { ServContext } from '../serv-context.js'\nimport type { AuthenticatedServContext } from '../serv-router.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'\n\n/**\n * 已认证请求到反馈范围的可信映射,返回 null 时拒绝访问。\n * @example\n * ```ts\n * import type { FeedbackProcedureDeps } from '@h-ai/serv/features/feedback'\n * import { feedback } from '@h-ai/feedback'\n * const deps: FeedbackProcedureDeps = { feedback, resolveContext: ({ session }) => ({ scopeId: 'tenant-from-server', userId: session.userId, permissions: session.permissions }) }\n * ```\n */\nexport interface FeedbackProcedureDeps {\n readonly feedback: FeedbackFunctions\n readonly resolveContext: (context: AuthenticatedServContext) => FeedbackContext | null | Promise<FeedbackContext | null>\n}\n\n/**\n * 创建反馈的默认 procedures。宿主解析租户与权限,领域模块再次检查资源范围。\n * @example\n * ```ts\n * import { feedback } from '@h-ai/feedback'\n * import { createFeedbackProcedures } from '@h-ai/serv/features/feedback'\n * const procedures = createFeedbackProcedures({ feedback, resolveContext: ({ session }) => ({ scopeId: 'trusted-tenant', userId: session.userId, permissions: session.permissions }) })\n * ```\n */\nexport function createFeedbackProcedures(deps: FeedbackProcedureDeps) {\n const contextFor = async (context: AuthenticatedServContext) => {\n try {\n const actor = await deps.resolveContext(context)\n if (!actor?.scopeId || actor.userId !== context.session.userId)\n return err(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale: context.locale }))\n return { success: true as const, data: actor }\n }\n catch {\n return err(HaiCommonError.INTERNAL_ERROR, servM('serv_errorInternal', { locale: context.locale }))\n }\n }\n const router = implement(apiContract.feedback).context<ServContext>()\n return router\n .route('reports.policy')\n .auth()\n .handle(async ({ context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return deps.feedback.isInitialized === false ? err(HaiCommonError.SERVICE_UNAVAILABLE, servM('serv_feedbackUnavailable')) : ok(deps.feedback.budget)\n })\n .route('reports.submit')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.submit(input, actor.data))\n })\n .route('reports.list')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.list(input, actor.data))\n })\n .route('reports.get')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.get(input.id, actor.data))\n })\n .route('reports.getEvidence')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.getEvidence(input, actor.data))\n })\n .route('reports.setStatus')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.setStatus(input, actor.data))\n })\n .route('reports.retry')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.retry(input, actor.data))\n })\n .route('reports.export')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.export(input, actor.data))\n })\n .route('reports.getExport')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.getExport(input, actor.data))\n })\n .route('reports.appendEvidence')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.appendEvidence(input, actor.data))\n })\n .route('reports.completeCollection')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.completeCollection(input, actor.data))\n })\n .route('attachments.upload')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.uploadAttachment(input, actor.data))\n })\n .route('attachments.get')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.getAttachment(input, actor.data))\n })\n .build()\n}\n"]}
1
+ {"version":3,"sources":["../../src/features/serv-feature-feedback.ts"],"names":[],"mappings":"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","file":"feedback.js","sourcesContent":["/** @h-ai/serv — 按需挂载反馈领域,不接收客户端身份或对象存储路径。 */\nimport type { FeedbackContext, FeedbackFunctions } from '@h-ai/feedback'\nimport type { ServContext } from '../serv-context.js'\nimport type { AuthenticatedServContext } from '../serv-router.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'\n\n/**\n * 已认证请求到反馈范围的可信映射,返回 null 时拒绝访问。\n * @example\n * ```ts\n * import type { FeedbackProcedureDeps } from '@h-ai/serv/features/feedback'\n * import { feedback } from '@h-ai/feedback'\n * const deps: FeedbackProcedureDeps = { feedback, resolveContext: ({ session }) => ({ scopeId: 'tenant-from-server', userId: session.userId, permissions: session.permissions }) }\n * ```\n */\nexport interface FeedbackProcedureDeps {\n readonly feedback: FeedbackFunctions\n readonly resolveContext: (context: AuthenticatedServContext) => FeedbackContext | null | Promise<FeedbackContext | null>\n}\n\n/**\n * 创建反馈的默认 procedures。宿主解析租户与权限,领域模块再次检查资源范围。\n * @example\n * ```ts\n * import { feedback } from '@h-ai/feedback'\n * import { createFeedbackProcedures } from '@h-ai/serv/features/feedback'\n * const procedures = createFeedbackProcedures({ feedback, resolveContext: ({ session }) => ({ scopeId: 'trusted-tenant', userId: session.userId, permissions: session.permissions }) })\n * ```\n */\nexport function createFeedbackProcedures(deps: FeedbackProcedureDeps) {\n const contextFor = async (context: AuthenticatedServContext) => {\n try {\n const actor = await deps.resolveContext(context)\n if (!actor?.scopeId || actor.userId !== context.session.userId)\n return err(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale: context.locale }))\n return { success: true as const, data: actor }\n }\n catch {\n return err(HaiCommonError.INTERNAL_ERROR, servM('serv_errorInternal', { locale: context.locale }))\n }\n }\n const router = implement(apiContract.feedback).context<ServContext>()\n return router\n .route('reports.policy')\n .auth()\n .handle(async ({ context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return deps.feedback.isInitialized === false\n ? err(HaiCommonError.SERVICE_UNAVAILABLE, servM('serv_feedbackUnavailable'))\n : ok(deps.feedback.budget)\n })\n .route('reports.submit')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.submit(input, actor.data))\n })\n .route('reports.list')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.list(input, actor.data))\n })\n .route('reports.get')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.get(input.id, actor.data))\n })\n .route('reports.getEvidence')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.getEvidence(input, actor.data))\n })\n .route('reports.setStatus')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.setStatus(input, actor.data))\n })\n .route('reports.retry')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.retry(input, actor.data))\n })\n .route('reports.export')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.export(input, actor.data))\n })\n .route('reports.getExport')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.getExport(input, actor.data))\n })\n .route('reports.appendEvidence')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.appendEvidence(input, actor.data))\n })\n .route('reports.completeCollection')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.completeCollection(input, actor.data))\n })\n .route('attachments.upload')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.uploadAttachment(input, actor.data))\n })\n .route('attachments.get')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.getAttachment(input, actor.data))\n })\n .build()\n}\n"]}
@@ -4,7 +4,7 @@ import * as zod_v4_core from 'zod/v4/core';
4
4
  import * as zod from 'zod';
5
5
  import { IamFunctions } from '@h-ai/iam';
6
6
  export { TokenPair } from '@h-ai/iam';
7
- import { S as ServContext } from '../serv-context-DE1JH5bd.js';
7
+ import { ServContext } from '../index.js';
8
8
  import '@h-ai/core';
9
9
 
10
10
  /**
@@ -711,8 +711,8 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
711
711
  invite: _orpc_server.Procedure<ServContext & {}, any, zod.ZodObject<{
712
712
  metadata: zod.ZodOptional<zod.ZodRecord<zod.ZodString, zod.ZodUnknown>>;
713
713
  email: zod.ZodOptional<zod.ZodString>;
714
- username: zod.ZodString;
715
714
  phone: zod.ZodOptional<zod.ZodString>;
715
+ username: zod.ZodString;
716
716
  displayName: zod.ZodOptional<zod.ZodString>;
717
717
  accountType: zod.ZodOptional<zod.ZodEnum<{
718
718
  permanent: "permanent";
@@ -1335,4 +1335,5 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
1335
1335
  };
1336
1336
  };
1337
1337
 
1338
- export { type IamProcedureDeps, createIamProcedures };
1338
+ export { createIamProcedures };
1339
+ export type { IamProcedureDeps };
@@ -1,5 +1,5 @@
1
1
  import { mapHaiResult } from '../chunk-XKCLD42I.js';
2
- import { implement, servM } from '../chunk-I3F7RN4I.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":";;;;;AAsBA,IAAM,cAAc,WAAA,CAAY,GAAA;AAEhC,IAAM,gBAAA,GAAmB,KAAK,MAAA,CAAO,KAAA,CAAM,EAAE,MAAA,EAAQ,MAAA,EAAQ,KAAA,EAAO,aAAA,EAAe,CAAA;AAkC5E,SAAS,oBAAoB,IAAA,EAAwB;AAC1D,EAAA,MAAM,EAAE,KAAI,GAAI,IAAA;AAGhB,EAAA,eAAe,SAAA,CAAa,OAAA,EAAsB,SAAA,EAAmB,GAAA,EAAyD;AAC5H,IAAA,MAAM,MAAA,GAAS,IAAI,YAAA,EAAc,gBAAA;AACjC,IAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,MAAA,MAAM,OAAA,GAAU,MAAM,GAAA,CAAI,IAAA,CAAK,wBAAwB,OAAA,CAAQ,WAAA,IAAe,EAAA,EAAI,SAAA,EAAW,QAAQ,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,wBAAwB,KAAK,MAAS,CAAA;AAC/J,MAAA,IAAI,CAAC,OAAA,CAAQ,OAAA;AACX,QAAA,OAAO,OAAA;AAAA,IACX;AACA,IAAA,OAAO,GAAA,EAAI;AAAA,EACb;AAEA,EAAA,OAAO,SAAA,CAAU,WAAW,CAAA,CACzB,OAAA,EAAqB,CAErB,KAAA,CAAM,qBAAqB,CAAA,CAC3B,IAAA,EAAK,CACL,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM,GAAA,CAAI,IAAA,CAAK,cAAA,CAAe,OAAA,CAAQ,WAAA,IAAe,IAAI,KAAA,CAAM,QAAA,EAAU,KAAA,CAAM,SAAS,CAAC,CAAA,CAElH,KAAA,CAAM,YAAA,EAAc,CAAC,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM,GAAA,CAAI,IAAA,CAAK,KAAA,CAAM,KAAA,EAAO,OAAO,CAAC,CAAA,CAC1E,KAAA,CAAM,oBAAA,EAAsB,CAAC,EAAE,KAAA,OAAY,GAAA,CAAI,IAAA,CAAK,aAAA,CAAc,KAAK,CAAC,CAAA,CACxE,KAAA,CAAM,mBAAA,EAAqB,CAAC,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM,GAAA,CAAI,IAAA,CAAK,YAAA,CAAa,KAAA,EAAO,OAAO,CAAC,CAAA,CACxF,KAAA,CAAM,oBAAA,EAAsB,CAAC,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM,GAAA,CAAI,IAAA,CAAK,aAAA,CAAc,KAAA,EAAO,OAAO,CAAC,CAAA,CAC1F,MAAM,mBAAA,EAAqB,MAAM,GAAA,CAAI,eAAA,EAAiB,CAAA,CACtD,KAAA,CAAM,oBAAA,EAAsB,CAAC,EAAE,KAAA,EAAM,KAAM,GAAA,CAAI,IAAA,CAAK,aAAA,CAAc,KAAK,CAAC,EACxE,KAAA,CAAM,2BAAA,EAA6B,CAAC,EAAE,KAAA,EAAM,KAAM,GAAA,CAAI,IAAA,CAAK,oBAAA,CAAqB,KAAA,CAAM,UAAA,EAAY,KAAK,CAAC,CAAA,CACxG,KAAA,CAAM,2BAAA,EAA6B,CAAC,EAAE,KAAA,EAAM,KAAM,GAAA,CAAI,IAAA,CAAK,oBAAA,CAAqB,KAAA,CAAM,KAAA,EAAO,MAAM,WAAW,CAAC,CAAA,CAE/G,KAAA,CAAM,aAAA,EAAe,CAAC,EAAE,KAAA,EAAO,SAAQ,KAAM;AAE5C,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,WAAA,IAAe,OAAA,CAAQ,WAAA;AAC3C,IAAA,IAAI,CAAC,KAAA;AACH,MAAA,OAAO,GAAA,CAAI,cAAA,CAAe,YAAA,EAAc,KAAA,CAAM,wBAAA,EAA0B,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AACrG,IAAA,OAAO,GAAA,CAAI,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA;AAAA,EAC9B,CAAC,EAEA,KAAA,CAAM,mBAAA,EAAqB,CAAC,EAAE,KAAA,EAAO,SAAQ,KAAM;AAElD,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,WAAA,IAAe,OAAA,CAAQ,WAAA;AAC3C,IAAA,IAAI,CAAC,KAAA;AACH,MAAA,OAAO,GAAA,CAAI,cAAA,CAAe,YAAA,EAAc,KAAA,CAAM,wBAAA,EAA0B,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AACrG,IAAA,OAAO,GAAA,CAAI,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA;AAAA,EAC9B,CAAC,CAAA,CAEA,KAAA,CAAM,kBAAkB,CAAA,CACxB,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,OAAA,EAAQ,KAAM;AAC7B,IAAA,MAAM,QAAQ,OAAA,CAAQ,WAAA;AACtB,IAAA,IAAI,CAAC,KAAA;AACH,MAAA,OAAO,GAAA,CAAI,cAAA,CAAe,YAAA,EAAc,KAAA,CAAM,wBAAA,EAA0B,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AAErG,IAAA,OAAO,YAAA;AAAA,MACL,MAAM,GAAA,CAAI,IAAA,CAAK,cAAA,CAAe,KAAK,CAAA;AAAA,MACnC,CAAA,IAAA,MAAS;AAAA,QACP,GAAG,IAAA;AAAA,QACH,KAAA,EAAO,QAAQ,OAAA,CAAQ,KAAA;AAAA,QACvB,WAAA,EAAa,QAAQ,OAAA,CAAQ;AAAA,OAC/B;AAAA,KACF;AAAA,EACF,CAAC,CAAA,CAEA,KAAA,CAAM,gBAAgB,OAAO,EAAE,OAAM,KAAM;AAC1C,IAAA,OAAO,YAAA,CAAa,MAAM,GAAA,CAAI,OAAA,CAAQ,OAAA,CAAQ,KAAA,CAAM,YAAY,CAAA,EAAG,CAAA,MAAA,MAAW,EAAE,MAAA,EAAO,CAAE,CAAA;AAAA,EAC3F,CAAC,CAAA,CACA,KAAA,CAAM,gBAAgB,CAAC,EAAE,OAAM,KAAM,GAAA,CAAI,IAAA,CAAK,OAAA,CAAQ,MAAM,UAAA,EAAY,KAAK,CAAC,CAAA,CAC9E,KAAA,CAAM,iBAAiB,CAAC,EAAE,KAAA,EAAO,OAAA,OAAc,GAAA,CAAI,IAAA,CAAK,iBAAiB,KAAA,EAAO,OAAO,CAAC,CAAA,CAExF,KAAA,CAAM,qBAAqB,CAAA,CAC3B,MAAK,CACL,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,SAAQ,KAAM;AAC9B,IAAA,MAAM,QAAQ,OAAA,CAAQ,WAAA;AACtB,IAAA,IAAI,CAAC,KAAA;AACH,MAAA,OAAO,GAAA,CAAI,cAAA,CAAe,YAAA,EAAc,KAAA,CAAM,wBAAA,EAA0B,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AACrG,IAAA,OAAO,IAAI,IAAA,CAAK,yBAAA,CAA0B,OAAO,KAAA,CAAM,WAAA,EAAa,MAAM,WAAW,CAAA;AAAA,EACvF,CAAC,CAAA,CAEA,KAAA,CAAM,wBAAwB,CAAA,CAC9B,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,QAAQ,OAAA,CAAQ,WAAA;AACtB,IAAA,IAAI,CAAC,KAAA;AACH,MAAA,OAAO,GAAA,CAAI,cAAA,CAAe,YAAA,EAAc,KAAA,CAAM,wBAAA,EAA0B,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AACrG,IAAA,MAAM,MAAA,GAAS,MAAM,GAAA,CAAI,eAAA,EAAgB;AACzC,IAAA,IAAI,CAAC,MAAA,CAAO,OAAA;AACV,MAAA,OAAO,MAAA;AAET,IAAA,MAAM,UAAA,GAAa;AAAA,MACjB,GAAI,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,CAAE,IAAA,CAAK,CAAA,GAAA,KAAO,GAAA,KAAQ,WAAW,CAAA,GAClD,CAAC,oBAAoB,IACrB,EAAC;AAAA,MACL,GAAI,KAAA,CAAM,SAAA,KAAc,SACpB,CAAC,oBAAoB,IACrB;AAAC,KACP;AACA,IAAA,MAAM,SAAA,GAAY,UAAA,CAAW,IAAA,CAAK,CAAA,KAAA,KAAS,OAAO,IAAA,CAAK,gBAAA,CAAiB,UAAA,CAAW,QAAA,CAAS,KAAK,CAAC,CAAA,IAAK,UAAA,CAAW,CAAC,CAAA,IAAK,oBAAA;AACxH,IAAA,OAAO,SAAA,CAAU,SAAS,SAAA,EAAW,MAAM,IAAI,IAAA,CAAK,iBAAA,CAAkB,KAAA,EAAO,KAAK,CAAC,CAAA;AAAA,EACrF,CAAC,CAAA,CAEA,KAAA,CAAM,YAAY,EAClB,UAAA,CAAW,gBAAgB,CAAA,CAC3B,MAAA,CAAO,CAAC,EAAE,KAAA,EAAM,KAAM,GAAA,CAAI,KAAK,SAAA,CAAU,KAAK,CAAC,CAAA,CAE/C,KAAA,CAAM,WAAW,CAAA,CACjB,UAAA,CAAW,gBAAgB,CAAA,CAC3B,MAAA,CAAO,CAAC,EAAE,OAAM,KAAM,GAAA,CAAI,IAAA,CAAK,OAAA,CAAQ,MAAM,EAAA,EAAI;AAAA,IAChD,OAAA,EAAS,CAAC,OAAO;AAAA,GAClB,CAAC,CAAA,CAED,KAAA,CAAM,cAAc,EACpB,UAAA,CAAW,iBAAiB,CAAA,CAC5B,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,SAAQ,KAAM,SAAA,CAAU,SAAS,kBAAA,EAAoB,MAAM,UAAA,CAAW,GAAA,EAAK,KAAK,CAAC,CAAC,CAAA,CAEnG,KAAA,CAAM,cAAc,CAAA,CACpB,UAAA,CAAW,iBAAiB,CAAA,CAC5B,OAAO,CAAC,EAAE,OAAO,OAAA,EAAQ,KAAM,UAAU,OAAA,EAAS,kBAAA,EAAoB,MAAM,UAAA,CAAW,KAAK,KAAK,CAAC,CAAC,CAAA,CAEnG,MAAM,cAAc,CAAA,CACpB,UAAA,CAAW,iBAAiB,EAC5B,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,OAAA,OAAc,SAAA,CAAU,OAAA,EAAS,kBAAA,EAAoB,MAAM,WAAW,GAAA,EAAK,KAAK,CAAC,CAAC,EAEnG,KAAA,CAAM,cAAc,CAAA,CACpB,UAAA,CAAW,iBAAiB,CAAA,CAC5B,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,SAAQ,KAAM;AAE9B,IAAA,IAAI,OAAA,CAAQ,OAAA,CAAQ,MAAA,KAAW,KAAA,CAAM,EAAA;AACnC,MAAA,OAAO,GAAA,CAAI,cAAA,CAAe,SAAA,EAAW,KAAA,CAAM,iCAAA,EAAmC,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AAC3G,IAAA,OAAO,SAAA,CAAU,SAAS,kBAAA,EAAoB,MAAM,IAAI,IAAA,CAAK,UAAA,CAAW,KAAA,CAAM,EAAE,CAAC,CAAA;AAAA,EACnF,CAAC,EAEA,KAAA,CAAM,qBAAqB,EAC3B,UAAA,CAAW,iBAAiB,EAC5B,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,OAAA,OAAc,SAAA,CAAU,OAAA,EAAS,2BAA2B,MAAM,GAAA,CAAI,KAAK,kBAAA,CAAmB,KAAA,CAAM,IAAI,KAAA,CAAM,WAAW,CAAC,CAAC,CAAA,CAE5I,MAAM,YAAY,CAAA,CAClB,WAAW,gBAAgB,CAAA,CAC3B,OAAO,MAAM,GAAA,CAAI,MAAM,WAAA,EAAa,EAEpC,KAAA,CAAM,WAAW,EACjB,UAAA,CAAW,gBAAgB,EAC3B,MAAA,CAAO,CAAC,EAAE,KAAA,EAAM,KAAM,IAAI,KAAA,CAAM,OAAA,CAAQ,MAAM,EAAE,CAAC,EAEjD,KAAA,CAAM,cAAc,EACpB,UAAA,CAAW,iBAAiB,EAC5B,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,OAAA,OAAc,SAAA,CAAU,OAAA,EAAS,oBAAoB,MAAM,GAAA,CAAI,MAAM,UAAA,CAAW,KAAK,CAAC,CAAC,CAAA,CAExG,MAAM,cAAc,CAAA,CACpB,WAAW,iBAAiB,CAAA,CAC5B,OAAO,CAAC,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AAC9B,IAAA,MAAM,EAAE,EAAA,EAAI,GAAG,IAAA,EAAK,GAAI,KAAA;AACxB,IAAA,OAAO,SAAA,CAAU,SAAS,kBAAA,EAAoB,MAAM,IAAI,KAAA,CAAM,UAAA,CAAW,EAAA,EAAI,IAAI,CAAC,CAAA;AAAA,EACpF,CAAC,CAAA,CAEA,KAAA,CAAM,cAAc,CAAA,CACpB,UAAA,CAAW,iBAAiB,CAAA,CAC5B,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,SAAQ,KAAM,SAAA,CAAU,OAAA,EAAS,kBAAA,EAAoB,MAAM,GAAA,CAAI,KAAA,CAAM,UAAA,CAAW,KAAA,CAAM,EAAE,CAAC,CAAC,CAAA,CAE3G,KAAA,CAAM,kBAAkB,CAAA,CACxB,WAAW,sBAAsB,CAAA,CACjC,MAAA,CAAO,CAAC,EAAE,KAAA,EAAM,KAAM,GAAA,CAAI,KAAA,CAAM,iBAAA,CAAkB,KAAK,CAAC,CAAA,CAExD,KAAA,CAAM,iBAAiB,CAAA,CACvB,WAAW,sBAAsB,CAAA,CACjC,MAAA,CAAO,CAAC,EAAE,KAAA,EAAM,KAAM,GAAA,CAAI,MAAM,aAAA,CAAc,KAAA,CAAM,EAAE,CAAC,CAAA,CAEvD,KAAA,CAAM,oBAAoB,CAAA,CAC1B,WAAW,uBAAuB,CAAA,CAClC,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM,SAAA,CAAU,OAAA,EAAS,wBAAA,EAA0B,MAAM,GAAA,CAAI,KAAA,CAAM,gBAAA,CAAiB,KAAK,CAAC,CAAC,CAAA,CAEpH,KAAA,CAAM,oCAAoC,CAAA,CAC1C,UAAA,CAAW,uBAAuB,CAAA,CAClC,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM,SAAA,CAAU,OAAA,EAAS,uCAAuC,MAAM,GAAA,CAAI,KAAA,CAAM,gCAAA,CAAiC,KAAA,CAAM,EAAA,EAAI,KAAA,CAAM,mBAAmB,CAAC,CAAC,CAAA,CAE/K,KAAA,CAAM,oBAAoB,CAAA,CAC1B,UAAA,CAAW,uBAAuB,EAClC,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM,SAAA,CAAU,OAAA,EAAS,0BAA0B,MAAM,GAAA,CAAI,KAAA,CAAM,gBAAA,CAAiB,KAAA,CAAM,EAAE,CAAC,CAAC,EAEvH,KAAA,EAAM;AACX;AAEA,eAAe,UAAA,CAAW,KAAmB,KAAA,EAA+E;AAC1H,EAAA,MAAM,OAAA,GAAU,EAAE,UAAA,IAAc,KAAA,CAAA;AAChC,EAAA,MAAM,cAAA,GAAiB,UAAA,IAAc,KAAA,GACjC,MAAM,GAAA,CAAI,IAAA,CAAK,UAAA,CAAW,KAAK,CAAA,GAC/B,MAAM,GAAA,CAAI,IAAA,CAAK,WAAW,KAAK,CAAA;AACnC,EAAA,IAAI,CAAC,cAAA,CAAe,OAAA;AAClB,IAAA,OAAO,cAAA;AAET,EAAA,MAAM,MAAA,GAAS,cAAA,CAAe,IAAA,CAAK,IAAA,CAAK,EAAA;AAGxC,EAAA,eAAe,SAAS,MAAA,EAA+B;AACrD,IAAA,IAAI,OAAA,EAAS;AAEX,MAAA,gBAAA,CAAiB,KAAA,CAAM,+CAAA,EAAiD,EAAE,MAAA,EAAQ,QAAQ,CAAA;AAC1F,MAAA;AAAA,IACF;AACA,IAAA,MAAM,YAAA,GAAe,MAAM,GAAA,CAAI,IAAA,CAAK,WAAW,MAAM,CAAA;AACrD,IAAA,IAAI,CAAC,YAAA,CAAa,OAAA;AAChB,MAAA,gBAAA,CAAiB,KAAA,CAAM,qDAAqD,EAAE,MAAA,EAAQ,QAAQ,KAAA,EAAO,YAAA,CAAa,OAAO,CAAA;AAAA,EAC7H;AAEA,EAAA,IAAI,KAAA,CAAM,YAAY,MAAA,EAAW;AAC/B,IAAA,MAAM,cAAc,MAAM,GAAA,CAAI,MAAM,SAAA,CAAU,MAAA,EAAQ,MAAM,OAAO,CAAA;AACnE,IAAA,IAAI,CAAC,YAAY,OAAA,EAAS;AACxB,MAAA,MAAM,SAAS,kBAAkB,CAAA;AACjC,MAAA,OAAO,WAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,EAAA,CAAG,cAAA,CAAe,IAAA,CAAK,IAAI,CAAA;AACpC;AAEA,eAAe,UAAA,CAAW,KAAmB,KAAA,EAA0D;AACrG,EAAA,MAAM,EAAE,EAAA,EAAI,OAAA,EAAS,GAAG,MAAK,GAAI,KAAA;AACjC,EAAA,MAAM,eAAe,MAAM,GAAA,CAAI,IAAA,CAAK,UAAA,CAAW,IAAI,IAAI,CAAA;AACvD,EAAA,IAAI,CAAC,YAAA,CAAa,OAAA;AAChB,IAAA,OAAO,YAAA;AAET,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,MAAM,cAAc,MAAM,GAAA,CAAI,KAAA,CAAM,SAAA,CAAU,IAAI,OAAO,CAAA;AACzD,IAAA,IAAI,CAAC,WAAA,CAAY,OAAA;AACf,MAAA,OAAO,WAAA;AAAA,EACX;AAEA,EAAA,OAAO,YAAA;AACT","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-DE1JH5bd.js';
6
- import { A as AuthenticatedServContext } from '../serv-router-BJk04-oS.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-TWMOZPGU.js';
1
+ import { validateInputOrFail } from '../chunk-CZ6VA2EN.js';
2
2
  import { mapHaiResult } from '../chunk-XKCLD42I.js';
3
- import { implement, servM } from '../chunk-I3F7RN4I.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":";;;;;;;AAuBA,IAAM,kBAAkB,WAAA,CAAY,OAAA;AAEpC,SAAS,gCAAA,CACP,KAAA,EACA,MAAA,EACA,UAAA,EACA,MAAA,EACkB;AAClB,EAAA,MAAM,UAAU,KAAA,CAAM,UAAA,EAAY,EAAE,MAAA,EAAQ,QAAQ,CAAA;AACpD,EAAA,OAAO,GAAA;AAAA,IACL,cAAA,CAAe,gBAAA;AAAA,IACf,KAAA,CAAM,uBAAA,EAAyB,EAAE,MAAA,EAAQ,CAAA;AAAA,IACzC,CAAC,EAAE,KAAA,EAAO,OAAA,EAAS;AAAA,GACrB;AACF;AAOA,SAAS,wBAAA,CAAyB,GAAA,EAAa,MAAA,EAAgB,KAAA,GAAgB,KAAA,EAA0B;AACvG,EAAA,MAAM,KAAA,GAAQ,mBAAmB,GAAG,CAAA;AACpC,EAAA,IAAI,KAAA,KAAU,OAAA;AACZ,IAAA,OAAO,gCAAA,CAAiC,KAAA,EAAO,MAAA,EAAQ,yBAAyB,CAAA;AAClF,EAAA,IAAI,UAAU,SAAA,EAAW;AACvB,IAAA,OAAO,gCAAA,CAAiC,KAAA,EAAO,MAAA,EAAQ,wBAAA,EAA0B;AAAA,MAC/E,GAAA,EAAK;AAAA,KACN,CAAA;AAAA,EACH;AACA,EAAA,IAAI,KAAA,KAAU,YAAA;AACZ,IAAA,OAAO,gCAAA,CAAiC,KAAA,EAAO,MAAA,EAAQ,6BAA6B,CAAA;AACtF,EAAA,IAAI,KAAA,KAAU,UAAA;AACZ,IAAA,OAAO,gCAAA,CAAiC,KAAA,EAAO,MAAA,EAAQ,4BAA4B,CAAA;AACrF,EAAA,OAAO,GAAG,GAAG,CAAA;AACf;AAEA,SAAS,yBAAA,CAA0B,MAAgB,MAAA,EAAqC;AACtF,EAAA,MAAM,MAAA,GAAS,mBAAA,CAAoB,uBAAA,EAAyB,IAAA,EAAM,MAAM,CAAA;AACxE,EAAA,IAAI,CAAC,MAAA,CAAO,OAAA;AACV,IAAA,OAAO,MAAA;AACT,EAAA,KAAA,MAAW,CAAC,KAAA,EAAO,GAAG,CAAA,IAAK,IAAA,CAAK,SAAQ,EAAG;AACzC,IAAA,MAAM,YAAY,wBAAA,CAAyB,GAAA,EAAK,MAAA,EAAQ,CAAA,KAAA,EAAQ,KAAK,CAAA,CAAE,CAAA;AACvE,IAAA,IAAI,CAAC,SAAA,CAAU,OAAA;AACb,MAAA,OAAO,SAAA;AAAA,EACX;AACA,EAAA,OAAO,GAAG,IAAI,CAAA;AAChB;AAsFO,SAAS,wBAAwB,IAAA,EAA4B;AAClE,EAAA,MAAM,EAAE,SAAQ,GAAI,IAAA;AAEpB,EAAA,eAAe,UAAU,OAAA,EAAyD;AAChF,IAAA,IAAI,OAAO,KAAK,SAAA,KAAc,UAAA,IAAc,MAAM,IAAA,CAAK,SAAA,CAAU,OAAO,CAAA,KAAM,IAAA;AAC5E,MAAA,OAAO,GAAA,CAAI,cAAA,CAAe,SAAA,EAAW,KAAA,CAAM,qBAAA,EAAuB,EAAE,MAAA,EAAQ,OAAA,CAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AACvG,IAAA,OAAO,GAAG,MAAS,CAAA;AAAA,EACrB;AAEA,EAAA,OAAO,SAAA,CAAU,eAAe,CAAA,CAC7B,OAAA,GAEA,KAAA,CAAM,8BAA8B,CAAA,CACpC,IAAA,GACA,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,SAAQ,KAAM;AACpC,IAAA,MAAM,SAAA,GAAY,wBAAA,CAAyB,KAAA,CAAM,GAAA,EAAK,QAAQ,MAAM,CAAA;AACpE,IAAA,IAAI,CAAC,SAAA,CAAU,OAAA;AACb,MAAA,OAAO,SAAA;AACT,IAAA,MAAM,MAAA,GAAS,MAAM,SAAA,CAAU,EAAE,OAAA,EAAS,SAAA,EAAW,UAAA,EAAY,IAAA,EAAM,CAAC,SAAA,CAAU,IAAI,CAAA,EAAG,CAAA;AACzF,IAAA,IAAI,CAAC,MAAA,CAAO,OAAA;AACV,MAAA,OAAO,MAAA;AACT,IAAA,MAAM,SAAA,GAAY,MAAM,SAAA,IAAa,GAAA;AACrC,IAAA,OAAO,iBAAA;AAAA,MACL,SAAA,CAAU,IAAA;AAAA,MACV,SAAA;AAAA,MACA,MAAM,QAAQ,OAAA,CAAQ,MAAA,CAAO,UAAU,IAAA,EAAM,EAAE,WAAW;AAAA,KAC5D;AAAA,EACF,CAAC,CAAA,CAEA,KAAA,CAAM,4BAA4B,CAAA,CAClC,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,SAAA,GAAY,wBAAA,CAAyB,KAAA,CAAM,GAAA,EAAK,QAAQ,MAAM,CAAA;AACpE,IAAA,IAAI,CAAC,SAAA,CAAU,OAAA;AACb,MAAA,OAAO,SAAA;AACT,IAAA,MAAM,MAAA,GAAS,MAAM,SAAA,CAAU,EAAE,OAAA,EAAS,SAAA,EAAW,QAAA,EAAU,IAAA,EAAM,CAAC,SAAA,CAAU,IAAI,CAAA,EAAG,CAAA;AACvF,IAAA,IAAI,CAAC,MAAA,CAAO,OAAA;AACV,MAAA,OAAO,MAAA;AACT,IAAA,MAAM,SAAA,GAAY,MAAM,SAAA,IAAa,GAAA;AACrC,IAAA,MAAM,WAAA,GAAc,MAAM,WAAA,IAAe,0BAAA;AACzC,IAAA,MAAM,EAAE,GAAA,EAAK,CAAA,EAAG,GAAG,SAAQ,GAAI,KAAA;AAC/B,IAAA,OAAO,iBAAA;AAAA,MACL,SAAA,CAAU,IAAA;AAAA,MACV,SAAA;AAAA,MACA,MAAM,OAAA,CAAQ,OAAA,CAAQ,MAAA,CAAO,SAAA,CAAU,IAAA,EAAM,EAAE,GAAG,OAAA,EAAS,SAAA,EAAW,WAAA,EAAa;AAAA,KACrF;AAAA,EACF,CAAC,CAAA,CAEA,KAAA,CAAM,YAAY,CAAA,CAClB,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,MAAA,GAAS,MAAM,MAAA,IAAU,EAAA;AAC/B,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,MAAM,YAAA,GAAe,wBAAA,CAAyB,MAAA,EAAQ,OAAA,CAAQ,QAAQ,QAAQ,CAAA;AAC9E,MAAA,IAAI,CAAC,YAAA,CAAa,OAAA;AAChB,QAAA,OAAO,YAAA;AAAA,IACX;AACA,IAAA,MAAM,MAAA,GAAS,MAAM,SAAA,CAAU,EAAE,OAAA,EAAS,SAAA,EAAW,MAAA,EAAQ,IAAA,EAAM,CAAC,MAAM,CAAA,EAAG,CAAA;AAC7E,IAAA,IAAI,CAAC,MAAA,CAAO,OAAA;AACV,MAAA,OAAO,MAAA;AACT,IAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,KAAK,CAAA;AAAA,EAC/B,CAAC,CAAA,CAEA,KAAA,CAAM,mBAAmB,CAAA,CACzB,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,SAAA,GAAY,wBAAA,CAAyB,KAAA,CAAM,GAAA,EAAK,QAAQ,MAAM,CAAA;AACpE,IAAA,IAAI,CAAC,SAAA,CAAU,OAAA;AACb,MAAA,OAAO,SAAA;AACT,IAAA,MAAM,MAAA,GAAS,MAAM,SAAA,CAAU,EAAE,OAAA,EAAS,SAAA,EAAW,UAAA,EAAY,IAAA,EAAM,CAAC,SAAA,CAAU,IAAI,CAAA,EAAG,CAAA;AACzF,IAAA,IAAI,CAAC,MAAA,CAAO,OAAA;AACV,MAAA,OAAO,MAAA;AACT,IAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA;AAAA,EACzC,CAAC,CAAA,CAEA,KAAA,CAAM,cAAc,CAAA,CACpB,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,SAAA,GAAY,wBAAA,CAAyB,KAAA,CAAM,GAAA,EAAK,QAAQ,MAAM,CAAA;AACpE,IAAA,IAAI,CAAC,SAAA,CAAU,OAAA;AACb,MAAA,OAAO,SAAA;AACT,IAAA,MAAM,MAAA,GAAS,MAAM,SAAA,CAAU,EAAE,OAAA,EAAS,SAAA,EAAW,QAAA,EAAU,IAAA,EAAM,CAAC,SAAA,CAAU,IAAI,CAAA,EAAG,CAAA;AACvF,IAAA,IAAI,CAAC,MAAA,CAAO,OAAA;AACV,MAAA,OAAO,MAAA;AACT,IAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,IAAI,CAAA;AAAA,EAC3C,CAAC,CAAA,CAEA,KAAA,CAAM,kBAAkB,CAAA,CACxB,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,UAAA,GAAa,yBAAA,CAA0B,KAAA,CAAM,IAAA,EAAM,QAAQ,MAAM,CAAA;AACvE,IAAA,IAAI,CAAC,UAAA,CAAW,OAAA;AACd,MAAA,OAAO,UAAA;AACT,IAAA,MAAM,MAAA,GAAS,MAAM,SAAA,CAAU,EAAE,OAAA,EAAS,WAAW,YAAA,EAAc,IAAA,EAAM,UAAA,CAAW,IAAA,EAAM,CAAA;AAC1F,IAAA,IAAI,CAAC,MAAA,CAAO,OAAA;AACV,MAAA,OAAO,MAAA;AACT,IAAA,MAAM,SAAS,MAAM,OAAA,CAAQ,IAAA,CAAK,UAAA,CAAW,WAAW,IAAI,CAAA;AAC5D,IAAA,IAAI,MAAA,CAAO,OAAA;AACT,MAAA,OAAO,MAAA;AACT,IAAA,MAAM,WAAW,0BAAA,CAA2B,SAAA,CAAU,MAAA,CAAO,KAAA,CAAM,KAAK,UAAU,CAAA;AAClF,IAAA,OAAO,GAAA,CAAI,EAAE,GAAG,MAAA,CAAO,OAAO,SAAA,EAAW,QAAA,CAAS,OAAA,GAAU,EAAE,UAAA,EAAY,QAAA,CAAS,IAAA,EAAK,GAAI,QAAW,CAAA;AAAA,EACzG,CAAC,EAEA,KAAA,EAAM;AACX;AAEA,SAAS,iBAAA,CACP,GAAA,EACA,SAAA,EACA,MAAA,EAC8B;AAC9B,EAAA,OAAO,YAAA,CAAa,QAAQ,CAAA,GAAA,MAAQ;AAAA,IAClC,GAAA;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,YAAY,IAAI,IAAA,CAAK,KAAK,GAAA,EAAI,GAAI,SAAA,GAAY,GAAI,CAAA,GAAI;AAAA,GACnE,CAAE,CAAA;AACJ","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"]}