@h-ai/storage 1.0.0-beta.20 → 1.0.0-beta.22

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 CHANGED
@@ -211,9 +211,11 @@ Lifecycle: init takes backend/business configuration; use the module only after
211
211
 
212
212
  Local storage rejects file symlinks and directory junctions in key paths; reserved `.meta.json` / `.upload.tmp` suffixes are case insensitive. Overwrite put replaces old metadata, while copy inherits source metadata by default. The browser root exports schemas, types and client helpers, without the server singleton.
213
213
 
214
- - **S3**: requires `bucket / region / accessKeyId / secretAccessKey`; optional `createBucket / endpoint / publicEndpoint / forcePathStyle / prefix / publicUrl`.
214
+ - **S3** requires `bucket / region / accessKeyId / secretAccessKey`. Optional settings include `createBucket / endpoint / publicEndpoint / forcePathStyle / prefix / publicUrl / multipart`.
215
215
  - **Local**: requires `root`; optional `directoryMode / fileMode`.
216
216
 
217
+ Server-side S3 streaming uploads default to 5 MiB parts and two concurrent parts per upload. Set `multipart.partSizeBytes` and `multipart.queueSize` in `_storage.yml`, or use `HAI_STORAGE_MULTIPART_PARTSIZEBYTES=8388608` and `HAI_STORAGE_MULTIPART_QUEUESIZE=3` for 8 MiB and three concurrent parts. Part size accepts integer byte counts from 5 MiB to 5 GiB. Queue size accepts positive integers. Budget approximately part size times concurrency for each active upload. The settings take effect at initialization.
218
+
217
219
  MinIO and RustFS share the S3 provider with `type: 's3'`; no separate storage type is needed. For self-hosted instances, set the S3 API endpoint (usually port 9000) and enable path-style access.
218
220
 
219
221
  `createBucket` defaults to `false`, requiring an existing bucket. When enabled, `storage.init()` checks `HeadBucket` and creates the bucket only on an explicit 404. Existing buckets and objects are preserved. Permission, network, creation, and subsequent connection-check failures return failure, dispose clients, and leave storage uninitialized. A concurrent `BucketAlreadyOwnedByYou` is accepted only after another access check. Creation omits LocationConstraint for AWS `us-east-1` and uses the configured region elsewhere. Default self-hosted S3 deployments enable `HAI_STORAGE_CREATEBUCKET=true`; external S3 creation requires explicit permission.
@@ -252,7 +254,7 @@ For empty self-hosted bucket/region settings, deployment injects hai/us-east-1;
252
254
 
253
255
  #### Operation logging
254
256
 
255
- Enable file, directory, and presigned URL operation logs with `operationLog`. Logs are emitted at the actual Local/S3 provider operations, without a shared wrapper in `storage-main.ts`. File contents are never logged; uploads log only the byte length.
257
+ Local and S3 log writes by default. Set `operationLog.read` to enable read logs. Operation records use `debug` and follow the Core log level. Failed operations return the original error and emit `warn`, including failures while consuming a download stream. Initialization, connection, and shutdown failures emit `error`. Operation logs contain safe summaries such as the operation name, byte length when known, or item count. The presigned upload and download helpers report failures in the browser.
256
258
 
257
259
  ```yaml
258
260
  # config/_storage.yml
@@ -260,9 +262,8 @@ type: local
260
262
  root: ./data/uploads
261
263
  operationLog:
262
264
  read: false # file.get/head/exists, dir.list, presign.getUrl/publicUrl
263
- write: false # file.put/delete/deleteMany/copy, dir.delete, presign.putUrl
264
- maxLength: 1000 # 参数序列化后的最大输出长度,超出会截断
265
- level: debug # info | debug | trace,默认 debug
265
+ write: true # file.put/delete/deleteMany/copy, dir.delete, presign.putUrl
266
+ maxLength: 1000 # maximum summary characters, 0 omits the summary
266
267
  ```
267
268
 
268
269
  ```ts
@@ -273,11 +274,18 @@ await storage.init({
273
274
  read: true,
274
275
  write: true,
275
276
  maxLength: 500,
276
- level: 'debug',
277
277
  },
278
278
  })
279
279
  ```
280
280
 
281
+ Set `HAI_STORAGE_OPERATIONLOG_WRITE=false` to disable debug records for writes. When upgrading, remove `operationLog.level` and `HAI_STORAGE_OPERATIONLOG_LEVEL`. Set the global threshold through `HAI_CORE_LOGGING_LEVEL`.
282
+
283
+ For a failed `storage.file.put()`, inspect `error.code` and `error.message`. When available, `error.cause` holds the original error. A destination conflict with `ifNoneMatch: '*'` returns `ALREADY_EXISTS`. A source read failure, including a failed `createReadStream()`, returns `UPLOAD_FAILED`. Cancellation preserves `AbortSignal.reason`. Warnings include a reason summary and the underlying error code or HTTP status when available.
284
+
285
+ For S3 streaming uploads, `error.ext?.s3Operation` identifies the failed request when available. If multipart cleanup also fails, `error.cause` keeps the original failure and `error.ext?.cleanupError` holds the cleanup failure. The warning records the error codes and available storage request IDs for both.
286
+
287
+ A successful part response with a missing `ETag` produces a `MissingETag` cause. The warning also includes the part number.
288
+
281
289
  `storage.presign.putUrl()` currently supports signing options such as `contentType` and `expiresIn`, but no `maxSize` constraint.
282
290
 
283
291
  Local `storage.dir.list()` treats `prefix` as a key string prefix (for example, `reports/2026-`) and paginates in UTF-8 byte order. `maxKeys` is an integer from 1 to 1000, default 1000; files and common prefixes share that budget. `delimiter` folds keys at its first occurrence after the prefix into one `commonPrefixes` entry. An empty delimiter disables grouping; empty physical directories produce no entries. The grouping and counting rules follow [S3 ListObjectsV2](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html).
package/README.ja.md CHANGED
@@ -211,9 +211,11 @@ checksum の中止時は次のチャンクを待つ処理を終了し、入力
211
211
 
212
212
  Local は key 内のシンボリックリンクと junction を拒否し、予約拡張子 `.meta.json` / `.upload.tmp` は大文字小文字を区別せず保護します。put 上書きは旧メタデータを置換し、copy は既定で元を継承します。ブラウザー入口にサーバー単一インスタンスは含まれません。
213
213
 
214
- - **S3**:`bucket / region / accessKeyId / secretAccessKey` は必須。`createBucket / endpoint / publicEndpoint / forcePathStyle / prefix / publicUrl` は任意。
214
+ - **S3** の `bucket / region / accessKeyId / secretAccessKey` は必須です。`createBucket / endpoint / publicEndpoint / forcePathStyle / prefix / publicUrl / multipart` は任意です。
215
215
  - **Local**:`root` は必須。`directoryMode / fileMode` は任意。
216
216
 
217
+ サーバー側の S3 ストリーミングアップロードは、既定で各パート 5 MiB、1 回のアップロードにつき同時に 2 パートです。`_storage.yml` の `multipart.partSizeBytes` と `multipart.queueSize`、または環境変数 `HAI_STORAGE_MULTIPART_PARTSIZEBYTES=8388608` と `HAI_STORAGE_MULTIPART_QUEUESIZE=3` で 8 MiB と同時 3 パートを設定できます。パートサイズは 5 MiB から 5 GiB の整数バイト数、同時数は正の整数です。主なバッファー使用量はアップロードごとにパートサイズと同時数の積が目安です。設定は初期化時に適用されます。
218
+
217
219
  MinIO と RustFS は S3 Provider を共有し、`type: 's3'` を使います。専用のストレージ種別は不要です。自前のインスタンスでは S3 API endpoint(通常ポート 9000)とパス形式を設定します。
218
220
 
219
221
  `createBucket` の既定値は `false` で、既存バケットが必要です。`true` では `storage.init()` が HeadBucket を確認し、明示的な 404 の場合のみ作成します。既存バケットとオブジェクトは保持します。権限・ネットワーク・作成・後続の接続確認が失敗すると、失敗結果を返しクライアントを解放して未初期化状態を維持します。同時作成の `BucketAlreadyOwnedByYou` は再度アクセスを確認してから受け入れます。AWS `us-east-1` では LocationConstraint を省略し、他のリージョンでは設定値を渡します。標準の自前 S3 デプロイでは `HAI_STORAGE_CREATEBUCKET=true` を有効にし、外部 S3 は必要な権限に応じて明示的に設定します。
@@ -252,7 +254,7 @@ if (!initResult.success) {
252
254
 
253
255
  #### 操作ログ
254
256
 
255
- `operationLog` でファイル・ディレクトリー・署名付き URL の操作ログを有効にできます。Local/S3 Provider の実際の操作箇所で出力し、`storage-main.ts` で一括ラップしません。ファイル内容は記録せず、アップロードデータはバイト数のみを記録します。
257
+ Local と S3 は書き込み操作を既定で記録します。読み取りを記録するには `operationLog.read` を有効にします。操作ログは `debug` 固定で、Core のログレベルに従います。操作の失敗時は元のエラーを返し、`warn` を出力します。ダウンロードストリームの読み取り中の失敗も対象です。初期化、接続、終了時の失敗は `error` を出力します。操作名、既知のバイト数、件数などの安全な要約を記録します。署名付き URL のアップロードとダウンロードのヘルパーはブラウザーで失敗を記録します。
256
258
 
257
259
  ```yaml
258
260
  # config/_storage.yml
@@ -260,9 +262,8 @@ type: local
260
262
  root: ./data/uploads
261
263
  operationLog:
262
264
  read: false # file.get/head/exists, dir.list, presign.getUrl/publicUrl
263
- write: false # file.put/delete/deleteMany/copy, dir.delete, presign.putUrl
264
- maxLength: 1000 # 参数序列化后的最大输出长度,超出会截断
265
- level: debug # info | debug | trace,默认 debug
265
+ write: true # file.put/delete/deleteMany/copy, dir.delete, presign.putUrl
266
+ maxLength: 1000 # 要約の最大文字数、0 の場合は要約を省略
266
267
  ```
267
268
 
268
269
  ```ts
@@ -273,11 +274,18 @@ await storage.init({
273
274
  read: true,
274
275
  write: true,
275
276
  maxLength: 500,
276
- level: 'debug',
277
277
  },
278
278
  })
279
279
  ```
280
280
 
281
+ `HAI_STORAGE_OPERATIONLOG_WRITE=false` で書き込み操作の `debug` ログを無効にできます。アップグレード時は `operationLog.level` と `HAI_STORAGE_OPERATIONLOG_LEVEL` を削除し、全体のログレベルを `HAI_CORE_LOGGING_LEVEL` で設定してください。
282
+
283
+ `storage.file.put()` が失敗した場合は `error.code` と `error.message` を確認します。元のエラーがある場合は `error.cause` に保持します。`ifNoneMatch: '*'` で保存先が既に存在する場合は `ALREADY_EXISTS`、`createReadStream()` などのアップロード元を読み取れない場合は `UPLOAD_FAILED` を返します。キャンセル時は `AbortSignal.reason` を保持します。警告には失敗理由の要約と、取得できるエラーコードや HTTP ステータスが含まれます。
284
+
285
+ S3 のストリーミングアップロードでは、失敗したリクエストを取得できる場合に `error.ext?.s3Operation` に保持します。分割アップロードの後処理も失敗した場合は、元の原因を `error.cause`、後処理の原因を `error.ext?.cleanupError` に保持します。警告にはそれぞれのエラーコードと取得できるストレージのリクエスト ID を記録します。
286
+
287
+ パートの応答が成功でも `ETag` がない場合、エラーの原因は `MissingETag` になります。警告にはパート番号も記録されます。
288
+
281
289
  `storage.presign.putUrl()` は現在 `contentType` や `expiresIn` などの署名パラメーターに対応しますが、`maxSize` 制限は提供しません。
282
290
 
283
291
  ローカルの `storage.dir.list()` は `prefix` を key の文字列接頭辞(例:`reports/2026-`)として扱い、UTF-8 バイト順でページ分割します。`maxKeys` は 1–1000 の整数で既定値は 1000、`files` と `commonPrefixes` の合計件数に適用します。`delimiter` は prefix の後で最初に現れる区切りまでを一つの `commonPrefixes` 項目にまとめます。空文字列ではグループ化せず、空の物理ディレクトリーは項目になりません。グループ化と件数の規則は [S3 ListObjectsV2](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html) に基づきます。
package/README.md CHANGED
@@ -253,9 +253,11 @@ if (key.success) {
253
253
 
254
254
  Local 后端拒绝 key 路径中的文件符号链接和目录 junction,保留后缀 `.meta.json` / `.upload.tmp` 的保护不区分大小写。覆盖 put 会替换旧元数据,copy 默认继承源元数据;浏览器根入口仅提供 Schema、类型和客户端工具,不包含服务端 storage 单例。
255
255
 
256
- - **S3**:`bucket / region / accessKeyId / secretAccessKey` 必填,可选 `createBucket / endpoint / publicEndpoint / forcePathStyle / prefix / publicUrl`
256
+ - **S3**:`bucket / region / accessKeyId / secretAccessKey` 必填,可选 `createBucket / endpoint / publicEndpoint / forcePathStyle / prefix / publicUrl / multipart`
257
257
  - **Local**:`root` 必填,可选 `directoryMode / fileMode`
258
258
 
259
+ S3 服务端流式上传默认每片 5 MiB,单次上传最多并发 2 片。在 `_storage.yml` 使用 `multipart.partSizeBytes` 和 `multipart.queueSize`,也可设置 `HAI_STORAGE_MULTIPART_PARTSIZEBYTES=8388608` 和 `HAI_STORAGE_MULTIPART_QUEUESIZE=3`,分别表示 8 MiB 和 3 片并发。片大小接受 5 MiB 至 5 GiB 的整数,队列大小为正整数。每次上传的主要缓冲预算约为片大小乘以并发数,配置在初始化时应用。
260
+
259
261
  MinIO、RustFS 共用 S3 Provider,使用 `type: 's3'`,无需单独的存储类型。连接自建实例时指定 S3 API 端点(通常为 9000 端口)并启用路径风格。
260
262
 
261
263
  `createBucket` 默认 `false`,桶须预先存在。设为 `true` 后,`storage.init()` 先用 `HeadBucket` 检查,仅在明确返回 404 时创建缺失的桶;已有桶及对象保持不变。权限、网络、建桶或后续连接检查失败均返回失败,清理客户端且不发布就绪状态。并发创建只接受 `BucketAlreadyOwnedByYou`,并再次确认可访问。AWS `us-east-1` 创建时省略 LocationConstraint,其他区域按配置传入;默认自建 S3 部署通过 `HAI_STORAGE_CREATEBUCKET=true` 启用,外部 S3 按需授权。
@@ -294,7 +296,7 @@ if (!initResult.success) {
294
296
 
295
297
  #### 操作日志
296
298
 
297
- 可在存储配置中通过 `operationLog` 开启文件、目录和预签名 URL 操作日志。日志在 Local/S3 Provider 的真实操作处输出,不在 `storage-main.ts` 中做统一包装;文件内容不会写入日志,上传数据只记录字节长度。
299
+ Local 和 S3 默认记录写操作,读操作可通过 `operationLog.read` 开启。操作记录固定为 `debug`,由 Core 日志级别过滤。业务操作失败返回原错误并记录 `warn`,包括消费下载流时的失败。初始化、连接和关闭失败记录 `error`。操作日志只包含操作名、已知字节数或数量等安全摘要。浏览器签名上传和下载助手在浏览器记录失败。
298
300
 
299
301
  ```yaml
300
302
  # config/_storage.yml
@@ -302,9 +304,8 @@ type: local
302
304
  root: ./data/uploads
303
305
  operationLog:
304
306
  read: false # file.get/head/exists, dir.list, presign.getUrl/publicUrl
305
- write: false # file.put/delete/deleteMany/copy, dir.delete, presign.putUrl
306
- maxLength: 1000 # 参数序列化后的最大输出长度,超出会截断
307
- level: debug # info | debug | trace,默认 debug
307
+ write: true # file.put/delete/deleteMany/copy, dir.delete, presign.putUrl
308
+ maxLength: 1000 # 摘要字符上限,0 省略摘要
308
309
  ```
309
310
 
310
311
  ```ts
@@ -315,11 +316,18 @@ await storage.init({
315
316
  read: true,
316
317
  write: true,
317
318
  maxLength: 500,
318
- level: 'debug',
319
319
  },
320
320
  })
321
321
  ```
322
322
 
323
+ 设置 `HAI_STORAGE_OPERATIONLOG_WRITE=false` 可关闭写操作的 `debug` 记录。升级时移除原有的 `operationLog.level` 和 `HAI_STORAGE_OPERATIONLOG_LEVEL`,通过 `HAI_CORE_LOGGING_LEVEL` 调整全局阈值。
324
+
325
+ `storage.file.put()` 失败时通过 `error.code` 和 `error.message` 区分场景,底层原因可用时保留在 `error.cause`。`ifNoneMatch: '*'` 的目标冲突返回 `ALREADY_EXISTS`,读取 `createReadStream()` 等上传源失败返回 `UPLOAD_FAILED`。主动取消保留 `AbortSignal.reason`。对应告警包含原因摘要及可用的底层错误码和 HTTP 状态。
326
+
327
+ S3 流式上传的错误可通过 `error.ext?.s3Operation` 查看失败操作。分片写入失败后若清理也失败,`error.cause` 保留原始失败,`error.ext?.cleanupError` 记录清理失败。告警记录两个失败的错误码及可用的服务端请求编号。
328
+
329
+ 分片响应成功但缺少 `ETag` 时,原因为 `MissingETag`,告警还会记录分片编号。
330
+
323
331
  `storage.presign.putUrl()` 当前仅支持 `contentType` 与 `expiresIn` 等签名参数,不提供 `maxSize` 约束。
324
332
 
325
333
  本地 `storage.dir.list()` 将 `prefix` 作为 key 字符串前缀(如 `reports/2026-`),按 UTF-8 字节序分页。`maxKeys` 为 1–1000 的整数,默认 1000,`files` 与 `commonPrefixes` 共用该预算。`delimiter` 将前缀之后首次出现的分隔符及其后内容折叠为一个 `commonPrefixes` 条目;空字符串不折叠,空物理目录不生成条目。这些分组与计数规则参照 [S3 ListObjectsV2](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html).
package/dist/browser.d.ts CHANGED
@@ -45,7 +45,7 @@ type StorageType = z.infer<typeof StorageTypeSchema>;
45
45
  * ```ts
46
46
  * import { StorageOperationLogConfigSchema } from '@h-ai/storage'
47
47
  *
48
- * const value = StorageOperationLogConfigSchema.parse({ read: false, write: true, maxLength: 500, level: 'debug' })
48
+ * const value = StorageOperationLogConfigSchema.parse({ read: false, write: true, maxLength: 500 })
49
49
  * void value
50
50
  * ```
51
51
  */
@@ -53,11 +53,6 @@ declare const StorageOperationLogConfigSchema: z.ZodObject<{
53
53
  read: z.ZodDefault<z.ZodBoolean>;
54
54
  write: z.ZodDefault<z.ZodBoolean>;
55
55
  maxLength: z.ZodDefault<z.ZodNumber>;
56
- level: z.ZodDefault<z.ZodEnum<{
57
- info: "info";
58
- debug: "debug";
59
- trace: "trace";
60
- }>>;
61
56
  }, z.core.$strip>;
62
57
  /**
63
58
  * StorageOperationLogConfig 的数据约束与操作契约。
@@ -65,7 +60,7 @@ declare const StorageOperationLogConfigSchema: z.ZodObject<{
65
60
  * ```ts
66
61
  * import { StorageOperationLogConfigSchema, type StorageOperationLogConfig } from '@h-ai/storage'
67
62
  *
68
- * const value: StorageOperationLogConfig = StorageOperationLogConfigSchema.parse({ read: false, write: true, maxLength: 500, level: 'debug' })
63
+ * const value: StorageOperationLogConfig = StorageOperationLogConfigSchema.parse({ read: false, write: true, maxLength: 500 })
69
64
  * void value
70
65
  * ```
71
66
  */
@@ -84,15 +79,10 @@ type StorageOperationLogConfig = z.infer<typeof StorageOperationLogConfigSchema>
84
79
  * ```
85
80
  */
86
81
  declare const S3ConfigSchema: z.ZodObject<{
87
- operationLog: z.ZodOptional<z.ZodObject<{
82
+ operationLog: z.ZodPrefault<z.ZodObject<{
88
83
  read: z.ZodDefault<z.ZodBoolean>;
89
84
  write: z.ZodDefault<z.ZodBoolean>;
90
85
  maxLength: z.ZodDefault<z.ZodNumber>;
91
- level: z.ZodDefault<z.ZodEnum<{
92
- info: "info";
93
- debug: "debug";
94
- trace: "trace";
95
- }>>;
96
86
  }, z.core.$strip>>;
97
87
  type: z.ZodLiteral<"s3">;
98
88
  bucket: z.ZodString;
@@ -105,6 +95,10 @@ declare const S3ConfigSchema: z.ZodObject<{
105
95
  forcePathStyle: z.ZodDefault<z.ZodBoolean>;
106
96
  prefix: z.ZodDefault<z.ZodString>;
107
97
  publicUrl: z.ZodOptional<z.ZodString>;
98
+ multipart: z.ZodPrefault<z.ZodObject<{
99
+ partSizeBytes: z.ZodDefault<z.ZodNumber>;
100
+ queueSize: z.ZodDefault<z.ZodNumber>;
101
+ }, z.core.$strip>>;
108
102
  }, z.core.$strip>;
109
103
  /**
110
104
  * S3 配置类型(parse 后,带默认值的字段已填充,optional 字段仍可缺省)
@@ -130,15 +124,10 @@ type S3Config = z.infer<typeof S3ConfigSchema>;
130
124
  * ```
131
125
  */
132
126
  declare const LocalConfigSchema: z.ZodObject<{
133
- operationLog: z.ZodOptional<z.ZodObject<{
127
+ operationLog: z.ZodPrefault<z.ZodObject<{
134
128
  read: z.ZodDefault<z.ZodBoolean>;
135
129
  write: z.ZodDefault<z.ZodBoolean>;
136
130
  maxLength: z.ZodDefault<z.ZodNumber>;
137
- level: z.ZodDefault<z.ZodEnum<{
138
- info: "info";
139
- debug: "debug";
140
- trace: "trace";
141
- }>>;
142
131
  }, z.core.$strip>>;
143
132
  type: z.ZodLiteral<"local">;
144
133
  root: z.ZodString;
@@ -169,15 +158,10 @@ type LocalConfig = z.infer<typeof LocalConfigSchema>;
169
158
  * ```
170
159
  */
171
160
  declare const StorageConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
172
- operationLog: z.ZodOptional<z.ZodObject<{
161
+ operationLog: z.ZodPrefault<z.ZodObject<{
173
162
  read: z.ZodDefault<z.ZodBoolean>;
174
163
  write: z.ZodDefault<z.ZodBoolean>;
175
164
  maxLength: z.ZodDefault<z.ZodNumber>;
176
- level: z.ZodDefault<z.ZodEnum<{
177
- info: "info";
178
- debug: "debug";
179
- trace: "trace";
180
- }>>;
181
165
  }, z.core.$strip>>;
182
166
  type: z.ZodLiteral<"s3">;
183
167
  bucket: z.ZodString;
@@ -190,16 +174,15 @@ declare const StorageConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
190
174
  forcePathStyle: z.ZodDefault<z.ZodBoolean>;
191
175
  prefix: z.ZodDefault<z.ZodString>;
192
176
  publicUrl: z.ZodOptional<z.ZodString>;
177
+ multipart: z.ZodPrefault<z.ZodObject<{
178
+ partSizeBytes: z.ZodDefault<z.ZodNumber>;
179
+ queueSize: z.ZodDefault<z.ZodNumber>;
180
+ }, z.core.$strip>>;
193
181
  }, z.core.$strip>, z.ZodObject<{
194
- operationLog: z.ZodOptional<z.ZodObject<{
182
+ operationLog: z.ZodPrefault<z.ZodObject<{
195
183
  read: z.ZodDefault<z.ZodBoolean>;
196
184
  write: z.ZodDefault<z.ZodBoolean>;
197
185
  maxLength: z.ZodDefault<z.ZodNumber>;
198
- level: z.ZodDefault<z.ZodEnum<{
199
- info: "info";
200
- debug: "debug";
201
- trace: "trace";
202
- }>>;
203
186
  }, z.core.$strip>>;
204
187
  type: z.ZodLiteral<"local">;
205
188
  root: z.ZodString;
@@ -593,7 +576,9 @@ interface FileOperations {
593
576
  * @param key - 文件键/路径
594
577
  * @param data - 文件内容(Buffer、Uint8Array、字符串或异步字节流)
595
578
  * @param options - 上传选项
596
- * @returns 上传结果包含文件元数据
579
+ * @returns 成功包含文件元数据。失败包含错误码和消息,底层原因可用时保留在 error.cause。
580
+ * `ifNoneMatch: '*'` 的目标冲突返回 ALREADY_EXISTS,上传源读取失败返回 UPLOAD_FAILED。
581
+ * S3 流式上传可从 error.ext.s3Operation 获取失败操作,清理错误单独保存在 error.ext.cleanupError。
597
582
  * @example
598
583
  * ```ts
599
584
  * const result = await storage.file.put('uploads/a.txt', 'hello')
package/dist/browser.js CHANGED
@@ -1,5 +1,5 @@
1
- export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema } from './chunk-FK7KRCML.js';
2
- export { downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, uploadWithPresignedUrl } from './chunk-JAJYHGLI.js';
1
+ export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema } from './chunk-WOVOQXRY.js';
2
+ export { downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, uploadWithPresignedUrl } from './chunk-RXYTWURA.js';
3
3
  export { STORAGE_DELETE_MAX_KEYS, STORAGE_KEY_MAX_BYTES, StorageDeleteKeysSchema, StorageKeySchema, StoragePrefixSchema, StoragePresignExpiresInSchema, getStorageKeyIssue } from './chunk-6YG3N3CW.js';
4
4
  //# sourceMappingURL=browser.js.map
5
5
  //# sourceMappingURL=browser.js.map
@@ -1,6 +1,6 @@
1
1
  import { core } from '@h-ai/core';
2
2
 
3
- // src/storage-i18n.ts
3
+ // src/client/storage-client.ts
4
4
 
5
5
  // messages/en-US.json
6
6
  var en_US_default = {
@@ -36,6 +36,7 @@ var en_US_default = {
36
36
  storage_s3ClientNotInitialized: "S3 client not initialized",
37
37
  storage_s3ConfigNotInitialized: "S3 config not initialized",
38
38
  storage_uploadCanceled: "Upload canceled",
39
+ storage_uploadSourceReadFailed: "Could not read the upload source file or input stream",
39
40
  storage_downloadCanceled: "Download canceled",
40
41
  storage_uploadFailedStatus: "Upload failed (status {status}): {statusText}",
41
42
  storage_downloadFailedStatus: "Download failed (status {status}): {statusText}",
@@ -87,6 +88,7 @@ var ja_JP_default = {
87
88
  storage_s3ClientNotInitialized: "S3\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093",
88
89
  storage_s3ConfigNotInitialized: "S3\u8A2D\u5B9A\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093",
89
90
  storage_uploadCanceled: "\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u3092\u30AD\u30E3\u30F3\u30BB\u30EB\u3057\u307E\u3057\u305F",
91
+ storage_uploadSourceReadFailed: "\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u307E\u305F\u306F\u5165\u529B\u30B9\u30C8\u30EA\u30FC\u30E0\u3092\u8AAD\u307F\u53D6\u308C\u307E\u305B\u3093\u3067\u3057\u305F",
90
92
  storage_downloadCanceled: "\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3092\u30AD\u30E3\u30F3\u30BB\u30EB\u3057\u307E\u3057\u305F",
91
93
  storage_uploadFailedStatus: "\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08\u30B9\u30C6\u30FC\u30BF\u30B9{status}\uFF09: {statusText}",
92
94
  storage_downloadFailedStatus: "\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08\u30B9\u30C6\u30FC\u30BF\u30B9{status}\uFF09: {statusText}",
@@ -138,6 +140,7 @@ var zh_CN_default = {
138
140
  storage_s3ClientNotInitialized: "S3 \u5BA2\u6237\u7AEF\u672A\u521D\u59CB\u5316",
139
141
  storage_s3ConfigNotInitialized: "S3 \u914D\u7F6E\u672A\u521D\u59CB\u5316",
140
142
  storage_uploadCanceled: "\u4E0A\u4F20\u5DF2\u53D6\u6D88",
143
+ storage_uploadSourceReadFailed: "\u65E0\u6CD5\u8BFB\u53D6\u4E0A\u4F20\u6E90\u6587\u4EF6\u6216\u8F93\u5165\u6D41",
141
144
  storage_downloadCanceled: "\u4E0B\u8F7D\u5DF2\u53D6\u6D88",
142
145
  storage_uploadFailedStatus: "\u4E0A\u4F20\u5931\u8D25\uFF08\u72B6\u6001\u7801 {status}\uFF09\uFF1A{statusText}",
143
146
  storage_downloadFailedStatus: "\u4E0B\u8F7D\u5931\u8D25\uFF08\u72B6\u6001\u7801 {status}\uFF09\uFF1A{statusText}",
@@ -210,21 +213,25 @@ var MIME_TYPES = {
210
213
  var MIME_TYPE_DEFAULT = "application/octet-stream";
211
214
 
212
215
  // src/client/storage-client.ts
216
+ var logger = core.logger.child({ module: "storage", scope: "client" });
217
+ function transferFailure(operation, error, httpStatus) {
218
+ logger.warn("Storage client operation failed", { operation, httpStatus });
219
+ return { success: false, error };
220
+ }
213
221
  async function uploadWithPresignedUrl(url, data, options = {}) {
222
+ const operation = "uploadWithPresignedUrl";
223
+ logger.debug("Storage client operation started", { operation });
214
224
  try {
215
225
  const signal = options.abortController?.signal;
216
226
  if (signal?.aborted) {
217
- return {
218
- success: false,
219
- error: storageM("storage_uploadCanceled")
220
- };
227
+ return transferFailure(operation, storageM("storage_uploadCanceled"));
221
228
  }
222
229
  const body = data;
223
230
  if (options.onProgress) {
224
231
  const xhr = new XMLHttpRequest();
225
232
  return new Promise((resolve) => {
226
233
  let settled = false;
227
- function finish(result) {
234
+ function finish(result, httpStatus) {
228
235
  if (settled)
229
236
  return;
230
237
  settled = true;
@@ -234,7 +241,7 @@ async function uploadWithPresignedUrl(url, data, options = {}) {
234
241
  xhr.removeEventListener("error", onError);
235
242
  xhr.removeEventListener("abort", onAbort);
236
243
  xhr.removeEventListener("timeout", onError);
237
- resolve(result);
244
+ resolve(result.success ? result : transferFailure(operation, result.error, httpStatus));
238
245
  }
239
246
  function onAbort() {
240
247
  finish({
@@ -258,7 +265,7 @@ async function uploadWithPresignedUrl(url, data, options = {}) {
258
265
  error: storageM("storage_uploadFailedStatus", {
259
266
  params: { status: xhr.status, statusText: xhr.statusText }
260
267
  })
261
- });
268
+ }, xhr.status);
262
269
  }
263
270
  function onProgress(event) {
264
271
  if (settled || !event.lengthComputable)
@@ -310,54 +317,37 @@ async function uploadWithPresignedUrl(url, data, options = {}) {
310
317
  signal
311
318
  });
312
319
  if (!response.ok) {
313
- return {
314
- success: false,
315
- error: storageM("storage_uploadFailedStatus", {
316
- params: { status: response.status, statusText: response.statusText }
317
- })
318
- };
320
+ return transferFailure(operation, storageM("storage_uploadFailedStatus", {
321
+ params: { status: response.status, statusText: response.statusText }
322
+ }), response.status);
319
323
  }
320
324
  return { success: true };
321
325
  } catch (error) {
322
326
  if (error instanceof Error && error.name === "AbortError") {
323
- return {
324
- success: false,
325
- error: storageM("storage_uploadCanceled")
326
- };
327
+ return transferFailure(operation, storageM("storage_uploadCanceled"));
327
328
  }
328
- return {
329
- success: false,
330
- error: error instanceof Error ? error.message : storageM("storage_unknownError")
331
- };
329
+ return transferFailure(operation, error instanceof Error ? error.message : storageM("storage_unknownError"));
332
330
  }
333
331
  }
334
332
  async function downloadWithPresignedUrl(url, options = {}) {
333
+ const operation = "downloadWithPresignedUrl";
335
334
  try {
336
335
  const response = await fetch(url, {
337
336
  method: "GET",
338
337
  signal: options.abortController?.signal
339
338
  });
340
339
  if (!response.ok) {
341
- return {
342
- success: false,
343
- error: storageM("storage_downloadFailedStatus", {
344
- params: { status: response.status, statusText: response.statusText }
345
- })
346
- };
340
+ return transferFailure(operation, storageM("storage_downloadFailedStatus", {
341
+ params: { status: response.status, statusText: response.statusText }
342
+ }), response.status);
347
343
  }
348
344
  const blob = await response.blob();
349
345
  return { success: true, data: blob };
350
346
  } catch (error) {
351
347
  if (error instanceof Error && error.name === "AbortError") {
352
- return {
353
- success: false,
354
- error: storageM("storage_downloadCanceled")
355
- };
348
+ return transferFailure(operation, storageM("storage_downloadCanceled"));
356
349
  }
357
- return {
358
- success: false,
359
- error: error instanceof Error ? error.message : storageM("storage_unknownError")
360
- };
350
+ return transferFailure(operation, error instanceof Error ? error.message : storageM("storage_unknownError"));
361
351
  }
362
352
  }
363
353
  async function downloadAndSave(url, options = {}) {
@@ -376,10 +366,7 @@ async function downloadAndSave(url, options = {}) {
376
366
  URL.revokeObjectURL(blobUrl);
377
367
  return { success: true };
378
368
  } catch (error) {
379
- return {
380
- success: false,
381
- error: error instanceof Error ? error.message : storageM("storage_saveFileFailed")
382
- };
369
+ return transferFailure("downloadAndSave", error instanceof Error ? error.message : storageM("storage_saveFileFailed"));
383
370
  }
384
371
  }
385
372
  function getFileExtension(file) {
@@ -400,5 +387,5 @@ function formatFileSize(bytes) {
400
387
  }
401
388
 
402
389
  export { MIME_TYPES, MIME_TYPE_DEFAULT, downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, storageM, uploadWithPresignedUrl };
403
- //# sourceMappingURL=chunk-JAJYHGLI.js.map
404
- //# sourceMappingURL=chunk-JAJYHGLI.js.map
390
+ //# sourceMappingURL=chunk-RXYTWURA.js.map
391
+ //# sourceMappingURL=chunk-RXYTWURA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../messages/en-US.json","../messages/ja-JP.json","../messages/zh-CN.json","../src/storage-i18n.ts","../src/storage-mime.ts","../src/client/storage-client.ts"],"names":["core"],"mappings":"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","file":"chunk-RXYTWURA.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_fileChanged\": \"The file changed while reading metadata. Retry the operation.\",\n \"storage_metadataRequired\": \"File metadata is missing. Migrate the data or upload the file again.\",\n \"storage_metadataInvalid\": \"File metadata is invalid. Restore the complete backup or upload the file again.\",\n \"storage_presignUrlFailed\": \"Failed to generate presigned URL\",\n \"storage_presignUploadUrlFailed\": \"Failed to generate presigned upload URL\",\n \"storage_localPresignUnsupported\": \"Local storage does not support presigned URLs; upload or download through an authenticated application file endpoint\",\n \"storage_listOptionsInvalid\": \"Invalid list options: maxKeys must be an integer between 1 and 1000, and prefix and delimiter must be well-formed Unicode strings\",\n \"storage_listCursorInvalid\": \"Invalid list cursor or mismatched root, prefix or delimiter\",\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_publicEndpointInvalid\": \"Use an HTTP(S) endpoint with an optional proxy path containing letters, numbers, underscores, dots and hyphens. Credentials, query parameters and fragments are not allowed.\",\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\": \"File operation failed. Please try again later.\",\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_uploadSourceReadFailed\": \"Could not read the upload source file or input stream\",\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 internal files is not allowed: {key}\",\n \"storage_fileAlreadyExists\": \"File already exists: {key}\",\n \"storage_checksumFailed\": \"Failed to calculate checksum\",\n \"storage_checksumInvalid\": \"Invalid checksum or algorithm\",\n \"storage_streamInvalidChunk\": \"File stream must contain byte chunks\",\n \"storage_sharedBackendRequired\": \"Production mode requires a shared backend; local files and memory backends are for development or explicit end-to-end tests.\"\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_fileChanged\": \"ファイルの更新中です。もう一度お試しください\",\n \"storage_metadataRequired\": \"ファイルのメタデータがありません。データを移行するか再アップロードしてください\",\n \"storage_metadataInvalid\": \"ファイルのメタデータが破損しています。完全なバックアップを復元するか再アップロードしてください\",\n \"storage_presignUrlFailed\": \"署名付きURLの生成に失敗しました\",\n \"storage_presignUploadUrlFailed\": \"アップロード用署名付きURLの生成に失敗しました\",\n \"storage_localPresignUnsupported\": \"ローカルストレージは署名付き URL に対応していません。認証付きのアプリケーションのファイルエンドポイントでアップロードまたはダウンロードしてください\",\n \"storage_listOptionsInvalid\": \"一覧オプションが無効です。maxKeys は1〜1000の整数、prefix と delimiter は有効な Unicode 文字列で指定してください\",\n \"storage_listCursorInvalid\": \"一覧カーソルが無効か、ルート、プレフィックス、区切り文字が一致しません\",\n \"storage_s3ConfigTypeError\": \"設定の型が無効です。S3プロバイダーはtype=s3のみに対応しています\",\n \"storage_diskSpaceInsufficient\": \"ディスクの空き容量が不足しています\",\n \"storage_localConfigTypeError\": \"設定の型が無効です。ローカルプロバイダーはtype=localのみに対応しています\",\n \"storage_operationFailed\": \"操作に失敗しました: {error}\",\n \"storage_unsupportedType\": \"未対応のストレージの種類です: {type}\",\n \"storage_config_bucketRequired\": \"バケットは必須です\",\n \"storage_config_publicEndpointInvalid\": \"HTTP(S) のアクセス先を指定してください。プロキシのパスには英数字、アンダースコア、ドット、ハイフンを使用できます。認証情報、クエリ、フラグメントは含められません。\",\n \"storage_config_regionRequired\": \"リージョンは必須です\",\n \"storage_config_accessKeyIdRequired\": \"Access Key IDは必須です\",\n \"storage_config_secretAccessKeyRequired\": \"Secret Access Keyは必須です\",\n \"storage_config_rootRequired\": \"ルートパスは必須です\",\n \"storage_fileNotFound\": \"ファイルが見つかりません: {key}\",\n \"storage_permissionDenied\": \"アクセス権限がありません: {key}\",\n \"storage_pathIsDir\": \"パスはディレクトリです: {key}\",\n \"storage_ioError\": \"ファイルの読み書きに失敗しました。しばらくしてからもう一度お試しください\",\n \"storage_deleteManyFailed\": \"{count}個のファイルの削除に失敗しました\",\n \"storage_s3ClientNotInitialized\": \"S3クライアントが初期化されていません\",\n \"storage_s3ConfigNotInitialized\": \"S3設定が初期化されていません\",\n \"storage_uploadCanceled\": \"アップロードをキャンセルしました\",\n \"storage_uploadSourceReadFailed\": \"アップロード元のファイルまたは入力ストリームを読み取れませんでした\",\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 \"storage_fileAlreadyExists\": \"ファイルは既に存在します: {key}\",\n \"storage_checksumFailed\": \"チェックサムの計算に失敗しました\",\n \"storage_checksumInvalid\": \"チェックサムまたはアルゴリズムが無効です\",\n \"storage_streamInvalidChunk\": \"ファイルストリームにはバイトチャンクが必要です\",\n \"storage_sharedBackendRequired\": \"本番環境には共有バックエンドが必要です。ローカルファイルとメモリーバックエンドは開発環境または明示的な E2E テストでのみ使用できます。\"\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_fileChanged\": \"文件正在更新,请重试\",\n \"storage_metadataRequired\": \"文件元数据缺失,请迁移数据或重新上传文件\",\n \"storage_metadataInvalid\": \"文件元数据损坏,请恢复完整备份或重新上传文件\",\n \"storage_presignUrlFailed\": \"生成签名 URL 失败\",\n \"storage_presignUploadUrlFailed\": \"生成上传签名 URL 失败\",\n \"storage_localPresignUnsupported\": \"本地存储不支持预签名 URL,请通过应用的鉴权文件接口上传或下载\",\n \"storage_listOptionsInvalid\": \"列表参数无效:maxKeys 必须为 1 到 1000 的整数,prefix 和 delimiter 必须为有效的 Unicode 字符串\",\n \"storage_listCursorInvalid\": \"列表游标无效或与当前根目录、前缀、分隔符不匹配\",\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_publicEndpointInvalid\": \"预签名访问地址必须是 HTTP(S) 地址,可包含代理目录;目录仅使用字母、数字、下划线、点和连字符,不能包含凭据、查询参数或片段。\",\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\": \"文件读写失败,请稍后重试\",\n \"storage_deleteManyFailed\": \"删除 {count} 个文件失败\",\n \"storage_s3ClientNotInitialized\": \"S3 客户端未初始化\",\n \"storage_s3ConfigNotInitialized\": \"S3 配置未初始化\",\n \"storage_uploadCanceled\": \"上传已取消\",\n \"storage_uploadSourceReadFailed\": \"无法读取上传源文件或输入流\",\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 \"storage_fileAlreadyExists\": \"文件已存在:{key}\",\n \"storage_checksumFailed\": \"计算校验和失败\",\n \"storage_checksumInvalid\": \"校验和或算法不合法\",\n \"storage_streamInvalidChunk\": \"文件流必须包含字节块\",\n \"storage_sharedBackendRequired\": \"生产模式需要跨节点共享后端;本机文件或内存后端仅用于开发和显式端到端测试。\"\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 messagesJaJP from '../messages/ja-JP.json'\nimport messagesZhCN from '../messages/zh-CN.json'\n\n/**\n * 消息键类型(从 zh-CN 翻译文件自动推断)\n * @example 模块内部上下文\n * ```ts\n * const key: StorageMessageKey = 'storage_notInitialized'\n * void storageM(key)\n * ```\n */\ntype StorageMessageKey = keyof typeof messagesZhCN\n\n/**\n * 获取存储模块本地化文案\n *\n * 根据当前 locale 自动选择 zh-CN、en-US 或 ja-JP 的文案,\n * 支持带参数插值(通过 `{ params: { key: value } }`)。\n * @example 模块内部上下文\n * ```ts\n * void storageM('storage_notInitialized', { locale: 'en-US' })\n * ```\n */\nexport const storageM = core.i18n.createMessageGetter<StorageMessageKey>({\n 'zh-CN': messagesZhCN,\n 'en-US': messagesEnUS,\n 'ja-JP': messagesJaJP,\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 * @example 模块内部上下文\n * ```ts\n * const mime = MIME_TYPES['pdf'] ?? MIME_TYPE_DEFAULT\n * void mime\n * ```\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 * @example 模块内部上下文\n * ```ts\n * void MIME_TYPE_DEFAULT // application/octet-stream\n * ```\n */\nexport const MIME_TYPE_DEFAULT = 'application/octet-stream'\n","/**\n * @h-ai/storage — 前端存储客户端\n *\n * 提供前端通过签名 URL 直接上传下载文件的功能。\n * @module storage-client\n */\n\nimport { core } from '@h-ai/core'\nimport { storageM } from '../storage-i18n.js'\nimport { MIME_TYPE_DEFAULT, MIME_TYPES } from '../storage-mime.js'\n\nconst logger = core.logger.child({ module: 'storage', scope: 'client' })\n\nfunction transferFailure(operation: string, error: string, httpStatus?: number): { success: false, error: string } {\n logger.warn('Storage client operation failed', { operation, httpStatus })\n return { success: false, error }\n}\n\n// ─── 客户端类型 ───\n\n/**\n * 上传进度回调数据\n *\n * 由 `uploadWithPresignedUrl` 在上传过程中通过 `onProgress` 回调返回。\n * @example\n * ```ts\n * import type { UploadProgress } from '@h-ai/storage'\n *\n * const value: UploadProgress = { loaded: 50, total: 100, percent: 50 }\n * void value\n * ```\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 * @example\n * ```ts\n * import type { ClientUploadOptions } from '@h-ai/storage'\n *\n * const value: ClientUploadOptions = { contentType: 'text/plain', onProgress: progress => { document.title = `${progress.percent}%` } }\n * void value\n * ```\n */\nexport interface ClientUploadOptions {\n /** 内容类型(设置后会加到请求 Content-Type 头) */\n contentType?: string\n /** 进度回调(设置后会使用 XMLHttpRequest;仅 lengthComputable=true 时回调) */\n onProgress?: (progress: UploadProgress) => void\n /** 取消上传;预取消不发送请求,进行中取消返回失败,完成后移除本次监听。取消不能撤销远端已写入对象。 */\n abortController?: AbortController\n}\n\n/**\n * 前端下载选项\n *\n * 控制 `downloadWithPresignedUrl` / `downloadAndSave` 的行为。\n * @example\n * ```ts\n * import type { ClientDownloadOptions } from '@h-ai/storage'\n *\n * const value: ClientDownloadOptions = { filename: 'report.pdf', abortController: new AbortController() }\n * void value\n * ```\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 - 已成功获取的 S3 HTTP(S) 上传签名 URL;本地存储须使用应用的鉴权文件接口。\n * @param data - 要上传的数据(File、Blob、ArrayBuffer 或字符串)\n * @param options - 上传选项\n * @returns 上传结果;预取消不发送,进行中取消/网络失败/初始化异常结算为失败,终态移除本次监听。\n * 取消不代表远端对象已回滚;上传完成后的撤销由应用另行授权删除。\n * @example\n * ```ts\n * import { uploadWithPresignedUrl } from '@h-ai/storage/client'\n *\n * // url 由应用鉴权接口返回,且 S3 签名包含相同 Content-Type。\n * async function upload(url: string, file: File) {\n * const result = await uploadWithPresignedUrl(url, file, { contentType: file.type })\n * if (!result.success) throw new Error(result.error)\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 const operation = 'uploadWithPresignedUrl'\n logger.debug('Storage client operation started', { operation })\n try {\n const signal = options.abortController?.signal\n if (signal?.aborted) {\n return transferFailure(operation, storageM('storage_uploadCanceled'))\n }\n\n // 准备请求体\n const body: BodyInit = data\n\n // 使用 XMLHttpRequest 以支持上传进度\n if (options.onProgress) {\n const xhr = new XMLHttpRequest()\n return new Promise((resolve) => {\n let settled = false\n\n /**\n * 所有终态共用一次结算,先移除XHR/上传/signal监听,迟到事件不能改变结果。\n * 后续调用跳过,完成后取消不会再触碰旧XHR。\n * @param result - 本次最终结果,不推断远端是否已写对象。\n * @param result.success - 是否已成功完成本地请求。\n * @param result.error - 可选失败说明。\n * @param httpStatus - HTTP 响应状态,适用于服务端拒绝上传。\n * @example 模块内部上下文\n * ```ts\n * finish({ success: false, error: storageM('storage_networkError') })\n * ```\n */\n function finish(result: { success: true } | { success: false, error: string }, httpStatus?: number): void {\n if (settled)\n return\n settled = true\n signal?.removeEventListener('abort', onCancel)\n xhr.upload.removeEventListener('progress', onProgress)\n xhr.removeEventListener('load', onLoad)\n xhr.removeEventListener('error', onError)\n xhr.removeEventListener('abort', onAbort)\n xhr.removeEventListener('timeout', onError)\n resolve(result.success\n ? result\n : transferFailure(operation, result.error, httpStatus))\n }\n\n function onAbort(): void {\n finish({\n success: false,\n error: storageM('storage_uploadCanceled'),\n })\n }\n\n /**\n * signal取消先主动结算再终止XHR,覆盖未send时abort不发事件的原生行为。\n * 调用方在准备流程中检查settled后停止发送。\n * @example 模块内部上下文\n * ```ts\n * onCancel()\n * // 后续准备阶段读取settled,不再执行xhr.send。\n * ```\n */\n function onCancel(): void {\n // 未 send 的 XHR.abort 不一定发 abort 事件,取消方必须主动结算。\n onAbort()\n xhr.abort()\n }\n\n function onError(): void {\n finish({\n success: false,\n error: storageM('storage_networkError'),\n })\n }\n\n function onLoad(): void {\n finish(xhr.status >= 200 && xhr.status < 300\n ? { success: true }\n : {\n success: false,\n error: storageM('storage_uploadFailedStatus', {\n params: { status: xhr.status, statusText: xhr.statusText },\n }),\n }, xhr.status)\n }\n\n function onProgress(event: ProgressEvent): void {\n if (settled || !event.lengthComputable)\n return\n try {\n options.onProgress?.({\n loaded: event.loaded,\n total: event.total,\n percent: Math.round((event.loaded / event.total) * 100),\n })\n }\n catch (error) {\n finish({\n success: false,\n error: error instanceof Error\n ? error.message\n : storageM('storage_unknownError'),\n })\n xhr.abort()\n }\n }\n\n try {\n xhr.upload.addEventListener('progress', onProgress)\n xhr.addEventListener('load', onLoad)\n xhr.addEventListener('error', onError)\n xhr.addEventListener('abort', onAbort)\n xhr.addEventListener('timeout', onError)\n signal?.addEventListener('abort', onCancel, { once: true })\n xhr.open('PUT', url)\n if (settled)\n return\n if (options.contentType)\n xhr.setRequestHeader('Content-Type', options.contentType)\n // 覆盖从预检查到监听安装/请求准备之间发生的取消,不再进入 send。\n if (signal?.aborted) {\n onCancel()\n return\n }\n xhr.send(body)\n }\n catch (error) {\n finish({\n success: false,\n error: error instanceof Error\n ? error.message\n : storageM('storage_unknownError'),\n })\n xhr.abort()\n }\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,\n })\n\n if (!response.ok) {\n return transferFailure(operation, storageM('storage_uploadFailedStatus', {\n params: { status: response.status, statusText: response.statusText },\n }), response.status)\n }\n\n return { success: true }\n }\n catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n return transferFailure(operation, storageM('storage_uploadCanceled'))\n }\n return transferFailure(operation, error instanceof Error\n ? error.message\n : storageM('storage_unknownError'))\n }\n}\n\n// ─── 下载功能 ───\n\n/**\n * 使用签名 URL 下载文件\n *\n * @param url - 后端生成的下载签名 URL\n * @param options - 下载选项\n * @returns 下载结果(成功时 data 为 Blob)\n * @example\n * ```ts\n * import { downloadWithPresignedUrl } from '@h-ai/storage/client'\n *\n * async function download(url: string) {\n * const result = await downloadWithPresignedUrl(url)\n * if (!result.success || !result.data) throw new Error(result.error ?? 'Empty download')\n * return result.data\n * }\n * ```\n */\nexport async function downloadWithPresignedUrl(\n url: string,\n options: ClientDownloadOptions = {},\n): Promise<{ success: boolean, data?: Blob, error?: string }> {\n const operation = 'downloadWithPresignedUrl'\n try {\n const response = await fetch(url, {\n method: 'GET',\n signal: options.abortController?.signal,\n })\n\n if (!response.ok) {\n return transferFailure(operation, storageM('storage_downloadFailedStatus', {\n params: { status: response.status, statusText: response.statusText },\n }), response.status)\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 transferFailure(operation, storageM('storage_downloadCanceled'))\n }\n return transferFailure(operation, error instanceof Error\n ? error.message\n : storageM('storage_unknownError'))\n }\n}\n\n/**\n * 下载并保存到本地(浏览器环境)\n *\n * @param url - 下载 URL\n * @param options - 下载选项\n * @returns 声明返回类型对应的操作结果。\n * @example\n * ```ts\n * import { downloadAndSave } from '@h-ai/storage/client'\n *\n * async function save(url: string) {\n * const result = await downloadAndSave(url, { filename: 'report.pdf' })\n * if (!result.success) throw new Error(result.error)\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 transferFailure('downloadAndSave', error instanceof Error\n ? error.message\n : storageM('storage_saveFileFailed'))\n }\n}\n\n// ─── 辅助功能 ───\n\n/**\n * 从 File 对象获取文件扩展名\n *\n * 取文件名中最后一个 '.' 之后的部分,转为小写。\n * 没有点号或以点号结尾时返回空字符串;仅前导点号的文件名按其后文本返回扩展名。\n *\n * @param file - 浏览器 File 对象\n * @returns 扩展名(不含点),如 'png'、'tar.gz' 返回 'gz'\n * @example\n * ```ts\n * import { getFileExtension } from '@h-ai/storage/client'\n *\n * const extension = getFileExtension(new File(['hello'], 'report.txt'))\n * void extension\n * ```\n */\nexport function getFileExtension(file: File): string {\n const name = file.name\n const lastDot = name.lastIndexOf('.')\n return lastDot > -1\n ? name.slice(lastDot + 1).toLowerCase()\n : ''\n}\n\n/**\n * 根据文件扩展名获取 MIME 类型\n *\n * 内置常见扩展名映射(图片/文档/文本/音视频/压缩包),\n * 未匹配时返回 'application/octet-stream'。\n *\n * @param extension - 文件扩展名(不含点,大小写不敏感)\n * @returns MIME 类型字符串\n * @example\n * ```ts\n * import { getMimeType } from '@h-ai/storage/client'\n *\n * const mime = getMimeType('pdf')\n * void mime\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 * 非零值使用对应单位并保留 2 位小数。\n *\n * @param bytes - 字节数(非负整数)\n * @returns 格式化后的字符串,如 '0 B'、'1.50 KB'、'2.30 MB'\n * @example\n * ```ts\n * import { formatFileSize } from '@h-ai/storage/client'\n *\n * void formatFileSize(1536)\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\n ? 2\n : 0)} ${units[i]}`\n}\n"]}
@@ -1,17 +1,22 @@
1
- import { storageM } from './chunk-JAJYHGLI.js';
1
+ import { storageM } from './chunk-RXYTWURA.js';
2
2
  import { StoragePresignExpiresInSchema } from './chunk-6YG3N3CW.js';
3
3
  import { z } from 'zod';
4
4
  import { core } from '@h-ai/core';
5
5
 
6
+ var S3_MIN_PART_SIZE_BYTES = 5 * 1024 ** 2;
7
+ var S3_MAX_PART_SIZE_BYTES = 5 * 1024 ** 3;
6
8
  var StorageTypeSchema = z.enum(["s3", "local"]);
7
9
  var StorageOperationLogConfigSchema = z.object({
10
+ /** 以 debug 记录读取操作,默认关闭。 */
8
11
  read: z.boolean().default(false),
9
- write: z.boolean().default(false),
10
- maxLength: z.number().int().min(0).default(1e3),
11
- level: z.enum(["info", "debug", "trace"]).default("debug")
12
+ /** 以 debug 记录写入操作,默认开启。 */
13
+ write: z.boolean().default(true),
14
+ /** 诊断摘要的字符上限,0 省略摘要。 */
15
+ maxLength: z.number().int().min(0).default(1e3)
12
16
  });
13
17
  var OperationLogConfigFieldSchema = {
14
- operationLog: StorageOperationLogConfigSchema.optional()
18
+ /** 文件、目录和签名操作的 debug 日志开关与安全摘要长度。 */
19
+ operationLog: StorageOperationLogConfigSchema.prefault({})
15
20
  };
16
21
  var S3ConfigSchema = z.object({
17
22
  /** 存储类型 */
@@ -45,6 +50,13 @@ var S3ConfigSchema = z.object({
45
50
  prefix: z.string().default(""),
46
51
  /** 公开访问基础 URL(用于生成公开 URL) */
47
52
  publicUrl: z.string().url().optional(),
53
+ /** S3 服务端流式上传的分片设置。 */
54
+ multipart: z.object({
55
+ /** 每片字节数,默认 5 MiB,范围 5 MiB 至 5 GiB。 */
56
+ partSizeBytes: z.number().int().min(S3_MIN_PART_SIZE_BYTES).max(S3_MAX_PART_SIZE_BYTES).default(S3_MIN_PART_SIZE_BYTES),
57
+ /** 单次上传的最大并发片数,默认 2。 */
58
+ queueSize: z.number().int().min(1).default(2)
59
+ }).prefault({}),
48
60
  ...OperationLogConfigFieldSchema
49
61
  });
50
62
  var LocalConfigSchema = z.object({
@@ -99,5 +111,5 @@ var StorageErrorInfo = {
99
111
  var HaiStorageError = core.error.buildHaiErrorsDef("storage", StorageErrorInfo);
100
112
 
101
113
  export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema };
102
- //# sourceMappingURL=chunk-FK7KRCML.js.map
103
- //# sourceMappingURL=chunk-FK7KRCML.js.map
114
+ //# sourceMappingURL=chunk-WOVOQXRY.js.map
115
+ //# sourceMappingURL=chunk-WOVOQXRY.js.map