@h-ai/cli 0.1.0-alpha.19 → 0.1.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -7
- package/dist/{chunk-GSMXGUYF.js → chunk-5IGMJVV5.js} +56 -38
- package/dist/chunk-5IGMJVV5.js.map +1 -0
- package/dist/{chunk-BPO34NRL.js → chunk-CFCXOKGV.js} +27 -5
- package/dist/chunk-CFCXOKGV.js.map +1 -0
- package/dist/cli-config-templates-5J5VKWWD.js +5 -0
- package/dist/{cli-config-templates-JCS7T3NU.js.map → cli-config-templates-5J5VKWWD.js.map} +1 -1
- package/dist/cli-skill-templates-JMSV2SVW.js +5 -0
- package/dist/{cli-skill-templates-IMYZPQRG.js.map → cli-skill-templates-JMSV2SVW.js.map} +1 -1
- package/dist/index.js +627 -107
- package/dist/index.js.map +1 -1
- package/dist/{src-N4NPS7AG.js → src-QNEJJ3BV.js} +261 -103
- package/dist/src-QNEJJ3BV.js.map +1 -0
- package/package.json +3 -2
- package/templates/README.md +40 -29
- package/templates/apps/admin/messages/en-US.json +5 -0
- package/templates/apps/admin/messages/zh-CN.json +5 -0
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +5 -5
- package/templates/apps/admin/src/routes/+page.svelte.hbs +32 -12
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +37 -20
- package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +33 -24
- package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +16 -13
- package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +16 -0
- package/templates/apps/api/apps/{{projectName}}-contract/package.json.hbs +38 -0
- package/templates/apps/api/apps/{{projectName}}-contract/src/app-contract.ts.hbs +27 -0
- package/templates/apps/api/apps/{{projectName}}-contract/src/app-schemas.ts.hbs +33 -0
- package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +3 -0
- package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +12 -0
- package/templates/apps/api/apps/{{projectName}}-contract/tests/contract.test.ts.hbs +18 -0
- package/templates/apps/api/apps/{{projectName}}-contract/tsconfig.json.hbs +12 -0
- package/templates/apps/api/apps/{{projectName}}-contract/tsup.config.ts.hbs +11 -0
- package/templates/apps/api/apps/{{projectName}}-contract/vitest.config.ts.hbs +9 -0
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +17 -0
- package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +19 -0
- package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +26 -0
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +55 -0
- package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +98 -0
- package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +21 -0
- package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +184 -0
- package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/app-procedures.ts.hbs +49 -0
- package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +18 -0
- package/templates/apps/api/apps/{{projectName}}-service/tests/service.test.ts.hbs +168 -0
- package/templates/apps/api/apps/{{projectName}}-service/tsconfig.json.hbs +16 -0
- package/templates/apps/api/apps/{{projectName}}-service/tsup.config.ts.hbs +11 -0
- package/templates/apps/api/apps/{{projectName}}-service/vitest.config.ts.hbs +9 -0
- package/templates/apps/api/e2e/api-service.spec.ts.hbs +18 -0
- package/templates/apps/api/eslint.config.js +32 -0
- package/templates/apps/api/package.json.hbs +29 -0
- package/templates/apps/api/playwright.config.ts.hbs +58 -0
- package/templates/apps/api/pnpm-workspace.yaml.hbs +14 -0
- package/templates/apps/api/tsconfig.base.json +15 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/.env.example.hbs +10 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +24 -7
- package/templates/apps/fullstack/apps/{{projectName}}-app/capacitor.config.ts.hbs +18 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +16 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/messages/en-US.json.hbs +6 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/messages/zh-CN.json.hbs +6 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +51 -29
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +186 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.css.hbs +2 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.d.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/lib/capacitor.ts.hbs +18 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/main.ts.hbs +14 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/svelte.config.js.hbs +1 -5
- package/templates/apps/fullstack/apps/{{projectName}}-app/tsconfig.json.hbs +10 -4
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +4 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/.env.example.hbs +6 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +20 -6
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/eslint.config.js.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/index.html.hbs +15 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +33 -30
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/App.svelte.hbs +152 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.d.ts.hbs +1 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/main.ts.hbs +13 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/Cargo.toml.hbs +22 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/build.rs.hbs +6 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/capabilities/default.json.hbs +12 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/128x128.png +0 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/128x128@2x.png +0 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/32x32.png +0 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/icon.icns +0 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/icon.ico +0 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +16 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/main.rs.hbs +8 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +41 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/svelte.config.js.hbs +2 -8
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/tsconfig.json.hbs +9 -4
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +44 -21
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +15 -6
- package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/index.html.hbs +15 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +25 -27
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/App.svelte.hbs +153 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.d.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/main.ts.hbs +14 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/svelte.config.js.hbs +1 -5
- package/templates/apps/fullstack/apps/{{projectName}}-web/tsconfig.json.hbs +10 -4
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +3 -2
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +2 -1
- package/templates/apps/fullstack/eslint.config.js +0 -1
- package/templates/apps/fullstack/package.json.hbs +18 -17
- package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +5 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +14 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +13 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/{server-app.ts.hbs → app-server.ts.hbs} +23 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +4 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/{server-app.test.ts.hbs → app-server.test.ts.hbs} +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +9 -8
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/index.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +5 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/ThemeSwitcher.svelte.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tsconfig.json.hbs +1 -1
- package/templates/apps/fullstack/playwright.config.ts.hbs +41 -4
- package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +15 -0
- package/templates/apps/h5/messages/en-US.json +3 -0
- package/templates/apps/h5/messages/zh-CN.json +3 -0
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +30 -29
- package/templates/apps/h5/src/routes/+page.svelte.hbs +17 -6
- package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +17 -6
- package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +17 -6
- package/templates/apps/mobile-app/.env.hbs +3 -0
- package/templates/apps/{android-app → mobile-app}/capacitor.config.ts.hbs +2 -2
- package/templates/apps/mobile-app/index.html.hbs +13 -0
- package/templates/apps/mobile-app/messages/en-US.json +36 -0
- package/templates/apps/mobile-app/messages/zh-CN.json +36 -0
- package/templates/apps/mobile-app/src/App.svelte.hbs +162 -0
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +54 -0
- package/templates/apps/{android-app → mobile-app}/src/lib/capacitor.ts +6 -7
- package/templates/apps/mobile-app/src/main.ts.hbs +11 -0
- package/templates/apps/website/src/routes/+layout.svelte.hbs +35 -26
- package/templates/apps/website/src/routes/+page.svelte.hbs +28 -10
- package/templates/apps/website/src/routes/about/+page.svelte.hbs +11 -6
- package/templates/apps/website/src/routes/contact/+page.svelte.hbs +37 -34
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +27 -40
- package/templates/base/.gitignore.hbs +16 -0
- package/templates/base/{eslint.config.js → eslint.config.js.hbs} +4 -1
- package/templates/base/package.json.hbs +55 -31
- package/templates/base/playwright.config.ts +30 -3
- package/templates/base/pnpm-workspace.yaml.hbs +14 -0
- package/templates/base/src/app.d.ts.hbs +3 -0
- package/templates/base/src/app.html.hbs +7 -0
- package/templates/base/src/hooks.server.ts.hbs +17 -7
- package/templates/base/src/lib/server/init.ts.hbs +20 -12
- package/templates/base/static/favicon.png +0 -0
- package/templates/base/svelte.config.js.hbs +43 -3
- package/templates/base/tests/scaffold.test.ts.hbs +16 -1
- package/templates/base/tsconfig.json.hbs +50 -0
- package/templates/base/vite.config.ts.hbs +13 -3
- package/templates/features/iam/routes-admin/(auth)/+layout.svelte.hbs +5 -5
- package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +34 -32
- package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +46 -48
- package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +35 -31
- package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +41 -38
- package/templates/skills/AGENTS.md +60 -17
- package/templates/skills/CLAUDE.md +18 -6
- package/templates/skills/bridges/admin/AGENTS.md +66 -0
- package/templates/skills/bridges/admin/CLAUDE.md +19 -0
- package/templates/skills/bridges/admin/copilot-instructions.md +46 -0
- package/templates/skills/bridges/api/AGENTS.md +65 -0
- package/templates/skills/bridges/api/CLAUDE.md +19 -0
- package/templates/skills/bridges/api/copilot-instructions.md +46 -0
- package/templates/skills/bridges/fullstack/AGENTS.md +84 -0
- package/templates/skills/bridges/fullstack/CLAUDE.md +19 -0
- package/templates/skills/bridges/fullstack/copilot-instructions.md +54 -0
- package/templates/skills/bridges/h5/AGENTS.md +65 -0
- package/templates/skills/bridges/h5/CLAUDE.md +19 -0
- package/templates/skills/bridges/h5/copilot-instructions.md +44 -0
- package/templates/skills/bridges/mobile-app/AGENTS.md +77 -0
- package/templates/skills/bridges/mobile-app/CLAUDE.md +19 -0
- package/templates/skills/bridges/mobile-app/copilot-instructions.md +45 -0
- package/templates/skills/bridges/website/AGENTS.md +65 -0
- package/templates/skills/bridges/website/CLAUDE.md +19 -0
- package/templates/skills/bridges/website/copilot-instructions.md +44 -0
- package/templates/skills/copilot-instructions.md +29 -50
- package/templates/skills/hai-ai/SKILL.md +115 -648
- package/templates/skills/hai-ai/reference.md +197 -0
- package/templates/skills/hai-api-client/SKILL.md +3 -1
- package/templates/skills/hai-app-create/SKILL.md +2 -1
- package/templates/skills/hai-app-review/SKILL.md +2 -2
- package/templates/skills/hai-app-tests/SKILL.md +28 -3
- package/templates/skills/hai-capacitor/SKILL.md +14 -22
- package/templates/skills/hai-deploy/SKILL.md +12 -9
- package/templates/skills/hai-kit/SKILL.md +5 -3
- package/templates/skills/hai-serv/SKILL.md +2 -0
- package/templates/skills/hai-ui/SKILL.md +25 -11
- package/templates/skills/opencode.json +1 -6
- package/dist/chunk-BPO34NRL.js.map +0 -1
- package/dist/chunk-GSMXGUYF.js.map +0 -1
- package/dist/cli-config-templates-JCS7T3NU.js +0 -5
- package/dist/cli-skill-templates-IMYZPQRG.js +0 -5
- package/dist/src-N4NPS7AG.js.map +0 -1
- package/templates/apps/android-app/.env.hbs +0 -3
- package/templates/apps/android-app/messages/en-US.json +0 -24
- package/templates/apps/android-app/messages/zh-CN.json +0 -24
- package/templates/apps/android-app/src/lib/api.ts.hbs +0 -31
- package/templates/apps/android-app/src/routes/+layout.svelte.hbs +0 -42
- package/templates/apps/android-app/src/routes/+layout.ts +0 -7
- package/templates/apps/android-app/src/routes/+page.svelte.hbs +0 -27
- package/templates/apps/api/src/routes/+layout.svelte +0 -14
- package/templates/apps/api/src/routes/+page.svelte +0 -11
- package/templates/apps/api/src/routes/api/v1/health/+server.ts +0 -14
- package/templates/apps/api/src/routes/api/v1/items/+server.ts +0 -37
- package/templates/apps/api/src/routes/api/v1/items/[id]/+server.ts +0 -30
- package/templates/apps/api/tests/health.test.ts.hbs +0 -26
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.html.hbs +0 -14
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/routes/+layout.svelte.hbs +0 -18
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/routes/+page.svelte.hbs +0 -162
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.html.hbs +0 -14
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/routes/+layout.svelte.hbs +0 -18
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/routes/+page.svelte.hbs +0 -162
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.html.hbs +0 -14
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/routes/+layout.svelte.hbs +0 -18
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/routes/+page.svelte.hbs +0 -162
- package/templates/apps/fullstack/pnpm-workspace.yaml +0 -7
- package/templates/base/tsconfig.json +0 -20
- /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/{theme.svelte.ts.hbs → theme-store.svelte.ts.hbs} +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# {{api.servicePackageName}}
|
|
2
|
+
|
|
3
|
+
{{projectName}} API service 包,负责模块初始化、procedure 装配与 HTTP 入口。
|
|
4
|
+
|
|
5
|
+
## 常用命令
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm --filter {{api.servicePackageName}} dev
|
|
9
|
+
pnpm --filter {{api.servicePackageName}} test
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 关键目录
|
|
13
|
+
|
|
14
|
+
- `config/`:service 配置
|
|
15
|
+
- `src/app.ts`:HTTP App 装配
|
|
16
|
+
- `src/lib/server/init.ts`:模块初始化与关闭
|
|
17
|
+
- `src/server/procedures/`:应用级 procedures
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# {{projectName}} API Service - 应用配置
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
name: {{projectName}} API Service
|
|
6
|
+
version: 0.1.0
|
|
7
|
+
env: ${HAI_ENV:development}
|
|
8
|
+
debug: false
|
|
9
|
+
|
|
10
|
+
defaultLocale: {{defaultLocale}}
|
|
11
|
+
supportedLocales:
|
|
12
|
+
{{#if_eq defaultLocale 'en-US'}} - en-US
|
|
13
|
+
- zh-CN
|
|
14
|
+
{{else}} - zh-CN
|
|
15
|
+
- en-US
|
|
16
|
+
{{/if_eq}}
|
|
17
|
+
|
|
18
|
+
features:
|
|
19
|
+
apiAccess: true
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# {{projectName}} API Service - Serv / HTTP 入口配置
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
http:
|
|
6
|
+
apiPrefix: /api/v1
|
|
7
|
+
openapi:
|
|
8
|
+
path: /openapi.json
|
|
9
|
+
docs:
|
|
10
|
+
path: /docs
|
|
11
|
+
health:
|
|
12
|
+
path: /health
|
|
13
|
+
readyPath: /ready
|
|
14
|
+
rpc: false
|
|
15
|
+
|
|
16
|
+
{{#if features.crypto}}transport:
|
|
17
|
+
keyExchangePath: /_hai/key-exchange
|
|
18
|
+
excludePaths:
|
|
19
|
+
- /health
|
|
20
|
+
- /ready
|
|
21
|
+
- /openapi.json
|
|
22
|
+
- /docs
|
|
23
|
+
- /_hai/scalar.js
|
|
24
|
+
maxClients: 10000
|
|
25
|
+
{{else}}transport: false
|
|
26
|
+
{{/if}}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{api.servicePackageName}}",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"private": true,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"lint": "eslint .",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"clean": "rimraf dist .turbo",
|
|
26
|
+
"dev": "pnpm build && node dist/index.js",
|
|
27
|
+
"start": "node dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@h-ai/core": "{{haiPackageSpecifier}}",
|
|
31
|
+
"@h-ai/serv": "{{haiPackageSpecifier}}",
|
|
32
|
+
"{{api.contractPackageName}}": "workspace:*"{{#if features.ai}},
|
|
33
|
+
"@h-ai/ai": "{{haiPackageSpecifier}}"{{/if}}{{#if features.audit}},
|
|
34
|
+
"@h-ai/audit": "{{haiPackageSpecifier}}"{{/if}}{{#if features.cache}},
|
|
35
|
+
"@h-ai/cache": "{{haiPackageSpecifier}}"{{/if}}{{#if features.crypto}},
|
|
36
|
+
"@h-ai/crypto": "{{haiPackageSpecifier}}"{{/if}}{{#if features.datapipe}},
|
|
37
|
+
"@h-ai/datapipe": "{{haiPackageSpecifier}}"{{/if}}{{#if features.deploy}},
|
|
38
|
+
"@h-ai/deploy": "{{haiPackageSpecifier}}"{{/if}}{{#if features.iam}},
|
|
39
|
+
"@h-ai/iam": "{{haiPackageSpecifier}}"{{/if}}{{#if features.payment}},
|
|
40
|
+
"@h-ai/payment": "{{haiPackageSpecifier}}"{{/if}}{{#if features.reach}},
|
|
41
|
+
"@h-ai/reach": "{{haiPackageSpecifier}}"{{/if}}{{#if features.db}},
|
|
42
|
+
"@h-ai/reldb": "{{haiPackageSpecifier}}"{{/if}}{{#if features.scheduler}},
|
|
43
|
+
"@h-ai/scheduler": "{{haiPackageSpecifier}}"{{/if}}{{#if features.storage}},
|
|
44
|
+
"@h-ai/storage": "{{haiPackageSpecifier}}"{{/if}}{{#if features.vecdb}},
|
|
45
|
+
"@h-ai/vecdb": "{{haiPackageSpecifier}}"{{/if}}
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@h-ai/api-client": "{{haiPackageSpecifier}}",
|
|
49
|
+
"@types/node": "{{api.packageSpecifiers.nodeTypes}}",
|
|
50
|
+
"rimraf": "{{api.packageSpecifiers.rimraf}}",
|
|
51
|
+
"tsup": "{{api.packageSpecifiers.tsup}}",
|
|
52
|
+
"typescript": "{{api.packageSpecifiers.typescript}}",
|
|
53
|
+
"vitest": "{{api.packageSpecifiers.vitest}}"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { ServConfig } from '@h-ai/serv'
|
|
2
|
+
import { core } from '@h-ai/core'
|
|
3
|
+
{{#if features.crypto}}import { crypto, TRANSPORT_PROTOCOL } from '@h-ai/crypto'
|
|
4
|
+
{{/if}}{{#if features.iam}}import { iam } from '@h-ai/iam'
|
|
5
|
+
{{/if}}import { serv } from '@h-ai/serv'
|
|
6
|
+
import { {{api.contractExportName}} } from '{{api.contractPackageName}}'
|
|
7
|
+
import pkg from '../package.json' with { type: 'json' }
|
|
8
|
+
import { {{api.serviceProceduresFactoryName}} } from './server/procedures/index.js'
|
|
9
|
+
|
|
10
|
+
const startedAt = Date.now()
|
|
11
|
+
|
|
12
|
+
interface CreateApiServiceAppOptions {
|
|
13
|
+
transport?: 'config' | 'disabled'
|
|
14
|
+
refreshCookie?: 'enabled' | 'disabled'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const LOOPBACK_ORIGIN_RE = /^https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/
|
|
18
|
+
const WEBVIEW_ORIGINS = new Set([
|
|
19
|
+
'capacitor://localhost',
|
|
20
|
+
'http://localhost',
|
|
21
|
+
'https://tauri.localhost',
|
|
22
|
+
'tauri://localhost',
|
|
23
|
+
])
|
|
24
|
+
const DEFAULT_CORS_ALLOWED_HEADERS = [
|
|
25
|
+
'Authorization',
|
|
26
|
+
'Content-Type',
|
|
27
|
+
{{#if features.crypto}} TRANSPORT_PROTOCOL.CLIENT_ID_HEADER,
|
|
28
|
+
TRANSPORT_PROTOCOL.ENCRYPTED_HEADER,
|
|
29
|
+
{{/if}} 'X-Hai-Locale',
|
|
30
|
+
'X-Request-Id',
|
|
31
|
+
'X-Requested-With',
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
/** 创建 API Service HTTP 应用。 */
|
|
35
|
+
export function {{api.serviceAppFactoryName}}(_options: CreateApiServiceAppOptions = {}) {
|
|
36
|
+
const servConfig = core.config.getOrThrow<ServConfig>('serv')
|
|
37
|
+
{{#if features.crypto}} const transportMode = _options.transport ?? 'config'
|
|
38
|
+
const transport = transportMode === 'disabled' || servConfig.transport === false
|
|
39
|
+
? undefined
|
|
40
|
+
: {
|
|
41
|
+
crypto,
|
|
42
|
+
keyExchangePath: servConfig.transport.keyExchangePath,
|
|
43
|
+
excludePaths: [...servConfig.transport.excludePaths],
|
|
44
|
+
maxClients: servConfig.transport.maxClients,
|
|
45
|
+
}
|
|
46
|
+
{{else}} const transport = undefined
|
|
47
|
+
{{/if}}
|
|
48
|
+
{{#if features.iam}}
|
|
49
|
+
const refreshCookie = _options.refreshCookie === 'disabled' ? undefined : {}
|
|
50
|
+
{{/if}}
|
|
51
|
+
|
|
52
|
+
const appOptions: Parameters<typeof serv.createApp>[0] = {
|
|
53
|
+
contract: {{api.contractExportName}},
|
|
54
|
+
procedures: {{api.serviceProceduresFactoryName}}({
|
|
55
|
+
app: {
|
|
56
|
+
info: {
|
|
57
|
+
name: pkg.name,
|
|
58
|
+
version: pkg.version,
|
|
59
|
+
transportEnabled: transport !== undefined,
|
|
60
|
+
},
|
|
61
|
+
startedAt,
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
http: servConfig.http,
|
|
65
|
+
{{#if features.iam}}
|
|
66
|
+
iam,
|
|
67
|
+
{{/if}}
|
|
68
|
+
middlewares: [
|
|
69
|
+
{
|
|
70
|
+
middleware: serv.cors({
|
|
71
|
+
origin: origin => resolveCorsOrigin(origin) !== null,
|
|
72
|
+
{{#if features.iam}} credentials: true,
|
|
73
|
+
{{/if}} allowedHeaders: DEFAULT_CORS_ALLOWED_HEADERS,{{#if features.crypto}}
|
|
74
|
+
exposedHeaders: [TRANSPORT_PROTOCOL.ENCRYPTED_HEADER, 'X-Request-Id'],{{/if}}
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
}
|
|
79
|
+
{{#if features.iam}}
|
|
80
|
+
|
|
81
|
+
appOptions.refreshCookie = refreshCookie
|
|
82
|
+
{{/if}}{{#if features.crypto}}
|
|
83
|
+
if (transport)
|
|
84
|
+
appOptions.transport = transport
|
|
85
|
+
{{/if}}
|
|
86
|
+
|
|
87
|
+
return serv.createApp(appOptions)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function resolveCorsOrigin(origin: string | undefined): string | null {
|
|
91
|
+
if (!origin)
|
|
92
|
+
return null
|
|
93
|
+
if (WEBVIEW_ORIGINS.has(origin))
|
|
94
|
+
return origin
|
|
95
|
+
if (LOOPBACK_ORIGIN_RE.test(origin))
|
|
96
|
+
return origin
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
import { core } from '@h-ai/core'
|
|
3
|
+
import { serv } from '@h-ai/serv'
|
|
4
|
+
import { {{api.serviceAppFactoryName}} } from './app.js'
|
|
5
|
+
import { closeApp, initApp } from './lib/server/init.js'
|
|
6
|
+
|
|
7
|
+
const logger = core.logger.child({ app: '{{api.servicePackageName}}', scope: 'entrypoint' })
|
|
8
|
+
|
|
9
|
+
async function main(): Promise<void> {
|
|
10
|
+
await initApp()
|
|
11
|
+
|
|
12
|
+
serv.listen({{api.serviceAppFactoryName}}(), {
|
|
13
|
+
onListening: info => logger.info('API service listening', { address: info.address, port: info.port }),
|
|
14
|
+
onClose: closeApp,
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
main().catch((error: unknown) => {
|
|
19
|
+
logger.error('API service failed to start', { error })
|
|
20
|
+
process.exitCode = 1
|
|
21
|
+
})
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
{{#if features.ai}}import type { AIConfigInput } from '@h-ai/ai'
|
|
2
|
+
{{/if}}{{#if features.cache}}import type { CacheConfigInput } from '@h-ai/cache'
|
|
3
|
+
{{/if}}{{#if features.iam}}import type { IamConfigInput } from '@h-ai/iam'
|
|
4
|
+
{{/if}}{{#if features.crypto}}import type { ServConfig } from '@h-ai/serv'
|
|
5
|
+
{{/if}}{{#if features.storage}}import type { StorageConfigInput } from '@h-ai/storage'
|
|
6
|
+
{{/if}}{{#if features.ai}}import { ai, AIConfigSchema } from '@h-ai/ai'
|
|
7
|
+
{{/if}}{{#if features.cache}}import { cache, CacheConfigSchema } from '@h-ai/cache'
|
|
8
|
+
{{/if}}import { core } from '@h-ai/core'
|
|
9
|
+
{{#if features.crypto}}import { crypto } from '@h-ai/crypto'
|
|
10
|
+
{{/if}}{{#if features.iam}}import { iam, IamConfigSchema } from '@h-ai/iam'
|
|
11
|
+
{{/if}}{{#if features.db}}import { reldb, ReldbConfigSchema } from '@h-ai/reldb'
|
|
12
|
+
{{/if}}import { ServConfigSchema } from '@h-ai/serv'
|
|
13
|
+
{{#if features.storage}}import { storage, StorageConfigSchema } from '@h-ai/storage'
|
|
14
|
+
{{/if}}{{#if features.vecdb}}import { vecdb, VecdbConfigSchema } from '@h-ai/vecdb'
|
|
15
|
+
{{/if}}
|
|
16
|
+
|
|
17
|
+
{{#if features.db}}type DbConfigInput = Parameters<typeof reldb.init>[0]
|
|
18
|
+
{{/if}}{{#if features.vecdb}}type VecdbConfigInput = Parameters<typeof vecdb.init>[0]
|
|
19
|
+
{{/if}}
|
|
20
|
+
|
|
21
|
+
let initialized = false
|
|
22
|
+
let initPromise: Promise<void> | null = null
|
|
23
|
+
{{#if features.crypto}}let cryptoInitialized = false
|
|
24
|
+
{{/if}}
|
|
25
|
+
|
|
26
|
+
export async function initApp(): Promise<void> {
|
|
27
|
+
if (initialized)
|
|
28
|
+
return
|
|
29
|
+
if (initPromise)
|
|
30
|
+
return initPromise
|
|
31
|
+
|
|
32
|
+
initPromise = doInit()
|
|
33
|
+
try {
|
|
34
|
+
await initPromise
|
|
35
|
+
initialized = true
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
initPromise = null
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function doInit(): Promise<void> {
|
|
43
|
+
core.init({
|
|
44
|
+
configDir: './config',
|
|
45
|
+
logging: { level: 'info' },
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const servValidation = core.config.validate('serv', ServConfigSchema)
|
|
49
|
+
if (!servValidation.success) {
|
|
50
|
+
throw new Error(`Serv config invalid: ${servValidation.error.message}`)
|
|
51
|
+
}
|
|
52
|
+
{{#if features.crypto}}
|
|
53
|
+
const servConfig = core.config.getOrThrow<ServConfig>('serv')
|
|
54
|
+
if (servConfig.transport !== false) {
|
|
55
|
+
const cryptoResult = await crypto.init()
|
|
56
|
+
if (!cryptoResult.success) {
|
|
57
|
+
throw new Error(`Crypto initialization failed: ${cryptoResult.error.message}`)
|
|
58
|
+
}
|
|
59
|
+
cryptoInitialized = true
|
|
60
|
+
}
|
|
61
|
+
{{/if}}{{#if features.db}}
|
|
62
|
+
|
|
63
|
+
const dbValidation = core.config.validate('db', ReldbConfigSchema)
|
|
64
|
+
if (!dbValidation.success) {
|
|
65
|
+
throw new Error(`DB config invalid: ${dbValidation.error.message}`)
|
|
66
|
+
}
|
|
67
|
+
{{/if}}{{#if features.cache}}
|
|
68
|
+
|
|
69
|
+
const cacheValidation = core.config.validate('cache', CacheConfigSchema)
|
|
70
|
+
if (!cacheValidation.success) {
|
|
71
|
+
throw new Error(`Cache config invalid: ${cacheValidation.error.message}`)
|
|
72
|
+
}
|
|
73
|
+
{{/if}}{{#if features.vecdb}}
|
|
74
|
+
|
|
75
|
+
const vecdbValidation = core.config.validate('vecdb', VecdbConfigSchema)
|
|
76
|
+
if (!vecdbValidation.success) {
|
|
77
|
+
throw new Error(`VecDB config invalid: ${vecdbValidation.error.message}`)
|
|
78
|
+
}
|
|
79
|
+
{{/if}}{{#if features.iam}}
|
|
80
|
+
|
|
81
|
+
const iamValidation = core.config.validate('iam', IamConfigSchema)
|
|
82
|
+
if (!iamValidation.success) {
|
|
83
|
+
throw new Error(`IAM config invalid: ${iamValidation.error.message}`)
|
|
84
|
+
}
|
|
85
|
+
{{/if}}{{#if features.storage}}
|
|
86
|
+
|
|
87
|
+
const storageValidation = core.config.validate('storage', StorageConfigSchema)
|
|
88
|
+
if (!storageValidation.success) {
|
|
89
|
+
throw new Error(`Storage config invalid: ${storageValidation.error.message}`)
|
|
90
|
+
}
|
|
91
|
+
{{/if}}{{#if features.ai}}
|
|
92
|
+
|
|
93
|
+
const aiValidation = core.config.validate('ai', AIConfigSchema)
|
|
94
|
+
if (!aiValidation.success) {
|
|
95
|
+
throw new Error(`AI config invalid: ${aiValidation.error.message}`)
|
|
96
|
+
}
|
|
97
|
+
{{/if}}{{#if features.db}}
|
|
98
|
+
|
|
99
|
+
const dbConfig = core.config.getOrThrow<DbConfigInput>('db')
|
|
100
|
+
{{/if}}{{#if features.cache}} const cacheConfig = core.config.getOrThrow<CacheConfigInput>('cache')
|
|
101
|
+
{{/if}}{{#if features.vecdb}} const vecdbConfig = core.config.getOrThrow<VecdbConfigInput>('vecdb')
|
|
102
|
+
{{/if}}{{#if features.iam}} const iamConfig = core.config.getOrThrow<IamConfigInput>('iam')
|
|
103
|
+
{{/if}}{{#if features.storage}} const storageConfig = core.config.getOrThrow<StorageConfigInput>('storage')
|
|
104
|
+
{{/if}}{{#if features.ai}} const aiConfig = core.config.getOrThrow<AIConfigInput>('ai')
|
|
105
|
+
{{/if}}{{#if features.db}}
|
|
106
|
+
|
|
107
|
+
if (dbConfig.type === 'sqlite') {
|
|
108
|
+
const fs = await import('node:fs')
|
|
109
|
+
const path = await import('node:path')
|
|
110
|
+
const dbDir = path.dirname(dbConfig.database)
|
|
111
|
+
if (!fs.existsSync(dbDir)) {
|
|
112
|
+
fs.mkdirSync(dbDir, { recursive: true })
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const dbResult = await reldb.init(dbConfig)
|
|
117
|
+
if (!dbResult.success) {
|
|
118
|
+
throw new Error(`Database initialization failed: ${dbResult.error.message}`)
|
|
119
|
+
}
|
|
120
|
+
{{/if}}{{#if features.cache}}
|
|
121
|
+
|
|
122
|
+
const cacheResult = await cache.init(cacheConfig)
|
|
123
|
+
if (!cacheResult.success) {
|
|
124
|
+
throw new Error(`Cache initialization failed: ${cacheResult.error.message}`)
|
|
125
|
+
}
|
|
126
|
+
{{/if}}{{#if features.vecdb}}
|
|
127
|
+
|
|
128
|
+
const vecdbResult = await vecdb.init(vecdbConfig)
|
|
129
|
+
if (!vecdbResult.success) {
|
|
130
|
+
throw new Error(`VecDB initialization failed: ${vecdbResult.error.message}`)
|
|
131
|
+
}
|
|
132
|
+
{{/if}}{{#if features.iam}}
|
|
133
|
+
|
|
134
|
+
const iamResult = await iam.init(iamConfig)
|
|
135
|
+
if (!iamResult.success) {
|
|
136
|
+
throw new Error(`IAM initialization failed: ${iamResult.error.message}`)
|
|
137
|
+
}
|
|
138
|
+
{{/if}}{{#if features.storage}}
|
|
139
|
+
|
|
140
|
+
const storageResult = await storage.init(storageConfig)
|
|
141
|
+
if (!storageResult.success) {
|
|
142
|
+
throw new Error(`Storage initialization failed: ${storageResult.error.message}`)
|
|
143
|
+
}
|
|
144
|
+
{{/if}}{{#if features.ai}}
|
|
145
|
+
|
|
146
|
+
const aiResult = await ai.init(aiConfig)
|
|
147
|
+
if (!aiResult.success) {
|
|
148
|
+
throw new Error(`AI initialization failed: ${aiResult.error.message}`)
|
|
149
|
+
}
|
|
150
|
+
{{/if}}
|
|
151
|
+
|
|
152
|
+
core.logger.info('API service initialized')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function closeApp(): Promise<void> {
|
|
156
|
+
if (!initialized && !initPromise)
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
const closers: Array<readonly [string, () => unknown]> = [
|
|
160
|
+
{{#if features.ai}} ['ai', () => ai.close()],
|
|
161
|
+
{{/if}}{{#if features.storage}} ['storage', () => storage.close()],
|
|
162
|
+
{{/if}}{{#if features.iam}} ['iam', () => iam.close()],
|
|
163
|
+
{{/if}}{{#if features.vecdb}} ['vecdb', () => vecdb.close()],
|
|
164
|
+
{{/if}}{{#if features.cache}} ['cache', () => cache.close()],
|
|
165
|
+
{{/if}}{{#if features.db}} ['reldb', () => reldb.close()],
|
|
166
|
+
{{/if}} ]
|
|
167
|
+
{{#if features.crypto}} if (cryptoInitialized) {
|
|
168
|
+
closers.push(['crypto', () => crypto.close()])
|
|
169
|
+
}
|
|
170
|
+
{{/if}}
|
|
171
|
+
|
|
172
|
+
for (const [name, close] of closers) {
|
|
173
|
+
try {
|
|
174
|
+
await close()
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
core.logger.warn(`Module close failed: ${name}`, { error })
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
initialized = false
|
|
182
|
+
{{#if features.crypto}} cryptoInitialized = false
|
|
183
|
+
{{/if}} core.logger.info('API service closed')
|
|
184
|
+
}
|
package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/app-procedures.ts.hbs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ServContext } from '@h-ai/serv'
|
|
2
|
+
{{#if features.iam}}import type { AppEchoInput, AppEchoOutputData, AppInfoOutputData } from '{{api.contractPackageName}}'
|
|
3
|
+
{{else}}import type { AppEchoOutputData, AppInfoOutputData } from '{{api.contractPackageName}}'
|
|
4
|
+
{{/if}}
|
|
5
|
+
import { ok } from '@h-ai/core'
|
|
6
|
+
import { serv } from '@h-ai/serv'
|
|
7
|
+
import { appContract } from '{{api.contractPackageName}}'
|
|
8
|
+
|
|
9
|
+
export interface AppProcedureDeps {
|
|
10
|
+
readonly info: {
|
|
11
|
+
readonly name: string
|
|
12
|
+
readonly version: string
|
|
13
|
+
readonly transportEnabled: boolean
|
|
14
|
+
}
|
|
15
|
+
readonly startedAt: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const p = serv.implement(appContract).$context<ServContext>()
|
|
19
|
+
|
|
20
|
+
/** 创建应用自有 procedures。 */
|
|
21
|
+
export function createAppProcedures(deps: AppProcedureDeps) {
|
|
22
|
+
return p.router({
|
|
23
|
+
info: p.info.handler(() => {
|
|
24
|
+
const data: AppInfoOutputData = {
|
|
25
|
+
name: deps.info.name,
|
|
26
|
+
version: deps.info.version,
|
|
27
|
+
transportEnabled: deps.info.transportEnabled,
|
|
28
|
+
uptimeMs: Math.max(0, Date.now() - deps.startedAt),
|
|
29
|
+
}
|
|
30
|
+
return ok(data)
|
|
31
|
+
}),
|
|
32
|
+
echo: {{#if features.iam}}p.echo.handler(serv.requireAuth<AppEchoInput, AppEchoOutputData>(({ input, context }) => {
|
|
33
|
+
const data: AppEchoOutputData = {
|
|
34
|
+
message: input.message,
|
|
35
|
+
userId: context.session!.userId,
|
|
36
|
+
requestId: context.requestId,
|
|
37
|
+
timestamp: new Date().toISOString(),
|
|
38
|
+
}
|
|
39
|
+
return Promise.resolve(ok(data))
|
|
40
|
+
})){{else}}p.echo.handler(({ input, context }) => {
|
|
41
|
+
const data: AppEchoOutputData = {
|
|
42
|
+
message: input.message,
|
|
43
|
+
requestId: context.requestId,
|
|
44
|
+
timestamp: new Date().toISOString(),
|
|
45
|
+
}
|
|
46
|
+
return ok(data)
|
|
47
|
+
}){{/if}},
|
|
48
|
+
})
|
|
49
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AppProcedureDeps } from './app-procedures.js'
|
|
2
|
+
{{#if features.ai}}import { ai } from '@h-ai/ai'
|
|
3
|
+
{{/if}}{{#if features.iam}}import { iam } from '@h-ai/iam'
|
|
4
|
+
{{/if}}{{#if features.ai}}import { createAiProcedures } from '@h-ai/serv/features/ai'
|
|
5
|
+
{{/if}}{{#if features.iam}}import { createIamProcedures } from '@h-ai/serv/features/iam'
|
|
6
|
+
{{/if}}{{#if features.storage}}import { createStorageProcedures } from '@h-ai/serv/features/storage'
|
|
7
|
+
import { storage } from '@h-ai/storage'
|
|
8
|
+
{{/if}}import { createAppProcedures } from './app-procedures.js'
|
|
9
|
+
|
|
10
|
+
/** 创建应用级 procedures 聚合。 */
|
|
11
|
+
export function {{api.serviceProceduresFactoryName}}(deps: { app: AppProcedureDeps }) {
|
|
12
|
+
return {
|
|
13
|
+
{{#if features.iam}} iam: createIamProcedures({ iam }),
|
|
14
|
+
{{/if}}{{#if features.storage}} storage: createStorageProcedures({ storage }),
|
|
15
|
+
{{/if}}{{#if features.ai}} ai: createAiProcedures({ ai }),
|
|
16
|
+
{{/if}} app: createAppProcedures(deps.app),
|
|
17
|
+
}
|
|
18
|
+
}
|