@h-ai/serv 1.0.0-beta.1 → 1.0.0-beta.10
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.en.md +10 -10
- package/README.ja.md +10 -10
- package/README.md +10 -10
- package/dist/{chunk-I3F7RN4I.js → chunk-FEG22LCX.js} +8 -5
- package/dist/chunk-FEG22LCX.js.map +1 -0
- package/dist/{chunk-TWMOZPGU.js → chunk-IQZQTAEW.js} +3 -3
- package/dist/{chunk-TWMOZPGU.js.map → chunk-IQZQTAEW.js.map} +1 -1
- package/dist/features/ai.js +1 -1
- package/dist/features/feedback.js +1 -1
- package/dist/features/iam.js +1 -1
- package/dist/features/storage.js +2 -2
- package/dist/index.d.ts +8 -44
- package/dist/index.js +15 -96
- package/dist/index.js.map +1 -1
- package/package.json +16 -16
- package/dist/chunk-I3F7RN4I.js.map +0 -1
package/README.en.md
CHANGED
|
@@ -18,7 +18,7 @@ hai-framework's API service runtime: a stable public HTTP App abstraction that m
|
|
|
18
18
|
- Default feature procedures: `createIamProcedures()`, `createStorageProcedures()`, `createAiProcedures()`.
|
|
19
19
|
- `build()` synchronously returns a plain router, so nested default feature procedures can be selected directly. Match the mounted contract to the selected procedures; unselected routes stay unavailable.
|
|
20
20
|
- Missing routes return localized HaiResult JSON with HTTP 404. Transport encryption preserves that status instead of producing a 500 for a non-JSON response.
|
|
21
|
-
- Built-in security headers and health checks; optional OpenAPI JSON, Scalar docs
|
|
21
|
+
- Built-in security headers and health checks; optional OpenAPI JSON, Scalar docs.
|
|
22
22
|
- Optional transport encryption: `serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })` mounts key exchange and request/response encryption automatically.
|
|
23
23
|
- Optional audio: `serv.createApp({ audio: { ai, verifyTicket, authorize } })` mounts a unified audio WebSocket. Connections authenticate immediately with one-time tickets (`{ session, grant? }`, separate pre-auth timeout). Every frame uses runtime Zod validation and a strict state machine, with input/queue/send-buffer backpressure and cascading cancellation. Authorization verifies operations, models, voices, quotas, and concurrency on the server.
|
|
24
24
|
|
|
@@ -102,7 +102,7 @@ const handler = serv.toFetch(app)
|
|
|
102
102
|
export default { fetch: handler }
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
Node `serv.listen()` supplies the socket peer address. Forwarded headers are ignored by default. Only proxy IPs/CIDRs in `http.trustedProxies` may supply them: XFF is walked from right to left until the first untrusted address; content to the left of that address is ignored. An invalid or excessively long trusted chain leaves IP undefined rather than promoting the proxy to client identity. Proxies must sanitize or append forwarded headers. Fetch platforms explicitly provide a trusted peer through `app.fetch(request, { remoteAddress: platformPeerAddress })`; without metadata IP is undefined. Do not copy arbitrary headers into metadata. Custom `createContext(input)` forwards the complete input to preserve resolved `input.ip`.
|
|
105
|
+
Node `serv.listen()` supplies the socket peer address. Forwarded headers are ignored by default. Only proxy IPs/CIDRs in `http.trustedProxies` may supply them: XFF is walked from right to left until the first untrusted address; content to the left of that address is ignored. An invalid or excessively long trusted chain leaves IP undefined rather than promoting the proxy to client identity. Proxies must sanitize or append forwarded headers. Fetch platforms explicitly provide a trusted peer through `app.fetch(request, { remoteAddress: platformPeerAddress })`; without metadata IP is undefined. Do not copy arbitrary headers into metadata. Custom `createContext(input)` forwards the complete input to preserve resolved `input.ip`.
|
|
106
106
|
|
|
107
107
|
toFetch constructs Request(input, init) for Request, URL and string inputs, applying RequestInit overrides including method, headers, body and signal without mutating input fields. Consumed or locked bodies and invalid body combinations reject the returned Promise. Cancellation before dispatch skips the handler; in-flight cancellation ends the caller wait and reaches the app through the effective signal. Listeners are released on completion, failure or cancellation, and a discarded late response body is cancelled. Cancellation cannot roll back external effects already started; after Response delivery the app owns streaming cleanup through request.signal.
|
|
108
108
|
|
|
@@ -355,7 +355,7 @@ Execution order is fixed:
|
|
|
355
355
|
2. Built-in CORS response wrapping; access checks for key exchange and preflight.
|
|
356
356
|
3. Built-in `transport`, if enabled, and request access checks.
|
|
357
357
|
4. Your `middlewares`.
|
|
358
|
-
5. health / refresh-cookie / OpenAPI /
|
|
358
|
+
5. health / refresh-cookie / OpenAPI / docs / oRPC routes
|
|
359
359
|
|
|
360
360
|
Notes:
|
|
361
361
|
|
|
@@ -426,7 +426,7 @@ Public API boundaries:
|
|
|
426
426
|
|
|
427
427
|
- `src/pipelines/*` holds internal defaults and is not a public subpath API.
|
|
428
428
|
- The stable application-facing entry remains `@h-ai/serv`.
|
|
429
|
-
- Internal helpers such as `securityHeaders` / `
|
|
429
|
+
- Internal helpers such as `securityHeaders` / `buildHaiErrorBody` are not documented as separate subpath exports.
|
|
430
430
|
|
|
431
431
|
#### httpOnly cookie authentication (recommended for production)
|
|
432
432
|
|
|
@@ -489,7 +489,7 @@ await apiClient.init({
|
|
|
489
489
|
|
|
490
490
|
### API overview
|
|
491
491
|
|
|
492
|
-
- `serv.createApp(options)`: create the HTTP App with custom middleware, health checks, OpenAPI, optional docs
|
|
492
|
+
- `serv.createApp(options)`: create the HTTP App with custom middleware, health checks, OpenAPI, optional docs.
|
|
493
493
|
- `serv.parseRequestContext({ request })`: extract Bearer token + requestId without populating session.
|
|
494
494
|
- `serv.listen(app, options)`: start Node.js HTTP service; return `{ server, address, close }`.
|
|
495
495
|
- `serv.toFetch(app)`: wrap as a standard `fetch(Request)` handler.
|
|
@@ -574,7 +574,6 @@ http:
|
|
|
574
574
|
health:
|
|
575
575
|
path: /health
|
|
576
576
|
readyPath: /ready
|
|
577
|
-
rpc: false
|
|
578
577
|
|
|
579
578
|
cors:
|
|
580
579
|
origin: https://app.example.com
|
|
@@ -601,11 +600,12 @@ transport:
|
|
|
601
600
|
|
|
602
601
|
`ServHttpConfigInput`:
|
|
603
602
|
|
|
604
|
-
|
|
603
|
+
Native RPC endpoints, `HAI_SERV_HTTP_RPC` and `ServRpcHttpConfig` have been removed. Existing OpenAPI/REST APIs, `api-client`, and the oRPC contracts and procedures remain supported.
|
|
604
|
+
|
|
605
|
+
- `apiPrefix`: defaults to `/api/v1`; accepts full absolute prefixes such as `/somepath/api/v1`. Segments use letters, digits, underscores, hyphens and dots; trailing slashes, empty segments, standalone `.`/`..`, queries and fragments are rejected. Configure health, docs and public storage paths under the same directory separately.
|
|
605
606
|
- `health`: defaults to `{ path: '/health', readyPath: '/ready' }`.
|
|
606
607
|
- `openapi`: defaults to `false`; enable with `{ path: '/openapi.json' }`.
|
|
607
|
-
- `docs`: defaults to `false`; enable with `{ path: '/docs' }
|
|
608
|
-
- `rpc`: defaults to `false`; enable with `{ prefix: '/rpc', access: 'loopback' | 'private-network' | 'gateway-only', gatewayHeader?, gatewaySecret? }`.
|
|
608
|
+
- `docs`: defaults to `false`; enable with `{ path: '/docs' }`. Scalar is mounted as `_hai/scalar.js` in the document directory (default `/_hai/scalar.js`), without an external CDN. Script and spec URLs are relative. When a gateway preserves a prefix, place docs and OpenAPI paths within it; stripping a prefix also works. Backend `requireAuth` checks remain enforced.
|
|
609
609
|
- `cors`: configured origins, credentials, allowedHeaders, and exposedHeaders; mount through `serv.cors(...)`.
|
|
610
610
|
- `transport`: top-level `config/_serv.yml` option passed as `servConfig.transport` to `createApp({ transport })`; default key exchange subpath is `/_hai/key-exchange`.
|
|
611
611
|
|
|
@@ -652,7 +652,7 @@ createIamProcedures({ iam }) implements public config, password/LDAP/OTP login,
|
|
|
652
652
|
|
|
653
653
|
Ordinary procedures return authentication, authorization, business failures and caught handler exceptions as HaiResult over HTTP 200 by default. `error.httpStatus` is domain-error metadata and does not change the response status. Clients must check `success` and `error.code`: an unauthenticated failure has `error.httpStatus=401`, while database query failures or INTERNAL_ERROR have 500.
|
|
654
654
|
|
|
655
|
-
oRPC contract input validation fails before procedures with HTTP 400 and a localized body containing `error.httpStatus=400` and field errors. Procedure-level `serv.validateInputOrFail` failures remain HTTP 200. HTTP middleware, refresh-cookie, OpenAPI
|
|
655
|
+
oRPC contract input validation fails before procedures with HTTP 400 and a localized body containing `error.httpStatus=400` and field errors. Procedure-level `serv.validateInputOrFail` failures remain HTTP 200. HTTP middleware, refresh-cookie, OpenAPI access checks and transport protocol failures follow their own HTTP response contracts. api-client refreshes after an actual HTTP 401 or a valid failed HaiResult over HTTP 200 (`success=false`, string `error.code/message` and `error.httpStatus=401`) and retries once on success. Built-in login and registration and the configured refresh path are excluded; 403/500 do not trigger refresh. With transport enabled, refresh and retry share the encrypted fetch path.
|
|
656
656
|
|
|
657
657
|
- All default procedures return `HaiResult<T>`; clients check `result.success`.
|
|
658
658
|
- Authentication failure → `HaiCommonError.UNAUTHORIZED`, localized by `.auth()` using `context.locale`.
|
package/README.ja.md
CHANGED
|
@@ -18,7 +18,7 @@ hai-framework の API サービスランタイム。公開 HTTP App 抽象を通
|
|
|
18
18
|
- 既定の機能 procedures:`createIamProcedures()`、`createStorageProcedures()`、`createAiProcedures()`。
|
|
19
19
|
- `build()` は通常の router を同期的に返すため、標準 feature のネストした procedures を直接選択できます。接続する contract と procedures を一致させ、未選択の入口は公開しません。
|
|
20
20
|
- 存在しないルートは翻訳された HaiResult JSON と HTTP 404 を返します。通信暗号化でも 404 を保ち、非 JSON 応答による 500 にしません。
|
|
21
|
-
- 安全な応答ヘッダー・ヘルスチェックを内蔵し、OpenAPI JSON・Scalar
|
|
21
|
+
- 安全な応答ヘッダー・ヘルスチェックを内蔵し、OpenAPI JSON・Scalar 文書・
|
|
22
22
|
- 任意の通信暗号化:`serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })` で鍵交換とリクエスト・レスポンスの暗号化を自動接続。
|
|
23
23
|
- 任意の音声入口:`serv.createApp({ audio: { ai, verifyTicket, authorize } })` が共通 WebSocket を接続します。接続直後に単回 ticket で認証(`{ session, grant? }`、独立した事前認証 timeout)。各フレームを Zod と厳密な状態機械で検証し、入力・キュー・送信バッファの背圧と連鎖キャンセルを管理します。サーバーの認可処理で操作・モデル・声・割当量・同時使用数を確認します。
|
|
24
24
|
|
|
@@ -102,7 +102,7 @@ const handler = serv.toFetch(app)
|
|
|
102
102
|
export default { fetch: handler }
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
Node `serv.listen()` は socket の接続元を自動で渡し、既定では転送ヘッダーを無視します。`http.trustedProxies` の IP/CIDR のみ信頼し、XFF を右から左へ調べ、最初の非信頼アドレスで停止します。その左側は無視します。信頼チェーンが不正・長すぎる場合は IP 未定義となり、代理をクライアントとして扱いません。代理は転送ヘッダーを清掃・追加してください。Fetch は `app.fetch(request, { remoteAddress: platformPeerAddress })` で信頼できる接続元を渡します。メタデータなしは IP 未定義です。任意のヘッダーをコピーしないでください。独自 `createContext(input)` は input 全体を再利用し、解決済み `input.ip`
|
|
105
|
+
Node `serv.listen()` は socket の接続元を自動で渡し、既定では転送ヘッダーを無視します。`http.trustedProxies` の IP/CIDR のみ信頼し、XFF を右から左へ調べ、最初の非信頼アドレスで停止します。その左側は無視します。信頼チェーンが不正・長すぎる場合は IP 未定義となり、代理をクライアントとして扱いません。代理は転送ヘッダーを清掃・追加してください。Fetch は `app.fetch(request, { remoteAddress: platformPeerAddress })` で信頼できる接続元を渡します。メタデータなしは IP 未定義です。任意のヘッダーをコピーしないでください。独自 `createContext(input)` は input 全体を再利用し、解決済み `input.ip` を保持します。
|
|
106
106
|
|
|
107
107
|
toFetch は Request・URL・文字列を標準 Request(input, init) で構築し、入力フィールドを変更せず method・headers・body・signal などを上書きします。消費済み・ロック中の本文や不正な本文指定は返却 Promise を reject します。派発前の取消しは handler を呼ばず、実行中の取消しは呼出側の待機を終了し、有効な signal をアプリへ伝えます。終了・失敗・取消しで listener を外し、取消し後に遅れて返る本文も cancel します。開始済みの外部効果は巻き戻せず、Response 返却後のストリーム終了はアプリが request.signal を消費して管理します。
|
|
108
108
|
|
|
@@ -355,7 +355,7 @@ const app = serv.createApp({
|
|
|
355
355
|
2. 組み込み CORS 応答処理;鍵交換と preflight のアクセス判定。
|
|
356
356
|
3. 組み込み `transport`(有効時)と各リクエストのアクセス判定。
|
|
357
357
|
4. 独自の `middlewares`。
|
|
358
|
-
5. health / refresh-cookie / OpenAPI /
|
|
358
|
+
5. health / refresh-cookie / OpenAPI / docs / oRPC routes
|
|
359
359
|
|
|
360
360
|
補足:
|
|
361
361
|
|
|
@@ -426,7 +426,7 @@ const procedures = serv
|
|
|
426
426
|
|
|
427
427
|
- `src/pipelines/*` は内部の既定実装で、公開サブパス API ではありません。
|
|
428
428
|
- アプリ向けの安定した入口はルートの `@h-ai/serv`。
|
|
429
|
-
- `securityHeaders` / `
|
|
429
|
+
- `securityHeaders` / `buildHaiErrorBody` などの内部 helper は独立サブパスとして公開しません。
|
|
430
430
|
|
|
431
431
|
#### httpOnly Cookie 認証(本番推奨)
|
|
432
432
|
|
|
@@ -489,7 +489,7 @@ await apiClient.init({
|
|
|
489
489
|
|
|
490
490
|
### API 一覧
|
|
491
491
|
|
|
492
|
-
- `serv.createApp(options)`:独自 middleware、ヘルスチェック、OpenAPI
|
|
492
|
+
- `serv.createApp(options)`:独自 middleware、ヘルスチェック、OpenAPI、任意の文書を備えた HTTP App を作成。
|
|
493
493
|
- `serv.parseRequestContext({ request })`:Bearer token + requestId を抽出し、session は設定しません。
|
|
494
494
|
- `serv.listen(app, options)`:Node.js HTTP サービスを起動し、`{ server, address, close }` を返す。
|
|
495
495
|
- `serv.toFetch(app)`:標準の `fetch(Request)` handler に変換。
|
|
@@ -574,7 +574,6 @@ http:
|
|
|
574
574
|
health:
|
|
575
575
|
path: /health
|
|
576
576
|
readyPath: /ready
|
|
577
|
-
rpc: false
|
|
578
577
|
|
|
579
578
|
cors:
|
|
580
579
|
origin: https://app.example.com
|
|
@@ -601,11 +600,12 @@ transport:
|
|
|
601
600
|
|
|
602
601
|
`ServHttpConfigInput`:
|
|
603
602
|
|
|
604
|
-
|
|
603
|
+
ネイティブ RPC 入口、`HAI_SERV_HTTP_RPC`、`ServRpcHttpConfig` は削除しました。既存の OpenAPI/REST API、`api-client`、oRPC contract と procedure は引き続き利用できます。
|
|
604
|
+
|
|
605
|
+
- `apiPrefix`:既定 `/api/v1`。`/somepath/api/v1` などの絶対接頭辞を指定できます。英数字・アンダースコア・ハイフン・点を使用し、末尾のスラッシュ、空区間、単独の `.`/`..`、クエリとフラグメントは拒否します。ヘルス・文書・公開ストレージのパスも同じディレクトリに別途設定します。
|
|
605
606
|
- `health`:既定 `{ path: '/health', readyPath: '/ready' }`。
|
|
606
607
|
- `openapi`:既定 `false`。`{ path: '/openapi.json' }` で有効化。
|
|
607
|
-
- `docs`:既定 `false`。`{ path: '/docs' }`
|
|
608
|
-
- `rpc`:既定 `false`。`{ prefix: '/rpc', access: 'loopback' | 'private-network' | 'gateway-only', gatewayHeader?, gatewaySecret? }` で有効化。
|
|
608
|
+
- `docs`:既定 `false`。`{ path: '/docs' }` で有効化。文書ディレクトリ内の `_hai/scalar.js`(既定 `/_hai/scalar.js`)で Scalar UI を配信し、外部 CDN は不要です。スクリプトと spec の URL は相対指定です。ゲートウェイが接頭辞を保持する場合は docs/openapi も転送範囲内に配置し、接頭辞を除去する構成にも対応します。`requireAuth` は後端で検証します。
|
|
609
609
|
- `cors`:origin・credentials・allowedHeaders・exposedHeaders を設定し、`serv.cors(...)` で接続。
|
|
610
610
|
- `transport`:`config/_serv.yml` のトップレベル設定。`servConfig.transport` を `createApp({ transport })` に渡し、既定の鍵交換サブパスは `/_hai/key-exchange`。
|
|
611
611
|
|
|
@@ -652,7 +652,7 @@ createIamProcedures({ iam }) は公開設定、LDAP/OTP/パスワード、登録
|
|
|
652
652
|
|
|
653
653
|
通常の procedure は認証・認可・業務の失敗と捕捉した handler 例外を HaiResult として既定で HTTP 200 で返します。`error.httpStatus` はドメインエラーのメタデータで、HTTP 応答ステータスを変更しません。クライアントは `success` と `error.code` を確認してください。未認証では `error.httpStatus=401`、DB クエリー失敗や INTERNAL_ERROR では 500 です。
|
|
654
654
|
|
|
655
|
-
oRPC contract の入力検証は procedure の前に失敗すると HTTP 400 となり、ローカライズ済み body に `error.httpStatus=400` とフィールドエラーを含みます。procedure 内の `serv.validateInputOrFail` の失敗は HTTP 200 のままです。HTTP middleware、refresh-cookie、OpenAPI
|
|
655
|
+
oRPC contract の入力検証は procedure の前に失敗すると HTTP 400 となり、ローカライズ済み body に `error.httpStatus=400` とフィールドエラーを含みます。procedure 内の `serv.validateInputOrFail` の失敗は HTTP 200 のままです。HTTP middleware、refresh-cookie、OpenAPI のアクセス確認、transport のプロトコルエラーは各自の HTTP 応答契約に従います。api-client は実際の HTTP 401、または HTTP 200 の有効な失敗 HaiResult(`success=false`、文字列の `error.code/message`、`error.httpStatus=401`)で自動更新し、成功後に一度だけ再試行します。組み込みのログイン・登録と設定された更新パスは対象外で、403/500 でも更新しません。transport 有効時は更新と再試行も同じ暗号化 fetch を使用します。
|
|
656
656
|
|
|
657
657
|
- 既定 procedures はすべて `HaiResult<T>` を返し、クライアントで `result.success` を確認。
|
|
658
658
|
- 認証失敗 → `HaiCommonError.UNAUTHORIZED`。`.auth()` が `context.locale` でローカライズ。
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ hai-framework 的 API 服务运行时:对外暴露稳定的 HTTP App 抽象,
|
|
|
18
18
|
- 默认 feature procedures:`createIamProcedures()`、`createStorageProcedures()`、`createAiProcedures()`
|
|
19
19
|
- `build()` 同步返回普通 router,可按嵌套属性选取默认 feature procedures;挂载的 contract 与所选 procedures 保持一致,未选入口不开放。
|
|
20
20
|
- 缺失路由返回本地化的 HaiResult JSON 404,启用传输加密后保留 404,不误报为非 JSON 响应导致的 500。
|
|
21
|
-
- 内置安全响应头、健康检查、可选 OpenAPI JSON、可选 Scalar
|
|
21
|
+
- 内置安全响应头、健康检查、可选 OpenAPI JSON、可选 Scalar 文档页
|
|
22
22
|
- 可选传输加密:`serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })` 自动挂载密钥协商与请求/响应加解密
|
|
23
23
|
- 可选语音入口:`serv.createApp({ audio: { ai, verifyTicket, authorize } })` 挂载统一语音 WebSocket;连接建立即用一次性 ticket 鉴权(返回 `{ session, grant? }`,独立预鉴权超时),每帧运行时 Zod 校验 + 严格状态机,输入/积压/发送缓冲背压与级联取消;授权回调负责服务端确认操作、模型、音色、配额与并发占用
|
|
24
24
|
|
|
@@ -102,7 +102,7 @@ const handler = serv.toFetch(app)
|
|
|
102
102
|
export default { fetch: handler }
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
Node `serv.listen()` 自动提供 socket 对端地址;默认忽略 `X-Forwarded-For` / `X-Real-IP`。只有 `http.trustedProxies` 中列出的代理 IP/CIDR 才能提供转发头,XFF 从右向左停在首个不可信地址,忽略其左侧内容;可信链无法解析或过长时 IP 为 undefined,不能回退为代理身份。代理必须清洗或追加转发头。Fetch 平台通过 `app.fetch(request, { remoteAddress: platformPeerAddress })` 显式提供可信对端,缺失时 IP 为 undefined,不能从任意请求头复制。自定义 `createContext(input)` 复用完整 input,保留框架已解析的 `input.ip
|
|
105
|
+
Node `serv.listen()` 自动提供 socket 对端地址;默认忽略 `X-Forwarded-For` / `X-Real-IP`。只有 `http.trustedProxies` 中列出的代理 IP/CIDR 才能提供转发头,XFF 从右向左停在首个不可信地址,忽略其左侧内容;可信链无法解析或过长时 IP 为 undefined,不能回退为代理身份。代理必须清洗或追加转发头。Fetch 平台通过 `app.fetch(request, { remoteAddress: platformPeerAddress })` 显式提供可信对端,缺失时 IP 为 undefined,不能从任意请求头复制。自定义 `createContext(input)` 复用完整 input,保留框架已解析的 `input.ip`。
|
|
106
106
|
|
|
107
107
|
toFetch 对 Request、URL、字符串统一调用标准 Request(input, init),RequestInit 可覆盖 method、headers、body、signal 等字段;不修改原 Request 的字段,正文已消费/锁定或非法正文组合会以 Promise 拒绝。预取消和派发前取消不进入 handler,进行中取消结束调用方等待并把有效 signal 传给应用;监听器在完成/失败/取消后释放,取消后的迟到响应会取消未消费正文。取消不能回滚已经开始的外部副作用;Response 交付后的流由应用监听 request.signal 并自行关闭。
|
|
108
108
|
|
|
@@ -355,7 +355,7 @@ const app = serv.createApp({
|
|
|
355
355
|
2. 内置 CORS 响应包装;密钥交换和预检先检查访问策略
|
|
356
356
|
3. 内置 `transport`(若启用)及每次请求访问策略
|
|
357
357
|
4. 你的 `middlewares`
|
|
358
|
-
5. health / refresh-cookie / OpenAPI /
|
|
358
|
+
5. health / refresh-cookie / OpenAPI / docs / oRPC routes
|
|
359
359
|
|
|
360
360
|
说明:
|
|
361
361
|
|
|
@@ -426,7 +426,7 @@ const procedures = serv
|
|
|
426
426
|
|
|
427
427
|
- `src/pipelines/*` 是 `@h-ai/serv` 的内部默认实现目录,不作为公开子路径 API 承诺
|
|
428
428
|
- 对应用层公开的稳定入口仍然是根入口 `@h-ai/serv`
|
|
429
|
-
- `securityHeaders` / `
|
|
429
|
+
- `securityHeaders` / `buildHaiErrorBody` 等内部 helper/middleware 不作为单独子路径文档暴露
|
|
430
430
|
|
|
431
431
|
#### httpOnly Cookie 认证(推荐生产方案)
|
|
432
432
|
|
|
@@ -489,7 +489,7 @@ await apiClient.init({
|
|
|
489
489
|
|
|
490
490
|
### API 概览
|
|
491
491
|
|
|
492
|
-
- `serv.createApp(options)`:创建 HTTP App 抽象,挂载自定义 `middlewares`、健康检查、OpenAPI handler
|
|
492
|
+
- `serv.createApp(options)`:创建 HTTP App 抽象,挂载自定义 `middlewares`、健康检查、OpenAPI handler 与可选文档
|
|
493
493
|
- `serv.parseRequestContext({ request })`:默认上下文解析(提取 Bearer token + requestId,不填充 session)
|
|
494
494
|
- `serv.listen(app, options)`:在 Node.js 启动 HTTP 服务,返回 `{ server, address, close }`
|
|
495
495
|
- `serv.toFetch(app)`:包装为标准 `fetch(Request)` handler
|
|
@@ -574,7 +574,6 @@ http:
|
|
|
574
574
|
health:
|
|
575
575
|
path: /health
|
|
576
576
|
readyPath: /ready
|
|
577
|
-
rpc: false
|
|
578
577
|
|
|
579
578
|
cors:
|
|
580
579
|
origin: https://app.example.com
|
|
@@ -601,11 +600,12 @@ transport:
|
|
|
601
600
|
|
|
602
601
|
`ServHttpConfigInput`:
|
|
603
602
|
|
|
604
|
-
|
|
603
|
+
原生 RPC 入口及 `HAI_SERV_HTTP_RPC`、`ServRpcHttpConfig` 已删除。业务继续使用现有 OpenAPI/REST 路径和 `api-client`,底层 oRPC contract 与 procedure 保留。
|
|
604
|
+
|
|
605
|
+
- `apiPrefix`:默认 `/api/v1`;支持 `/somepath/api/v1` 等完整绝对前缀。路径段只含字母、数字、下划线、连字符和点,不接受末尾斜杠、空段、单独的 `.`/`..`、查询或片段。健康、文档和存储公开路径分别配置到同一目录。
|
|
605
606
|
- `health`:默认 `{ path: '/health', readyPath: '/ready' }`
|
|
606
607
|
- `openapi`:默认 `false`;启用:`{ path: '/openapi.json' }`
|
|
607
|
-
- `docs`:默认 `false`;启用:`{ path: '/docs' }
|
|
608
|
-
- `rpc`:默认 `false`;启用:`{ prefix: '/rpc', access: 'loopback' | 'private-network' | 'gateway-only', gatewayHeader?, gatewaySecret? }`
|
|
608
|
+
- `docs`:默认 `false`;启用:`{ path: '/docs' }`;文档目录下自动挂载 `_hai/scalar.js`(默认 `/_hai/scalar.js`),提供本地 Scalar UI,无需外网 CDN。页面中的脚本和 spec 使用相对地址;网关保留前缀时 docs/openapi 都应配置在转发范围内,剥离前缀时仍可用后端原路径。`requireAuth` 仍由后端校验。
|
|
609
609
|
- `cors`:配置文件中的跨端 origin、credentials、allowedHeaders 与 exposedHeaders;应用通过 `serv.cors(...)` 装配。
|
|
610
610
|
- `transport`:`config/_serv.yml` 顶层配置;启用后可通过 `servConfig.transport` 装配到 `createApp({ transport })`,默认密钥协商子路径 `/_hai/key-exchange`
|
|
611
611
|
|
|
@@ -652,7 +652,7 @@ const app = serv.createApp({
|
|
|
652
652
|
|
|
653
653
|
普通 procedure 的认证、授权、业务失败和被捕获的 handler 异常都作为 `HaiResult` 输出,默认通过 HTTP 200 返回;`error.httpStatus` 是领域错误元数据,不会自动改写响应状态。客户端必须检查 `success` 与 `error.code`。例如未登录为 `error.httpStatus=401`,数据库查询失败或 INTERNAL_ERROR 为 500。
|
|
654
654
|
|
|
655
|
-
oRPC contract 输入校验在 procedure 之前失败时返回 HTTP 400,本地化失败体包含 `error.httpStatus=400` 和字段错误;procedure 内 `serv.validateInputOrFail` 返回的失败仍为 HTTP 200。HTTP middleware、refresh-cookie、OpenAPI
|
|
655
|
+
oRPC contract 输入校验在 procedure 之前失败时返回 HTTP 400,本地化失败体包含 `error.httpStatus=400` 和字段错误;procedure 内 `serv.validateInputOrFail` 返回的失败仍为 HTTP 200。HTTP middleware、refresh-cookie、OpenAPI 权限与 transport 协议错误另按自身契约返回真实 HTTP 状态。api-client 在真实 HTTP 401 或 HTTP 200 的有效失败 HaiResult(`success=false`、字符串 `error.code/message`、`error.httpStatus=401`)后自动刷新,成功后只重试一次;内置登录、注册和配置的刷新路径除外,403/500 不刷新。启用 transport 时刷新和重试复用加密链路。
|
|
656
656
|
|
|
657
657
|
- Default procedures 全部返回 `HaiResult<T>`,客户端直接判断 `result.success`
|
|
658
658
|
- 认证失败 → `HaiCommonError.UNAUTHORIZED`(由 `.auth()` 按 `context.locale` 本地化)
|
|
@@ -41,7 +41,8 @@ var en_US_default = {
|
|
|
41
41
|
serv_transportEncryptFailed: "Failed to encrypt response",
|
|
42
42
|
serv_transportKeyExchangeFailed: "Key exchange failed",
|
|
43
43
|
serv_transportBusy: "Material processing is busy. Please retry shortly.",
|
|
44
|
-
serv_feedbackUnavailable: "Feedback is temporarily unavailable. Business features remain available."
|
|
44
|
+
serv_feedbackUnavailable: "Feedback is temporarily unavailable. Business features remain available.",
|
|
45
|
+
serv_invalidApiPrefix: "API prefix must be a valid absolute path without empty segments, queries, fragments or traversal."
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
// messages/ja-JP.json
|
|
@@ -81,7 +82,8 @@ var ja_JP_default = {
|
|
|
81
82
|
serv_transportEncryptFailed: "\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u6697\u53F7\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
82
83
|
serv_transportKeyExchangeFailed: "\u9375\u4EA4\u63DB\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
83
84
|
serv_transportBusy: "\u8CC7\u6599\u306E\u51E6\u7406\u304C\u6DF7\u307F\u5408\u3063\u3066\u3044\u307E\u3059\u3002\u3057\u3070\u3089\u304F\u3057\u3066\u304B\u3089\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
84
|
-
serv_feedbackUnavailable: "\u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF\u306F\u4E00\u6642\u7684\u306B\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u696D\u52D9\u6A5F\u80FD\u306F\u5F15\u304D\u7D9A\u304D\u5229\u7528\u3067\u304D\u307E\u3059\u3002"
|
|
85
|
+
serv_feedbackUnavailable: "\u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF\u306F\u4E00\u6642\u7684\u306B\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u696D\u52D9\u6A5F\u80FD\u306F\u5F15\u304D\u7D9A\u304D\u5229\u7528\u3067\u304D\u307E\u3059\u3002",
|
|
86
|
+
serv_invalidApiPrefix: "API \u63A5\u982D\u8F9E\u306F\u7A7A\u306E\u533A\u9593\u30FB\u30AF\u30A8\u30EA\u30FB\u30D5\u30E9\u30B0\u30E1\u30F3\u30C8\u30FB\u76F8\u5BFE\u79FB\u52D5\u3092\u542B\u307E\u306A\u3044\u7D76\u5BFE\u30D1\u30B9\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
85
87
|
};
|
|
86
88
|
|
|
87
89
|
// messages/zh-CN.json
|
|
@@ -121,7 +123,8 @@ var zh_CN_default = {
|
|
|
121
123
|
serv_transportEncryptFailed: "\u54CD\u5E94\u52A0\u5BC6\u5931\u8D25",
|
|
122
124
|
serv_transportKeyExchangeFailed: "\u5BC6\u94A5\u534F\u5546\u5931\u8D25",
|
|
123
125
|
serv_transportBusy: "\u6750\u6599\u5904\u7406\u7E41\u5FD9\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
|
|
124
|
-
serv_feedbackUnavailable: "\u95EE\u9898\u53CD\u9988\u6682\u4E0D\u53EF\u7528\uFF0C\u4E1A\u52A1\u529F\u80FD\u53EF\u7EE7\u7EED\u4F7F\u7528"
|
|
126
|
+
serv_feedbackUnavailable: "\u95EE\u9898\u53CD\u9988\u6682\u4E0D\u53EF\u7528\uFF0C\u4E1A\u52A1\u529F\u80FD\u53EF\u7EE7\u7EED\u4F7F\u7528",
|
|
127
|
+
serv_invalidApiPrefix: "API \u524D\u7F00\u5FC5\u987B\u662F\u5408\u6CD5\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u4E0D\u80FD\u5305\u542B\u7A7A\u8DEF\u5F84\u6BB5\u3001\u67E5\u8BE2\u3001\u7247\u6BB5\u6216\u8DEF\u5F84\u7A7F\u8D8A\u3002"
|
|
125
128
|
};
|
|
126
129
|
|
|
127
130
|
// src/serv-i18n.ts
|
|
@@ -403,5 +406,5 @@ function getHandlerMethod(value) {
|
|
|
403
406
|
}
|
|
404
407
|
|
|
405
408
|
export { WILDCARD_PERMISSION, WILDCARD_ROLE, buildHaiErrorBody, implement, normalizeServLocale, servM };
|
|
406
|
-
//# sourceMappingURL=chunk-
|
|
407
|
-
//# sourceMappingURL=chunk-
|
|
409
|
+
//# sourceMappingURL=chunk-FEG22LCX.js.map
|
|
410
|
+
//# sourceMappingURL=chunk-FEG22LCX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../messages/en-US.json","../messages/ja-JP.json","../messages/zh-CN.json","../src/serv-i18n.ts","../src/pipelines/serv-pipeline-guard.ts","../src/pipelines/serv-pipeline-helper.ts","../src/serv-router.ts"],"names":["core","HaiCommonError","err","orpcImplement"],"mappings":"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","file":"chunk-FEG22LCX.js","sourcesContent":["{\n \"serv_transportPlaintextRequired\": \"This endpoint accepts unencrypted application data. Disable transport encryption for this request and retry over HTTPS.\",\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"serv_validationFailed\": \"Validation failed\",\n \"serv_validationRequired\": \"This field is required\",\n \"serv_validationInvalid\": \"Invalid input format\",\n \"serv_validationInvalidType\": \"Invalid field type\",\n \"serv_validationUnexpectedFields\": \"The submitted data contains unsupported fields. Refresh the page and try again.\",\n \"serv_validationStringMin\": \"Enter at least {min} characters\",\n \"serv_validationStringMax\": \"Enter at most {max} UTF-8 bytes\",\n \"serv_validationNumberMin\": \"Value must be at least {min}\",\n \"serv_validationNumberMax\": \"Value must be at most {max}\",\n \"serv_validationArrayMin\": \"Select at least {min} item(s)\",\n \"serv_validationArrayMax\": \"Select at most {max} item(s)\",\n \"serv_validationTooSmall\": \"Value must be at least {min}\",\n \"serv_validationTooBig\": \"Value must be at most {max}\",\n \"serv_validationEmail\": \"Enter a valid email address\",\n \"serv_validationUrl\": \"Enter a valid URL\",\n \"serv_validationUuid\": \"Enter a valid UUID\",\n \"serv_validationEnum\": \"Select a valid option\",\n \"serv_errorUnauthorized\": \"Unauthorized\",\n \"serv_errorForbidden\": \"Forbidden\",\n \"serv_errorInternal\": \"Internal server error\",\n \"serv_storageKeyRequired\": \"Storage key must not be empty\",\n \"serv_storageKeyTooLong\": \"Storage key must not exceed {max} characters\",\n \"serv_storageKeyIllegalChars\": \"Storage key contains illegal characters\",\n \"serv_storageKeyDotSegments\": \"Storage key must not contain . or .. path segments\",\n \"serv_iamCannotDeleteCurrentUser\": \"Cannot delete the current logged-in user\",\n \"serv_transportInvalidPayload\": \"Invalid encrypted transport payload\",\n \"serv_transportUnknownClient\": \"Unknown encryption client; key exchange required\",\n \"serv_transportClientIdRequired\": \"Missing X-Client-Id header; key exchange required\",\n \"serv_transportClientKeyNotFound\": \"Client not registered or session expired; complete key exchange again\",\n \"serv_transportDecryptFailed\": \"Failed to decrypt request\",\n \"serv_transportEncryptFailed\": \"Failed to encrypt response\",\n \"serv_transportKeyExchangeFailed\": \"Key exchange failed\",\n \"serv_transportBusy\": \"Material processing is busy. Please retry shortly.\",\n \"serv_feedbackUnavailable\": \"Feedback is temporarily unavailable. Business features remain available.\",\n \"serv_invalidApiPrefix\": \"API prefix must be a valid absolute path without empty segments, queries, fragments or traversal.\"\n}\n","{\n \"serv_transportPlaintextRequired\": \"このエンドポイントはアプリケーション層で暗号化されていないデータを受け付けます。リクエストの伝送暗号化を無効にし、HTTPS で再試行してください。\",\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"serv_validationFailed\": \"入力内容の検証に失敗しました\",\n \"serv_validationRequired\": \"この項目は必須です\",\n \"serv_validationInvalid\": \"入力形式が無効です\",\n \"serv_validationInvalidType\": \"フィールドの型が無効です\",\n \"serv_validationUnexpectedFields\": \"送信データにこの操作で使用できない項目が含まれています。ページを更新して再度お試しください。\",\n \"serv_validationStringMin\": \"{min}文字以上で入力してください\",\n \"serv_validationStringMax\": \"UTF-8 で {max} バイト以内で入力してください\",\n \"serv_validationNumberMin\": \"{min}以上の値を入力してください\",\n \"serv_validationNumberMax\": \"{max}以下の値を入力してください\",\n \"serv_validationArrayMin\": \"{min}件以上選択してください\",\n \"serv_validationArrayMax\": \"{max}件以内で選択してください\",\n \"serv_validationTooSmall\": \"{min}以上の値を入力してください\",\n \"serv_validationTooBig\": \"{max}以下の値を入力してください\",\n \"serv_validationEmail\": \"有効なメールアドレスを入力してください\",\n \"serv_validationUrl\": \"有効なURLを入力してください\",\n \"serv_validationUuid\": \"有効なUUIDを入力してください\",\n \"serv_validationEnum\": \"有効な選択肢を選んでください\",\n \"serv_errorUnauthorized\": \"認証されていません\",\n \"serv_errorForbidden\": \"アクセスが拒否されました\",\n \"serv_errorInternal\": \"サーバー内部エラー\",\n \"serv_storageKeyRequired\": \"ストレージキーを空にすることはできません\",\n \"serv_storageKeyTooLong\": \"ストレージキーは{max}文字以内で指定してください\",\n \"serv_storageKeyIllegalChars\": \"ストレージキーに無効な文字が含まれています\",\n \"serv_storageKeyDotSegments\": \"ストレージキーに「.」や「..」のパス要素を含めることはできません\",\n \"serv_iamCannotDeleteCurrentUser\": \"現在ログイン中のユーザーは削除できません\",\n \"serv_transportInvalidPayload\": \"暗号化された通信ペイロードが無効です\",\n \"serv_transportUnknownClient\": \"暗号化クライアントが不明です。鍵交換が必要です\",\n \"serv_transportClientIdRequired\": \"X-Client-Idヘッダーがありません。鍵交換が必要です\",\n \"serv_transportClientKeyNotFound\": \"クライアントが未登録か、セッションの有効期限が切れています。再度鍵交換を行ってください\",\n \"serv_transportDecryptFailed\": \"リクエストの復号に失敗しました\",\n \"serv_transportEncryptFailed\": \"レスポンスの暗号化に失敗しました\",\n \"serv_transportKeyExchangeFailed\": \"鍵交換に失敗しました\",\n \"serv_transportBusy\": \"資料の処理が混み合っています。しばらくしてから再試行してください。\",\n \"serv_feedbackUnavailable\": \"フィードバックは一時的に利用できません。業務機能は引き続き利用できます。\",\n \"serv_invalidApiPrefix\": \"API 接頭辞は空の区間・クエリ・フラグメント・相対移動を含まない絶対パスにしてください。\"\n}\n","{\n \"serv_transportPlaintextRequired\": \"此接口接收未加密的应用数据,请关闭传输加密后通过 HTTPS 重试。\",\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"serv_validationFailed\": \"数据验证失败\",\n \"serv_validationRequired\": \"此字段必填\",\n \"serv_validationInvalid\": \"输入格式无效\",\n \"serv_validationInvalidType\": \"字段类型不正确\",\n \"serv_validationUnexpectedFields\": \"提交的数据包含当前操作不支持的内容,请刷新页面后重试。\",\n \"serv_validationStringMin\": \"至少输入 {min} 个字符\",\n \"serv_validationStringMax\": \"最多输入 {max} 个 UTF-8 字节\",\n \"serv_validationNumberMin\": \"数值不能小于 {min}\",\n \"serv_validationNumberMax\": \"数值不能大于 {max}\",\n \"serv_validationArrayMin\": \"至少选择 {min} 项\",\n \"serv_validationArrayMax\": \"最多选择 {max} 项\",\n \"serv_validationTooSmall\": \"不能小于 {min}\",\n \"serv_validationTooBig\": \"不能大于 {max}\",\n \"serv_validationEmail\": \"请输入有效的邮箱地址\",\n \"serv_validationUrl\": \"请输入有效的网址\",\n \"serv_validationUuid\": \"请输入有效的 UUID\",\n \"serv_validationEnum\": \"请选择有效选项\",\n \"serv_errorUnauthorized\": \"未登录或登录已失效\",\n \"serv_errorForbidden\": \"无权执行该操作\",\n \"serv_errorInternal\": \"服务器内部错误\",\n \"serv_storageKeyRequired\": \"存储 key 不能为空\",\n \"serv_storageKeyTooLong\": \"存储 key 不能超过 {max} 个字符\",\n \"serv_storageKeyIllegalChars\": \"存储 key 包含非法字符\",\n \"serv_storageKeyDotSegments\": \"存储 key 不能包含 . 或 .. 路径段\",\n \"serv_iamCannotDeleteCurrentUser\": \"不能删除当前登录用户\",\n \"serv_transportInvalidPayload\": \"传输层加密载荷格式无效\",\n \"serv_transportUnknownClient\": \"未识别的加密客户端,请先完成密钥协商\",\n \"serv_transportClientIdRequired\": \"缺少 X-Client-Id 请求头,请先完成密钥协商\",\n \"serv_transportClientKeyNotFound\": \"客户端未注册或会话已过期,请重新完成密钥协商\",\n \"serv_transportDecryptFailed\": \"请求解密失败\",\n \"serv_transportEncryptFailed\": \"响应加密失败\",\n \"serv_transportKeyExchangeFailed\": \"密钥协商失败\",\n \"serv_transportBusy\": \"材料处理繁忙,请稍后重试\",\n \"serv_feedbackUnavailable\": \"问题反馈暂不可用,业务功能可继续使用\",\n \"serv_invalidApiPrefix\": \"API 前缀必须是合法的绝对路径,不能包含空路径段、查询、片段或路径穿越。\"\n}\n","/**\n * @h-ai/serv — i18n 工具\n *\n * Serv 模块的国际化消息工具,从 @h-ai/core 调用 i18n API。\n * 与 kit-i18n 结构对齐,便于跨包统一维护。\n * @module serv-i18n\n */\n\nimport { core } from '@h-ai/core'\nimport messagesEnUS from '../messages/en-US.json'\nimport messagesJaJP from '../messages/ja-JP.json'\nimport messagesZhCN from '../messages/zh-CN.json'\n\n/**\n * Serv 本地化消息的真实 key 联合。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const key: ServMessageKey = 'serv_errorForbidden'\n * const message = servM(key, { locale: 'en-US' })\n * ```\n */\ntype ServMessageKey = keyof typeof messagesZhCN\n\nconst SUPPORTED_SERV_LOCALES = ['zh-CN', 'en-US', 'ja-JP'] as const\nconst DEFAULT_SERV_LOCALE = 'zh-CN'\n\n/**\n * 规范化 serv 请求 locale。\n *\n * 支持:\n * - 完整 locale:`zh-CN` / `en-US` / `ja-JP`\n * - 简写:`zh` / `en` / `ja`\n * - `Accept-Language` 风格值:`en-US,en;q=0.9`\n *\n * 未识别时回退到默认 `zh-CN`。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const english = normalizeServLocale('en-US,en;q=0.9')\n * const fallback = normalizeServLocale(undefined)\n * ```\n */\nexport function normalizeServLocale(locale: string | undefined): string {\n const candidate = locale?.split(',')[0]?.trim()\n if (!candidate)\n return DEFAULT_SERV_LOCALE\n\n const exact = SUPPORTED_SERV_LOCALES.find(item => item.toLowerCase() === candidate.toLowerCase())\n if (exact)\n return exact\n\n const baseLanguage = candidate.split('-')[0]?.toLowerCase()\n if (baseLanguage === 'en')\n return 'en-US'\n if (baseLanguage === 'ja')\n return 'ja-JP'\n if (baseLanguage === 'zh')\n return 'zh-CN'\n\n return DEFAULT_SERV_LOCALE\n}\n\n/**\n * 获取 Serv 模块的 i18n 消息。\n *\n * 单次调用可通过 `options.locale` 指定该次调用使用的语言,\n * 用于在请求处理过程中根据 `Accept-Language` 头本地化错误消息,\n * 而不污染全局 locale(多并发请求安全)。\n *\n * @example\n * ```ts\n * import { serv } from '@h-ai/serv'\n *\n * const message = serv.m('serv_validationStringMin', { locale: 'en-US', params: { min: 3 } })\n * // 单次 locale 不修改并发请求的全局语言。\n * ```\n */\nexport const servM = core.i18n.createMessageGetter<ServMessageKey>({\n 'zh-CN': messagesZhCN,\n 'en-US': messagesEnUS,\n 'ja-JP': messagesJaJP,\n})\n\nexport type { ServMessageKey }\n","/**\n * @h-ai/serv — procedure 认证与授权 guard\n *\n * 将认证、权限、角色及异常映射合并为单个 handler 包装层,并在类型上把\n * `context.session` 收窄为非空。\n *\n * @module pipelines/serv-pipeline-guard\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { ErrorMap, Meta, ProcedureHandler } from '@orpc/server'\nimport type { ServContext } from '../serv-context.js'\nimport type { AuthenticatedServContext } from '../serv-router.js'\nimport { err, HaiCommonError } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\n\n/**\n * 通配符权限。仅应授予受信任的超级管理员,可通过任意 permission guard。\n *\n * @example\n * ```ts\n * import { serv } from '@h-ai/serv'\n *\n * const wildcard = serv.WILDCARD_PERMISSION\n * // 仅可信超级管理员可拥有通配符 permissions;普通用户使用明确能力列表。\n * ```\n */\nexport const WILDCARD_PERMISSION = '*'\n\n/**\n * 通配符角色。仅应授予受信任的超级管理员,可通过任意 role guard。\n *\n * @example\n * ```ts\n * import { serv } from '@h-ai/serv'\n *\n * const wildcard = serv.WILDCARD_ROLE\n * // 仅可信超级管理员可拥有通配符 roles;普通用户使用明确能力列表。\n * ```\n */\nexport const WILDCARD_ROLE = '*'\n\n/**\n * 内部 route guard 要求;权限/角色检查以可信非空会话为前提。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const requirements: ServGuardRequirements = { auth: true, permissions: ['reports.read'] }\n * const guarded = applyServGuards(requirements, ({ context }) => ({ success: true, data: { userId: context.session.userId } }))\n * ```\n */\nexport interface ServGuardRequirements {\n readonly auth?: boolean\n readonly permissions?: readonly string[]\n readonly roles?: readonly string[]\n}\n\n/**\n * 将 guard 应用于一个使用 HaiResult 输出的 oRPC handler。\n *\n * 完整保留 oRPC 的 `path`、`procedure`、`signal`、`lastEventId`、`errors` 等\n * options;只在校验成功后把 `context` 收窄为带非空 session 的类型。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const guarded = applyServGuards({ permissions: ['reports.read'] }, ({ context }) => ({ success: true, data: { userId: context.session.userId } }))\n * // 业务 handler 只在可信 session 满足全部权限后执行。\n * ```\n */\nexport function applyServGuards<\n TContext extends ServContext,\n TInput,\n TData,\n TErrorMap extends ErrorMap,\n TMeta extends Meta,\n>(\n requirements: ServGuardRequirements,\n handler: ProcedureHandler<\n AuthenticatedServContext<TContext>,\n TInput,\n HaiResult<TData>,\n TErrorMap,\n TMeta\n >,\n): ProcedureHandler<TContext, TInput, HaiResult<TData>, TErrorMap, TMeta> {\n return async (options) => {\n try {\n if (!hasAuthenticatedSession(options.context)) {\n return err(\n HaiCommonError.UNAUTHORIZED,\n servM('serv_errorUnauthorized', { locale: options.context.locale }),\n )\n }\n\n const { session } = options.context\n if (requirements.permissions?.length) {\n const allowed = session.permissions.includes(WILDCARD_PERMISSION)\n || requirements.permissions.every(\n permission => session.permissions.includes(permission),\n )\n if (!allowed) {\n return err(\n HaiCommonError.FORBIDDEN,\n servM('serv_errorForbidden', { locale: options.context.locale }),\n )\n }\n }\n\n if (requirements.roles?.length) {\n const allowed = session.roles.includes(WILDCARD_ROLE)\n || requirements.roles.every(\n role => session.roles.includes(role),\n )\n if (!allowed) {\n return err(\n HaiCommonError.FORBIDDEN,\n servM('serv_errorForbidden', { locale: options.context.locale }),\n )\n }\n }\n\n return await handler({\n ...options,\n context: options.context,\n })\n }\n catch (error) {\n return err(\n HaiCommonError.INTERNAL_ERROR,\n servM('serv_errorInternal', { locale: options.context.locale }),\n error,\n )\n }\n }\n}\n\nfunction hasAuthenticatedSession<TContext extends ServContext>(\n context: TContext,\n): context is AuthenticatedServContext<TContext> {\n return context.session !== undefined && context.session !== null\n}\n","/**\n * @h-ai/serv — Pipeline 公共 helper\n *\n * 放置不属于 Hono middleware / route guard 默认实现本身、\n * 但会被多个 pipeline 复用的公共工具。\n * @module pipelines/serv-pipeline-helper\n */\n\nimport type { ServProcedureHandler } from './serv-pipeline-types.js'\nimport { audit } from '@h-ai/audit'\nimport { core, err, HaiCommonError } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\n\n/**\n * 构造 `HaiResult` 失败分支同构的 HTTP 错误定义。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const definition: ServErrorBodyDef = HaiCommonError.UNAUTHORIZED\n * const body = buildHaiErrorBody(definition, servM('serv_errorUnauthorized'))\n * ```\n */\nexport interface ServErrorBodyDef {\n readonly code: string | number\n readonly httpStatus: number\n readonly system: string\n readonly module: string\n}\n\n/**\n * 构造与 `HaiResult` 失败分支同构的 HTTP 响应体。\n *\n * - 字段顺序与 `haiResultSchema()` 输出一致,便于客户端统一解析。\n * - 仅暴露最小信息(code/message/httpStatus),避免泄漏内部细节。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const body = buildHaiErrorBody(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden'))\n * // HTTP middleware 再设置 status;body 只包含安全的领域错误字段。\n * ```\n */\nexport function buildHaiErrorBody(def: ServErrorBodyDef, message: string) {\n return {\n success: false as const,\n error: {\n code: def.code,\n message: core.error.formatMessage({ message, traceId: core.context.get().traceId ?? core.context.get().requestId }),\n traceId: core.context.get().traceId ?? core.context.get().requestId,\n httpStatus: def.httpStatus,\n system: def.system,\n module: def.module,\n },\n }\n}\n\n/**\n * 捕获未处理异常并转换为 HaiResult。\n *\n * 由 contract router 在注册 handler 时统一应用,业务代码无需手动包装。\n *\n * @param handler - 被包装的 procedure handler\n * @returns 带异常保护的新 handler\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const wrapped = mapHaiError<{ id: string }, { id: string }>(async ({ input }) => ({ success: true, data: { id: input.id } }))\n * // oRPC 原始 options 原样传入;未处理异常在此转换为领域错误。\n * ```\n */\nexport function mapHaiError<TInput, TOutput>(handler: ServProcedureHandler<TInput, TOutput>): ServProcedureHandler<TInput, TOutput> {\n return options => audit.withContext({\n actor: options.context.session ? { id: options.context.session.userId, username: options.context.session.username, roles: options.context.session.roles } : null,\n requestId: options.context.requestId,\n traceId: options.context.requestId,\n scopeId: options.context.scopeId,\n ipAddress: options.context.ip,\n userAgent: options.context.userAgent?.slice(0, 4096),\n }, async () => {\n const resource = options.path.join('.') || 'procedure'\n const capture = async (status: number, reasonCode?: string | number) => {\n if (!audit.isInitialized)\n return\n const written = await audit.capture({\n action: 'request.execute',\n resource,\n category: /^(?:iam|auth|audit)(?:\\.|$)/.test(resource) ? 'system' : 'business',\n outcome: [401, 403, 423, 429].includes(status) ? 'denied' : status >= 400 ? 'failure' : 'success',\n details: { method: options.context.request.method, status, ...(reasonCode === undefined ? {} : { reasonCode }) },\n }, { read: ['GET', 'HEAD', 'OPTIONS'].includes(options.context.request.method) })\n if (!written.success)\n options.context.logger.error('Request audit failed', { resource, error: written.error })\n }\n try {\n const result = await handler(options)\n await capture(result.success ? 200 : result.error.httpStatus ?? 500, result.success ? undefined : result.error.code)\n if (result.success)\n return result\n const error = core.error.toPublic(result.error, options.context.requestId, options.context.locale)\n options.context.logger.error('Procedure failed', { traceId: error.traceId, error: result.error })\n return { success: false, error }\n }\n catch (error) {\n // 所有未捕获异常统一在这里收口,避免直接把异常冒泡到 HTTP 层。\n const safe = core.error.toPublic({ ...HaiCommonError.INTERNAL_ERROR, message: servM('serv_errorInternal', { locale: options.context.locale }) }, options.context.requestId, options.context.locale)\n options.context.logger.error('Procedure threw an exception', { traceId: safe.traceId, error })\n await capture(500, HaiCommonError.INTERNAL_ERROR.code)\n return err(safe)\n }\n })\n}\n","/**\n * @h-ai/serv — contract-first 链式 procedure 实现器\n *\n * 在 oRPC `implement()` 之上提供 route 名只出现一次的链式 DSL。\n * 应用只接触 contract 路径、上下文与 guard,不需要组装 oRPC router 对象。\n *\n * @module serv-router\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type {\n Context,\n ContractProcedure,\n ContractRouter,\n ErrorMap,\n Implementer,\n InferSchemaInput,\n InferSchemaOutput,\n Meta,\n ProcedureHandler,\n Router,\n UnlaziedRouter,\n} from '@orpc/server'\nimport type { ServGuardRequirements } from './pipelines/serv-pipeline-guard.js'\nimport type { ServContext } from './serv-context.js'\nimport { implement as orpcImplement } from '@orpc/server'\nimport { applyServGuards } from './pipelines/serv-pipeline-guard.js'\nimport { mapHaiError } from './pipelines/serv-pipeline-helper.js'\n\n/**\n * 内部初始 context 空约束,用于保持 oRPC 推导而不伪造运行时字段。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const { serv } = await import('@h-ai/serv')\n * const context: ServContext & EmptyContext = serv.parseRequestContext({ request: new Request('https://example.com/ping') })\n * const requestId = context.requestId\n * ```\n */\ntype EmptyContext = Record<never, never>\n/**\n * 提取 contract 的字符串键,排除 symbol/数字索引。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const { apiContract } = await import('@h-ai/api-contract')\n * const { z } = await import('zod')\n * const { ok } = await import('@h-ai/core')\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const path: StringKey<typeof contract> = 'ping'\n * const procedure = contract[path]\n * ```\n */\ntype StringKey<T> = Extract<keyof T, string>\n/**\n * 内部 contract 泛型边界;真实 procedure 输出须为 HaiResult。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const { apiContract } = await import('@h-ai/api-contract')\n * const { z } = await import('zod')\n * const { ok } = await import('@h-ai/core')\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const checked = contract satisfies ServContract\n * const procedures = implement(checked).context<ServContext>().route('ping', () => ok({ ready: true })).build()\n * ```\n */\ntype ServContract = ContractRouter<Meta>\n\n/**\n * contract 中所有 procedure 的点分路径。\n *\n * @example\n * ```ts\n * import type { ServRoutePath } from '@h-ai/serv'\n * import type { ServContext } from '@h-ai/serv'\n * import { apiContract } from '@h-ai/api-contract'\n * import { ok } from '@h-ai/core'\n * import { serv } from '@h-ai/serv'\n * import { z } from 'zod'\n *\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const path: ServRoutePath<typeof contract> = 'ping'\n * const procedures = serv.implement(contract).context<ServContext>().route(path, () => ok({ ready: true })).build()\n * ```\n */\nexport type ServRoutePath<TContract extends ServContract>\n = TContract extends ContractProcedure<\n infer _TInputSchema,\n infer _TOutputSchema,\n infer _TErrorMap,\n infer _TMeta\n >\n ? never\n : {\n [K in StringKey<TContract>]: TContract[K] extends ContractProcedure<\n infer _TChildInputSchema,\n infer _TChildOutputSchema,\n infer _TChildErrorMap,\n infer _TChildMeta\n >\n ? K\n : TContract[K] extends ServContract\n ? ServRoutePath<TContract[K]> extends infer TChild extends string\n ? `${K}.${TChild}`\n : never\n : never\n }[StringKey<TContract>]\n\n/**\n * 按点分路径查找原始 contract procedure;不存在的路径推导为 never。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const { apiContract } = await import('@h-ai/api-contract')\n * const { z } = await import('zod')\n * const { ok } = await import('@h-ai/core')\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const procedure: ContractAtPath<typeof contract, 'ping'> = contract.ping\n * const scoped = implement({ ping: procedure }).context<ServContext>().route('ping', () => ok({ ready: true })).build()\n * ```\n */\ntype ContractAtPath<TContract, TPath extends string>\n = TPath extends `${infer THead}.${infer TTail}`\n ? THead extends keyof TContract\n ? ContractAtPath<TContract[THead], TTail>\n : never\n : TPath extends keyof TContract\n ? TContract[TPath]\n : never\n\n/**\n * 将合法路由路径映射到对应 contract,供 handler 推导复用。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const { apiContract } = await import('@h-ai/api-contract')\n * const { z } = await import('zod')\n * const { ok } = await import('@h-ai/core')\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const procedure: RouteContract<typeof contract, 'ping'> = contract.ping\n * const scoped = implement({ ping: procedure }).context<ServContext>().route('ping', () => ok({ ready: true })).build()\n * ```\n */\ntype RouteContract<\n TContract extends ServContract,\n TPath extends ServRoutePath<TContract>,\n> = ContractAtPath<TContract, TPath>\n\n/**\n * `.auth()`、`.permission()`、`.role()` 后的非空会话上下文。\n *\n * @example\n * ```ts\n * import type { AuthenticatedServContext } from '@h-ai/serv'\n *\n * function owner(context: AuthenticatedServContext): string {\n * return context.session.userId\n * }\n * // 仅成功 guard 或可信认证结果能建立此上下文,禁止用客户端字段断言。\n * ```\n */\nexport type AuthenticatedServContext<TContext extends ServContext = ServContext>\n = TContext & {\n readonly session: NonNullable<TContext['session']>\n }\n\n/**\n * 内部从具体 procedure 的输入/输出与 context 推导 HaiResult handler。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const { apiContract } = await import('@h-ai/api-contract')\n * const { z } = await import('zod')\n * const { ok } = await import('@h-ai/core')\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const handler: RouteHandler<typeof contract.ping, ServContext> = () => ok({ ready: true })\n * const procedures = implement(contract).context<ServContext>().route('ping', handler).build()\n * ```\n */\ntype RouteHandler<\n TRouteContract,\n TContext extends Context,\n> = TRouteContract extends ContractProcedure<\n infer TInputSchema,\n infer TOutputSchema,\n infer TErrorMap,\n infer TMeta\n>\n ? InferSchemaInput<TOutputSchema> extends HaiResult<unknown>\n ? ProcedureHandler<\n TContext,\n InferSchemaOutput<TInputSchema>,\n InferSchemaInput<TOutputSchema>,\n TErrorMap,\n TMeta\n >\n : never\n : never\n\n/**\n * 从 contract procedure 精确推导当前 route 的 HaiResult handler。\n *\n * @example\n * ```ts\n * import type { ServRouteHandler } from '@h-ai/serv'\n * import type { ServContext } from '@h-ai/serv'\n * import { apiContract } from '@h-ai/api-contract'\n * import { ok } from '@h-ai/core'\n * import { serv } from '@h-ai/serv'\n * import { z } from 'zod'\n *\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const handler: ServRouteHandler<typeof contract, 'ping', ServContext> = () => ok({ ready: true })\n * const procedures = serv.implement(contract).context<ServContext>().route('ping', handler).build()\n * ```\n */\nexport type ServRouteHandler<\n TContract extends ServContract,\n TPath extends ServRoutePath<TContract>,\n TContext extends Context,\n> = RouteHandler<RouteContract<TContract, TPath>, TContext>\n\n/**\n * 内部认证成功 handler 类型;session 在 guard 之后非空。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const { apiContract } = await import('@h-ai/api-contract')\n * const { z } = await import('zod')\n * const { ok } = await import('@h-ai/core')\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const handler: ServGuardedRouteHandler<typeof contract, 'ping', ServContext> = ({ context }) => ok({ ready: context.session.userId.length > 0 })\n * const procedures = implement(contract).context<ServContext>().route('ping').auth().handle(handler).build()\n * ```\n */\ntype ServGuardedRouteHandler<\n TContract extends ServContract,\n TPath extends ServRoutePath<TContract>,\n TContext extends ServContext,\n> = RouteHandler<RouteContract<TContract, TPath>, AuthenticatedServContext<TContext>>\n\n/**\n * 一次注册后移除该路径的 builder;全部路径完成才提供 build。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const { apiContract } = await import('@h-ai/api-contract')\n * const { z } = await import('zod')\n * const { ok } = await import('@h-ai/core')\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const next: NextBuilder<typeof contract, ServContext, ServContext, 'ping', 'ping'> = implement(contract).context<ServContext>().route('ping', () => ok({ ready: true }))\n * const procedures = next.build()\n * ```\n */\ntype NextBuilder<\n TContract extends ServContract,\n TInitialContext extends Context,\n TCurrentContext extends ServContext,\n TRemaining extends ServRoutePath<TContract>,\n TPath extends TRemaining,\n> = ServRouterBuilder<\n TContract,\n TInitialContext,\n TCurrentContext,\n Exclude<TRemaining, TPath>\n>\n\n/**\n * 尚未声明 guard 的单 route 注册器。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const { apiContract } = await import('@h-ai/api-contract')\n * const { z } = await import('zod')\n * const { ok } = await import('@h-ai/core')\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const route: ServRouteRegistration<typeof contract, ServContext, ServContext, 'ping', 'ping'> = implement(contract).context<ServContext>().route('ping')\n * const procedures = route.auth().handle(() => ok({ ready: true })).build()\n * ```\n */\nexport interface ServRouteRegistration<\n TContract extends ServContract,\n TInitialContext extends Context,\n TCurrentContext extends ServContext,\n TRemaining extends ServRoutePath<TContract>,\n TPath extends TRemaining,\n> {\n readonly auth: () => ServGuardedRouteRegistration<\n TContract,\n TInitialContext,\n TCurrentContext,\n TRemaining,\n TPath\n >\n readonly permission: (permission: string) => ServGuardedRouteRegistration<\n TContract,\n TInitialContext,\n TCurrentContext,\n TRemaining,\n TPath\n >\n readonly role: (role: string) => ServGuardedRouteRegistration<\n TContract,\n TInitialContext,\n TCurrentContext,\n TRemaining,\n TPath\n >\n}\n\n/**\n * 已声明认证或授权 guard 的单 route 注册器。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const { apiContract } = await import('@h-ai/api-contract')\n * const { z } = await import('zod')\n * const { ok } = await import('@h-ai/core')\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const guarded: ServGuardedRouteRegistration<typeof contract, ServContext, ServContext, 'ping', 'ping'> = implement(contract).context<ServContext>().route('ping').permission('reports.read')\n * const procedures = guarded.handle(() => ok({ ready: true })).build()\n * ```\n */\nexport interface ServGuardedRouteRegistration<\n TContract extends ServContract,\n TInitialContext extends Context,\n TCurrentContext extends ServContext,\n TRemaining extends ServRoutePath<TContract>,\n TPath extends TRemaining,\n> {\n readonly auth: () => ServGuardedRouteRegistration<\n TContract,\n TInitialContext,\n TCurrentContext,\n TRemaining,\n TPath\n >\n readonly permission: (permission: string) => ServGuardedRouteRegistration<\n TContract,\n TInitialContext,\n TCurrentContext,\n TRemaining,\n TPath\n >\n readonly role: (role: string) => ServGuardedRouteRegistration<\n TContract,\n TInitialContext,\n TCurrentContext,\n TRemaining,\n TPath\n >\n readonly handle: (\n handler: ServGuardedRouteHandler<TContract, TPath, TCurrentContext>,\n ) => NextBuilder<TContract, TInitialContext, TCurrentContext, TRemaining, TPath>\n}\n\n/**\n * 内部 route 重载:直接 handler 或先声明 guard,再收窄剩余路径。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const { apiContract } = await import('@h-ai/api-contract')\n * const { z } = await import('zod')\n * const { ok } = await import('@h-ai/core')\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const route: ServRouteMethod<typeof contract, ServContext, ServContext, 'ping'> = implement(contract).context<ServContext>().route\n * const procedures = route('ping', () => ok({ ready: true })).build()\n * ```\n */\ninterface ServRouteMethod<\n TContract extends ServContract,\n TInitialContext extends Context,\n TCurrentContext extends ServContext,\n TRemaining extends ServRoutePath<TContract>,\n> {\n <TPath extends TRemaining>(\n path: TPath,\n handler: ServRouteHandler<TContract, TPath, TCurrentContext>,\n ): NextBuilder<TContract, TInitialContext, TCurrentContext, TRemaining, TPath>\n <TPath extends TRemaining>(\n path: TPath,\n ): ServRouteRegistration<\n TContract,\n TInitialContext,\n TCurrentContext,\n TRemaining,\n TPath\n >\n}\n\n/**\n * 剩余路径为 never 时才暴露 build,否则仅提供缺失路径类型提示。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const { apiContract } = await import('@h-ai/api-contract')\n * const { z } = await import('zod')\n * const { ok } = await import('@h-ai/core')\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const complete: ServRouterBuilderBuild<typeof contract, ServContext, never> = implement(contract).context<ServContext>().route('ping', () => ok({ ready: true }))\n * const procedures = complete.build()\n * ```\n */\ntype ServRouterBuilderBuild<\n TContract extends ServContract,\n TInitialContext extends Context,\n TRemaining,\n> = [TRemaining] extends [never]\n ? {\n /** 同步返回完整的普通 router,允许按需选用嵌套的默认 procedures。 */\n readonly build: () => UnlaziedRouter<Router<TContract, TInitialContext>>\n }\n : {\n /** 仅供 TypeScript/IDE 展示尚未实现的 route;运行时不存在。 */\n readonly missingRoutes: TRemaining\n }\n\n/**\n * 链式 router builder。\n *\n * `TRemaining` 会随着 `.route()` 注册逐步收窄;未实现完整 contract 时没有 `.build()`。\n *\n * @example\n * ```ts\n * import type { ServRouterBuilder } from '@h-ai/serv'\n * import type { ServContext } from '@h-ai/serv'\n * import { apiContract } from '@h-ai/api-contract'\n * import { ok } from '@h-ai/core'\n * import { serv } from '@h-ai/serv'\n * import { z } from 'zod'\n *\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const builder: ServRouterBuilder<typeof contract, ServContext, ServContext> = serv.implement(contract).context<ServContext>()\n * const procedures = builder.route('ping', () => ok({ ready: true })).build()\n * ```\n */\nexport type ServRouterBuilder<\n TContract extends ServContract,\n TInitialContext extends Context,\n TCurrentContext extends ServContext,\n TRemaining extends ServRoutePath<TContract> = ServRoutePath<TContract>,\n> = {\n readonly route: ServRouteMethod<\n TContract,\n TInitialContext,\n TCurrentContext,\n TRemaining\n >\n} & ServRouterBuilderBuild<TContract, TInitialContext, TRemaining>\n\n/**\n * hai-framework 链式 contract 实现器。\n *\n * @example\n * ```ts\n * import type { ServImplementer } from '@h-ai/serv'\n * import type { ServContext } from '@h-ai/serv'\n * import { apiContract } from '@h-ai/api-contract'\n * import { ok } from '@h-ai/core'\n * import { serv } from '@h-ai/serv'\n * import { z } from 'zod'\n *\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const implementer: ServImplementer<typeof contract> = serv.implement(contract)\n * const procedures = implementer.context<ServContext>().route('ping', () => ok({ ready: true })).build()\n * ```\n */\nexport interface ServImplementer<TContract extends ServContract> {\n readonly context: <TContext extends ServContext>() => ServRouterBuilder<\n TContract,\n TContext & EmptyContext,\n TContext\n >\n}\n\n/**\n * 创建链式 contract 实现器。\n *\n * @example\n * ```ts\n * import type { ServContext } from '@h-ai/serv'\n * import { apiContract } from '@h-ai/api-contract'\n * import { ok } from '@h-ai/core'\n * import { serv } from '@h-ai/serv'\n * import { z } from 'zod'\n *\n * const contract = { ping: apiContract.route({ method: 'GET', path: '/ping' }).output(apiContract.haiResultSchema(z.object({ ready: z.boolean() }))) }\n * const procedures = serv.implement(contract).context<ServContext>().route('ping', () => ok({ ready: true })).build()\n * ```\n */\nexport function implement<TContract extends ServContract>(\n contract: TContract,\n): ServImplementer<TContract> {\n return {\n context: <TContext extends ServContext>() => {\n const native = orpcImplement(contract).$context<TContext>()\n return createServRouterBuilder(contract, native)\n },\n }\n}\n\nfunction createServRouterBuilder<\n TContract extends ServContract,\n TInitialContext extends Context,\n TCurrentContext extends ServContext,\n>(\n contract: TContract,\n implementer: Implementer<TContract, TInitialContext, TCurrentContext>,\n): ServRouterBuilder<TContract, TInitialContext, TCurrentContext> {\n const runtime = new ServRouterBuilderRuntime(contract, implementer)\n\n // oRPC 的 contract 泛型只能在编译期表示;运行时 builder 由同一 contract\n // 创建并逐项校验路径,因此这里将动态实现收敛到唯一的类型边界。\n return runtime as unknown as ServRouterBuilder<\n TContract,\n TInitialContext,\n TCurrentContext\n >\n}\n\n/**\n * 内部运行时 handler 边界;最终输入/输出验证仍由对应 contract 决定。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const { ok } = await import('@h-ai/core')\n * const handler: RuntimeProcedureHandler = ({ input }) => ok(input)\n * // 动态装配边界仍由相同 contract 校验路径与 options,不提升未知输入类型。\n * ```\n */\ntype RuntimeProcedureHandler = ProcedureHandler<\n ServContext,\n unknown,\n HaiResult<unknown>,\n ErrorMap,\n Meta\n>\n/**\n * 内部认证后的运行时 handler,原始 oRPC options 与可信 session 保留。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const { ok } = await import('@h-ai/core')\n * const handler: AuthenticatedRuntimeProcedureHandler = ({ context }) => ok({ userId: context.session.userId })\n * const guarded = applyServGuards({ auth: true }, handler)\n * ```\n */\ntype AuthenticatedRuntimeProcedureHandler = ProcedureHandler<\n AuthenticatedServContext,\n unknown,\n HaiResult<unknown>,\n ErrorMap,\n Meta\n>\n\nclass ServRouterBuilderRuntime {\n private readonly expectedPaths: ReadonlySet<string>\n private readonly implementedRoutes: Record<string, unknown>\n private readonly registeredPaths = new Set<string>()\n\n constructor(\n contract: ServContract,\n private readonly implementer: object,\n ) {\n this.expectedPaths = new Set(collectProcedurePaths(contract))\n this.implementedRoutes = createRouterShape(contract)\n }\n\n route(path: string, handler?: unknown): unknown {\n if (handler !== undefined)\n return this.register(path, {}, handler)\n\n return new ServRouteRegistrationRuntime(this, path)\n }\n\n register(path: string, guards: ServGuardRequirements, handler: unknown): this {\n if (!this.expectedPaths.has(path))\n throw new TypeError(`Contract path is not implementable: ${path}`)\n if (this.registeredPaths.has(path))\n throw new TypeError(`Duplicate procedure implementation: ${path}`)\n if (typeof handler !== 'function')\n throw new TypeError(`Procedure handler must be a function: ${path}`)\n\n const procedureImplementer = getAtPath(this.implementer, path)\n const implementHandler = getHandlerMethod(procedureImplementer)\n if (!implementHandler)\n throw new TypeError(`Contract path is not implementable: ${path}`)\n\n // 点路径反射会丢失单条 contract 的泛型;路径已由 expectedPaths 校验,\n // handler 的精确 input/output 则由公开的 ServRouteHandler 在调用侧保证。\n const guardedHandler = hasGuards(guards)\n ? applyServGuards(guards, handler as AuthenticatedRuntimeProcedureHandler)\n : handler as RuntimeProcedureHandler\n const safeHandler = mapHaiError(guardedHandler)\n const implemented = Reflect.apply(\n implementHandler,\n procedureImplementer,\n [safeHandler],\n )\n\n setAtPath(this.implementedRoutes, path, implemented)\n this.registeredPaths.add(path)\n return this\n }\n\n build(): unknown {\n const missingPaths = [...this.expectedPaths]\n .filter(path => !this.registeredPaths.has(path))\n .sort()\n if (missingPaths.length > 0) {\n throw new TypeError(\n `Cannot build router. Missing procedures: ${missingPaths.join(', ')}`,\n )\n }\n\n const routerMethod = Reflect.get(this.implementer, 'router')\n if (typeof routerMethod !== 'function')\n throw new TypeError('The oRPC implementer does not expose router()')\n\n return Reflect.apply(routerMethod, this.implementer, [this.implementedRoutes])\n }\n}\n\nclass ServRouteRegistrationRuntime {\n private authRequired = false\n private readonly permissions = new Set<string>()\n private readonly roles = new Set<string>()\n\n constructor(\n private readonly parent: ServRouterBuilderRuntime,\n private readonly path: string,\n ) {}\n\n auth(): this {\n this.authRequired = true\n return this\n }\n\n permission(permission: string): this {\n this.authRequired = true\n this.permissions.add(permission)\n return this\n }\n\n role(role: string): this {\n this.authRequired = true\n this.roles.add(role)\n return this\n }\n\n handle(handler: unknown): ServRouterBuilderRuntime {\n return this.parent.register(this.path, {\n auth: this.authRequired,\n permissions: [...this.permissions],\n roles: [...this.roles],\n }, handler)\n }\n}\n\nfunction hasGuards(guards: ServGuardRequirements): boolean {\n return guards.auth === true\n || (guards.permissions?.length ?? 0) > 0\n || (guards.roles?.length ?? 0) > 0\n}\n\nfunction collectProcedurePaths(\n contract: ServContract,\n prefix: readonly string[] = [],\n): string[] {\n if (isContractProcedure(contract))\n return prefix.length > 0 ? [prefix.join('.')] : []\n\n return Object.entries(contract).flatMap(([key, child]) =>\n collectProcedurePaths(child, [...prefix, key]))\n}\n\nfunction isContractProcedure(contract: ServContract): boolean {\n return '~orpc' in contract\n}\n\nfunction createRouterShape(contract: ServContract): Record<string, unknown> {\n const shape: Record<string, unknown> = Object.create(null)\n\n for (const [key, child] of Object.entries(contract)) {\n if (!isContractProcedure(child))\n shape[key] = createRouterShape(child)\n }\n\n return shape\n}\n\nfunction getAtPath(root: object, path: string): unknown {\n let current: unknown = root\n\n for (const segment of path.split('.')) {\n if (\n (typeof current !== 'object' && typeof current !== 'function')\n || current === null\n || !(segment in current)\n ) {\n return undefined\n }\n\n current = Reflect.get(current, segment)\n }\n\n return current\n}\n\nfunction setAtPath(root: Record<string, unknown>, path: string, value: unknown): void {\n const segments = path.split('.')\n const leaf = segments.pop()\n if (!leaf)\n throw new TypeError('Procedure path cannot be empty')\n\n let current = root\n for (const segment of segments) {\n const existing = current[segment]\n if (existing === undefined) {\n const child: Record<string, unknown> = Object.create(null)\n current[segment] = child\n current = child\n continue\n }\n\n if (!existing || typeof existing !== 'object')\n throw new TypeError(`Procedure path conflicts with an existing route: ${path}`)\n\n current = existing as Record<string, unknown>\n }\n\n current[leaf] = value\n}\n\nfunction getHandlerMethod(value: unknown): ((handler: unknown) => unknown) | undefined {\n if ((typeof value !== 'object' && typeof value !== 'function') || value === null)\n return undefined\n\n const handler = Reflect.get(value, 'handler')\n return typeof handler === 'function'\n ? handler as (handler: unknown) => unknown\n : undefined\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normalizeServLocale, servM } from './chunk-
|
|
1
|
+
import { normalizeServLocale, servM } from './chunk-FEG22LCX.js';
|
|
2
2
|
import { core, ok, err, HaiCommonError } from '@h-ai/core';
|
|
3
3
|
|
|
4
4
|
var DEFAULT_LOCALE = "zh-CN";
|
|
@@ -33,5 +33,5 @@ function validateInputOrFail(schema, input, locale = DEFAULT_LOCALE) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export { localizeZodError, resolveRequestLocale, validateInputOrFail };
|
|
36
|
-
//# sourceMappingURL=chunk-
|
|
37
|
-
//# sourceMappingURL=chunk-
|
|
36
|
+
//# sourceMappingURL=chunk-IQZQTAEW.js.map
|
|
37
|
+
//# sourceMappingURL=chunk-IQZQTAEW.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/serv-validation.ts"],"names":[],"mappings":";;;AAiBA,IAAM,cAAA,GAAiB,OAAA;AAmDhB,SAAS,qBAAqB,OAAA,EAA+D;AAClG,EAAA,MAAM,GAAA,GAAM,CAAC,IAAA,KAAqC;AAChD,IAAA,IAAI,OAAA,YAAmB,OAAA;AACrB,MAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA,IAAK,MAAA;AAC9B,IAAA,OAAO,QAAQ,IAAI,CAAA,IAAK,OAAA,CAAQ,IAAA,CAAK,aAAa,CAAA;AAAA,EACpD,CAAA;AAEA,EAAA,MAAM,QAAA,GAAW,IAAI,cAAc,CAAA;AACnC,EAAA,IAAI,QAAA;AACF,IAAA,OAAO,oBAAoB,QAAQ,CAAA;AAErC,EAAA,OAAO,oBAAoB,GAAA,CAAI,iBAAiB,KAAK,GAAA,CAAI,iBAAiB,KAAK,cAAc,CAAA;AAC/F;AAiBO,SAAS,gBAAA,CAAiB,UAAmB,MAAA,EAAuC;AACzF,EAAA,MAAM,cAAA,GAAiB,oBAAoB,MAAM,CAAA;AACjD,EAAA,OAAO,KAAK,aAAA,CAAc,uBAAA;AAAA,IACxB,QAAA;AAAA,IACA,KAAK,aAAA,CAAc,8BAAA;AAAA,MACjB,MAAA;AAAA,MACA,CAAC,YAAY,MAAA,KAAW,KAAA,CAAM,YAAY,EAAE,MAAA,EAAQ,cAAA,EAAgB,MAAA,EAAQ;AAAA;AAC9E,GACF;AACF;AA2CO,SAAS,mBAAA,CACd,MAAA,EACA,KAAA,EACA,MAAA,GAAiB,cAAA,EACG;AACpB,EAAA,MAAM,cAAA,GAAiB,oBAAoB,MAAM,CAAA;AACjD,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,SAAA,CAAU,KAAK,CAAA;AACrC,EAAA,IAAI,MAAA,CAAO,OAAA;AACT,IAAA,OAAO,EAAA,CAAG,OAAO,IAAI,CAAA;AACvB,EAAA,MAAM,MAAA,GAAS,gBAAA,CAAiB,MAAA,CAAO,KAAA,EAAO,cAAc,CAAA;AAC5D,EAAA,OAAO,GAAA,CAAI,cAAA,CAAe,gBAAA,EAAkB,KAAA,CAAM,uBAAA,EAAyB,EAAE,MAAA,EAAQ,cAAA,EAAgB,CAAA,EAAG,MAAM,CAAA;AAChH","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/serv-validation.ts"],"names":[],"mappings":";;;AAiBA,IAAM,cAAA,GAAiB,OAAA;AAmDhB,SAAS,qBAAqB,OAAA,EAA+D;AAClG,EAAA,MAAM,GAAA,GAAM,CAAC,IAAA,KAAqC;AAChD,IAAA,IAAI,OAAA,YAAmB,OAAA;AACrB,MAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA,IAAK,MAAA;AAC9B,IAAA,OAAO,QAAQ,IAAI,CAAA,IAAK,OAAA,CAAQ,IAAA,CAAK,aAAa,CAAA;AAAA,EACpD,CAAA;AAEA,EAAA,MAAM,QAAA,GAAW,IAAI,cAAc,CAAA;AACnC,EAAA,IAAI,QAAA;AACF,IAAA,OAAO,oBAAoB,QAAQ,CAAA;AAErC,EAAA,OAAO,oBAAoB,GAAA,CAAI,iBAAiB,KAAK,GAAA,CAAI,iBAAiB,KAAK,cAAc,CAAA;AAC/F;AAiBO,SAAS,gBAAA,CAAiB,UAAmB,MAAA,EAAuC;AACzF,EAAA,MAAM,cAAA,GAAiB,oBAAoB,MAAM,CAAA;AACjD,EAAA,OAAO,KAAK,aAAA,CAAc,uBAAA;AAAA,IACxB,QAAA;AAAA,IACA,KAAK,aAAA,CAAc,8BAAA;AAAA,MACjB,MAAA;AAAA,MACA,CAAC,YAAY,MAAA,KAAW,KAAA,CAAM,YAAY,EAAE,MAAA,EAAQ,cAAA,EAAgB,MAAA,EAAQ;AAAA;AAC9E,GACF;AACF;AA2CO,SAAS,mBAAA,CACd,MAAA,EACA,KAAA,EACA,MAAA,GAAiB,cAAA,EACG;AACpB,EAAA,MAAM,cAAA,GAAiB,oBAAoB,MAAM,CAAA;AACjD,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,SAAA,CAAU,KAAK,CAAA;AACrC,EAAA,IAAI,MAAA,CAAO,OAAA;AACT,IAAA,OAAO,EAAA,CAAG,OAAO,IAAI,CAAA;AACvB,EAAA,MAAM,MAAA,GAAS,gBAAA,CAAiB,MAAA,CAAO,KAAA,EAAO,cAAc,CAAA;AAC5D,EAAA,OAAO,GAAA,CAAI,cAAA,CAAe,gBAAA,EAAkB,KAAA,CAAM,uBAAA,EAAyB,EAAE,MAAA,EAAQ,cAAA,EAAgB,CAAA,EAAG,MAAM,CAAA;AAChH","file":"chunk-IQZQTAEW.js","sourcesContent":["/**\n * @h-ai/serv — 校验工具\n *\n * 为 API service 提供与 kit 对等的校验 i18n 能力:\n * - {@link resolveRequestLocale}:从请求头(`x-hai-locale` / `Accept-Language`)解析语言。\n * - {@link validateInputOrFail}:在 procedure 内显式校验数据,失败返回 `HaiResult` 错误。\n * - oRPC contract 输入校验失败的自动本地化由 `serv-app.ts` 中的 oRPC 响应拦截器处理。\n *\n * @module serv-validation\n */\n\nimport type { HaiResult, ValidationFormError } from '@h-ai/core'\nimport type { ZodType } from 'zod'\nimport type { ServMessageKey } from './serv-i18n.js'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\nimport { normalizeServLocale, servM } from './serv-i18n.js'\n\nconst DEFAULT_LOCALE = 'zh-CN'\n\n/**\n * 校验失败响应体(含 i18n 错误列表)。\n *\n * 复用 `HaiResult` 失败分支结构。字段错误同时在两处出现:\n * - `error.cause`:与 `validateInputOrFail` 输出对齐,HaiResult 标准位置\n * - 顶层 `errors`:保留以兼容已有客户端\n *\n * @example\n * ```ts\n * import type { ServValidationFailureBody } from '@h-ai/serv'\n * import { serv } from '@h-ai/serv'\n *\n * function fields(body: ServValidationFailureBody) {\n * return body.error.cause ?? body.errors\n * }\n * // 业务调用先判断 success,字段错误仅包含安全的本地化文案。\n * ```\n */\nexport interface ServValidationFailureBody {\n readonly success: false\n readonly error: {\n readonly code: string | number\n readonly message: string\n readonly traceId?: string\n readonly httpStatus: number\n readonly system: string\n readonly module: string\n readonly cause?: readonly ValidationFormError[]\n }\n readonly errors: readonly ValidationFormError[]\n}\n\n/**\n * 从请求头解析客户端期望 locale。\n *\n * 优先级:`x-hai-locale` > `Accept-Language` 第一个匹配项 > 默认值(`zh-CN`)。\n * 返回值会被规范化为 serv 当前支持的 locale(如 `en` → `en-US`)。\n *\n * @param headers - 请求头(`Headers` 或与之等价的对象)\n * @returns 解析得到的 locale\n *\n * @example\n * ```ts\n * import { serv } from '@h-ai/serv'\n *\n * const locale = serv.resolveRequestLocale(new Headers({ 'x-hai-locale': 'en-US' }))\n * const fallback = serv.resolveRequestLocale(new Headers())\n * ```\n */\nexport function resolveRequestLocale(headers: Headers | Record<string, string | undefined>): string {\n const get = (name: string): string | undefined => {\n if (headers instanceof Headers)\n return headers.get(name) ?? undefined\n return headers[name] ?? headers[name.toLowerCase()]\n }\n\n const explicit = get('x-hai-locale')\n if (explicit)\n return normalizeServLocale(explicit)\n\n return normalizeServLocale(get('accept-language') ?? get('Accept-Language') ?? DEFAULT_LOCALE)\n}\n\n/**\n * 把 Zod 错误转为本地化的扁平错误列表。\n *\n * @param zodError - Zod `SafeParseError` 或 `ZodError`\n * @param locale - 期望 locale\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * const { z } = await import('zod')\n * const parsed = z.object({ name: z.string().min(1) }).safeParse({ name: '' })\n * if (parsed.success) return []\n * return localizeZodError(parsed.error, 'en-US')\n * ```\n */\nexport function localizeZodError(zodError: unknown, locale: string): ValidationFormError[] {\n const resolvedLocale = normalizeServLocale(locale)\n return core.zodValidation.mapZodErrorToFormErrors(\n zodError,\n core.zodValidation.createPrefixedZodMessageGetter<ServMessageKey>(\n 'serv',\n (messageKey, params) => servM(messageKey, { locale: resolvedLocale, params }),\n ),\n )\n}\n\n/**\n * 在 procedure handler 内显式校验数据。\n *\n * 与 oRPC contract 自动校验不同,本函数用于业务层二次校验(例如对从数据库读到的对象再校验)。\n * 失败时返回 `HaiResult` 错误分支,错误的 `cause` 字段携带 `ValidationFormError[]`。\n *\n * 推荐使用方式:\n * ```ts\n * import { z } from 'zod'\n * import { serv } from '@h-ai/serv'\n *\n * const Schema = z.object({ name: z.string().min(1) })\n *\n * export const procedures = serv\n * .implement(contract)\n * .context<ServContext>()\n * .route('profile.update', ({ input, context }) => {\n * const result = serv.validateInputOrFail(Schema, input, context.locale)\n * if (!result.success) return result\n * return profileService.update(result.data)\n * })\n * .build()\n * ```\n *\n * @param schema - Zod schema\n * @param input - 待校验数据\n * @param locale - 期望 locale(通常来自请求上下文)\n *\n * @example\n * ```ts\n * import { serv } from '@h-ai/serv'\n * import { z } from 'zod'\n *\n * const result = serv.validateInputOrFail(z.object({ name: z.string().min(1) }), { name: '' }, 'en-US')\n * if (!result.success) {\n * const fields = result.error.cause\n * } else {\n * const name = result.data.name\n * }\n * ```\n */\nexport function validateInputOrFail<TOutput>(\n schema: ZodType<TOutput>,\n input: unknown,\n locale: string = DEFAULT_LOCALE,\n): HaiResult<TOutput> {\n const resolvedLocale = normalizeServLocale(locale)\n const result = schema.safeParse(input)\n if (result.success)\n return ok(result.data)\n const errors = localizeZodError(result.error, resolvedLocale)\n return err(HaiCommonError.VALIDATION_ERROR, servM('serv_validationFailed', { locale: resolvedLocale }), errors)\n}\n"]}
|
package/dist/features/ai.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { wrapItemsResult } from '../chunk-XKCLD42I.js';
|
|
2
|
-
import { implement, servM } from '../chunk-
|
|
2
|
+
import { implement, servM } from '../chunk-FEG22LCX.js';
|
|
3
3
|
import { apiContract } from '@h-ai/api-contract';
|
|
4
4
|
import { ok, err, HaiCommonError } from '@h-ai/core';
|
|
5
5
|
|
package/dist/features/iam.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mapHaiResult } from '../chunk-XKCLD42I.js';
|
|
2
|
-
import { implement, servM } from '../chunk-
|
|
2
|
+
import { implement, servM } from '../chunk-FEG22LCX.js';
|
|
3
3
|
import { apiContract } from '@h-ai/api-contract';
|
|
4
4
|
import { core, err, HaiCommonError, ok } from '@h-ai/core';
|
|
5
5
|
|
package/dist/features/storage.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { validateInputOrFail } from '../chunk-
|
|
1
|
+
import { validateInputOrFail } from '../chunk-IQZQTAEW.js';
|
|
2
2
|
import { mapHaiResult } from '../chunk-XKCLD42I.js';
|
|
3
|
-
import { implement, servM } from '../chunk-
|
|
3
|
+
import { implement, servM } from '../chunk-FEG22LCX.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';
|