@h-ai/serv 0.1.0-alpha.46 → 0.1.0-alpha.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -69
- package/dist/chunk-KIWHKWLD.js +317 -0
- package/dist/chunk-KIWHKWLD.js.map +1 -0
- package/dist/features/ai.d.ts +20 -21
- package/dist/features/ai.js +14 -31
- package/dist/features/ai.js.map +1 -1
- package/dist/features/iam.d.ts +58 -58
- package/dist/features/iam.js +38 -97
- package/dist/features/iam.js.map +1 -1
- package/dist/features/storage.d.ts +18 -19
- package/dist/features/storage.js +39 -44
- package/dist/features/storage.js.map +1 -1
- package/dist/index.d.ts +129 -129
- package/dist/index.js +32 -26
- package/dist/index.js.map +1 -1
- package/dist/{serv-context-BUrtWoFC.d.ts → serv-context-jc3rZxbj.d.ts} +2 -2
- package/package.json +10 -10
- package/dist/chunk-4QBF4PUG.js +0 -128
- package/dist/chunk-4QBF4PUG.js.map +0 -1
- package/dist/chunk-YQV2OMQK.js +0 -16
- package/dist/chunk-YQV2OMQK.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as _h_ai_core from '@h-ai/core';
|
|
2
2
|
import { HaiResult, ValidationFormError } from '@h-ai/core';
|
|
3
|
-
import { Router, implement } from '@orpc/server';
|
|
4
3
|
import { AIFunctions, AudioWsStartMessage, AudioFormat } from '@h-ai/ai';
|
|
5
4
|
import { AnyContractRouter, OpenAPI } from '@orpc/contract';
|
|
6
|
-
import {
|
|
7
|
-
|
|
5
|
+
import { ProcedureHandler, ErrorMap, Meta, ProcedureHandlerOptions, ORPCErrorConstructorMap, Router, ContractRouter, Context, ContractProcedure, InferSchemaInput, InferSchemaOutput } from '@orpc/server';
|
|
6
|
+
import { S as ServSession, a as ServContext, b as ServIam, R as RefreshCookieConfig, C as CreateServContext, p as parseRequestContext, c as buildAuthContextFactory } from './serv-context-jc3rZxbj.js';
|
|
7
|
+
export { d as CreateServContextInput, N as NativeRefreshTokenTransportConfig, e as RefreshTokenPair } from './serv-context-jc3rZxbj.js';
|
|
8
8
|
import { MiddlewareHandler } from 'hono';
|
|
9
9
|
import { z, ZodType } from 'zod';
|
|
10
10
|
import { CryptoFunctions, TransportKeyStore } from '@h-ai/crypto';
|
|
@@ -55,8 +55,7 @@ interface AudioWsDeps {
|
|
|
55
55
|
/**
|
|
56
56
|
* @h-ai/serv — Pipeline 共享类型
|
|
57
57
|
*
|
|
58
|
-
* 统一定义 Hono middleware 与 oRPC procedure
|
|
59
|
-
* 供默认实现与使用方自定义实现复用。
|
|
58
|
+
* 统一定义 Hono middleware 与 oRPC procedure handler 类型。
|
|
60
59
|
* @module pipelines/serv-pipeline-types
|
|
61
60
|
*/
|
|
62
61
|
|
|
@@ -69,17 +68,13 @@ type ServMiddleware = MiddlewareHandler;
|
|
|
69
68
|
* - 带配置 middleware:`ServMiddlewareFactory<MyConfig>`
|
|
70
69
|
*/
|
|
71
70
|
type ServMiddlewareFactory<TConfig = void> = [TConfig] extends [void] ? () => ServMiddleware : (config: TConfig) => ServMiddleware;
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
type ServProcedureHandler<TInput, TOutput> =
|
|
79
|
-
/** 单参数 procedure 包装器(例如 `mapHaiError`、`requireAuth`)。 */
|
|
80
|
-
type ServProcedureWrapper = <TInput, TOutput>(handler: ServProcedureHandler<TInput, TOutput>) => ServProcedureHandler<TInput, TOutput>;
|
|
81
|
-
/** 带额外参数的 procedure 包装器(例如 `requirePermission('x', handler)`)。 */
|
|
82
|
-
type ServGuardedProcedureWrapper<TRequirement> = <TInput, TOutput>(requirement: TRequirement, handler: ServProcedureHandler<TInput, TOutput>) => ServProcedureHandler<TInput, TOutput>;
|
|
71
|
+
/**
|
|
72
|
+
* 使用 oRPC 公开 `ProcedureHandler` 定义的 HaiResult procedure handler。
|
|
73
|
+
*
|
|
74
|
+
* handler options 完整保留 oRPC 的路径、procedure、signal 与错误构造器信息。
|
|
75
|
+
*/
|
|
76
|
+
type ServProcedureOptions<TInput = unknown, TContext extends ServContext = ServContext> = ProcedureHandlerOptions<TContext, TInput, ORPCErrorConstructorMap<ErrorMap>, Meta>;
|
|
77
|
+
type ServProcedureHandler<TInput, TOutput, TContext extends ServContext = ServContext> = ProcedureHandler<TContext, TInput, HaiResult<TOutput>, ErrorMap, Meta>;
|
|
83
78
|
|
|
84
79
|
/**
|
|
85
80
|
* @h-ai/serv — HTTP 配置
|
|
@@ -89,6 +84,13 @@ type ServGuardedProcedureWrapper<TRequirement> = <TInput, TOutput>(requirement:
|
|
|
89
84
|
* @module serv-config
|
|
90
85
|
*/
|
|
91
86
|
|
|
87
|
+
/** 服务器监听配置(对应 `config/_serv.yml` 的 `server`)。 */
|
|
88
|
+
interface ServServerConfig {
|
|
89
|
+
readonly host: string;
|
|
90
|
+
readonly port: number;
|
|
91
|
+
}
|
|
92
|
+
/** `config/_serv.yml` 中的服务器监听输入类型。 */
|
|
93
|
+
type ServServerConfigInput = Partial<ServServerConfig>;
|
|
92
94
|
/** OpenAPI JSON endpoint 配置。 */
|
|
93
95
|
interface ServOpenAPIHttpConfig {
|
|
94
96
|
readonly path: `/${string}`;
|
|
@@ -134,6 +136,7 @@ interface ServHttpConfig {
|
|
|
134
136
|
}
|
|
135
137
|
/** `config/_serv.yml` 的顶层配置结构。 */
|
|
136
138
|
interface ServConfig {
|
|
139
|
+
readonly server: ServServerConfig;
|
|
137
140
|
readonly http: ServHttpConfig;
|
|
138
141
|
readonly cors: ServCorsRuntimeConfig;
|
|
139
142
|
readonly transport: false | ServTransportRuntimeConfig;
|
|
@@ -156,12 +159,17 @@ type ServHttpConfigInput = Partial<{
|
|
|
156
159
|
}>;
|
|
157
160
|
/** `config/_serv.yml` 的顶层输入类型。 */
|
|
158
161
|
interface ServConfigInput {
|
|
162
|
+
readonly server?: ServServerConfigInput;
|
|
159
163
|
readonly http?: ServHttpConfigInput;
|
|
160
164
|
readonly cors?: ServCorsRuntimeConfigInput;
|
|
161
165
|
readonly transport?: false | ServTransportRuntimeConfigInput;
|
|
162
166
|
}
|
|
163
167
|
/** `config/_serv.yml` 对应的配置 Schema。 */
|
|
164
168
|
declare const ServConfigSchema: z.ZodPipe<z.ZodObject<{
|
|
169
|
+
server: z.ZodOptional<z.ZodObject<{
|
|
170
|
+
host: z.ZodDefault<z.ZodString>;
|
|
171
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
172
|
+
}, z.core.$strip>>;
|
|
165
173
|
http: z.ZodOptional<z.ZodObject<{
|
|
166
174
|
apiPrefix: z.ZodDefault<z.ZodCustom<`/api/${string}`, `/api/${string}`>>;
|
|
167
175
|
openapi: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<false>, z.ZodObject<{
|
|
@@ -199,10 +207,15 @@ declare const ServConfigSchema: z.ZodPipe<z.ZodObject<{
|
|
|
199
207
|
maxClients: z.ZodDefault<z.ZodNumber>;
|
|
200
208
|
}, z.core.$strip>]>>;
|
|
201
209
|
}, z.core.$strip>, z.ZodTransform<{
|
|
210
|
+
server: ServServerConfig;
|
|
202
211
|
http: ServHttpConfig;
|
|
203
212
|
cors: ServCorsRuntimeConfig;
|
|
204
213
|
transport: false | ServTransportRuntimeConfig;
|
|
205
214
|
}, {
|
|
215
|
+
server?: {
|
|
216
|
+
host: string;
|
|
217
|
+
port: number;
|
|
218
|
+
} | undefined;
|
|
206
219
|
http?: {
|
|
207
220
|
apiPrefix: `/api/${string}`;
|
|
208
221
|
openapi: false | {
|
|
@@ -323,7 +336,7 @@ interface ServHttpApp {
|
|
|
323
336
|
* **认证 / 会话填充设计**(遵循最小知识原则):
|
|
324
337
|
*
|
|
325
338
|
* - **`iam`**(推荐):单一顶层引用。提供后 serv 自动:
|
|
326
|
-
* 1. 使用 `iam.session.verifyToken` 填充 `context.session` →
|
|
339
|
+
* 1. 使用 `iam.session.verifyToken` 填充 `context.session` → route guard 生效
|
|
327
340
|
* 2. 若同时启用 `refreshCookie`,使用 `iam.session.refresh` 实现 refresh token 轮换
|
|
328
341
|
*
|
|
329
342
|
* - **`refreshCookie`**(可选):浏览器默认用 httpOnly cookie;受信原生客户端可配置 body 通道。与 `iam` 正交:
|
|
@@ -534,12 +547,13 @@ declare function toFetch(app: ServHttpApp): ServFetchHandler;
|
|
|
534
547
|
interface ServListenOptions {
|
|
535
548
|
/**
|
|
536
549
|
* 监听端口。
|
|
537
|
-
* 优先级:显式值 >
|
|
550
|
+
* 优先级:显式值 > 默认 `3000`。
|
|
551
|
+
* 环境变量 / 配置文件的 host/port 由 `serv` 配置(`server`)解析后由应用显式传入。
|
|
538
552
|
*/
|
|
539
553
|
readonly port?: number;
|
|
540
554
|
/**
|
|
541
555
|
* 监听地址。
|
|
542
|
-
* 优先级:显式值 >
|
|
556
|
+
* 优先级:显式值 > 默认 `'127.0.0.1'`(仅本机可达)。
|
|
543
557
|
*
|
|
544
558
|
* - `'127.0.0.1'`:仅本机(推荐用于本地开发、容器内 sidecar、反向代理后端)
|
|
545
559
|
* - `'0.0.0.0'`:监听所有 IPv4 网卡(容器对外暴露、需公网/内网访问)
|
|
@@ -574,7 +588,8 @@ interface ServNodeServer {
|
|
|
574
588
|
/**
|
|
575
589
|
* 在 Node.js 中启动 ServHttpApp。
|
|
576
590
|
*
|
|
577
|
-
* 默认监听 `127.0.0.1:3000
|
|
591
|
+
* 默认监听 `127.0.0.1:3000`;host/port 建议由应用从 `serv` 配置的 `server`
|
|
592
|
+
*(支持 `HOST` / `PORT` 环境变量覆盖,环境变量高于配置文件)解析后显式传入。
|
|
578
593
|
* 传入 `onClose` 后自动注册 SIGINT/SIGTERM 优雅关闭,应用层无需手动处理信号。
|
|
579
594
|
*
|
|
580
595
|
* @param app - `serv.createApp()` 返回的 HTTP app
|
|
@@ -583,111 +598,18 @@ interface ServNodeServer {
|
|
|
583
598
|
*
|
|
584
599
|
* @example
|
|
585
600
|
* ```ts
|
|
586
|
-
* //
|
|
601
|
+
* // host/port 来自已解析的 serv 配置(env > 配置文件 > 默认)
|
|
602
|
+
* const { server } = core.config.getOrThrow<ServConfig>('serv')
|
|
587
603
|
* serv.listen(app, {
|
|
604
|
+
* host: server.host,
|
|
605
|
+
* port: server.port,
|
|
588
606
|
* onListening: info => logger.info('listening', { port: info.port }),
|
|
589
607
|
* onClose: closeApp,
|
|
590
608
|
* })
|
|
591
|
-
*
|
|
592
|
-
* // 容器对外暴露
|
|
593
|
-
* serv.listen(app, { host: '0.0.0.0', onClose: closeApp })
|
|
594
609
|
* ```
|
|
595
610
|
*/
|
|
596
611
|
declare function listen(app: ServHttpApp, options?: ServListenOptions): ServNodeServer;
|
|
597
612
|
|
|
598
|
-
/**
|
|
599
|
-
* @h-ai/serv — Pipeline 公共 helper
|
|
600
|
-
*
|
|
601
|
-
* 放置不属于 Hono middleware / oRPC wrapper 默认实现本身、
|
|
602
|
-
* 但会被多个 pipeline 复用的公共工具。
|
|
603
|
-
* @module pipelines/serv-pipeline-helper
|
|
604
|
-
*/
|
|
605
|
-
|
|
606
|
-
/**
|
|
607
|
-
* 捕获未处理异常并转换为 HaiResult。
|
|
608
|
-
*
|
|
609
|
-
* 虽然它不是 middleware / 鉴权 wrapper,但属于所有 procedure pipeline
|
|
610
|
-
* 都可能复用的“公共包装能力”,因此与 `buildHaiErrorBody()` 一起放在 helper 中。
|
|
611
|
-
*
|
|
612
|
-
* @param handler - 被包装的 procedure handler
|
|
613
|
-
* @returns 带异常保护的新 handler
|
|
614
|
-
*
|
|
615
|
-
* @example
|
|
616
|
-
* ```ts
|
|
617
|
-
* const safeHandler = serv.mapHaiError(myHandler)
|
|
618
|
-
* ```
|
|
619
|
-
*/
|
|
620
|
-
declare function mapHaiError<TInput, TOutput>(handler: ServProcedureHandler<TInput, TOutput>): ServProcedureHandler<TInput, TOutput>;
|
|
621
|
-
|
|
622
|
-
/**
|
|
623
|
-
* @h-ai/serv — requireAuth 默认实现
|
|
624
|
-
* @module pipelines/serv-pipeline-require-auth
|
|
625
|
-
*/
|
|
626
|
-
|
|
627
|
-
/**
|
|
628
|
-
* 需要已验证会话的 procedure 包装器。
|
|
629
|
-
*
|
|
630
|
-
* `context.session` 由 `buildAuthContextFactory(verifyToken)`(或 `serv.createApp({ iam })` 自动启用)填充。
|
|
631
|
-
* token 不存在或无效(verifyToken 失败)时 `session` 均为 `undefined`,统一返回 401 UNAUTHORIZED。
|
|
632
|
-
* 内置调用 `mapHaiError`,无需外层再次封装。
|
|
633
|
-
*
|
|
634
|
-
* @param handler - 被包装的 procedure handler
|
|
635
|
-
* @returns 带认证检查的新 handler
|
|
636
|
-
*
|
|
637
|
-
* @example
|
|
638
|
-
* ```ts
|
|
639
|
-
* const protectedHandler = serv.requireAuth(myHandler)
|
|
640
|
-
* ```
|
|
641
|
-
*/
|
|
642
|
-
declare function requireAuth<TInput, TOutput>(handler: ServProcedureHandler<TInput, TOutput>): ServProcedureHandler<TInput, TOutput>;
|
|
643
|
-
|
|
644
|
-
/**
|
|
645
|
-
* @h-ai/serv — requirePermission 默认实现
|
|
646
|
-
* @module pipelines/serv-pipeline-require-permission
|
|
647
|
-
*/
|
|
648
|
-
|
|
649
|
-
/**
|
|
650
|
-
* 需要指定权限的 procedure 包装器。
|
|
651
|
-
*
|
|
652
|
-
* 内置调用 `requireAuth`,在验证 Token 后进一步匹配 `session.permissions`。
|
|
653
|
-
* 拥有 {@link WILDCARD_PERMISSION}(`'*'`)的用户自动通过所有权限检查。
|
|
654
|
-
*
|
|
655
|
-
* @param permission - 所需权限字符串(如 `'iam.users.read'`)
|
|
656
|
-
* @param handler - 被包装的 procedure handler
|
|
657
|
-
* @returns 带权限检查的新 handler
|
|
658
|
-
*
|
|
659
|
-
* @example
|
|
660
|
-
* ```ts
|
|
661
|
-
* const adminHandler = serv.requirePermission('iam.users.write', myHandler)
|
|
662
|
-
* ```
|
|
663
|
-
*/
|
|
664
|
-
declare function requirePermission<TInput, TOutput>(permission: string, handler: ServProcedureHandler<TInput, TOutput>): ServProcedureHandler<TInput, TOutput>;
|
|
665
|
-
|
|
666
|
-
/**
|
|
667
|
-
* @h-ai/serv — requireRole 默认实现
|
|
668
|
-
* @module pipelines/serv-pipeline-require-role
|
|
669
|
-
*/
|
|
670
|
-
|
|
671
|
-
/**
|
|
672
|
-
* 需要指定角色的 procedure 包装器。
|
|
673
|
-
*
|
|
674
|
-
* 内置调用 `requireAuth`,在验证 Token 后进一步匹配 `session.roles`。
|
|
675
|
-
* 拥有 {@link WILDCARD_ROLE}(`'*'`)的用户自动通过所有角色检查。
|
|
676
|
-
*
|
|
677
|
-
* **使用建议**:优先使用 {@link requirePermission}(基于行为的细粒度授权);
|
|
678
|
-
* `requireRole` 适用于按角色分流的粗粒度路由(如 `'admin'` 专属入口)。
|
|
679
|
-
*
|
|
680
|
-
* @param role - 所需角色字符串(如 `'admin'`)
|
|
681
|
-
* @param handler - 被包装的 procedure handler
|
|
682
|
-
* @returns 带角色检查的新 handler
|
|
683
|
-
*
|
|
684
|
-
* @example
|
|
685
|
-
* ```ts
|
|
686
|
-
* const adminOnly = serv.requireRole('admin', myHandler)
|
|
687
|
-
* ```
|
|
688
|
-
*/
|
|
689
|
-
declare function requireRole<TInput, TOutput>(role: string, handler: ServProcedureHandler<TInput, TOutput>): ServProcedureHandler<TInput, TOutput>;
|
|
690
|
-
|
|
691
613
|
/**
|
|
692
614
|
* @h-ai/serv — CORS middleware
|
|
693
615
|
*
|
|
@@ -746,6 +668,86 @@ interface GenerateOpenAPISpecOptions {
|
|
|
746
668
|
*/
|
|
747
669
|
declare function generateSpec(contract: AnyContractRouter, options?: GenerateOpenAPISpecOptions): Promise<OpenAPI.Document>;
|
|
748
670
|
|
|
671
|
+
/**
|
|
672
|
+
* @h-ai/serv — contract-first 链式 procedure 实现器
|
|
673
|
+
*
|
|
674
|
+
* 在 oRPC `implement()` 之上提供 route 名只出现一次的链式 DSL。
|
|
675
|
+
* 应用只接触 contract 路径、上下文与 guard,不需要组装 oRPC router 对象。
|
|
676
|
+
*
|
|
677
|
+
* @module serv-router
|
|
678
|
+
*/
|
|
679
|
+
|
|
680
|
+
type EmptyContext = Record<never, never>;
|
|
681
|
+
type StringKey<T> = Extract<keyof T, string>;
|
|
682
|
+
type ServContract = ContractRouter<Meta>;
|
|
683
|
+
/** contract 中所有 procedure 的点分路径。 */
|
|
684
|
+
type ServRoutePath<TContract extends ServContract> = TContract extends ContractProcedure<infer _TInputSchema, infer _TOutputSchema, infer _TErrorMap, infer _TMeta> ? never : {
|
|
685
|
+
[K in StringKey<TContract>]: TContract[K] extends ContractProcedure<infer _TChildInputSchema, infer _TChildOutputSchema, infer _TChildErrorMap, infer _TChildMeta> ? K : TContract[K] extends ServContract ? ServRoutePath<TContract[K]> extends infer TChild extends string ? `${K}.${TChild}` : never : never;
|
|
686
|
+
}[StringKey<TContract>];
|
|
687
|
+
type ContractAtPath<TContract, TPath extends string> = TPath extends `${infer THead}.${infer TTail}` ? THead extends keyof TContract ? ContractAtPath<TContract[THead], TTail> : never : TPath extends keyof TContract ? TContract[TPath] : never;
|
|
688
|
+
type RouteContract<TContract extends ServContract, TPath extends ServRoutePath<TContract>> = ContractAtPath<TContract, TPath>;
|
|
689
|
+
/** `.auth()`、`.permission()`、`.role()` 后的非空会话上下文。 */
|
|
690
|
+
type AuthenticatedServContext<TContext extends ServContext = ServContext> = TContext & {
|
|
691
|
+
readonly session: NonNullable<TContext['session']>;
|
|
692
|
+
};
|
|
693
|
+
type RouteHandler<TRouteContract, TContext extends Context> = TRouteContract extends ContractProcedure<infer TInputSchema, infer TOutputSchema, infer TErrorMap, infer TMeta> ? InferSchemaInput<TOutputSchema> extends HaiResult<unknown> ? ProcedureHandler<TContext, InferSchemaOutput<TInputSchema>, InferSchemaInput<TOutputSchema>, TErrorMap, TMeta> : never : never;
|
|
694
|
+
/** 从 contract procedure 精确推导当前 route 的 HaiResult handler。 */
|
|
695
|
+
type ServRouteHandler<TContract extends ServContract, TPath extends ServRoutePath<TContract>, TContext extends Context> = RouteHandler<RouteContract<TContract, TPath>, TContext>;
|
|
696
|
+
type ServGuardedRouteHandler<TContract extends ServContract, TPath extends ServRoutePath<TContract>, TContext extends ServContext> = RouteHandler<RouteContract<TContract, TPath>, AuthenticatedServContext<TContext>>;
|
|
697
|
+
type NextBuilder<TContract extends ServContract, TInitialContext extends Context, TCurrentContext extends ServContext, TRemaining extends ServRoutePath<TContract>, TPath extends TRemaining> = ServRouterBuilder<TContract, TInitialContext, TCurrentContext, Exclude<TRemaining, TPath>>;
|
|
698
|
+
/** 尚未声明 guard 的单 route 注册器。 */
|
|
699
|
+
interface ServRouteRegistration<TContract extends ServContract, TInitialContext extends Context, TCurrentContext extends ServContext, TRemaining extends ServRoutePath<TContract>, TPath extends TRemaining> {
|
|
700
|
+
readonly auth: () => ServGuardedRouteRegistration<TContract, TInitialContext, TCurrentContext, TRemaining, TPath>;
|
|
701
|
+
readonly permission: (permission: string) => ServGuardedRouteRegistration<TContract, TInitialContext, TCurrentContext, TRemaining, TPath>;
|
|
702
|
+
readonly role: (role: string) => ServGuardedRouteRegistration<TContract, TInitialContext, TCurrentContext, TRemaining, TPath>;
|
|
703
|
+
}
|
|
704
|
+
/** 已声明认证或授权 guard 的单 route 注册器。 */
|
|
705
|
+
interface ServGuardedRouteRegistration<TContract extends ServContract, TInitialContext extends Context, TCurrentContext extends ServContext, TRemaining extends ServRoutePath<TContract>, TPath extends TRemaining> {
|
|
706
|
+
readonly auth: () => ServGuardedRouteRegistration<TContract, TInitialContext, TCurrentContext, TRemaining, TPath>;
|
|
707
|
+
readonly permission: (permission: string) => ServGuardedRouteRegistration<TContract, TInitialContext, TCurrentContext, TRemaining, TPath>;
|
|
708
|
+
readonly role: (role: string) => ServGuardedRouteRegistration<TContract, TInitialContext, TCurrentContext, TRemaining, TPath>;
|
|
709
|
+
readonly handle: (handler: ServGuardedRouteHandler<TContract, TPath, TCurrentContext>) => NextBuilder<TContract, TInitialContext, TCurrentContext, TRemaining, TPath>;
|
|
710
|
+
}
|
|
711
|
+
interface ServRouteMethod<TContract extends ServContract, TInitialContext extends Context, TCurrentContext extends ServContext, TRemaining extends ServRoutePath<TContract>> {
|
|
712
|
+
<TPath extends TRemaining>(path: TPath, handler: ServRouteHandler<TContract, TPath, TCurrentContext>): NextBuilder<TContract, TInitialContext, TCurrentContext, TRemaining, TPath>;
|
|
713
|
+
<TPath extends TRemaining>(path: TPath): ServRouteRegistration<TContract, TInitialContext, TCurrentContext, TRemaining, TPath>;
|
|
714
|
+
}
|
|
715
|
+
type ServRouterBuilderBuild<TContract extends ServContract, TInitialContext extends Context, TRemaining> = [TRemaining] extends [never] ? {
|
|
716
|
+
/** 所有 contract procedures 均实现后才可调用。 */
|
|
717
|
+
readonly build: () => Router<TContract, TInitialContext>;
|
|
718
|
+
} : {
|
|
719
|
+
/** 仅供 TypeScript/IDE 展示尚未实现的 route;运行时不存在。 */
|
|
720
|
+
readonly missingRoutes: TRemaining;
|
|
721
|
+
};
|
|
722
|
+
/**
|
|
723
|
+
* 链式 router builder。
|
|
724
|
+
*
|
|
725
|
+
* `TRemaining` 会随着 `.route()` 注册逐步收窄;未实现完整 contract 时没有 `.build()`。
|
|
726
|
+
*/
|
|
727
|
+
type ServRouterBuilder<TContract extends ServContract, TInitialContext extends Context, TCurrentContext extends ServContext, TRemaining extends ServRoutePath<TContract> = ServRoutePath<TContract>> = {
|
|
728
|
+
readonly route: ServRouteMethod<TContract, TInitialContext, TCurrentContext, TRemaining>;
|
|
729
|
+
} & ServRouterBuilderBuild<TContract, TInitialContext, TRemaining>;
|
|
730
|
+
/** hai-framework 链式 contract 实现器。 */
|
|
731
|
+
interface ServImplementer<TContract extends ServContract> {
|
|
732
|
+
readonly context: <TContext extends ServContext>() => ServRouterBuilder<TContract, TContext & EmptyContext, TContext>;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* 创建链式 contract 实现器。
|
|
736
|
+
*
|
|
737
|
+
* @example
|
|
738
|
+
* ```ts
|
|
739
|
+
* serv
|
|
740
|
+
* .implement(contract)
|
|
741
|
+
* .context<ServContext>()
|
|
742
|
+
* .route('info', infoHandler)
|
|
743
|
+
* .route('echo')
|
|
744
|
+
* .auth()
|
|
745
|
+
* .handle(echoHandler)
|
|
746
|
+
* .build()
|
|
747
|
+
* ```
|
|
748
|
+
*/
|
|
749
|
+
declare function implement<TContract extends ServContract>(contract: TContract): ServImplementer<TContract>;
|
|
750
|
+
|
|
749
751
|
/**
|
|
750
752
|
* @h-ai/serv — 部署环境 HTTP 安全策略
|
|
751
753
|
*
|
|
@@ -877,12 +879,15 @@ declare function resolveRequestLocale(headers: Headers | Record<string, string |
|
|
|
877
879
|
*
|
|
878
880
|
* const Schema = z.object({ name: z.string().min(1) })
|
|
879
881
|
*
|
|
880
|
-
* export const
|
|
881
|
-
*
|
|
882
|
-
*
|
|
883
|
-
*
|
|
884
|
-
*
|
|
885
|
-
*
|
|
882
|
+
* export const procedures = serv
|
|
883
|
+
* .implement(contract)
|
|
884
|
+
* .context<ServContext>()
|
|
885
|
+
* .route('profile.update', ({ input, context }) => {
|
|
886
|
+
* const result = serv.validateInputOrFail(Schema, input, context.locale)
|
|
887
|
+
* if (!result.success) return result
|
|
888
|
+
* return profileService.update(result.data)
|
|
889
|
+
* })
|
|
890
|
+
* .build()
|
|
886
891
|
* ```
|
|
887
892
|
*
|
|
888
893
|
* @param schema - Zod schema
|
|
@@ -903,16 +908,11 @@ declare const serv: {
|
|
|
903
908
|
storageAssets: typeof storageAssets;
|
|
904
909
|
createRuntimeSecurityPolicy: typeof createRuntimeSecurityPolicy;
|
|
905
910
|
generateSpec: typeof generateSpec;
|
|
906
|
-
/** 基于 contract 创建可注入 context 的 procedure builder(封装 `@orpc/server` 的 `implement`)。 */
|
|
907
911
|
implement: typeof implement;
|
|
908
|
-
requireAuth: typeof requireAuth;
|
|
909
|
-
requirePermission: typeof requirePermission;
|
|
910
|
-
requireRole: typeof requireRole;
|
|
911
|
-
mapHaiError: typeof mapHaiError;
|
|
912
912
|
validateInputOrFail: typeof validateInputOrFail;
|
|
913
913
|
WILDCARD_PERMISSION: string;
|
|
914
914
|
WILDCARD_ROLE: string;
|
|
915
915
|
m: (key: "$schema" | "serv_validationFailed" | "serv_validationRequired" | "serv_validationInvalid" | "serv_validationInvalidType" | "serv_validationStringMin" | "serv_validationStringMax" | "serv_validationNumberMin" | "serv_validationNumberMax" | "serv_validationArrayMin" | "serv_validationArrayMax" | "serv_validationTooSmall" | "serv_validationTooBig" | "serv_validationEmail" | "serv_validationUrl" | "serv_validationUuid" | "serv_validationEnum" | "serv_errorUnauthorized" | "serv_errorForbidden" | "serv_errorInternal" | "serv_storageKeyRequired" | "serv_storageKeyTooLong" | "serv_storageKeyIllegalChars" | "serv_storageKeyDotSegments" | "serv_iamCannotDeleteCurrentUser" | "serv_transportInvalidPayload" | "serv_transportUnknownClient" | "serv_transportClientIdRequired" | "serv_transportClientKeyNotFound" | "serv_transportDecryptFailed" | "serv_transportEncryptFailed" | "serv_transportKeyExchangeFailed", options?: _h_ai_core.MessageOptions) => string;
|
|
916
916
|
};
|
|
917
917
|
|
|
918
|
-
export { type AudioTicketGrant, type AudioTicketVerification, type AuthorizedAudioRequest, type CreateServAppOptions, CreateServContext, type GenerateOpenAPISpecOptions, RefreshCookieConfig, type ServAudioConfig, type ServConfig, type ServConfigInput, ServConfigSchema, ServContext, type ServCorsConfig, type ServCorsRuntimeConfig, type ServCorsRuntimeConfigInput, type ServDocsHttpConfig, type ServFetchHandler, type
|
|
918
|
+
export { type AudioTicketGrant, type AudioTicketVerification, type AuthenticatedServContext, type AuthorizedAudioRequest, type CreateServAppOptions, CreateServContext, type GenerateOpenAPISpecOptions, RefreshCookieConfig, type ServAudioConfig, type ServConfig, type ServConfigInput, ServConfigSchema, ServContext, type ServCorsConfig, type ServCorsRuntimeConfig, type ServCorsRuntimeConfigInput, type ServDocsHttpConfig, type ServFetchHandler, type ServHealthHttpConfig, type ServHttpApp, type ServHttpConfig, type ServHttpConfigInput, ServIam, type ServImplementer, type ServListenOptions, type ServMiddleware, type ServMiddlewareFactory, type ServMiddlewareMount, type ServNodeServer, type ServOpenAPIHttpConfig, type ServProcedureHandler, type ServProcedureOptions, type ServRouteHandler, type ServRoutePath, type ServRouterBuilder, type ServRpcHttpConfig, type ServRuntimeSecurityPolicy, type ServRuntimeSecurityPolicyInput, type ServServerConfig, type ServServerConfigInput, ServSession, type ServStorageAssetsConfig, type ServTransportConfig, type ServTransportRuntimeConfig, type ServTransportRuntimeConfigInput, type ServValidationFailureBody, serv };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { WILDCARD_PERMISSION,
|
|
2
|
-
import
|
|
3
|
-
import { implement } from '@orpc/server';
|
|
4
|
-
import process2 from 'process';
|
|
1
|
+
import { servM, WILDCARD_ROLE, WILDCARD_PERMISSION, implement, normalizeServLocale, buildHaiErrorBody } from './chunk-KIWHKWLD.js';
|
|
2
|
+
import process from 'process';
|
|
5
3
|
import { serve } from '@hono/node-server';
|
|
6
4
|
import { apiContract, AUDIO_WS_PATH, AudioWsClientMessageSchema } from '@h-ai/api-contract';
|
|
7
5
|
import { core, ok, err, HaiCommonError } from '@h-ai/core';
|
|
@@ -662,6 +660,10 @@ var TransportRuntimeConfigInputSchema = z.union([
|
|
|
662
660
|
maxClients: z.number().int().positive().default(1e4)
|
|
663
661
|
})
|
|
664
662
|
]);
|
|
663
|
+
var ServerConfigInputSchema = z.object({
|
|
664
|
+
host: z.string().min(1).default("127.0.0.1"),
|
|
665
|
+
port: z.number().int().min(1).max(65535).default(3e3)
|
|
666
|
+
});
|
|
665
667
|
var CorsRuntimeConfigInputSchema = z.object({
|
|
666
668
|
origin: z.string().optional(),
|
|
667
669
|
nativeOrigins: z.string().optional(),
|
|
@@ -677,14 +679,32 @@ var ServHttpConfigInputSchema = z.object({
|
|
|
677
679
|
rpc: RpcHttpConfigInputSchema.default(false)
|
|
678
680
|
});
|
|
679
681
|
var ServConfigSchema = z.object({
|
|
682
|
+
server: ServerConfigInputSchema.optional(),
|
|
680
683
|
http: ServHttpConfigInputSchema.optional(),
|
|
681
684
|
cors: CorsRuntimeConfigInputSchema.optional(),
|
|
682
685
|
transport: TransportRuntimeConfigInputSchema.optional()
|
|
683
|
-
}).transform(({ http, cors: cors2, transport }) => ({
|
|
686
|
+
}).transform(({ server, http, cors: cors2, transport }) => ({
|
|
687
|
+
server: resolveServServerConfig(server ?? {}),
|
|
684
688
|
http: resolveServHttpConfig(http ?? {}),
|
|
685
689
|
cors: resolveServCorsRuntimeConfig(cors2 ?? {}),
|
|
686
690
|
transport: resolveServTransportRuntimeConfig(transport ?? false)
|
|
687
691
|
}));
|
|
692
|
+
function readServerEnvOverride() {
|
|
693
|
+
const override = {};
|
|
694
|
+
const host = process.env.HOST;
|
|
695
|
+
if (host)
|
|
696
|
+
override.host = host;
|
|
697
|
+
const rawPort = process.env.PORT;
|
|
698
|
+
if (rawPort) {
|
|
699
|
+
const port = Number(rawPort);
|
|
700
|
+
if (Number.isInteger(port) && port >= 1 && port <= 65535)
|
|
701
|
+
override.port = port;
|
|
702
|
+
}
|
|
703
|
+
return override;
|
|
704
|
+
}
|
|
705
|
+
function resolveServServerConfig(input = {}) {
|
|
706
|
+
return { ...ServerConfigInputSchema.parse(input), ...readServerEnvOverride() };
|
|
707
|
+
}
|
|
688
708
|
function resolveServHttpConfig(input = {}) {
|
|
689
709
|
return ServHttpConfigInputSchema.parse(input);
|
|
690
710
|
}
|
|
@@ -757,7 +777,7 @@ function resolveRefreshCookieConfig(apiPrefix, config, iam) {
|
|
|
757
777
|
return {
|
|
758
778
|
cookieName: config.cookieName ?? "hai_refresh_token",
|
|
759
779
|
maxAge: config.maxAge ?? 30 * 24 * 3600,
|
|
760
|
-
isSecure: config.secure ??
|
|
780
|
+
isSecure: config.secure ?? process.env.NODE_ENV === "production",
|
|
761
781
|
refreshCookiePath: `${apiPrefix}${IAM_AUTH_PATHS.refresh}`,
|
|
762
782
|
onRefresh,
|
|
763
783
|
nativeTokenTransport: config.nativeTokenTransport
|
|
@@ -1288,8 +1308,8 @@ function isOrpcValidationErrorPayload(value) {
|
|
|
1288
1308
|
var DEFAULT_SERV_HOST = "127.0.0.1";
|
|
1289
1309
|
var DEFAULT_SERV_PORT = 3e3;
|
|
1290
1310
|
function listen(app, options = {}) {
|
|
1291
|
-
const host = options.host ??
|
|
1292
|
-
const port = options.port ??
|
|
1311
|
+
const host = options.host ?? DEFAULT_SERV_HOST;
|
|
1312
|
+
const port = options.port ?? DEFAULT_SERV_PORT;
|
|
1293
1313
|
const server = serve(
|
|
1294
1314
|
{ fetch: app.fetch, hostname: host, port },
|
|
1295
1315
|
options.onListening
|
|
@@ -1324,20 +1344,11 @@ function listen(app, options = {}) {
|
|
|
1324
1344
|
} catch {
|
|
1325
1345
|
}
|
|
1326
1346
|
};
|
|
1327
|
-
|
|
1328
|
-
|
|
1347
|
+
process.once("SIGINT", () => void shutdown());
|
|
1348
|
+
process.once("SIGTERM", () => void shutdown());
|
|
1329
1349
|
}
|
|
1330
1350
|
return handle;
|
|
1331
1351
|
}
|
|
1332
|
-
var WILDCARD_ROLE = "*";
|
|
1333
|
-
function requireRole(role, handler) {
|
|
1334
|
-
return requireAuth(async (options) => {
|
|
1335
|
-
const roles = options.context.session?.roles ?? [];
|
|
1336
|
-
if (!roles.includes(role) && !roles.includes(WILDCARD_ROLE))
|
|
1337
|
-
return err(HaiCommonError.FORBIDDEN, servM("serv_errorForbidden", { locale: options.context.locale }));
|
|
1338
|
-
return handler(options);
|
|
1339
|
-
});
|
|
1340
|
-
}
|
|
1341
1352
|
|
|
1342
1353
|
// src/serv-cors.ts
|
|
1343
1354
|
var DEFAULT_CORS_METHODS = ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE", "OPTIONS"];
|
|
@@ -1569,14 +1580,9 @@ var serv = {
|
|
|
1569
1580
|
createRuntimeSecurityPolicy,
|
|
1570
1581
|
// OpenAPI / 导出
|
|
1571
1582
|
generateSpec,
|
|
1572
|
-
//
|
|
1573
|
-
/** 基于 contract 创建可注入 context 的 procedure builder(封装 `@orpc/server` 的 `implement`)。 */
|
|
1583
|
+
// Contract procedure 装配。
|
|
1574
1584
|
implement,
|
|
1575
|
-
//
|
|
1576
|
-
requireAuth,
|
|
1577
|
-
requirePermission,
|
|
1578
|
-
requireRole,
|
|
1579
|
-
mapHaiError,
|
|
1585
|
+
// Procedure 工具;认证与授权统一由 route 链声明。
|
|
1580
1586
|
validateInputOrFail,
|
|
1581
1587
|
// 通配符常量
|
|
1582
1588
|
WILDCARD_PERMISSION,
|