@h-ai/cli 0.1.0-alpha.65 → 0.1.0-alpha.67
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 +66 -251
- package/README.ja.md +67 -252
- package/README.md +66 -251
- package/dist/{chunk-47V5OT22.js → chunk-7DSURTJ4.js} +32 -115
- package/dist/chunk-7DSURTJ4.js.map +1 -0
- package/dist/{chunk-IPG3QNYK.js → chunk-AOQ4N657.js} +212 -8
- package/dist/chunk-AOQ4N657.js.map +1 -0
- package/dist/cli-config-templates-6AHNUGBN.js +5 -0
- package/dist/{cli-config-templates-SJ2MBIR7.js.map → cli-config-templates-6AHNUGBN.js.map} +1 -1
- package/dist/{cli-skill-templates-A5VIR5J6.js → cli-skill-templates-FC3QRKDO.js} +3 -3
- package/dist/{cli-skill-templates-A5VIR5J6.js.map → cli-skill-templates-FC3QRKDO.js.map} +1 -1
- package/dist/index.js +644 -391
- package/dist/index.js.map +1 -1
- package/dist/{src-6PPBKQ6Q.js → src-2V6VP4I5.js} +972 -520
- package/dist/src-2V6VP4I5.js.map +1 -0
- package/package.json +8 -8
- package/templates/README.md +48 -145
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +1 -1
- package/templates/apps/api/.dockerignore.hbs +18 -12
- package/templates/apps/api/.gitignore.hbs +9 -1
- package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +28 -7
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -3
- package/templates/apps/api/hai.build.json.hbs +1 -0
- package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
- package/templates/apps/api/package.json.hbs +10 -8
- package/templates/apps/fullstack/.dockerignore.hbs +18 -14
- package/templates/apps/fullstack/.gitignore.hbs +9 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +23 -8
- package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +3 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +25 -10
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +2 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +73 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -10
- package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +3 -3
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +12 -1
- package/templates/apps/fullstack/hai.build.json.hbs +7 -0
- package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
- package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
- package/templates/apps/fullstack/package.json.hbs +7 -12
- package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +28 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +0 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +33 -12
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +2 -2
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +2 -2
- package/templates/apps/mobile-app/index.html.hbs +1 -0
- package/templates/apps/mobile-app/public/runtime-config.js +1 -0
- package/templates/apps/mobile-app/src/App.svelte.hbs +2 -2
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +2 -1
- package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +3 -2
- package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
- package/templates/apps/website/src/routes/+layout.svelte.hbs +3 -3
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
- package/templates/base/.dockerignore.hbs +18 -13
- package/templates/base/.gitignore.hbs +9 -1
- package/templates/base/e2e/app.spec.ts.hbs +10 -0
- package/templates/base/hai.build.json.hbs +1 -0
- package/templates/base/images/app.Dockerfile.hbs +64 -0
- package/templates/base/{docker → images/web}/nginx.conf.hbs +6 -0
- package/templates/base/package.json.hbs +20 -19
- package/templates/base/svelte.config.js.hbs +2 -0
- package/templates/base/vite.config.ts.hbs +9 -3
- package/templates/shared/.gitattributes +11 -0
- package/templates/shared/container-runtime-config.sh +30 -0
- package/templates/shared/run-e2e.mjs +65 -0
- package/templates/shared/vite-runtime-config.ts +54 -0
- package/templates/skills/AGENTS.md +3 -3
- package/templates/skills/bridges/admin/AGENTS.md +1 -1
- package/templates/skills/bridges/api/AGENTS.md +1 -1
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +1 -1
- package/templates/skills/bridges/h5/AGENTS.md +1 -1
- package/templates/skills/bridges/mobile-app/AGENTS.md +1 -1
- package/templates/skills/bridges/website/AGENTS.md +1 -1
- package/templates/skills/hai-api-client/SKILL.md +14 -161
- package/templates/skills/hai-api-client/reference.md +166 -0
- package/templates/skills/hai-api-contract/SKILL.md +15 -273
- package/templates/skills/hai-api-contract/reference.md +278 -0
- package/templates/skills/hai-app-create/SKILL.md +2 -2
- package/templates/skills/hai-app-create/development.md +6 -4
- package/templates/skills/hai-app-create/reference.md +5 -4
- package/templates/skills/hai-app-review/SKILL.md +1 -1
- package/templates/skills/hai-app-tests/SKILL.md +4 -3
- package/templates/skills/hai-app-tests/reference.md +2 -2
- package/templates/skills/hai-app-tests/validation.md +31 -0
- package/templates/skills/hai-audit/SKILL.md +17 -142
- package/templates/skills/hai-audit/reference.md +145 -0
- package/templates/skills/hai-build/SKILL.md +24 -3
- package/templates/skills/hai-build/reference.md +10 -9
- package/templates/skills/hai-cache/SKILL.md +15 -182
- package/templates/skills/hai-cache/reference.md +187 -0
- package/templates/skills/hai-capacitor/SKILL.md +4 -3
- package/templates/skills/hai-capacitor/reference.md +6 -4
- package/templates/skills/hai-ci/SKILL.md +2 -0
- package/templates/skills/hai-core/SKILL.md +3 -3
- package/templates/skills/hai-crypto/SKILL.md +16 -252
- package/templates/skills/hai-crypto/reference.md +256 -0
- package/templates/skills/hai-datapipe/SKILL.md +15 -196
- package/templates/skills/hai-datapipe/reference.md +202 -0
- package/templates/skills/hai-deploy/SKILL.md +28 -271
- package/templates/skills/hai-deploy/middleware.md +6 -18
- package/templates/skills/hai-framework-sync/SKILL.md +2 -2
- package/templates/skills/hai-framework-sync/sync.md +1 -1
- package/templates/skills/hai-iam/SKILL.md +1 -3
- package/templates/skills/hai-kit/SKILL.md +12 -167
- package/templates/skills/hai-kit/reference.md +172 -0
- package/templates/skills/hai-payment/SKILL.md +19 -251
- package/templates/skills/hai-payment/reference.md +256 -0
- package/templates/skills/hai-pr-review/SKILL.md +4 -4
- package/templates/skills/hai-reach/SKILL.md +2 -2
- package/templates/skills/hai-reldb/SKILL.md +1 -1
- package/templates/skills/hai-scheduler/SKILL.md +15 -195
- package/templates/skills/hai-scheduler/reference.md +202 -0
- package/templates/skills/hai-serv/SKILL.md +2 -2
- package/templates/skills/hai-spec-implement/SKILL.md +1 -1
- package/templates/skills/hai-spec-implement/workflow.md +3 -3
- package/templates/skills/hai-storage/SKILL.md +17 -238
- package/templates/skills/hai-storage/reference.md +243 -0
- package/templates/skills/hai-ui/SKILL.md +1 -1
- package/templates/skills/hai-ui/reference.md +11 -5
- package/templates/skills/hai-vecdb/SKILL.md +2 -2
- package/dist/chunk-47V5OT22.js.map +0 -1
- package/dist/chunk-IPG3QNYK.js.map +0 -1
- package/dist/cli-config-templates-SJ2MBIR7.js +0 -5
- package/dist/src-6PPBKQ6Q.js.map +0 -1
- package/templates/apps/api/Dockerfile.hbs +0 -48
- package/templates/apps/fullstack/Dockerfile.hbs +0 -69
- package/templates/base/Dockerfile.hbs +0 -79
- /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
- /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
- /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** 运行浏览器验收,并在报告写完后清理本次输出;失败和中断同样清理。 */
|
|
2
|
+
import { spawn } from 'node:child_process'
|
|
3
|
+
import { mkdirSync, mkdtempSync, rmSync } from 'node:fs'
|
|
4
|
+
import { createRequire } from 'node:module'
|
|
5
|
+
import { tmpdir } from 'node:os'
|
|
6
|
+
import { join, resolve } from 'node:path'
|
|
7
|
+
import process from 'node:process'
|
|
8
|
+
|
|
9
|
+
const cwd = process.cwd()
|
|
10
|
+
const require = createRequire(resolve(cwd, 'package.json'))
|
|
11
|
+
const cli = require.resolve('@playwright/test/cli')
|
|
12
|
+
const dataParent = resolve(process.env.HAI_E2E_DATA_ROOT || tmpdir())
|
|
13
|
+
mkdirSync(dataParent, { recursive: true })
|
|
14
|
+
const dataRoot = mkdtempSync(join(dataParent, 'hai-browser-e2e-'))
|
|
15
|
+
const groups = [[]]
|
|
16
|
+
for (const argument of process.argv.slice(2)) {
|
|
17
|
+
if (argument === '--next')
|
|
18
|
+
groups.push([])
|
|
19
|
+
else
|
|
20
|
+
groups.at(-1).push(argument)
|
|
21
|
+
}
|
|
22
|
+
let child
|
|
23
|
+
let interrupted = false
|
|
24
|
+
function stop() {
|
|
25
|
+
if (interrupted)
|
|
26
|
+
return
|
|
27
|
+
interrupted = true
|
|
28
|
+
if (process.platform === 'win32' && child?.pid && child.exitCode === null) {
|
|
29
|
+
// Windows 的 kill 不传递 SIGTERM;同时结束本次 Playwright 创建的浏览器与服务进程。
|
|
30
|
+
spawn('taskkill', ['/pid', String(child.pid), '/t', '/f'], { windowsHide: true, stdio: 'ignore' })
|
|
31
|
+
.once('error', () => child?.kill())
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
child?.kill()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
process.once('SIGINT', stop)
|
|
38
|
+
process.once('SIGTERM', stop)
|
|
39
|
+
try {
|
|
40
|
+
for (const args of groups) {
|
|
41
|
+
const code = await new Promise((resolve, reject) => {
|
|
42
|
+
child = spawn(process.execPath, [cli, 'test', ...args], {
|
|
43
|
+
cwd,
|
|
44
|
+
windowsHide: true,
|
|
45
|
+
stdio: 'inherit',
|
|
46
|
+
env: { ...process.env, HAI_AUTH_EVIDENCE_DIR: join(cwd, 'test-results/evidence'), HAI_E2E_DATA_ROOT: dataRoot },
|
|
47
|
+
})
|
|
48
|
+
child.once('error', reject)
|
|
49
|
+
child.once('exit', resolve)
|
|
50
|
+
})
|
|
51
|
+
if (code !== 0 || interrupted) {
|
|
52
|
+
process.exitCode = typeof code === 'number' && code !== 0 ? code : 1
|
|
53
|
+
break
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
process.removeListener('SIGINT', stop)
|
|
59
|
+
process.removeListener('SIGTERM', stop)
|
|
60
|
+
// 固定为当前测试工程中的标准输出目录,禁止删除调用方任意指定的路径。
|
|
61
|
+
for (const name of ['test-results', 'playwright-report'])
|
|
62
|
+
rmSync(join(cwd, name), { recursive: true, force: true, maxRetries: 3, retryDelay: 100 })
|
|
63
|
+
// Playwright 退出后服务进程已关闭,此时删除 SQLite / storage,避免 Windows 文件锁。
|
|
64
|
+
rmSync(dataRoot, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 })
|
|
65
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** Vite 开发与预览的公开运行配置;同一份静态构建可连接不同环境的 API。 */
|
|
2
|
+
import type { CoreConfig } from '@h-ai/core'
|
|
3
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
4
|
+
import type { Plugin } from 'vite'
|
|
5
|
+
|
|
6
|
+
/** 只允许浏览器可见的地址、开关和品牌字段,禁止传入 process.env 或完整服务配置。 */
|
|
7
|
+
export interface PublicRuntimeConfig {
|
|
8
|
+
/** 包含 API 前缀的公开地址,例如 https://example.test/api/v1。 */
|
|
9
|
+
apiBase?: string
|
|
10
|
+
/** 客户端传输开关,与服务端一致。 */
|
|
11
|
+
apiTransport?: string
|
|
12
|
+
/** 公开密钥交换端点。 */
|
|
13
|
+
apiKeyExchangePath?: string
|
|
14
|
+
/** 客户端公开元数据;不含密钥或连接参数。 */
|
|
15
|
+
core?: Pick<CoreConfig, 'name' | 'version' | 'env' | 'defaultLocale'> & { logging?: Pick<NonNullable<CoreConfig['logging']>, 'level' | 'format'> }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 在加载应用前提供无缓存的公开配置;构建只复制空的占位文件。
|
|
20
|
+
* @example `plugins: [publicRuntimeConfig({ apiBase: process.env.PUBLIC_API_BASE })]`
|
|
21
|
+
*/
|
|
22
|
+
export function publicRuntimeConfig(config: PublicRuntimeConfig): Plugin {
|
|
23
|
+
const metadata = config.core
|
|
24
|
+
// 明确投影字段,JavaScript 调用方即使误传完整配置也不能公开连接参数或密钥。
|
|
25
|
+
const publicConfig: PublicRuntimeConfig = {
|
|
26
|
+
apiBase: config.apiBase,
|
|
27
|
+
apiTransport: config.apiTransport,
|
|
28
|
+
apiKeyExchangePath: config.apiKeyExchangePath,
|
|
29
|
+
core: metadata
|
|
30
|
+
? {
|
|
31
|
+
name: metadata.name,
|
|
32
|
+
version: metadata.version,
|
|
33
|
+
env: metadata.env,
|
|
34
|
+
defaultLocale: metadata.defaultLocale,
|
|
35
|
+
logging: metadata.logging ? { level: metadata.logging.level, format: metadata.logging.format } : undefined,
|
|
36
|
+
}
|
|
37
|
+
: undefined,
|
|
38
|
+
}
|
|
39
|
+
const body = `globalThis.__HAI_PUBLIC_CONFIG__ = { ...globalThis.__HAI_PUBLIC_CONFIG__, ...${JSON.stringify(publicConfig)} };\n`
|
|
40
|
+
const respond = (_request: IncomingMessage, response: ServerResponse): void => {
|
|
41
|
+
response.setHeader('Content-Type', 'application/javascript; charset=utf-8')
|
|
42
|
+
response.setHeader('Cache-Control', 'no-store')
|
|
43
|
+
response.end(body)
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
name: 'hai-public-runtime-config',
|
|
47
|
+
configureServer(server) {
|
|
48
|
+
server.middlewares.use('/runtime-config.js', respond)
|
|
49
|
+
},
|
|
50
|
+
configurePreviewServer(server) {
|
|
51
|
+
server.middlewares.use('/runtime-config.js', respond)
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
1. 先搜索现有实现、引用点、测试和 README。
|
|
42
42
|
2. 小步修改,优先保持目录结构、公开 API 和脚本稳定。
|
|
43
43
|
3. 改动公共类型、配置、模板或共享逻辑时,同步更新所有调用方与文档。
|
|
44
|
-
4.
|
|
44
|
+
4. 一轮修改完成后按影响范围收集全部可执行门禁结果,汇总问题再集中修复、复测;普通失败不立即从头重跑。
|
|
45
45
|
|
|
46
46
|
## 代码注释与示例
|
|
47
47
|
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
|
|
53
53
|
## 质量门禁
|
|
54
54
|
|
|
55
|
-
先核对 package.json
|
|
55
|
+
先核对 package.json,按 [分轮验证与集中修复](.agents/skills/hai-app-tests/validation.md) 收集适用检查与日志,再归并修复、复测失败及受影响项;普通失败不中断独立检查,真实前置阻塞才暂停依赖项。文档检查引用,UI/路由变更加 E2E,未运行项说明原因。
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
58
|
pnpm typecheck
|
|
@@ -64,7 +64,7 @@ pnpm test:e2e
|
|
|
64
64
|
|
|
65
65
|
## 完成条件
|
|
66
66
|
|
|
67
|
-
- typecheck / lint / build / test / test:e2e
|
|
67
|
+
- typecheck / lint / build / test / test:e2e 状态明确;整轮收集后集中修复,最终必需门禁对最终代码有效通过,未执行或阻塞明确原因,不能宣称完成。
|
|
68
68
|
- README、i18n、测试、代码注释与实现保持一致。
|
|
69
69
|
- 最终回复说明执行过的门禁、已同步的文档 / 依赖方,以及未完成项。
|
|
70
70
|
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
|
|
61
61
|
## 质量门禁
|
|
62
62
|
|
|
63
|
-
先核对 package.json
|
|
63
|
+
先核对 package.json,按 [分轮验证与集中修复](.agents/skills/hai-app-tests/validation.md) 收集适用检查与日志,再归并修复、复测失败及受影响项;普通失败不中断独立检查,真实前置阻塞才暂停依赖项。文档检查引用,UI/路由变更加 E2E,未运行项说明原因。
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
66
|
pnpm typecheck
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
|
|
58
58
|
## 质量门禁
|
|
59
59
|
|
|
60
|
-
先核对 package.json
|
|
60
|
+
先核对 package.json,按 [分轮验证与集中修复](.agents/skills/hai-app-tests/validation.md) 收集适用检查与日志,再归并修复、复测失败及受影响项;普通失败不中断独立检查,真实前置阻塞才暂停依赖项。文档检查引用,UI/路由变更加 E2E,未运行项说明原因。
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
63
|
pnpm typecheck
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
|
|
61
61
|
## 质量门禁
|
|
62
62
|
|
|
63
|
-
先核对 package.json
|
|
63
|
+
先核对 package.json,按 [分轮验证与集中修复](.agents/skills/hai-app-tests/validation.md) 收集适用检查与日志,再归并修复、复测失败及受影响项;普通失败不中断独立检查,真实前置阻塞才暂停依赖项。文档检查引用,UI/路由变更加 E2E,未运行项说明原因。
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
66
|
pnpm typecheck
|
|
@@ -71,7 +71,7 @@ pnpm cap:build:android:release
|
|
|
71
71
|
|
|
72
72
|
## 质量门禁
|
|
73
73
|
|
|
74
|
-
先核对 package.json
|
|
74
|
+
先核对 package.json,按 [分轮验证与集中修复](.agents/skills/hai-app-tests/validation.md) 收集适用检查与日志,再归并修复、复测失败及受影响项;普通失败不中断独立检查,真实前置阻塞才暂停依赖项。文档检查引用,UI/路由变更加 E2E,未运行项说明原因。
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
77
|
pnpm typecheck
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
|
|
61
61
|
## 质量门禁
|
|
62
62
|
|
|
63
|
-
先核对 package.json
|
|
63
|
+
先核对 package.json,按 [分轮验证与集中修复](.agents/skills/hai-app-tests/validation.md) 收集适用检查与日志,再归并修复、复测失败及受影响项;普通失败不中断独立检查,真实前置阻塞才暂停依赖项。文档检查引用,UI/路由变更加 E2E,未运行项说明原因。
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
66
|
pnpm typecheck
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hai-api-client
|
|
3
|
-
description: "
|
|
3
|
+
description: "接入或排查 @h-ai/api-client typed API、登录 Token、自动刷新与加密传输;适用于 Web、SSR 和原生客户端。"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# hai-api-client
|
|
@@ -15,167 +15,20 @@ description: "使用 @h-ai/api-client 调用 typed API,管理 Token、刷新
|
|
|
15
15
|
| 输出 | typed client;领域响应为 HaiResult |
|
|
16
16
|
| 限制 | 先 init,结束 close;浏览器 Cookie 与服务端配置匹配,原生端使用安全 TokenStorage。不要在多用户 SSR 中共享带用户 token 的单例。 |
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
## 使用路径
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
1. 读取服务端 contract、apiPrefix、CORS、Cookie 与 transport 配置,选择默认客户端或 `apiClient.create(contract)`。
|
|
21
|
+
2. 浏览器使用 Cookie/内存;SSR 按请求创建客户端,原生使用安全 TokenStorage,不跨用户复用单例。
|
|
22
|
+
3. 检查 init 与每次业务调用的 `success`。登录成功后设置 tokens;优先使用 `createAuthActions` 连接认证 UI。
|
|
23
|
+
4. 同步服务端与客户端的刷新、加密和错误处理;切换配置前 close,任务结束释放客户端。
|
|
24
|
+
5. 验证登录、刷新重试、失败、退出与会话恢复。
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
## 按需参考
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
按本次操作读取对应小节;以已安装版本的公开类型核验签名,示例中的业务变量由应用提供。
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
auth: {},
|
|
32
|
-
})
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
启用传输加密时,只传顶层 `crypto` 句柄;api-client 内部自动调用 `crypto.transport.createClient()`:
|
|
36
|
-
|
|
37
|
-
```ts
|
|
38
|
-
import { apiClient } from '@h-ai/api-client'
|
|
39
|
-
import { crypto } from '@h-ai/crypto'
|
|
40
|
-
|
|
41
|
-
await crypto.init()
|
|
42
|
-
|
|
43
|
-
await apiClient.init({
|
|
44
|
-
baseUrl: 'https://api.example.com/api/v1',
|
|
45
|
-
auth: {},
|
|
46
|
-
transport: { crypto }, // 默认协商路径:/api/v1/_hai/key-exchange
|
|
47
|
-
})
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
App/Capacitor 场景传入自定义 TokenStorage:
|
|
51
|
-
|
|
52
|
-
```ts
|
|
53
|
-
import { createCapacitorTokenStorage } from '@h-ai/capacitor'
|
|
54
|
-
|
|
55
|
-
await apiClient.init({
|
|
56
|
-
baseUrl: 'https://api.example.com/api/v1',
|
|
57
|
-
auth: { storage: createCapacitorTokenStorage() },
|
|
58
|
-
})
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
> `createCapacitorTokenStorage()` 仅在 Android/iOS 原生环境 + `@aparajita/capacitor-secure-storage` 已安装时持久化 token;纯 Web 不会回退到 `localStorage`。
|
|
62
|
-
|
|
63
|
-
SSR / Node.js 测试场景请显式传入内存存储:
|
|
64
|
-
|
|
65
|
-
```ts
|
|
66
|
-
import { apiClient } from '@h-ai/api-client'
|
|
67
|
-
|
|
68
|
-
await apiClient.init({
|
|
69
|
-
baseUrl: 'https://api.example.com/api/v1',
|
|
70
|
-
auth: { storage: apiClient.tokenStorage.memory() },
|
|
71
|
-
})
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### 2. 调用 typed API
|
|
75
|
-
|
|
76
|
-
```ts
|
|
77
|
-
const login = await apiClient.iam.auth.login({ identifier: 'alice', password: 'secret' })
|
|
78
|
-
if (login.success) {
|
|
79
|
-
// 默认 httpOnly cookie 模式下,refresh token 由服务端 Set-Cookie 管理。
|
|
80
|
-
await apiClient.auth.setTokens(login.data.tokens)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const me = await apiClient.iam.auth.currentUser()
|
|
84
|
-
if (me.success) {
|
|
85
|
-
// currentUser 返回服务端最新角色/权限;不要把权限快照持久化到 localStorage。
|
|
86
|
-
const { roles, permissions, ...user } = me.data
|
|
87
|
-
}
|
|
88
|
-
const upload = await apiClient.storage.presignedUrls.createUpload({ key: 'avatar.png' })
|
|
89
|
-
const answer = await apiClient.ai.chats.sendMessage({ message: 'hello' })
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### 3. 自定义 contract
|
|
93
|
-
|
|
94
|
-
```ts
|
|
95
|
-
import { apiClient } from '@h-ai/api-client'
|
|
96
|
-
import { apiContract } from '@h-ai/api-contract'
|
|
97
|
-
|
|
98
|
-
const contract = apiContract.create({ iam: apiContract.iam })
|
|
99
|
-
const client = apiClient.create(contract)
|
|
100
|
-
await client.init({ baseUrl: 'https://api.example.com/api/v1' })
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
`client.config` / `apiClient.config` 返回的是脱敏后的运行配置快照;`baseUrl` 的内嵌凭证与 `headers.authorization` 等敏感值会自动隐藏。
|
|
104
|
-
|
|
105
|
-
### 4. 关闭
|
|
106
|
-
|
|
107
|
-
```ts
|
|
108
|
-
await apiClient.close()
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
> 已初始化后再次 `init()` 会返回 `CONFIG_ERROR`,不会覆盖现有 client;切换 `baseUrl` / `fetch` / `transport` 前必须先 `close()`,避免旧 transport 会话泄漏。
|
|
112
|
-
|
|
113
|
-
## Token Storage 适配器
|
|
114
|
-
|
|
115
|
-
| 工厂调用 | 存储位置 | 适用场景 |
|
|
116
|
-
| --- | --- | --- |
|
|
117
|
-
| `apiClient.tokenStorage.httpOnlyCookie()` | httpOnly cookie(服务端管理) | **默认**;浏览器端推荐,refresh token 不暴露给 JS |
|
|
118
|
-
| `apiClient.tokenStorage.memory()` | 内存 | SSR / Node.js 单元测试(需显式传入) |
|
|
119
|
-
| `apiClient.tokenStorage.localStorage()` | localStorage | 非敏感场景(有 XSS 风险,生产不推荐) |
|
|
120
|
-
|
|
121
|
-
### httpOnly Cookie 模式
|
|
122
|
-
|
|
123
|
-
Access token 存在内存中,refresh token 由服务端通过 `HttpOnly` cookie 管理,浏览器 JS 无法读取,可有效防止 XSS 窃取 token。
|
|
124
|
-
|
|
125
|
-
**前提**:服务端需配置 `serv.createApp({ iam, refreshCookie: {} })`,参见 `hai-serv` skill。
|
|
126
|
-
|
|
127
|
-
启用 `transport` 时,登录、业务请求以及 401 后的自动刷新都会复用同一个加密 fetch 链路;不要在业务代码中手写明文 `/auth/refresh` 请求。
|
|
128
|
-
|
|
129
|
-
自动刷新识别真实 HTTP 401(`Response.status`),以及 HTTP 200 的有效失败 HaiResult(`success=false`、字符串 `error.code/message`、`error.httpStatus=401`),成功后只重试一次原请求。内置登录、注册与配置的 `auth.refreshPath` 排除自动刷新,403/500 等其它失败也不刷新。serv 普通 procedure 的 HTTP 状态仍为 200;调用方须检查最终 `success/error.code` 并处理刷新失败后的登录失效。
|
|
130
|
-
|
|
131
|
-
```ts
|
|
132
|
-
import { apiClient } from '@h-ai/api-client'
|
|
133
|
-
|
|
134
|
-
// apiClient.tokenStorage.httpOnlyCookie() 是默认存储,无需显式指定
|
|
135
|
-
await apiClient.init({
|
|
136
|
-
baseUrl: 'https://api.example.com/api/v1',
|
|
137
|
-
auth: {},
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
// 登录后设置 access token(refresh token 由服务端写入 cookie,前端无需操作)
|
|
141
|
-
const login = await apiClient.iam.auth.login({ identifier: 'alice', password: 'secret' })
|
|
142
|
-
if (login.success) {
|
|
143
|
-
await apiClient.auth.setTokens(login.data.tokens)
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
## 核心 API
|
|
148
|
-
|
|
149
|
-
| API | 用途 |
|
|
150
|
-
| --- | --- |
|
|
151
|
-
| `apiClient.init(config)` | 初始化默认 API Service client;重复初始化返回 `CONFIG_ERROR` |
|
|
152
|
-
| `apiClient.close()` | 清理 client 状态 |
|
|
153
|
-
| `apiClient.auth.setTokens(tokens)` | 写入 access token;非 httpOnly 存储会同时写入 refresh token |
|
|
154
|
-
| `apiClient.auth.clear()` | 清理 Token |
|
|
155
|
-
| `apiClient.auth.onTokenRefreshed(cb)` | 监听刷新结果 |
|
|
156
|
-
| `apiClient.create(contract)` | 创建自定义 typed client |
|
|
157
|
-
| `apiClient.tokenStorage.{memory,localStorage,httpOnlyCookie}()` | TokenStorage 工厂 |
|
|
158
|
-
|
|
159
|
-
### Transport 配置
|
|
160
|
-
|
|
161
|
-
| 字段 | 说明 |
|
|
162
|
-
| --- | --- |
|
|
163
|
-
| `transport.crypto` | 已初始化的 `@h-ai/crypto` 实例 |
|
|
164
|
-
| `transport.keyExchangePath` | 相对于 `baseUrl` 的协商子路径,默认 `/_hai/key-exchange` |
|
|
165
|
-
|
|
166
|
-
服务端必须对应启用 `serv.createApp({ transport: { crypto } })`。
|
|
167
|
-
自动刷新会沿用 `transport.encryptedFetch`,并在 httpOnly cookie 模式下发送空请求体 + `credentials: 'include'`,由服务端从 cookie 读取 refresh token。
|
|
168
|
-
|
|
169
|
-
## 常见模式
|
|
170
|
-
|
|
171
|
-
- `baseUrl` 通常包含服务端 `apiPrefix`,例如 `/api/v1`。
|
|
172
|
-
- 业务错误不 throw,统一判断 `HaiResult.success`。
|
|
173
|
-
- 客户端不导入服务端 `procedures` 或 `@h-ai/serv`。
|
|
174
|
-
- 客户端不导入 `@h-ai/crypto` 的内部 transport 工厂,只通过 `transport: { crypto }` 装配。
|
|
175
|
-
- 前端只依赖 `@h-ai/api-client` 和 contract 类型,不依赖业务模块实现。
|
|
176
|
-
- httpOnly cookie 模式需服务端与客户端同步配置,不支持跨域刷新(`SameSite=Strict`)。
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
## 配置驱动认证页面
|
|
180
|
-
|
|
181
|
-
`apiClient.createAuthActions(client)` 为含标准 iam contract 的客户端提供 publicConfig、login、register、sendOtp、requestPasswordReset、confirmPasswordReset。login 输入使用 identifier/password/method/code,method 为 password、ldap、email、sms。成功认证自动调用既有安全 TokenStorage;应用只需把动作传给 AuthPanel,并在 onauthenticated 中刷新自己的用户状态或导航。
|
|
30
|
+
- [使用步骤](reference.md#使用步骤)
|
|
31
|
+
- [Token Storage 适配器](reference.md#token-storage-适配器)
|
|
32
|
+
- [核心 API](reference.md#核心-api)
|
|
33
|
+
- [常见模式](reference.md#常见模式)
|
|
34
|
+
- [配置驱动认证页面](reference.md#配置驱动认证页面)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# hai-api-client 参考
|
|
2
|
+
|
|
3
|
+
> `@h-ai/api-client` 是跨端 typed API client。公共 HTTP API 由 `@h-ai/api-contract` 定义,服务端由 `@h-ai/serv` 挂载,客户端直接调用 `apiClient.<domain>.<group>.<operation>()`。
|
|
4
|
+
|
|
5
|
+
## 使用步骤
|
|
6
|
+
|
|
7
|
+
### 1. 初始化
|
|
8
|
+
|
|
9
|
+
浏览器默认使用 httpOnly cookie 存储(推荐):
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { apiClient } from '@h-ai/api-client'
|
|
13
|
+
|
|
14
|
+
await apiClient.init({
|
|
15
|
+
baseUrl: 'https://api.example.com/api/v1',
|
|
16
|
+
auth: {},
|
|
17
|
+
})
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
启用传输加密时,只传顶层 `crypto` 句柄;api-client 内部自动调用 `crypto.transport.createClient()`:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { apiClient } from '@h-ai/api-client'
|
|
24
|
+
import { crypto } from '@h-ai/crypto'
|
|
25
|
+
|
|
26
|
+
await crypto.init()
|
|
27
|
+
|
|
28
|
+
await apiClient.init({
|
|
29
|
+
baseUrl: 'https://api.example.com/api/v1',
|
|
30
|
+
auth: {},
|
|
31
|
+
transport: { crypto }, // 默认协商路径:/api/v1/_hai/key-exchange
|
|
32
|
+
})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
App/Capacitor 场景传入自定义 TokenStorage:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { createCapacitorTokenStorage } from '@h-ai/capacitor'
|
|
39
|
+
|
|
40
|
+
await apiClient.init({
|
|
41
|
+
baseUrl: 'https://api.example.com/api/v1',
|
|
42
|
+
auth: { storage: createCapacitorTokenStorage() },
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
> `createCapacitorTokenStorage()` 仅在 Android/iOS 原生环境 + `@aparajita/capacitor-secure-storage` 已安装时持久化 token;纯 Web 不会回退到 `localStorage`。
|
|
47
|
+
|
|
48
|
+
SSR / Node.js 测试场景请显式传入内存存储:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { apiClient } from '@h-ai/api-client'
|
|
52
|
+
|
|
53
|
+
await apiClient.init({
|
|
54
|
+
baseUrl: 'https://api.example.com/api/v1',
|
|
55
|
+
auth: { storage: apiClient.tokenStorage.memory() },
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. 调用 typed API
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const login = await apiClient.iam.auth.login({ identifier: 'alice', password: 'secret' })
|
|
63
|
+
if (login.success) {
|
|
64
|
+
// 默认 httpOnly cookie 模式下,refresh token 由服务端 Set-Cookie 管理。
|
|
65
|
+
await apiClient.auth.setTokens(login.data.tokens)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const me = await apiClient.iam.auth.currentUser()
|
|
69
|
+
if (me.success) {
|
|
70
|
+
// currentUser 返回服务端最新角色/权限;不要把权限快照持久化到 localStorage。
|
|
71
|
+
const { roles, permissions, ...user } = me.data
|
|
72
|
+
}
|
|
73
|
+
const upload = await apiClient.storage.presignedUrls.createUpload({ key: 'avatar.png' })
|
|
74
|
+
const answer = await apiClient.ai.chats.sendMessage({ message: 'hello' })
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. 自定义 contract
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { apiClient } from '@h-ai/api-client'
|
|
81
|
+
import { apiContract } from '@h-ai/api-contract'
|
|
82
|
+
|
|
83
|
+
const contract = apiContract.create({ iam: apiContract.iam })
|
|
84
|
+
const client = apiClient.create(contract)
|
|
85
|
+
await client.init({ baseUrl: 'https://api.example.com/api/v1' })
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`client.config` / `apiClient.config` 返回的是脱敏后的运行配置快照;`baseUrl` 的内嵌凭证与 `headers.authorization` 等敏感值会自动隐藏。
|
|
89
|
+
|
|
90
|
+
### 4. 关闭
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
await apiClient.close()
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
> 已初始化后再次 `init()` 会返回 `CONFIG_ERROR`,不会覆盖现有 client;切换 `baseUrl` / `fetch` / `transport` 前必须先 `close()`,避免旧 transport 会话泄漏。
|
|
97
|
+
|
|
98
|
+
## Token Storage 适配器
|
|
99
|
+
|
|
100
|
+
| 工厂调用 | 存储位置 | 适用场景 |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| `apiClient.tokenStorage.httpOnlyCookie()` | httpOnly cookie(服务端管理) | **默认**;浏览器端推荐,refresh token 不暴露给 JS |
|
|
103
|
+
| `apiClient.tokenStorage.memory()` | 内存 | SSR / Node.js 单元测试(需显式传入) |
|
|
104
|
+
| `apiClient.tokenStorage.localStorage()` | localStorage | 非敏感场景(有 XSS 风险,生产不推荐) |
|
|
105
|
+
|
|
106
|
+
### httpOnly Cookie 模式
|
|
107
|
+
|
|
108
|
+
Access token 存在内存中,refresh token 由服务端通过 `HttpOnly` cookie 管理,浏览器 JS 无法读取,可有效防止 XSS 窃取 token。
|
|
109
|
+
|
|
110
|
+
**前提**:服务端需配置 `serv.createApp({ iam, refreshCookie: {} })`,参见 `hai-serv` skill。
|
|
111
|
+
|
|
112
|
+
启用 `transport` 时,登录、业务请求以及 401 后的自动刷新都会复用同一个加密 fetch 链路;不要在业务代码中手写明文 `/auth/refresh` 请求。
|
|
113
|
+
|
|
114
|
+
自动刷新识别真实 HTTP 401(`Response.status`),以及 HTTP 200 的有效失败 HaiResult(`success=false`、字符串 `error.code/message`、`error.httpStatus=401`),成功后只重试一次原请求。内置登录、注册与配置的 `auth.refreshPath` 排除自动刷新,403/500 等其它失败也不刷新。serv 普通 procedure 的 HTTP 状态仍为 200;调用方须检查最终 `success/error.code` 并处理刷新失败后的登录失效。
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { apiClient } from '@h-ai/api-client'
|
|
118
|
+
|
|
119
|
+
// apiClient.tokenStorage.httpOnlyCookie() 是默认存储,无需显式指定
|
|
120
|
+
await apiClient.init({
|
|
121
|
+
baseUrl: 'https://api.example.com/api/v1',
|
|
122
|
+
auth: {},
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// 登录后设置 access token(refresh token 由服务端写入 cookie,前端无需操作)
|
|
126
|
+
const login = await apiClient.iam.auth.login({ identifier: 'alice', password: 'secret' })
|
|
127
|
+
if (login.success) {
|
|
128
|
+
await apiClient.auth.setTokens(login.data.tokens)
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## 核心 API
|
|
133
|
+
|
|
134
|
+
| API | 用途 |
|
|
135
|
+
| --- | --- |
|
|
136
|
+
| `apiClient.init(config)` | 初始化默认 API Service client;重复初始化返回 `CONFIG_ERROR` |
|
|
137
|
+
| `apiClient.close()` | 清理 client 状态 |
|
|
138
|
+
| `apiClient.auth.setTokens(tokens)` | 写入 access token;非 httpOnly 存储会同时写入 refresh token |
|
|
139
|
+
| `apiClient.auth.clear()` | 清理 Token |
|
|
140
|
+
| `apiClient.auth.onTokenRefreshed(cb)` | 监听刷新结果 |
|
|
141
|
+
| `apiClient.create(contract)` | 创建自定义 typed client |
|
|
142
|
+
| `apiClient.tokenStorage.{memory,localStorage,httpOnlyCookie}()` | TokenStorage 工厂 |
|
|
143
|
+
|
|
144
|
+
### Transport 配置
|
|
145
|
+
|
|
146
|
+
| 字段 | 说明 |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| `transport.crypto` | 已初始化的 `@h-ai/crypto` 实例 |
|
|
149
|
+
| `transport.keyExchangePath` | 相对于 `baseUrl` 的协商子路径,默认 `/_hai/key-exchange` |
|
|
150
|
+
|
|
151
|
+
服务端必须对应启用 `serv.createApp({ transport: { crypto } })`。
|
|
152
|
+
自动刷新会沿用 `transport.encryptedFetch`,并在 httpOnly cookie 模式下发送空请求体 + `credentials: 'include'`,由服务端从 cookie 读取 refresh token。
|
|
153
|
+
|
|
154
|
+
## 常见模式
|
|
155
|
+
|
|
156
|
+
- `baseUrl` 通常包含服务端 `apiPrefix`,例如 `/api/v1`。
|
|
157
|
+
- 业务错误不 throw,统一判断 `HaiResult.success`。
|
|
158
|
+
- 客户端不导入服务端 `procedures` 或 `@h-ai/serv`。
|
|
159
|
+
- 客户端不导入 `@h-ai/crypto` 的内部 transport 工厂,只通过 `transport: { crypto }` 装配。
|
|
160
|
+
- 前端只依赖 `@h-ai/api-client` 和 contract 类型,不依赖业务模块实现。
|
|
161
|
+
- httpOnly cookie 模式需服务端与客户端同步配置,跨 Origin 请求需正确设置 CORS 与 credentials;`SameSite=Strict` 限制跨站 Cookie,跨域不等于跨站。
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
## 配置驱动认证页面
|
|
165
|
+
|
|
166
|
+
`apiClient.createAuthActions(client)` 为含标准 iam contract 的客户端提供 publicConfig、login、register、sendOtp、requestPasswordReset、confirmPasswordReset。login 输入使用 identifier/password/method/code,method 为 password、ldap、email、sms。成功认证自动调用既有安全 TokenStorage;应用只需把动作传给 AuthPanel,并在 onauthenticated 中刷新自己的用户状态或导航。
|