@h-ai/cli 0.1.0-alpha.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/LICENSE +202 -0
- package/README.md +167 -0
- package/dist/chunk-2H7UOFLK.js +10 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/chunk-LANIDJC7.js +659 -0
- package/dist/chunk-LANIDJC7.js.map +1 -0
- package/dist/chunk-YV7WPUI6.js +206 -0
- package/dist/chunk-YV7WPUI6.js.map +1 -0
- package/dist/config-templates-S5RI2MC6.js +5 -0
- package/dist/config-templates-S5RI2MC6.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1650 -0
- package/dist/index.js.map +1 -0
- package/dist/skill-templates-N5DU5FNX.js +5 -0
- package/dist/skill-templates-N5DU5FNX.js.map +1 -0
- package/dist/src-XJ6YXNAF.js +15030 -0
- package/dist/src-XJ6YXNAF.js.map +1 -0
- package/package.json +59 -0
- package/templates/README.md +126 -0
- package/templates/apps/admin/messages/en-US.json +33 -0
- package/templates/apps/admin/messages/zh-CN.json +33 -0
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +30 -0
- package/templates/apps/admin/src/routes/+page.svelte.hbs +35 -0
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +44 -0
- package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +45 -0
- package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +37 -0
- package/templates/apps/admin/src/routes/api/health/+server.ts +12 -0
- package/templates/apps/android-app/.env.hbs +3 -0
- package/templates/apps/android-app/capacitor.config.ts.hbs +12 -0
- package/templates/apps/android-app/messages/en-US.json +24 -0
- package/templates/apps/android-app/messages/zh-CN.json +24 -0
- package/templates/apps/android-app/src/lib/api.ts.hbs +30 -0
- package/templates/apps/android-app/src/lib/capacitor.ts +21 -0
- package/templates/apps/android-app/src/routes/+layout.svelte.hbs +42 -0
- package/templates/apps/android-app/src/routes/+layout.ts +7 -0
- package/templates/apps/android-app/src/routes/+page.svelte.hbs +27 -0
- package/templates/apps/api/src/routes/+layout.svelte +14 -0
- package/templates/apps/api/src/routes/+page.svelte +11 -0
- package/templates/apps/api/src/routes/api/v1/health/+server.ts +14 -0
- package/templates/apps/api/src/routes/api/v1/items/+server.ts +37 -0
- package/templates/apps/api/src/routes/api/v1/items/[id]/+server.ts +30 -0
- package/templates/apps/h5/messages/en-US.json +37 -0
- package/templates/apps/h5/messages/zh-CN.json +37 -0
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +57 -0
- package/templates/apps/h5/src/routes/+page.svelte.hbs +27 -0
- package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +27 -0
- package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +27 -0
- package/templates/apps/website/messages/en-US.json +32 -0
- package/templates/apps/website/messages/zh-CN.json +32 -0
- package/templates/apps/website/src/routes/+layout.svelte.hbs +66 -0
- package/templates/apps/website/src/routes/+page.svelte.hbs +33 -0
- package/templates/apps/website/src/routes/about/+page.svelte.hbs +27 -0
- package/templates/apps/website/src/routes/api/health/+server.ts +12 -0
- package/templates/apps/website/src/routes/contact/+page.svelte.hbs +59 -0
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +63 -0
- package/templates/base/e2e/helpers.ts +49 -0
- package/templates/base/package.json.hbs +67 -0
- package/templates/base/playwright.config.ts +32 -0
- package/templates/base/src/app.css +2 -0
- package/templates/base/src/app.d.ts.hbs +18 -0
- package/templates/base/src/app.html.hbs +16 -0
- package/templates/base/src/hooks.server.ts.hbs +123 -0
- package/templates/base/src/lib/server/init.ts.hbs +190 -0
- package/templates/base/svelte.config.js.hbs +30 -0
- package/templates/base/tsconfig.json +8 -0
- package/templates/base/vite.config.ts.hbs +27 -0
- package/templates/features/ai/routes/api/chat/+server.ts +26 -0
- package/templates/features/datapipe/routes/api/datapipe/pipeline/+server.ts +41 -0
- package/templates/features/iam/routes-admin/(auth)/+layout.svelte.hbs +21 -0
- package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +98 -0
- package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +124 -0
- package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +98 -0
- package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +114 -0
- package/templates/features/iam/routes-shared/api/auth/login/+server.ts +32 -0
- package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +15 -0
- package/templates/features/iam/routes-shared/api/auth/me/+server.ts +12 -0
- package/templates/features/iam/routes-shared/api/auth/register/+server.ts +26 -0
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +30 -0
- package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +42 -0
- package/templates/features/payment/routes/api/v1/payment/query/[orderNo]/+server.ts +25 -0
- package/templates/features/storage/routes/api/upload/+server.ts +35 -0
- package/templates/features/vecdb/routes/api/vecdb/search/+server.ts +36 -0
- package/templates/i18n/project.inlang/settings.json +11 -0
- package/templates/skills/AGENTS.md +26 -0
- package/templates/skills/CLAUDE.md +12 -0
- package/templates/skills/copilot-instructions.md +61 -0
- package/templates/skills/hai-ai/SKILL.md +721 -0
- package/templates/skills/hai-api-client/SKILL.md +355 -0
- package/templates/skills/hai-app-create/SKILL.md +371 -0
- package/templates/skills/hai-app-review/SKILL.md +156 -0
- package/templates/skills/hai-app-tests/SKILL.md +468 -0
- package/templates/skills/hai-audit/SKILL.md +145 -0
- package/templates/skills/hai-build/SKILL.md +319 -0
- package/templates/skills/hai-cache/SKILL.md +190 -0
- package/templates/skills/hai-capacitor/SKILL.md +377 -0
- package/templates/skills/hai-core/SKILL.md +484 -0
- package/templates/skills/hai-crypto/SKILL.md +198 -0
- package/templates/skills/hai-datapipe/SKILL.md +220 -0
- package/templates/skills/hai-deploy/SKILL.md +216 -0
- package/templates/skills/hai-iam/SKILL.md +361 -0
- package/templates/skills/hai-kit/SKILL.md +484 -0
- package/templates/skills/hai-payment/SKILL.md +275 -0
- package/templates/skills/hai-reach/SKILL.md +315 -0
- package/templates/skills/hai-reldb/SKILL.md +465 -0
- package/templates/skills/hai-scheduler/SKILL.md +188 -0
- package/templates/skills/hai-storage/SKILL.md +276 -0
- package/templates/skills/hai-ui/SKILL.md +438 -0
- package/templates/skills/hai-vecdb/SKILL.md +279 -0
- package/templates/skills/opencode.json +11 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hai-storage
|
|
3
|
+
description: 使用 @h-ai/storage 进行文件存储操作(本地/S3),包括 file/dir/presign 三组能力;当需求涉及文件上传下载、目录批量删除、S3 预签名 URL 或客户端直传时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# hai-storage
|
|
7
|
+
|
|
8
|
+
> `@h-ai/storage` 提供统一文件存储接口,支持本地文件系统(LocalProvider)和 S3 兼容存储(S3Provider),通过 `storage.file`、`storage.dir`、`storage.presign` 三组接口访问能力。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 运行环境
|
|
13
|
+
|
|
14
|
+
> ⚠️ **服务端模块**。`storage.file` / `storage.dir` / `storage.presign` 均在 Node.js 端使用。浏览器端通过预签名 URL 或 API 端点间接访问存储(见下方「浏览器端文件上传」)。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 适用场景
|
|
19
|
+
|
|
20
|
+
- 文件上传与下载
|
|
21
|
+
- 预签名 URL 生成(客户端直传)
|
|
22
|
+
- S3 兼容存储对接(AWS S3、MinIO、阿里云 OSS 等)
|
|
23
|
+
- 本地开发文件存储
|
|
24
|
+
- 目录前缀清理与批量文件删除
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 使用步骤
|
|
29
|
+
|
|
30
|
+
### 1. 配置
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
# config/_storage.yml
|
|
34
|
+
type: ${HAI_STORAGE_TYPE:local} # local | s3
|
|
35
|
+
# 本地存储
|
|
36
|
+
root: ${HAI_STORAGE_PATH:./data/storage}
|
|
37
|
+
# S3 存储(type=s3 时生效)
|
|
38
|
+
# endpoint: ${HAI_STORAGE_S3_ENDPOINT:}
|
|
39
|
+
# region: ${HAI_STORAGE_S3_REGION:us-east-1}
|
|
40
|
+
# accessKeyId: ${HAI_STORAGE_S3_ACCESS_KEY:}
|
|
41
|
+
# secretAccessKey: ${HAI_STORAGE_S3_SECRET_KEY:}
|
|
42
|
+
# bucket: ${HAI_STORAGE_S3_BUCKET:my-bucket}
|
|
43
|
+
# forcePathStyle: false
|
|
44
|
+
# prefix: ''
|
|
45
|
+
# publicUrl: ''
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. 初始化与关闭
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { storage } from '@h-ai/storage'
|
|
52
|
+
|
|
53
|
+
await storage.init(core.config.get('storage'))
|
|
54
|
+
// 使用后关闭
|
|
55
|
+
await storage.close()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 核心 API
|
|
61
|
+
|
|
62
|
+
### 文件操作
|
|
63
|
+
|
|
64
|
+
`storage.file` 提供以下方法:
|
|
65
|
+
|
|
66
|
+
- `put(key, data, options?)`
|
|
67
|
+
- `get(key, options?)`
|
|
68
|
+
- `head(key)`
|
|
69
|
+
- `exists(key)`
|
|
70
|
+
- `delete(key)`
|
|
71
|
+
- `deleteMany(keys)`
|
|
72
|
+
- `copy(sourceKey, destKey, options?)`
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// 上传文件
|
|
76
|
+
const result = await storage.file.put('avatars/user-123.png', fileBuffer, {
|
|
77
|
+
contentType: 'image/png',
|
|
78
|
+
metadata: { userId: '123' },
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// 下载文件
|
|
82
|
+
const file = await storage.file.get('avatars/user-123.png')
|
|
83
|
+
|
|
84
|
+
// 列出文件
|
|
85
|
+
const files = await storage.dir.list({ prefix: 'avatars/', maxKeys: 100 })
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 预签名 URL
|
|
89
|
+
|
|
90
|
+
`storage.presign` 提供以下方法:
|
|
91
|
+
|
|
92
|
+
- `getUrl(key, options?)`
|
|
93
|
+
- `putUrl(key, options?)`
|
|
94
|
+
- `publicUrl(key)`
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// 客户端直传模式
|
|
98
|
+
const uploadUrl = await storage.presign.putUrl('uploads/doc.pdf', {
|
|
99
|
+
expiresIn: 3600, // 1 小时有效
|
|
100
|
+
contentType: 'application/pdf',
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// 临时下载链接
|
|
104
|
+
const downloadUrl = await storage.presign.getUrl('uploads/doc.pdf', {
|
|
105
|
+
expiresIn: 300, // 5 分钟有效
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 错误码 — `HaiStorageError`
|
|
112
|
+
|
|
113
|
+
| 错误码 | code | 说明 |
|
|
114
|
+
|--------|------|------|
|
|
115
|
+
| `HaiStorageError.CONNECTION_FAILED` | `hai:storage:001` | 连接失败 |
|
|
116
|
+
| `HaiStorageError.OPERATION_FAILED` | `hai:storage:002` | 操作失败 |
|
|
117
|
+
| `HaiStorageError.NOT_FOUND` | `hai:storage:003` | 文件不存在 |
|
|
118
|
+
| `HaiStorageError.ALREADY_EXISTS` | `hai:storage:004` | 文件已存在 |
|
|
119
|
+
| `HaiStorageError.PERMISSION_DENIED` | `hai:storage:005` | 权限不足 |
|
|
120
|
+
| `HaiStorageError.QUOTA_EXCEEDED` | `hai:storage:006` | 配额超限 |
|
|
121
|
+
| `HaiStorageError.INVALID_PATH` | `hai:storage:007` | 路径无效 |
|
|
122
|
+
| `HaiStorageError.IO_ERROR` | `hai:storage:008` | IO 错误 |
|
|
123
|
+
| `HaiStorageError.NETWORK_ERROR` | `hai:storage:009` | 网络错误 |
|
|
124
|
+
| `HaiStorageError.NOT_INITIALIZED` | `hai:storage:010` | 未初始化 |
|
|
125
|
+
| `HaiStorageError.UNSUPPORTED_TYPE` | `hai:storage:011` | 不支持的存储类型 |
|
|
126
|
+
| `HaiStorageError.CONFIG_ERROR` | `hai:storage:012` | 配置错误 |
|
|
127
|
+
| `HaiStorageError.PRESIGN_FAILED` | `hai:storage:013` | 预签名 URL 生成失败 |
|
|
128
|
+
| `HaiStorageError.UPLOAD_FAILED` | `hai:storage:014` | 上传失败 |
|
|
129
|
+
| `HaiStorageError.DOWNLOAD_FAILED` | `hai:storage:015` | 下载失败 |
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## API 契约(`@h-ai/storage/api`)
|
|
134
|
+
|
|
135
|
+
`@h-ai/storage/api` 子路径导出所有存储端点的 Zod Schema 和端点契约定义,客户端与服务端共享同一份真相源,编译时保证 I/O 一致性。
|
|
136
|
+
|
|
137
|
+
### 端点定义 — `storageEndpoints`
|
|
138
|
+
|
|
139
|
+
| 端点名 | method | path | input Schema | output Schema |
|
|
140
|
+
| ------------------ | ------ | ---------------------------- | ------------------------ | ------------------------ |
|
|
141
|
+
| `presignDownload` | POST | `/storage/presign/download` | `PresignGetInputSchema` | `PresignUrlOutputSchema` |
|
|
142
|
+
| `presignUpload` | POST | `/storage/presign/upload` | `PresignPutInputSchema` | `PresignUrlOutputSchema` |
|
|
143
|
+
| `fileInfo` | POST | `/storage/file/info` | `FileInfoInputSchema` | `FileMetadataSchema` |
|
|
144
|
+
| `listFiles` | GET | `/storage/files` | inline(prefix/maxKeys) | `ListFilesOutputSchema` |
|
|
145
|
+
| `deleteFile` | POST | `/storage/file/delete` | `DeleteFileInputSchema` | `z.void()` |
|
|
146
|
+
| `deleteFiles` | POST | `/storage/files/delete` | `DeleteFilesInputSchema` | `z.void()` |
|
|
147
|
+
|
|
148
|
+
### 使用示例
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { storageEndpoints, PresignPutInputSchema } from '@h-ai/storage/api'
|
|
152
|
+
|
|
153
|
+
// 客户端:通过 api-client 调用端点
|
|
154
|
+
const { url } = await api.call(storageEndpoints.presignUpload, { key: 'avatar.png' })
|
|
155
|
+
|
|
156
|
+
// 服务端:基于契约定义路由
|
|
157
|
+
export const POST = kit.fromContract(storageEndpoints.presignUpload, async (input) => {
|
|
158
|
+
const result = await storage.presign.putUrl(input.key, input)
|
|
159
|
+
return result.success ? result.data : kit.response.internalError(result.error.message)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// 独立 Schema 校验
|
|
163
|
+
const parsed = PresignPutInputSchema.safeParse(requestBody)
|
|
164
|
+
if (!parsed.success) { /* 校验失败 */ }
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 导出的 Schema
|
|
168
|
+
|
|
169
|
+
- `FileMetadataSchema` — 文件元数据(key / size / contentType / lastModified / etag? / metadata?)
|
|
170
|
+
- `PresignGetInputSchema` — 下载签名入参(key + expiresIn?)
|
|
171
|
+
- `PresignPutInputSchema` — 上传签名入参(key + contentType? + contentLength? + expiresIn?)
|
|
172
|
+
- `PresignUrlOutputSchema` — 签名 URL 出参(url + key + expiresAt?)
|
|
173
|
+
- `ListFilesOutputSchema` — 文件列表出参(files + commonPrefixes + isTruncated + nextContinuationToken?)
|
|
174
|
+
- `DeleteFileInputSchema` — 删除单文件入参(key)
|
|
175
|
+
- `DeleteFilesInputSchema` — 批量删除入参(keys[])
|
|
176
|
+
- `FileInfoInputSchema` — 文件信息入参(key)
|
|
177
|
+
|
|
178
|
+
### 导出的推导类型
|
|
179
|
+
|
|
180
|
+
`PresignGetInput` / `PresignPutInput` / `PresignUrlOutput` / `ListFilesOutput` / `DeleteFileInput` / `DeleteFilesInput` / `FileInfoInput`
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 常见模式
|
|
185
|
+
|
|
186
|
+
### 与 kit 集成(文件上传 API)
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { storage } from '$lib/server/init'
|
|
190
|
+
// src/routes/api/files/upload/+server.ts
|
|
191
|
+
import { kit } from '@h-ai/kit'
|
|
192
|
+
import { z } from 'zod'
|
|
193
|
+
|
|
194
|
+
const UploadSchema = z.object({
|
|
195
|
+
key: z.string().min(1),
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
export const POST = kit.handler(async ({ request, locals }) => {
|
|
199
|
+
kit.guard.requirePermission(locals.session, 'storage:write')
|
|
200
|
+
const formData = await request.formData()
|
|
201
|
+
const file = formData.get('file') as File
|
|
202
|
+
if (!file)
|
|
203
|
+
return kit.response.badRequest('Missing file')
|
|
204
|
+
|
|
205
|
+
const key = `uploads/${locals.session.userId}/${file.name}`
|
|
206
|
+
const buffer = new Uint8Array(await file.arrayBuffer())
|
|
207
|
+
const result = await storage.file.put(key, buffer, { contentType: file.type })
|
|
208
|
+
if (!result.success)
|
|
209
|
+
return kit.response.internalError()
|
|
210
|
+
return kit.response.created({ key })
|
|
211
|
+
})
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
> 说明:`@h-ai/storage` 只提供存储能力,不强制要求 `/api/storage` 路由命名。应用可按业务语义自定义 API 路径。
|
|
215
|
+
|
|
216
|
+
### 头像上传
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
const key = `avatars/${userId}/${Date.now()}.png`
|
|
220
|
+
const result = await storage.file.put(key, avatarBuffer, {
|
|
221
|
+
contentType: 'image/png',
|
|
222
|
+
})
|
|
223
|
+
if (result.success) {
|
|
224
|
+
const publicUrl = storage.presign.publicUrl(key)
|
|
225
|
+
await iam.user.updateUser(userId, { avatarUrl: publicUrl ?? '' })
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## 浏览器端文件上传(预签名 URL 模式)
|
|
232
|
+
|
|
233
|
+
浏览器端无法直接使用 `storage` 模块,应通过 API 获取预签名 URL 后直传:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { apiFetch } from '$lib/utils/api'
|
|
237
|
+
|
|
238
|
+
// 1. 向服务端请求预签名上传 URL
|
|
239
|
+
const res = await apiFetch('/api/storage/presign/upload', {
|
|
240
|
+
method: 'POST',
|
|
241
|
+
headers: { 'Content-Type': 'application/json' },
|
|
242
|
+
body: JSON.stringify({ key: `uploads/${file.name}`, contentType: file.type }),
|
|
243
|
+
})
|
|
244
|
+
const { url } = await res.json()
|
|
245
|
+
|
|
246
|
+
// 2. 浏览器直传到存储(不经过服务端,大文件友好)
|
|
247
|
+
await fetch(url, {
|
|
248
|
+
method: 'PUT',
|
|
249
|
+
body: file,
|
|
250
|
+
headers: { 'Content-Type': file.type },
|
|
251
|
+
})
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
也可使用契约调用(需配合 `@h-ai/api-client`):
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import { api } from '@h-ai/api-client'
|
|
258
|
+
import { storageEndpoints } from '@h-ai/storage/api'
|
|
259
|
+
|
|
260
|
+
const result = await api.call(storageEndpoints.presignUpload, {
|
|
261
|
+
key: `avatars/${userId}.png`,
|
|
262
|
+
contentType: 'image/png',
|
|
263
|
+
})
|
|
264
|
+
if (result.success) {
|
|
265
|
+
await fetch(result.data.url, { method: 'PUT', body: file })
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## 相关 Skills
|
|
272
|
+
|
|
273
|
+
- `hai-build`:模块初始化顺序
|
|
274
|
+
- `hai-core`:配置与 HaiResult 模型
|
|
275
|
+
- `hai-kit`:SvelteKit API 端点集成(`kit.handler` + `kit.guard`)
|
|
276
|
+
- `hai-ui`:Storage 场景组件(FileUpload/ImageUpload/AvatarUpload)
|