@h-ai/serv 0.1.0-alpha.70 → 0.1.0-alpha.71

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 +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 } 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.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 return actor.success ? ok(deps.feedback.budget) : actor\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"]}
@@ -102,6 +102,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
102
102
  error: zod.ZodObject<{
103
103
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
104
104
  message: zod.ZodString;
105
+ traceId: zod.ZodOptional<zod.ZodString>;
105
106
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
106
107
  system: zod.ZodOptional<zod.ZodString>;
107
108
  module: zod.ZodOptional<zod.ZodString>;
@@ -152,6 +153,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
152
153
  error: zod.ZodObject<{
153
154
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
154
155
  message: zod.ZodString;
156
+ traceId: zod.ZodOptional<zod.ZodString>;
155
157
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
156
158
  system: zod.ZodOptional<zod.ZodString>;
157
159
  module: zod.ZodOptional<zod.ZodString>;
@@ -170,6 +172,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
170
172
  error: zod.ZodObject<{
171
173
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
172
174
  message: zod.ZodString;
175
+ traceId: zod.ZodOptional<zod.ZodString>;
173
176
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
174
177
  system: zod.ZodOptional<zod.ZodString>;
175
178
  module: zod.ZodOptional<zod.ZodString>;
@@ -189,6 +192,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
189
192
  error: zod.ZodObject<{
190
193
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
191
194
  message: zod.ZodString;
195
+ traceId: zod.ZodOptional<zod.ZodString>;
192
196
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
193
197
  system: zod.ZodOptional<zod.ZodString>;
194
198
  module: zod.ZodOptional<zod.ZodString>;
@@ -239,6 +243,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
239
243
  error: zod.ZodObject<{
240
244
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
241
245
  message: zod.ZodString;
246
+ traceId: zod.ZodOptional<zod.ZodString>;
242
247
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
243
248
  system: zod.ZodOptional<zod.ZodString>;
244
249
  module: zod.ZodOptional<zod.ZodString>;
@@ -289,6 +294,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
289
294
  error: zod.ZodObject<{
290
295
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
291
296
  message: zod.ZodString;
297
+ traceId: zod.ZodOptional<zod.ZodString>;
292
298
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
293
299
  system: zod.ZodOptional<zod.ZodString>;
294
300
  module: zod.ZodOptional<zod.ZodString>;
@@ -307,6 +313,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
307
313
  error: zod.ZodObject<{
308
314
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
309
315
  message: zod.ZodString;
316
+ traceId: zod.ZodOptional<zod.ZodString>;
310
317
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
311
318
  system: zod.ZodOptional<zod.ZodString>;
312
319
  module: zod.ZodOptional<zod.ZodString>;
@@ -338,6 +345,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
338
345
  error: zod.ZodObject<{
339
346
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
340
347
  message: zod.ZodString;
348
+ traceId: zod.ZodOptional<zod.ZodString>;
341
349
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
342
350
  system: zod.ZodOptional<zod.ZodString>;
343
351
  module: zod.ZodOptional<zod.ZodString>;
@@ -363,6 +371,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
363
371
  error: zod.ZodObject<{
364
372
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
365
373
  message: zod.ZodString;
374
+ traceId: zod.ZodOptional<zod.ZodString>;
366
375
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
367
376
  system: zod.ZodOptional<zod.ZodString>;
368
377
  module: zod.ZodOptional<zod.ZodString>;
@@ -383,6 +392,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
383
392
  error: zod.ZodObject<{
384
393
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
385
394
  message: zod.ZodString;
395
+ traceId: zod.ZodOptional<zod.ZodString>;
386
396
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
387
397
  system: zod.ZodOptional<zod.ZodString>;
388
398
  module: zod.ZodOptional<zod.ZodString>;
@@ -437,6 +447,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
437
447
  error: zod.ZodObject<{
438
448
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
439
449
  message: zod.ZodString;
450
+ traceId: zod.ZodOptional<zod.ZodString>;
440
451
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
441
452
  system: zod.ZodOptional<zod.ZodString>;
442
453
  module: zod.ZodOptional<zod.ZodString>;
@@ -456,6 +467,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
456
467
  error: zod.ZodObject<{
457
468
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
458
469
  message: zod.ZodString;
470
+ traceId: zod.ZodOptional<zod.ZodString>;
459
471
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
460
472
  system: zod.ZodOptional<zod.ZodString>;
461
473
  module: zod.ZodOptional<zod.ZodString>;
@@ -492,6 +504,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
492
504
  error: zod.ZodObject<{
493
505
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
494
506
  message: zod.ZodString;
507
+ traceId: zod.ZodOptional<zod.ZodString>;
495
508
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
496
509
  system: zod.ZodOptional<zod.ZodString>;
497
510
  module: zod.ZodOptional<zod.ZodString>;
@@ -537,6 +550,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
537
550
  error: zod.ZodObject<{
538
551
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
539
552
  message: zod.ZodString;
553
+ traceId: zod.ZodOptional<zod.ZodString>;
540
554
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
541
555
  system: zod.ZodOptional<zod.ZodString>;
542
556
  module: zod.ZodOptional<zod.ZodString>;
@@ -568,6 +582,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
568
582
  error: zod.ZodObject<{
569
583
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
570
584
  message: zod.ZodString;
585
+ traceId: zod.ZodOptional<zod.ZodString>;
571
586
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
572
587
  system: zod.ZodOptional<zod.ZodString>;
573
588
  module: zod.ZodOptional<zod.ZodString>;
@@ -606,6 +621,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
606
621
  error: zod.ZodObject<{
607
622
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
608
623
  message: zod.ZodString;
624
+ traceId: zod.ZodOptional<zod.ZodString>;
609
625
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
610
626
  system: zod.ZodOptional<zod.ZodString>;
611
627
  module: zod.ZodOptional<zod.ZodString>;
@@ -645,6 +661,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
645
661
  error: zod.ZodObject<{
646
662
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
647
663
  message: zod.ZodString;
664
+ traceId: zod.ZodOptional<zod.ZodString>;
648
665
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
649
666
  system: zod.ZodOptional<zod.ZodString>;
650
667
  module: zod.ZodOptional<zod.ZodString>;
@@ -663,6 +680,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
663
680
  error: zod.ZodObject<{
664
681
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
665
682
  message: zod.ZodString;
683
+ traceId: zod.ZodOptional<zod.ZodString>;
666
684
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
667
685
  system: zod.ZodOptional<zod.ZodString>;
668
686
  module: zod.ZodOptional<zod.ZodString>;
@@ -682,6 +700,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
682
700
  error: zod.ZodObject<{
683
701
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
684
702
  message: zod.ZodString;
703
+ traceId: zod.ZodOptional<zod.ZodString>;
685
704
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
686
705
  system: zod.ZodOptional<zod.ZodString>;
687
706
  module: zod.ZodOptional<zod.ZodString>;
@@ -713,6 +732,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
713
732
  error: zod.ZodObject<{
714
733
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
715
734
  message: zod.ZodString;
735
+ traceId: zod.ZodOptional<zod.ZodString>;
716
736
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
717
737
  system: zod.ZodOptional<zod.ZodString>;
718
738
  module: zod.ZodOptional<zod.ZodString>;
@@ -739,6 +759,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
739
759
  error: zod.ZodObject<{
740
760
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
741
761
  message: zod.ZodString;
762
+ traceId: zod.ZodOptional<zod.ZodString>;
742
763
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
743
764
  system: zod.ZodOptional<zod.ZodString>;
744
765
  module: zod.ZodOptional<zod.ZodString>;
@@ -768,6 +789,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
768
789
  error: zod.ZodObject<{
769
790
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
770
791
  message: zod.ZodString;
792
+ traceId: zod.ZodOptional<zod.ZodString>;
771
793
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
772
794
  system: zod.ZodOptional<zod.ZodString>;
773
795
  module: zod.ZodOptional<zod.ZodString>;
@@ -796,6 +818,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
796
818
  error: zod.ZodObject<{
797
819
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
798
820
  message: zod.ZodString;
821
+ traceId: zod.ZodOptional<zod.ZodString>;
799
822
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
800
823
  system: zod.ZodOptional<zod.ZodString>;
801
824
  module: zod.ZodOptional<zod.ZodString>;
@@ -814,6 +837,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
814
837
  error: zod.ZodObject<{
815
838
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
816
839
  message: zod.ZodString;
840
+ traceId: zod.ZodOptional<zod.ZodString>;
817
841
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
818
842
  system: zod.ZodOptional<zod.ZodString>;
819
843
  module: zod.ZodOptional<zod.ZodString>;
@@ -860,6 +884,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
860
884
  error: zod.ZodObject<{
861
885
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
862
886
  message: zod.ZodString;
887
+ traceId: zod.ZodOptional<zod.ZodString>;
863
888
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
864
889
  system: zod.ZodOptional<zod.ZodString>;
865
890
  module: zod.ZodOptional<zod.ZodString>;
@@ -892,6 +917,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
892
917
  error: zod.ZodObject<{
893
918
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
894
919
  message: zod.ZodString;
920
+ traceId: zod.ZodOptional<zod.ZodString>;
895
921
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
896
922
  system: zod.ZodOptional<zod.ZodString>;
897
923
  module: zod.ZodOptional<zod.ZodString>;
@@ -933,6 +959,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
933
959
  error: zod.ZodObject<{
934
960
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
935
961
  message: zod.ZodString;
962
+ traceId: zod.ZodOptional<zod.ZodString>;
936
963
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
937
964
  system: zod.ZodOptional<zod.ZodString>;
938
965
  module: zod.ZodOptional<zod.ZodString>;
@@ -951,6 +978,7 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
951
978
  error: zod.ZodObject<{
952
979
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
953
980
  message: zod.ZodString;
981
+ traceId: zod.ZodOptional<zod.ZodString>;
954
982
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
955
983
  system: zod.ZodOptional<zod.ZodString>;
956
984
  module: zod.ZodOptional<zod.ZodString>;
@@ -1,5 +1,5 @@
1
1
  import { mapHaiResult } from '../chunk-XKCLD42I.js';
2
- import { implement, servM } from '../chunk-TWHCU6HT.js';
2
+ import { implement, servM } from '../chunk-KR4H56KP.js';
3
3
  import { apiContract } from '@h-ai/api-contract';
4
4
  import { core, ok, err, HaiCommonError } from '@h-ai/core';
5
5
 
@@ -87,6 +87,7 @@ declare function createStorageProcedures(deps: StorageProcedureDeps): {
87
87
  error: zod.ZodObject<{
88
88
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
89
89
  message: zod.ZodString;
90
+ traceId: zod.ZodOptional<zod.ZodString>;
90
91
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
91
92
  system: zod.ZodOptional<zod.ZodString>;
92
93
  module: zod.ZodOptional<zod.ZodString>;
@@ -111,6 +112,7 @@ declare function createStorageProcedures(deps: StorageProcedureDeps): {
111
112
  error: zod.ZodObject<{
112
113
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
113
114
  message: zod.ZodString;
115
+ traceId: zod.ZodOptional<zod.ZodString>;
114
116
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
115
117
  system: zod.ZodOptional<zod.ZodString>;
116
118
  module: zod.ZodOptional<zod.ZodString>;
@@ -146,6 +148,7 @@ declare function createStorageProcedures(deps: StorageProcedureDeps): {
146
148
  error: zod.ZodObject<{
147
149
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
148
150
  message: zod.ZodString;
151
+ traceId: zod.ZodOptional<zod.ZodString>;
149
152
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
150
153
  system: zod.ZodOptional<zod.ZodString>;
151
154
  module: zod.ZodOptional<zod.ZodString>;
@@ -171,6 +174,7 @@ declare function createStorageProcedures(deps: StorageProcedureDeps): {
171
174
  error: zod.ZodObject<{
172
175
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
173
176
  message: zod.ZodString;
177
+ traceId: zod.ZodOptional<zod.ZodString>;
174
178
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
175
179
  system: zod.ZodOptional<zod.ZodString>;
176
180
  module: zod.ZodOptional<zod.ZodString>;
@@ -189,6 +193,7 @@ declare function createStorageProcedures(deps: StorageProcedureDeps): {
189
193
  error: zod.ZodObject<{
190
194
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
191
195
  message: zod.ZodString;
196
+ traceId: zod.ZodOptional<zod.ZodString>;
192
197
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
193
198
  system: zod.ZodOptional<zod.ZodString>;
194
199
  module: zod.ZodOptional<zod.ZodString>;
@@ -219,6 +224,7 @@ declare function createStorageProcedures(deps: StorageProcedureDeps): {
219
224
  error: zod.ZodObject<{
220
225
  code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
221
226
  message: zod.ZodString;
227
+ traceId: zod.ZodOptional<zod.ZodString>;
222
228
  httpStatus: zod.ZodOptional<zod.ZodNumber>;
223
229
  system: zod.ZodOptional<zod.ZodString>;
224
230
  module: zod.ZodOptional<zod.ZodString>;
@@ -1,7 +1,7 @@
1
- import { validateInputOrFail } from '../chunk-BGWE4S6Z.js';
1
+ import { validateInputOrFail } from '../chunk-WW25756Z.js';
2
2
  import { mapHaiResult } from '../chunk-XKCLD42I.js';
3
- import { implement, servM } from '../chunk-TWHCU6HT.js';
4
- import { apiContract } from '@h-ai/api-contract';
3
+ import { implement, servM } from '../chunk-KR4H56KP.js';
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';
7
7
 
@@ -109,7 +109,11 @@ function createStorageProcedures(deps) {
109
109
  const access = await authorize({ context, operation: "deleteMany", keys: keysResult.data });
110
110
  if (!access.success)
111
111
  return access;
112
- return storage.file.deleteMany(keysResult.data);
112
+ const result = await storage.file.deleteMany(keysResult.data);
113
+ if (result.success)
114
+ return result;
115
+ const progress = StorageDeleteSummarySchema.safeParse(result.error.ext?.deleteMany);
116
+ return err({ ...result.error, publicExt: progress.success ? { deleteMany: progress.data } : void 0 });
113
117
  }).build();
114
118
  }
115
119
  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 } 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 return storage.file.deleteMany(keysResult.data)\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 [{ 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"]}
package/dist/index.d.ts CHANGED
@@ -1224,6 +1224,7 @@ interface ServValidationFailureBody {
1224
1224
  readonly error: {
1225
1225
  readonly code: string | number;
1226
1226
  readonly message: string;
1227
+ readonly traceId?: string;
1227
1228
  readonly httpStatus: number;
1228
1229
  readonly system: string;
1229
1230
  readonly module: string;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { validateInputOrFail, resolveRequestLocale, localizeZodError } from './chunk-BGWE4S6Z.js';
2
- import { servM, WILDCARD_ROLE, WILDCARD_PERMISSION, implement, buildHaiErrorBody } from './chunk-TWHCU6HT.js';
1
+ import { validateInputOrFail, resolveRequestLocale, localizeZodError } from './chunk-WW25756Z.js';
2
+ import { servM, WILDCARD_ROLE, WILDCARD_PERMISSION, implement, buildHaiErrorBody } from './chunk-KR4H56KP.js';
3
3
  import process from 'process';
4
4
  import { upgradeWebSocket, serve } from '@hono/node-server';
5
5
  import { apiContract, AUDIO_WS_PATH, AudioWsClientMessageSchema } from '@h-ai/api-contract';
@@ -1212,10 +1212,18 @@ var audioWsServers = /* @__PURE__ */ new WeakMap();
1212
1212
  function createApp(options) {
1213
1213
  const http = resolveServHttpConfig(options.http);
1214
1214
  const app = new Hono();
1215
+ app.onError((error, c) => {
1216
+ const traceId = core.context.get().traceId ?? core.context.get().requestId ?? resolveRequestId(c.req.header("x-request-id") ?? null);
1217
+ core.logger.error("HTTP handler failed", { traceId, error, method: c.req.method });
1218
+ c.header("x-trace-id", traceId);
1219
+ const safe = core.error.toPublic({ ...HaiCommonError.INTERNAL_ERROR, message: "" }, traceId);
1220
+ return c.json({ success: false, error: safe }, 500);
1221
+ });
1215
1222
  app.use("*", async (c, next) => {
1216
1223
  const requestId = resolveRequestId(c.req.header("x-request-id") ?? null);
1217
1224
  await core.context.run({ requestId, userId: void 0, scopeId: void 0, jobId: void 0 }, () => core.i18n.runWithLocale(resolveRequestLocale(c.req.raw.headers), next));
1218
1225
  c.header("x-request-id", requestId);
1226
+ c.header("x-trace-id", requestId);
1219
1227
  });
1220
1228
  app.notFound((c) => c.json(buildHaiErrorBody(
1221
1229
  HaiCommonError.NOT_FOUND,
@@ -1348,7 +1356,8 @@ async function localizeValidationResponse(response, requestHeaders) {
1348
1356
  success: false,
1349
1357
  error: {
1350
1358
  code: HaiCommonError.VALIDATION_ERROR.code,
1351
- message: servM("serv_validationFailed", { locale }),
1359
+ traceId: core.context.get().traceId ?? core.context.get().requestId,
1360
+ message: core.error.formatMessage({ message: servM("serv_validationFailed", { locale }), traceId: core.context.get().traceId ?? core.context.get().requestId }),
1352
1361
  httpStatus: HaiCommonError.VALIDATION_ERROR.httpStatus,
1353
1362
  system: HaiCommonError.VALIDATION_ERROR.system,
1354
1363
  module: HaiCommonError.VALIDATION_ERROR.module,
@@ -1435,7 +1444,7 @@ var DEFAULT_CORS_MAX_AGE = 86400;
1435
1444
  function cors(config = {}) {
1436
1445
  const methods = [...config.methods ?? DEFAULT_CORS_METHODS];
1437
1446
  const allowedHeaders = [...config.allowedHeaders ?? DEFAULT_CORS_ALLOWED_HEADERS];
1438
- const exposedHeaders = [...config.exposedHeaders ?? []];
1447
+ const exposedHeaders = [.../* @__PURE__ */ new Set(["X-Request-Id", "X-Trace-Id", ...config.exposedHeaders ?? []])];
1439
1448
  const credentials = config.credentials ?? false;
1440
1449
  const maxAge = config.maxAge ?? DEFAULT_CORS_MAX_AGE;
1441
1450
  const originConfig = config.origin ?? "*";