@h-ai/storage 0.1.0-alpha5
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 +151 -0
- package/dist/api/index.d.ts +167 -0
- package/dist/api/index.js +105 -0
- package/dist/api/index.js.map +1 -0
- package/dist/browser.d.ts +506 -0
- package/dist/browser.js +4 -0
- package/dist/browser.js.map +1 -0
- package/dist/chunk-KHZOR6CR.js +76 -0
- package/dist/chunk-KHZOR6CR.js.map +1 -0
- package/dist/chunk-U427BQIQ.js +266 -0
- package/dist/chunk-U427BQIQ.js.map +1 -0
- package/dist/client/index.d.ts +172 -0
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -0
- package/dist/node.d.ts +37 -0
- package/dist/node.js +959 -0
- package/dist/node.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { core } from '@h-ai/core';
|
|
2
|
+
|
|
3
|
+
// src/storage-i18n.ts
|
|
4
|
+
|
|
5
|
+
// messages/en-US.json
|
|
6
|
+
var en_US_default = {
|
|
7
|
+
$schema: "https://inlang.com/schema/inlang-message-format",
|
|
8
|
+
storage_notInitialized: "Storage not initialized, please call storage.init() first",
|
|
9
|
+
storage_bucketNotExist: "Bucket does not exist",
|
|
10
|
+
storage_networkError: "Network error",
|
|
11
|
+
storage_responseBodyEmpty: "Response body is empty",
|
|
12
|
+
storage_presignUrlFailed: "Failed to generate presigned URL",
|
|
13
|
+
storage_presignUploadUrlFailed: "Failed to generate presigned upload URL",
|
|
14
|
+
storage_s3ConfigTypeError: "Config type error: S3 provider only supports type=s3",
|
|
15
|
+
storage_diskSpaceInsufficient: "Insufficient disk space",
|
|
16
|
+
storage_localConfigTypeError: "Config type error: Local provider only supports type=local",
|
|
17
|
+
storage_operationFailed: "Operation failed: {error}",
|
|
18
|
+
storage_unsupportedType: "Unsupported storage type: {type}",
|
|
19
|
+
storage_config_bucketRequired: "Bucket is required",
|
|
20
|
+
storage_config_regionRequired: "Region is required",
|
|
21
|
+
storage_config_accessKeyIdRequired: "Access Key ID is required",
|
|
22
|
+
storage_config_secretAccessKeyRequired: "Secret Access Key is required",
|
|
23
|
+
storage_config_rootRequired: "Root path is required",
|
|
24
|
+
storage_fileNotFound: "File not found: {key}",
|
|
25
|
+
storage_permissionDenied: "Permission denied: {key}",
|
|
26
|
+
storage_pathIsDir: "Path is a directory: {key}",
|
|
27
|
+
storage_ioError: "IO error: {error}",
|
|
28
|
+
storage_deleteManyFailed: "Failed to delete {count} files",
|
|
29
|
+
storage_s3ClientNotInitialized: "S3 client not initialized",
|
|
30
|
+
storage_s3ConfigNotInitialized: "S3 config not initialized",
|
|
31
|
+
storage_uploadCanceled: "Upload canceled",
|
|
32
|
+
storage_downloadCanceled: "Download canceled",
|
|
33
|
+
storage_uploadFailedStatus: "Upload failed (status {status}): {statusText}",
|
|
34
|
+
storage_downloadFailedStatus: "Download failed (status {status}): {statusText}",
|
|
35
|
+
storage_unknownError: "Unknown error",
|
|
36
|
+
storage_saveFileFailed: "Failed to save file",
|
|
37
|
+
storage_downloadDefaultFilename: "download",
|
|
38
|
+
storage_pathTraversal: "Path traversal attack detected",
|
|
39
|
+
storage_localNotInitialized: "Local storage not initialized",
|
|
40
|
+
storage_configError: "Storage config validation failed: {error}",
|
|
41
|
+
storage_metaFileAccess: "Direct access to metadata files is not allowed: {key}"
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// messages/zh-CN.json
|
|
45
|
+
var zh_CN_default = {
|
|
46
|
+
$schema: "https://inlang.com/schema/inlang-message-format",
|
|
47
|
+
storage_notInitialized: "\u5B58\u50A8\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 storage.init()",
|
|
48
|
+
storage_bucketNotExist: "\u5B58\u50A8\u6876\u4E0D\u5B58\u5728",
|
|
49
|
+
storage_networkError: "\u7F51\u7EDC\u9519\u8BEF",
|
|
50
|
+
storage_responseBodyEmpty: "\u54CD\u5E94\u4F53\u4E3A\u7A7A",
|
|
51
|
+
storage_presignUrlFailed: "\u751F\u6210\u7B7E\u540D URL \u5931\u8D25",
|
|
52
|
+
storage_presignUploadUrlFailed: "\u751F\u6210\u4E0A\u4F20\u7B7E\u540D URL \u5931\u8D25",
|
|
53
|
+
storage_s3ConfigTypeError: "\u914D\u7F6E\u7C7B\u578B\u9519\u8BEF\uFF1AS3 provider \u4EC5\u652F\u6301 type=s3",
|
|
54
|
+
storage_diskSpaceInsufficient: "\u78C1\u76D8\u7A7A\u95F4\u4E0D\u8DB3",
|
|
55
|
+
storage_localConfigTypeError: "\u914D\u7F6E\u7C7B\u578B\u9519\u8BEF\uFF1ALocal provider \u4EC5\u652F\u6301 type=local",
|
|
56
|
+
storage_operationFailed: "\u64CD\u4F5C\u5931\u8D25: {error}",
|
|
57
|
+
storage_unsupportedType: "\u4E0D\u652F\u6301\u7684\u5B58\u50A8\u7C7B\u578B\uFF1A{type}",
|
|
58
|
+
storage_config_bucketRequired: "\u5B58\u50A8\u6876\u4E0D\u80FD\u4E3A\u7A7A",
|
|
59
|
+
storage_config_regionRequired: "\u533A\u57DF\u4E0D\u80FD\u4E3A\u7A7A",
|
|
60
|
+
storage_config_accessKeyIdRequired: "Access Key ID \u4E0D\u80FD\u4E3A\u7A7A",
|
|
61
|
+
storage_config_secretAccessKeyRequired: "Secret Access Key \u4E0D\u80FD\u4E3A\u7A7A",
|
|
62
|
+
storage_config_rootRequired: "Root \u4E0D\u80FD\u4E3A\u7A7A",
|
|
63
|
+
storage_fileNotFound: "\u6587\u4EF6\u4E0D\u5B58\u5728: {key}",
|
|
64
|
+
storage_permissionDenied: "\u6743\u9650\u4E0D\u8DB3: {key}",
|
|
65
|
+
storage_pathIsDir: "\u8DEF\u5F84\u662F\u76EE\u5F55: {key}",
|
|
66
|
+
storage_ioError: "IO \u9519\u8BEF: {error}",
|
|
67
|
+
storage_deleteManyFailed: "\u5220\u9664 {count} \u4E2A\u6587\u4EF6\u5931\u8D25",
|
|
68
|
+
storage_s3ClientNotInitialized: "S3 \u5BA2\u6237\u7AEF\u672A\u521D\u59CB\u5316",
|
|
69
|
+
storage_s3ConfigNotInitialized: "S3 \u914D\u7F6E\u672A\u521D\u59CB\u5316",
|
|
70
|
+
storage_uploadCanceled: "\u4E0A\u4F20\u5DF2\u53D6\u6D88",
|
|
71
|
+
storage_downloadCanceled: "\u4E0B\u8F7D\u5DF2\u53D6\u6D88",
|
|
72
|
+
storage_uploadFailedStatus: "\u4E0A\u4F20\u5931\u8D25\uFF08\u72B6\u6001\u7801 {status}\uFF09\uFF1A{statusText}",
|
|
73
|
+
storage_downloadFailedStatus: "\u4E0B\u8F7D\u5931\u8D25\uFF08\u72B6\u6001\u7801 {status}\uFF09\uFF1A{statusText}",
|
|
74
|
+
storage_unknownError: "\u672A\u77E5\u9519\u8BEF",
|
|
75
|
+
storage_saveFileFailed: "\u4FDD\u5B58\u6587\u4EF6\u5931\u8D25",
|
|
76
|
+
storage_downloadDefaultFilename: "\u4E0B\u8F7D",
|
|
77
|
+
storage_pathTraversal: "\u68C0\u6D4B\u5230\u8DEF\u5F84\u7A7F\u8D8A\u653B\u51FB",
|
|
78
|
+
storage_localNotInitialized: "\u672C\u5730\u5B58\u50A8\u672A\u521D\u59CB\u5316",
|
|
79
|
+
storage_configError: "\u5B58\u50A8\u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A{error}",
|
|
80
|
+
storage_metaFileAccess: "\u4E0D\u5141\u8BB8\u76F4\u63A5\u8BBF\u95EE\u5143\u6570\u636E\u6587\u4EF6: {key}"
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// src/storage-i18n.ts
|
|
84
|
+
var storageM = core.i18n.createMessageGetter({
|
|
85
|
+
"zh-CN": zh_CN_default,
|
|
86
|
+
"en-US": en_US_default
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// src/storage-mime.ts
|
|
90
|
+
var MIME_TYPES = {
|
|
91
|
+
// 图片
|
|
92
|
+
"jpg": "image/jpeg",
|
|
93
|
+
"jpeg": "image/jpeg",
|
|
94
|
+
"png": "image/png",
|
|
95
|
+
"gif": "image/gif",
|
|
96
|
+
"webp": "image/webp",
|
|
97
|
+
"svg": "image/svg+xml",
|
|
98
|
+
"ico": "image/x-icon",
|
|
99
|
+
// 文档
|
|
100
|
+
"pdf": "application/pdf",
|
|
101
|
+
"doc": "application/msword",
|
|
102
|
+
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
103
|
+
"xls": "application/vnd.ms-excel",
|
|
104
|
+
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
105
|
+
"ppt": "application/vnd.ms-powerpoint",
|
|
106
|
+
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
107
|
+
// 文本
|
|
108
|
+
"txt": "text/plain",
|
|
109
|
+
"html": "text/html",
|
|
110
|
+
"css": "text/css",
|
|
111
|
+
"js": "text/javascript",
|
|
112
|
+
"json": "application/json",
|
|
113
|
+
"xml": "application/xml",
|
|
114
|
+
"csv": "text/csv",
|
|
115
|
+
"md": "text/markdown",
|
|
116
|
+
// 音视频
|
|
117
|
+
"mp3": "audio/mpeg",
|
|
118
|
+
"wav": "audio/wav",
|
|
119
|
+
"mp4": "video/mp4",
|
|
120
|
+
"webm": "video/webm",
|
|
121
|
+
"ogg": "audio/ogg",
|
|
122
|
+
// 压缩包
|
|
123
|
+
"zip": "application/zip",
|
|
124
|
+
"rar": "application/x-rar-compressed",
|
|
125
|
+
"7z": "application/x-7z-compressed",
|
|
126
|
+
"tar": "application/x-tar",
|
|
127
|
+
"gz": "application/gzip",
|
|
128
|
+
// 字体
|
|
129
|
+
"woff": "font/woff",
|
|
130
|
+
"woff2": "font/woff2",
|
|
131
|
+
"ttf": "font/ttf",
|
|
132
|
+
"otf": "font/otf"
|
|
133
|
+
};
|
|
134
|
+
var MIME_TYPE_DEFAULT = "application/octet-stream";
|
|
135
|
+
|
|
136
|
+
// src/client/storage-client.ts
|
|
137
|
+
async function uploadWithPresignedUrl(url, data, options = {}) {
|
|
138
|
+
try {
|
|
139
|
+
const body = data;
|
|
140
|
+
if (options.onProgress) {
|
|
141
|
+
return new Promise((resolve) => {
|
|
142
|
+
const xhr = new XMLHttpRequest();
|
|
143
|
+
if (options.abortController) {
|
|
144
|
+
options.abortController.signal.addEventListener("abort", () => {
|
|
145
|
+
xhr.abort();
|
|
146
|
+
resolve({ success: false, error: storageM("storage_uploadCanceled") });
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
xhr.upload.addEventListener("progress", (event) => {
|
|
150
|
+
if (event.lengthComputable && options.onProgress) {
|
|
151
|
+
options.onProgress({
|
|
152
|
+
loaded: event.loaded,
|
|
153
|
+
total: event.total,
|
|
154
|
+
percent: Math.round(event.loaded / event.total * 100)
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
xhr.addEventListener("load", () => {
|
|
159
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
160
|
+
resolve({ success: true });
|
|
161
|
+
} else {
|
|
162
|
+
resolve({
|
|
163
|
+
success: false,
|
|
164
|
+
error: storageM("storage_uploadFailedStatus", { params: { status: xhr.status, statusText: xhr.statusText } })
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
xhr.addEventListener("error", () => {
|
|
169
|
+
resolve({ success: false, error: storageM("storage_networkError") });
|
|
170
|
+
});
|
|
171
|
+
xhr.open("PUT", url);
|
|
172
|
+
if (options.contentType) {
|
|
173
|
+
xhr.setRequestHeader("Content-Type", options.contentType);
|
|
174
|
+
}
|
|
175
|
+
xhr.send(body);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const response = await fetch(url, {
|
|
179
|
+
method: "PUT",
|
|
180
|
+
body,
|
|
181
|
+
headers: options.contentType ? { "Content-Type": options.contentType } : void 0,
|
|
182
|
+
signal: options.abortController?.signal
|
|
183
|
+
});
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
return {
|
|
186
|
+
success: false,
|
|
187
|
+
error: storageM("storage_uploadFailedStatus", { params: { status: response.status, statusText: response.statusText } })
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return { success: true };
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
193
|
+
return { success: false, error: storageM("storage_uploadCanceled") };
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
success: false,
|
|
197
|
+
error: error instanceof Error ? error.message : storageM("storage_unknownError")
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async function downloadWithPresignedUrl(url, options = {}) {
|
|
202
|
+
try {
|
|
203
|
+
const response = await fetch(url, {
|
|
204
|
+
method: "GET",
|
|
205
|
+
signal: options.abortController?.signal
|
|
206
|
+
});
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
return {
|
|
209
|
+
success: false,
|
|
210
|
+
error: storageM("storage_downloadFailedStatus", { params: { status: response.status, statusText: response.statusText } })
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const blob = await response.blob();
|
|
214
|
+
return { success: true, data: blob };
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
217
|
+
return { success: false, error: storageM("storage_downloadCanceled") };
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
success: false,
|
|
221
|
+
error: error instanceof Error ? error.message : storageM("storage_unknownError")
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async function downloadAndSave(url, options = {}) {
|
|
226
|
+
const result = await downloadWithPresignedUrl(url, options);
|
|
227
|
+
if (!result.success || !result.data) {
|
|
228
|
+
return { success: false, error: result.error };
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
const blobUrl = URL.createObjectURL(result.data);
|
|
232
|
+
const link = document.createElement("a");
|
|
233
|
+
link.href = blobUrl;
|
|
234
|
+
link.download = options.filename || storageM("storage_downloadDefaultFilename");
|
|
235
|
+
document.body.appendChild(link);
|
|
236
|
+
link.click();
|
|
237
|
+
document.body.removeChild(link);
|
|
238
|
+
URL.revokeObjectURL(blobUrl);
|
|
239
|
+
return { success: true };
|
|
240
|
+
} catch (error) {
|
|
241
|
+
return {
|
|
242
|
+
success: false,
|
|
243
|
+
error: error instanceof Error ? error.message : storageM("storage_saveFileFailed")
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function getFileExtension(file) {
|
|
248
|
+
const name = file.name;
|
|
249
|
+
const lastDot = name.lastIndexOf(".");
|
|
250
|
+
return lastDot > -1 ? name.slice(lastDot + 1).toLowerCase() : "";
|
|
251
|
+
}
|
|
252
|
+
function getMimeType(extension) {
|
|
253
|
+
return MIME_TYPES[extension.toLowerCase()] || MIME_TYPE_DEFAULT;
|
|
254
|
+
}
|
|
255
|
+
function formatFileSize(bytes) {
|
|
256
|
+
if (bytes === 0)
|
|
257
|
+
return "0 B";
|
|
258
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
259
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
260
|
+
const size = bytes / 1024 ** i;
|
|
261
|
+
return `${size.toFixed(i > 0 ? 2 : 0)} ${units[i]}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export { MIME_TYPES, MIME_TYPE_DEFAULT, downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, storageM, uploadWithPresignedUrl };
|
|
265
|
+
//# sourceMappingURL=chunk-U427BQIQ.js.map
|
|
266
|
+
//# sourceMappingURL=chunk-U427BQIQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../messages/en-US.json","../messages/zh-CN.json","../src/storage-i18n.ts","../src/storage-mime.ts","../src/client/storage-client.ts"],"names":[],"mappings":"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","file":"chunk-U427BQIQ.js","sourcesContent":["{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"storage_notInitialized\": \"Storage not initialized, please call storage.init() first\",\n \"storage_bucketNotExist\": \"Bucket does not exist\",\n \"storage_networkError\": \"Network error\",\n \"storage_responseBodyEmpty\": \"Response body is empty\",\n \"storage_presignUrlFailed\": \"Failed to generate presigned URL\",\n \"storage_presignUploadUrlFailed\": \"Failed to generate presigned upload URL\",\n \"storage_s3ConfigTypeError\": \"Config type error: S3 provider only supports type=s3\",\n \"storage_diskSpaceInsufficient\": \"Insufficient disk space\",\n \"storage_localConfigTypeError\": \"Config type error: Local provider only supports type=local\",\n \"storage_operationFailed\": \"Operation failed: {error}\",\n \"storage_unsupportedType\": \"Unsupported storage type: {type}\",\n \"storage_config_bucketRequired\": \"Bucket is required\",\n \"storage_config_regionRequired\": \"Region is required\",\n \"storage_config_accessKeyIdRequired\": \"Access Key ID is required\",\n \"storage_config_secretAccessKeyRequired\": \"Secret Access Key is required\",\n \"storage_config_rootRequired\": \"Root path is required\",\n \"storage_fileNotFound\": \"File not found: {key}\",\n \"storage_permissionDenied\": \"Permission denied: {key}\",\n \"storage_pathIsDir\": \"Path is a directory: {key}\",\n \"storage_ioError\": \"IO error: {error}\",\n \"storage_deleteManyFailed\": \"Failed to delete {count} files\",\n \"storage_s3ClientNotInitialized\": \"S3 client not initialized\",\n \"storage_s3ConfigNotInitialized\": \"S3 config not initialized\",\n \"storage_uploadCanceled\": \"Upload canceled\",\n \"storage_downloadCanceled\": \"Download canceled\",\n \"storage_uploadFailedStatus\": \"Upload failed (status {status}): {statusText}\",\n \"storage_downloadFailedStatus\": \"Download failed (status {status}): {statusText}\",\n \"storage_unknownError\": \"Unknown error\",\n \"storage_saveFileFailed\": \"Failed to save file\",\n \"storage_downloadDefaultFilename\": \"download\",\n \"storage_pathTraversal\": \"Path traversal attack detected\",\n \"storage_localNotInitialized\": \"Local storage not initialized\",\n \"storage_configError\": \"Storage config validation failed: {error}\",\n \"storage_metaFileAccess\": \"Direct access to metadata files is not allowed: {key}\"\n}\n","{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"storage_notInitialized\": \"存储未初始化,请先调用 storage.init()\",\n \"storage_bucketNotExist\": \"存储桶不存在\",\n \"storage_networkError\": \"网络错误\",\n \"storage_responseBodyEmpty\": \"响应体为空\",\n \"storage_presignUrlFailed\": \"生成签名 URL 失败\",\n \"storage_presignUploadUrlFailed\": \"生成上传签名 URL 失败\",\n \"storage_s3ConfigTypeError\": \"配置类型错误:S3 provider 仅支持 type=s3\",\n \"storage_diskSpaceInsufficient\": \"磁盘空间不足\",\n \"storage_localConfigTypeError\": \"配置类型错误:Local provider 仅支持 type=local\",\n \"storage_operationFailed\": \"操作失败: {error}\",\n \"storage_unsupportedType\": \"不支持的存储类型:{type}\",\n \"storage_config_bucketRequired\": \"存储桶不能为空\",\n \"storage_config_regionRequired\": \"区域不能为空\",\n \"storage_config_accessKeyIdRequired\": \"Access Key ID 不能为空\",\n \"storage_config_secretAccessKeyRequired\": \"Secret Access Key 不能为空\",\n \"storage_config_rootRequired\": \"Root 不能为空\",\n \"storage_fileNotFound\": \"文件不存在: {key}\",\n \"storage_permissionDenied\": \"权限不足: {key}\",\n \"storage_pathIsDir\": \"路径是目录: {key}\",\n \"storage_ioError\": \"IO 错误: {error}\",\n \"storage_deleteManyFailed\": \"删除 {count} 个文件失败\",\n \"storage_s3ClientNotInitialized\": \"S3 客户端未初始化\",\n \"storage_s3ConfigNotInitialized\": \"S3 配置未初始化\",\n \"storage_uploadCanceled\": \"上传已取消\",\n \"storage_downloadCanceled\": \"下载已取消\",\n \"storage_uploadFailedStatus\": \"上传失败(状态码 {status}):{statusText}\",\n \"storage_downloadFailedStatus\": \"下载失败(状态码 {status}):{statusText}\",\n \"storage_unknownError\": \"未知错误\",\n \"storage_saveFileFailed\": \"保存文件失败\",\n \"storage_downloadDefaultFilename\": \"下载\",\n \"storage_pathTraversal\": \"检测到路径穿越攻击\",\n \"storage_localNotInitialized\": \"本地存储未初始化\",\n \"storage_configError\": \"存储配置校验失败:{error}\",\n \"storage_metaFileAccess\": \"不允许直接访问元数据文件: {key}\"\n}\n","/**\n * @h-ai/storage — i18n 消息获取器\n *\n * 加载存储模块的中英文翻译文件,提供统一的文案获取函数。\n * 内部使用,不对外导出。\n * @module storage-i18n\n */\n\nimport { core } from '@h-ai/core'\nimport messagesEnUS from '../messages/en-US.json'\nimport messagesZhCN from '../messages/zh-CN.json'\n\n/** 消息键类型(从 zh-CN 翻译文件自动推断) */\ntype StorageMessageKey = keyof typeof messagesZhCN\n\n/**\n * 获取存储模块本地化文案\n *\n * 根据当前 locale 自动选择 zh-CN 或 en-US 的文案,\n * 支持带参数插值(通过 `{ params: { key: value } }`)。\n *\n * @example\n * ```ts\n * storageM('storage_fileNotFound', { params: { key: 'a.txt' } })\n * ```\n */\nexport const storageM = core.i18n.createMessageGetter<StorageMessageKey>({\n 'zh-CN': messagesZhCN,\n 'en-US': messagesEnUS,\n})\n","/**\n * @h-ai/storage — MIME 类型映射\n *\n * 集中维护文件扩展名到 MIME 类型的映射表。 供 local provider 和 client 端共用,避免重复定义。\n * @module storage-mime\n */\n\n/**\n * 文件扩展名 → MIME 类型映射表\n *\n * 键为小写扩展名(不含点号),值为标准 MIME 类型字符串。\n * 未匹配项统一回退到 `application/octet-stream`。\n */\nexport const MIME_TYPES: Record<string, string> = {\n // 图片\n 'jpg': 'image/jpeg',\n 'jpeg': 'image/jpeg',\n 'png': 'image/png',\n 'gif': 'image/gif',\n 'webp': 'image/webp',\n 'svg': 'image/svg+xml',\n 'ico': 'image/x-icon',\n\n // 文档\n 'pdf': 'application/pdf',\n 'doc': 'application/msword',\n 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'xls': 'application/vnd.ms-excel',\n 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'ppt': 'application/vnd.ms-powerpoint',\n 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n\n // 文本\n 'txt': 'text/plain',\n 'html': 'text/html',\n 'css': 'text/css',\n 'js': 'text/javascript',\n 'json': 'application/json',\n 'xml': 'application/xml',\n 'csv': 'text/csv',\n 'md': 'text/markdown',\n\n // 音视频\n 'mp3': 'audio/mpeg',\n 'wav': 'audio/wav',\n 'mp4': 'video/mp4',\n 'webm': 'video/webm',\n 'ogg': 'audio/ogg',\n\n // 压缩包\n 'zip': 'application/zip',\n 'rar': 'application/x-rar-compressed',\n '7z': 'application/x-7z-compressed',\n 'tar': 'application/x-tar',\n 'gz': 'application/gzip',\n\n // 字体\n 'woff': 'font/woff',\n 'woff2': 'font/woff2',\n 'ttf': 'font/ttf',\n 'otf': 'font/otf',\n}\n\n/**\n * MIME 类型默认值(未知扩展名回退)\n */\nexport const MIME_TYPE_DEFAULT = 'application/octet-stream'\n","/**\n * @h-ai/storage — 前端存储客户端\n *\n * 提供前端通过签名 URL 直接上传下载文件的功能。\n * @module storage-client\n */\n\nimport { storageM } from '../storage-i18n.js'\nimport { MIME_TYPE_DEFAULT, MIME_TYPES } from '../storage-mime.js'\n\n// ─── 客户端类型 ───\n\n/**\n * 上传进度回调数据\n *\n * 由 `uploadWithPresignedUrl` 在上传过程中通过 `onProgress` 回调返回。\n */\nexport interface UploadProgress {\n /** 已上传字节数 */\n loaded: number\n /** 总字节数(仅在 lengthComputable 时可靠) */\n total: number\n /** 进度百分比 (0–100),四舍五入到整数 */\n percent: number\n}\n\n/**\n * 前端上传选项\n *\n * 控制 `uploadWithPresignedUrl` 的行为,包括内容类型、进度监听和取消。\n */\nexport interface ClientUploadOptions {\n /** 内容类型(设置后会加到请求 Content-Type 头) */\n contentType?: string\n /** 进度回调(设置后会使用 XMLHttpRequest;仅 lengthComputable=true 时回调) */\n onProgress?: (progress: UploadProgress) => void\n /** AbortController 用于取消上传(abort 后返回 {success: false}) */\n abortController?: AbortController\n}\n\n/**\n * 前端下载选项\n *\n * 控制 `downloadWithPresignedUrl` / `downloadAndSave` 的行为。\n */\nexport interface ClientDownloadOptions {\n /** 保存的文件名(仅 downloadAndSave 使用,默认为 'download') */\n filename?: string\n /** AbortController 用于取消下载(abort 后返回 {success: false}) */\n abortController?: AbortController\n}\n\n// ─── 上传功能 ───\n\n/**\n * 使用签名 URL 上传文件\n *\n * @param url - 后端生成的上传签名 URL\n * @param data - 要上传的数据(File、Blob、ArrayBuffer 或字符串)\n * @param options - 上传选项\n * @returns 上传结果\n *\n * @example\n * ```ts\n * // 获取签名 URL(通常从后端 API 获取)\n * const response = await fetch('/api/storage/presign', {\n * method: 'POST',\n * body: JSON.stringify({ key: 'uploads/image.png', contentType: 'image/png' })\n * })\n * const { uploadUrl } = await response.json()\n *\n * // 使用签名 URL 上传\n * const file = document.getElementById('fileInput').files[0]\n * const result = await uploadWithPresignedUrl(uploadUrl, file, {\n * contentType: 'image/png',\n * onProgress: (p) => {\n * // 在此更新进度条:p.percent\n * }\n * })\n *\n * if (result.success) {\n * // 上传成功\n * }\n * ```\n */\nexport async function uploadWithPresignedUrl(\n url: string,\n data: File | Blob | ArrayBuffer | string,\n options: ClientUploadOptions = {},\n): Promise<{ success: boolean, error?: string }> {\n try {\n // 准备请求体\n const body: BodyInit = data\n\n // 使用 XMLHttpRequest 以支持上传进度\n if (options.onProgress) {\n return new Promise((resolve) => {\n const xhr = new XMLHttpRequest()\n\n // 设置取消控制\n if (options.abortController) {\n options.abortController.signal.addEventListener('abort', () => {\n xhr.abort()\n resolve({ success: false, error: storageM('storage_uploadCanceled') })\n })\n }\n\n // 监听进度\n xhr.upload.addEventListener('progress', (event) => {\n if (event.lengthComputable && options.onProgress) {\n options.onProgress({\n loaded: event.loaded,\n total: event.total,\n percent: Math.round((event.loaded / event.total) * 100),\n })\n }\n })\n\n // 监听完成\n xhr.addEventListener('load', () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve({ success: true })\n }\n else {\n resolve({\n success: false,\n error: storageM('storage_uploadFailedStatus', { params: { status: xhr.status, statusText: xhr.statusText } }),\n })\n }\n })\n\n // 监听错误\n xhr.addEventListener('error', () => {\n resolve({ success: false, error: storageM('storage_networkError') })\n })\n\n // 发送请求\n xhr.open('PUT', url)\n if (options.contentType) {\n xhr.setRequestHeader('Content-Type', options.contentType)\n }\n xhr.send(body)\n })\n }\n\n // 不需要进度时使用 fetch\n const response = await fetch(url, {\n method: 'PUT',\n body,\n headers: options.contentType\n ? { 'Content-Type': options.contentType }\n : undefined,\n signal: options.abortController?.signal,\n })\n\n if (!response.ok) {\n return {\n success: false,\n error: storageM('storage_uploadFailedStatus', { params: { status: response.status, statusText: response.statusText } }),\n }\n }\n\n return { success: true }\n }\n catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n return { success: false, error: storageM('storage_uploadCanceled') }\n }\n return {\n success: false,\n error: error instanceof Error ? error.message : storageM('storage_unknownError'),\n }\n }\n}\n\n// ─── 下载功能 ───\n\n/**\n * 使用签名 URL 下载文件\n *\n * @param url - 后端生成的下载签名 URL\n * @param options - 下载选项\n * @returns 下载结果(成功时 data 为 Blob)\n *\n * @example\n * ```ts\n * // 下载为 Blob\n * const result = await downloadWithPresignedUrl(downloadUrl)\n * if (result.success) {\n * const blob = result.data\n * // 处理 blob...\n * }\n *\n * // 下载并保存为文件(浏览器环境)\n * await downloadAndSave(downloadUrl, { filename: 'document.pdf' })\n * ```\n */\nexport async function downloadWithPresignedUrl(\n url: string,\n options: ClientDownloadOptions = {},\n): Promise<{ success: boolean, data?: Blob, error?: string }> {\n try {\n const response = await fetch(url, {\n method: 'GET',\n signal: options.abortController?.signal,\n })\n\n if (!response.ok) {\n return {\n success: false,\n error: storageM('storage_downloadFailedStatus', { params: { status: response.status, statusText: response.statusText } }),\n }\n }\n\n const blob = await response.blob()\n return { success: true, data: blob }\n }\n catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n return { success: false, error: storageM('storage_downloadCanceled') }\n }\n return {\n success: false,\n error: error instanceof Error ? error.message : storageM('storage_unknownError'),\n }\n }\n}\n\n/**\n * 下载并保存到本地(浏览器环境)\n *\n * @param url - 下载 URL\n * @param options - 下载选项\n *\n * @example\n * ```ts\n * await downloadAndSave(presignedUrl, {\n * filename: 'report.pdf'\n * })\n * ```\n */\nexport async function downloadAndSave(\n url: string,\n options: ClientDownloadOptions = {},\n): Promise<{ success: boolean, error?: string }> {\n const result = await downloadWithPresignedUrl(url, options)\n\n if (!result.success || !result.data) {\n return { success: false, error: result.error }\n }\n\n try {\n // 创建下载链接\n const blobUrl = URL.createObjectURL(result.data)\n const link = document.createElement('a')\n link.href = blobUrl\n link.download = options.filename || storageM('storage_downloadDefaultFilename')\n\n // 触发下载\n document.body.appendChild(link)\n link.click()\n document.body.removeChild(link)\n\n // 清理 URL\n URL.revokeObjectURL(blobUrl)\n\n return { success: true }\n }\n catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : storageM('storage_saveFileFailed'),\n }\n }\n}\n\n// ─── 辅助功能 ───\n\n/**\n * 从 File 对象获取文件扩展名\n *\n * 取文件名中最后一个 '.' 之后的部分,转为小写。\n * 若文件名无点号或以点号开头且无其他点号,返回空字符串。\n *\n * @param file - 浏览器 File 对象\n * @returns 扩展名(不含点),如 'png'、'tar.gz' 返回 'gz'\n *\n * @example\n * ```ts\n * getFileExtension(new File([], 'photo.PNG')) // 'png'\n * getFileExtension(new File([], '.gitignore')) // 'gitignore'\n * getFileExtension(new File([], 'noext')) // ''\n * ```\n */\nexport function getFileExtension(file: File): string {\n const name = file.name\n const lastDot = name.lastIndexOf('.')\n return lastDot > -1 ? name.slice(lastDot + 1).toLowerCase() : ''\n}\n\n/**\n * 根据文件扩展名获取 MIME 类型\n *\n * 内置常见扩展名映射(图片/文档/文本/音视频/压缩包),\n * 未匹配时返回 'application/octet-stream'。\n *\n * @param extension - 文件扩展名(不含点,大小写不敏感)\n * @returns MIME 类型字符串\n *\n * @example\n * ```ts\n * getMimeType('png') // 'image/png'\n * getMimeType('xyz') // 'application/octet-stream'\n * ```\n */\nexport function getMimeType(extension: string): string {\n return MIME_TYPES[extension.toLowerCase()] || MIME_TYPE_DEFAULT\n}\n\n/**\n * 格式化文件大小为人类可读字符串\n *\n * 使用 1024 进制,输出单位为 B/KB/MB/GB/TB。\n * 大于 1 B 时保留 2 位小数。\n *\n * @param bytes - 字节数(非负整数)\n * @returns 格式化后的字符串,如 '0 B'、'1.50 KB'、'2.30 MB'\n *\n * @example\n * ```ts\n * formatFileSize(0) // '0 B'\n * formatFileSize(1536) // '1.50 KB'\n * formatFileSize(1048576) // '1.00 MB'\n * ```\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0)\n return '0 B'\n\n const units = ['B', 'KB', 'MB', 'GB', 'TB']\n const i = Math.floor(Math.log(bytes) / Math.log(1024))\n const size = bytes / 1024 ** i\n\n return `${size.toFixed(i > 0 ? 2 : 0)} ${units[i]}`\n}\n"]}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @h-ai/storage — 前端存储客户端
|
|
3
|
+
*
|
|
4
|
+
* 提供前端通过签名 URL 直接上传下载文件的功能。
|
|
5
|
+
* @module storage-client
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 上传进度回调数据
|
|
9
|
+
*
|
|
10
|
+
* 由 `uploadWithPresignedUrl` 在上传过程中通过 `onProgress` 回调返回。
|
|
11
|
+
*/
|
|
12
|
+
interface UploadProgress {
|
|
13
|
+
/** 已上传字节数 */
|
|
14
|
+
loaded: number;
|
|
15
|
+
/** 总字节数(仅在 lengthComputable 时可靠) */
|
|
16
|
+
total: number;
|
|
17
|
+
/** 进度百分比 (0–100),四舍五入到整数 */
|
|
18
|
+
percent: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 前端上传选项
|
|
22
|
+
*
|
|
23
|
+
* 控制 `uploadWithPresignedUrl` 的行为,包括内容类型、进度监听和取消。
|
|
24
|
+
*/
|
|
25
|
+
interface ClientUploadOptions {
|
|
26
|
+
/** 内容类型(设置后会加到请求 Content-Type 头) */
|
|
27
|
+
contentType?: string;
|
|
28
|
+
/** 进度回调(设置后会使用 XMLHttpRequest;仅 lengthComputable=true 时回调) */
|
|
29
|
+
onProgress?: (progress: UploadProgress) => void;
|
|
30
|
+
/** AbortController 用于取消上传(abort 后返回 {success: false}) */
|
|
31
|
+
abortController?: AbortController;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 前端下载选项
|
|
35
|
+
*
|
|
36
|
+
* 控制 `downloadWithPresignedUrl` / `downloadAndSave` 的行为。
|
|
37
|
+
*/
|
|
38
|
+
interface ClientDownloadOptions {
|
|
39
|
+
/** 保存的文件名(仅 downloadAndSave 使用,默认为 'download') */
|
|
40
|
+
filename?: string;
|
|
41
|
+
/** AbortController 用于取消下载(abort 后返回 {success: false}) */
|
|
42
|
+
abortController?: AbortController;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 使用签名 URL 上传文件
|
|
46
|
+
*
|
|
47
|
+
* @param url - 后端生成的上传签名 URL
|
|
48
|
+
* @param data - 要上传的数据(File、Blob、ArrayBuffer 或字符串)
|
|
49
|
+
* @param options - 上传选项
|
|
50
|
+
* @returns 上传结果
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* // 获取签名 URL(通常从后端 API 获取)
|
|
55
|
+
* const response = await fetch('/api/storage/presign', {
|
|
56
|
+
* method: 'POST',
|
|
57
|
+
* body: JSON.stringify({ key: 'uploads/image.png', contentType: 'image/png' })
|
|
58
|
+
* })
|
|
59
|
+
* const { uploadUrl } = await response.json()
|
|
60
|
+
*
|
|
61
|
+
* // 使用签名 URL 上传
|
|
62
|
+
* const file = document.getElementById('fileInput').files[0]
|
|
63
|
+
* const result = await uploadWithPresignedUrl(uploadUrl, file, {
|
|
64
|
+
* contentType: 'image/png',
|
|
65
|
+
* onProgress: (p) => {
|
|
66
|
+
* // 在此更新进度条:p.percent
|
|
67
|
+
* }
|
|
68
|
+
* })
|
|
69
|
+
*
|
|
70
|
+
* if (result.success) {
|
|
71
|
+
* // 上传成功
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
declare function uploadWithPresignedUrl(url: string, data: File | Blob | ArrayBuffer | string, options?: ClientUploadOptions): Promise<{
|
|
76
|
+
success: boolean;
|
|
77
|
+
error?: string;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* 使用签名 URL 下载文件
|
|
81
|
+
*
|
|
82
|
+
* @param url - 后端生成的下载签名 URL
|
|
83
|
+
* @param options - 下载选项
|
|
84
|
+
* @returns 下载结果(成功时 data 为 Blob)
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* // 下载为 Blob
|
|
89
|
+
* const result = await downloadWithPresignedUrl(downloadUrl)
|
|
90
|
+
* if (result.success) {
|
|
91
|
+
* const blob = result.data
|
|
92
|
+
* // 处理 blob...
|
|
93
|
+
* }
|
|
94
|
+
*
|
|
95
|
+
* // 下载并保存为文件(浏览器环境)
|
|
96
|
+
* await downloadAndSave(downloadUrl, { filename: 'document.pdf' })
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
declare function downloadWithPresignedUrl(url: string, options?: ClientDownloadOptions): Promise<{
|
|
100
|
+
success: boolean;
|
|
101
|
+
data?: Blob;
|
|
102
|
+
error?: string;
|
|
103
|
+
}>;
|
|
104
|
+
/**
|
|
105
|
+
* 下载并保存到本地(浏览器环境)
|
|
106
|
+
*
|
|
107
|
+
* @param url - 下载 URL
|
|
108
|
+
* @param options - 下载选项
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* await downloadAndSave(presignedUrl, {
|
|
113
|
+
* filename: 'report.pdf'
|
|
114
|
+
* })
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
declare function downloadAndSave(url: string, options?: ClientDownloadOptions): Promise<{
|
|
118
|
+
success: boolean;
|
|
119
|
+
error?: string;
|
|
120
|
+
}>;
|
|
121
|
+
/**
|
|
122
|
+
* 从 File 对象获取文件扩展名
|
|
123
|
+
*
|
|
124
|
+
* 取文件名中最后一个 '.' 之后的部分,转为小写。
|
|
125
|
+
* 若文件名无点号或以点号开头且无其他点号,返回空字符串。
|
|
126
|
+
*
|
|
127
|
+
* @param file - 浏览器 File 对象
|
|
128
|
+
* @returns 扩展名(不含点),如 'png'、'tar.gz' 返回 'gz'
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* getFileExtension(new File([], 'photo.PNG')) // 'png'
|
|
133
|
+
* getFileExtension(new File([], '.gitignore')) // 'gitignore'
|
|
134
|
+
* getFileExtension(new File([], 'noext')) // ''
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
declare function getFileExtension(file: File): string;
|
|
138
|
+
/**
|
|
139
|
+
* 根据文件扩展名获取 MIME 类型
|
|
140
|
+
*
|
|
141
|
+
* 内置常见扩展名映射(图片/文档/文本/音视频/压缩包),
|
|
142
|
+
* 未匹配时返回 'application/octet-stream'。
|
|
143
|
+
*
|
|
144
|
+
* @param extension - 文件扩展名(不含点,大小写不敏感)
|
|
145
|
+
* @returns MIME 类型字符串
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* getMimeType('png') // 'image/png'
|
|
150
|
+
* getMimeType('xyz') // 'application/octet-stream'
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
declare function getMimeType(extension: string): string;
|
|
154
|
+
/**
|
|
155
|
+
* 格式化文件大小为人类可读字符串
|
|
156
|
+
*
|
|
157
|
+
* 使用 1024 进制,输出单位为 B/KB/MB/GB/TB。
|
|
158
|
+
* 大于 1 B 时保留 2 位小数。
|
|
159
|
+
*
|
|
160
|
+
* @param bytes - 字节数(非负整数)
|
|
161
|
+
* @returns 格式化后的字符串,如 '0 B'、'1.50 KB'、'2.30 MB'
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* formatFileSize(0) // '0 B'
|
|
166
|
+
* formatFileSize(1536) // '1.50 KB'
|
|
167
|
+
* formatFileSize(1048576) // '1.00 MB'
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
declare function formatFileSize(bytes: number): string;
|
|
171
|
+
|
|
172
|
+
export { type ClientDownloadOptions, type ClientUploadOptions, type UploadProgress, downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, uploadWithPresignedUrl };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export { ClientDownloadOptions, ClientUploadOptions, UploadProgress, downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, uploadWithPresignedUrl } from './client/index.js';
|
|
2
|
+
import { StorageFunctions } from './browser.js';
|
|
3
|
+
export { CopyOptions, DirOperations, DownloadOptions, FileMetadata, FileOperations, HaiStorageError, ListOptions, ListResult, LocalConfig, LocalConfigSchema, PresignOperations, PresignOptions, PresignOptionsSchema, PresignUploadOptions, PresignUploadOptionsSchema, S3Config, S3ConfigSchema, StorageConfig, StorageConfigInput, StorageConfigSchema, StorageProvider, StorageType, StorageTypeSchema, UploadOptions } from './browser.js';
|
|
4
|
+
import 'zod';
|
|
5
|
+
import '@h-ai/core';
|
|
6
|
+
import 'node:buffer';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @h-ai/storage — 存储服务主入口
|
|
10
|
+
*
|
|
11
|
+
* 提供统一的 `storage` 对象,管理运行时状态与生命周期。
|
|
12
|
+
* @module storage-main
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 存储服务单例
|
|
17
|
+
*
|
|
18
|
+
* 使用前必须先调用 `storage.init()` 初始化,传入 S3 或本地存储配置。
|
|
19
|
+
* 初始化后通过 `storage.file`、`storage.dir`、`storage.presign` 访问各操作接口。
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { storage } from '@h-ai/storage'
|
|
24
|
+
*
|
|
25
|
+
* // 初始化
|
|
26
|
+
* await storage.init({ type: 'local', root: '/data/uploads' })
|
|
27
|
+
*
|
|
28
|
+
* // 上传文件
|
|
29
|
+
* await storage.file.put('hello.txt', 'Hello World')
|
|
30
|
+
*
|
|
31
|
+
* // 关闭连接
|
|
32
|
+
* await storage.close()
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare const storage: StorageFunctions;
|
|
36
|
+
|
|
37
|
+
export { StorageFunctions, storage };
|