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

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
@@ -4,34 +4,11 @@
4
4
 
5
5
  [Framework home](../../README.en.md) · Related modules: [@h-ai/core](../core/README.en.md)
6
6
 
7
- ## Introduction
8
-
9
7
  An object storage module with a unified `storage` entry point for S3-compatible storage and local filesystems.
10
8
 
11
- Connections become available only after a successful health check. Failed initialization disposes its candidate; `error.ext.cleanupPending === true` identifies cleanup that still needs retry. `storage.close()` returns `Promise<HaiResult<void>>`, waits for initialization, and coalesces concurrent close calls. Once cleanup starts, `isInitialized` is false and configuration/operations are unavailable. A failed cleanup retains the resource: the next `close()` or `init()` retries disposal before allocating a replacement. Always inspect the close result.
12
-
13
- ```ts
14
- const closed = await storage.close()
15
- if (!closed.success) {
16
- // Retry once after addressing the cleanup failure; report another failure.
17
- const retried = await storage.close()
18
- if (!retried.success)
19
- throw new Error(retried.error.message)
20
- }
21
- ```
22
-
23
- S3 disposes its normal client and its separate batch-delete client (SDK request retries disabled) independently. One failure does not prevent the other cleanup; successfully disposed clients are not destroyed again.
24
-
25
- ## Core features
26
-
27
- - S3 protocol (AWS S3 / MinIO / RustFS / OSS, etc.)
28
- - Local filesystem
29
-
30
- ## Getting started
31
-
32
- ### Quick start
9
+ ## Quick Start
33
10
 
34
- #### Node.js server
11
+ ### Node.js server
35
12
 
36
13
  ```ts
37
14
  import { storage } from '@h-ai/storage'
@@ -83,6 +60,72 @@ await uploadWithPresignedUrl(signed.data.url, file)
83
60
 
84
61
  > Note: `@h-ai/storage` does not include HTTP routes. Implement the signed URL API in the application layer.
85
62
 
63
+ ## Features
64
+
65
+ Connections become available only after a successful health check. Failed initialization disposes its candidate; `error.ext.cleanupPending === true` identifies cleanup that still needs retry. `storage.close()` returns `Promise<HaiResult<void>>`, waits for initialization, and coalesces concurrent close calls. Once cleanup starts, `isInitialized` is false and configuration/operations are unavailable. A failed cleanup retains the resource: the next `close()` or `init()` retries disposal before allocating a replacement. Always inspect the close result.
66
+
67
+ ```ts
68
+ const closed = await storage.close()
69
+ if (!closed.success) {
70
+ // Retry once after addressing the cleanup failure; report another failure.
71
+ const retried = await storage.close()
72
+ if (!retried.success)
73
+ throw new Error(retried.error.message)
74
+ }
75
+ ```
76
+
77
+ S3 disposes its normal client and its separate batch-delete client (SDK request retries disabled) independently. One failure does not prevent the other cleanup; successfully disposed clients are not destroyed again.
78
+
79
+ ### Operations and results
80
+
81
+ Use the complete initialization flow in this section first. The host validates and authorizes example business values; check success before reading data from asynchronous domain results. Here void means completion without data, null means absent; synchronous factories/tools return the listed type directly.
82
+
83
+ #### storage.file
84
+
85
+ Object lifecycle uses raw keys, bytes/streams and options: put/copy/head yield metadata, get Buffer, getStream an async byte stream, exists boolean, delete void; deleteMany reports per-key confirmed progress.
86
+
87
+ | Entry | Input parameters | Successful result | Call example |
88
+ | ------------------------- | --------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------- |
89
+ | `storage.file.put` | key, data, options | `FileMetadata` | `await storage.file.put('report.txt', 'text', { contentType: 'text/plain' })` |
90
+ | `storage.file.get` | key, options | `Buffer<ArrayBufferLike>` | `await storage.file.get('report.txt')` |
91
+ | `storage.file.getStream` | key, options | `AsyncIterable<Uint8Array<ArrayBufferLike>>` | `await storage.file.getStream('report.txt')` |
92
+ | `storage.file.head` | key | `FileMetadata` | `await storage.file.head('report.txt')` |
93
+ | `storage.file.exists` | key | `boolean` | `await storage.file.exists('report.txt')` |
94
+ | `storage.file.copy` | sourceKey, destKey, options | `FileMetadata` | `await storage.file.copy('report.txt', 'archive/report.txt')` |
95
+ | `storage.file.delete` | key | `void` | `await storage.file.delete('report.txt')` |
96
+ | `storage.file.deleteMany` | keys | `StorageDeleteResult` | `await storage.file.deleteMany(['report.txt', 'archive/report.txt'])` |
97
+
98
+ #### storage.dir
99
+
100
+ Browse/clean by prefix: list takes prefix/pagination and returns files with continuation; delete takes a prefix and yields void without preventing concurrent new objects.
101
+
102
+ | Entry | Input parameters | Successful result | Call example |
103
+ | -------------------- | ---------------- | ----------------- | ------------------------------------------------------------- |
104
+ | `storage.dir.list` | options | `ListResult` | `await storage.dir.list({ prefix: 'reports/', maxKeys: 20 })` |
105
+ | `storage.dir.delete` | prefix | `void` | `await storage.dir.delete('reports/')` |
106
+
107
+ #### storage.presign
108
+
109
+ Direct S3 upload/download: key plus expiry/response options yield a URL HaiResult; publicUrl synchronously yields a URL/null and grants no authorization.
110
+
111
+ | Entry | Input parameters | Successful result | Call example |
112
+ | --------------------------- | ---------------- | ----------------- | ------------------------------------------------------------------------------------------- |
113
+ | `storage.presign.getUrl` | key, options | `string` | `await storage.presign.getUrl('report.txt', { expiresIn: 300 })` |
114
+ | `storage.presign.putUrl` | key, options | `string` | `await storage.presign.putUrl('report.txt', { expiresIn: 300, contentType: 'text/plain' })` |
115
+ | `storage.presign.publicUrl` | key | `string \| null` | `storage.presign.publicUrl('report.txt')` |
116
+
117
+ #### storage.checksum
118
+
119
+ Stateless integrity: data plus algorithm/encoding yields a digest, expected digest yields matching boolean; a stream is consumed once.
120
+
121
+ | Entry | Input parameters | Successful result | Call example |
122
+ | --------------------------- | ----------------------- | ----------------- | ------------------------------------------------------ |
123
+ | `storage.checksum.generate` | data, options | `string` | `await storage.checksum.generate('text')` |
124
+ | `storage.checksum.verify` | data, expected, options | `boolean` | `await storage.checksum.verify(bytes, expectedDigest)` |
125
+
126
+ - S3 protocol (AWS S3 / MinIO / RustFS / OSS, etc.)
127
+ - Local filesystem
128
+
86
129
  ### HTTP API contracts
87
130
 
88
131
  Public HTTP contracts come from `@h-ai/api-contract`; `createStorageProcedures({ storage, authorize })` in `@h-ai/serv` binds them to this module.
@@ -112,6 +155,10 @@ Presigning requires S3. Local `getUrl()` / `putUrl()` return `HaiStorageError.UN
112
155
 
113
156
  `{ ifNoneMatch: '*' }` uses standard S3 conditional writes or atomic local publication to reject overwrites with `ALREADY_EXISTS`. Unsupported S3 conditions fail without falling back to an overwrite. Failed/cancelled uploads clean temporary files or unfinished parts; cancellation cannot undo an already published object.
114
157
 
158
+ Local prepares metadata bound to the content file identity in the reserved `.hai-storage-meta/` directory beside the file, then atomically publishes the content as the commit point. Metadata preparation failure cannot create or overwrite content. Default metadata has an explicit empty record and never inherits old object fields. Failure to clean a known old generation after publication is diagnostic only; concurrent writes or crashes may leave internal records that cannot safely be reclaimed, which are not deleted speculatively. Every path segment reserves `.meta.json`, `.upload.tmp` and `.hai-storage-meta`. Generation records are the only metadata format; a missing valid record returns an explicit error. Rebuild objects through `file.put()` with trusted content and business metadata. Back up content together with internal records and prohibit direct writes that bypass this format.
159
+
160
+ S3 `file.copy()` encodes each source path segment, preserving Unicode, spaces and URL delimiters as part of the object key. Pass raw keys without pre-encoding.
161
+
115
162
  `file.deleteMany(keys)` returns `StorageDeleteResult`. Success data is `{ deletedKeys, failures: [] }`; any unconfirmed deletion returns `OPERATION_FAILED` with progress in `error.ext?.deleteMany` (precondition errors such as uninitialized storage may omit progress). Duplicate inputs are processed once, and returned keys exclude the configured prefix. S3 batches contain at most 1000 keys and verify every `Deleted`/`Errors` receipt. HTTP 200 alone is insufficient; missing, duplicate or unknown receipts produce `unknown` outcomes. Local deletion uses at most 32 concurrent operations.
116
163
 
117
164
  Only explicit `InternalError / ServiceUnavailable / SlowDown / RequestTimeout` items receive up to three attempts. Confirmed successes and permission failures are not resent. A dedicated `DeleteObjects` client disables SDK request retries. Network interruption immediately produces `unknown`; later unsent batches are `notAttempted`, without replaying unknown outcomes. `retryable` describes the fault category; callers must reconcile unknown results and concurrent writes before retrying. `dir.delete()` propagates failures but cannot prevent concurrent new objects. S3 deletes omit VersionId and may create delete markers in versioned buckets; historical versions are not purged.
@@ -146,7 +193,23 @@ Cancelling a checksum stops waiting for the next chunk and requests closure of t
146
193
  - `getMimeType(extension)`
147
194
  - `formatFileSize(bytes)`
148
195
 
149
- ### Configuration
196
+ ### Production shared state
197
+
198
+ Production requires shared S3 or a compatible service; local is for development/explicit tests. Applications own object authorization and conditional read-modify-write commits. Initialization checks apply when NODE_ENV or HAI_CORE_ENV is production; only explicit HAI_E2E=1/HAI_E2E_MOCK=1 tests are exempt. [State, concurrency and recovery (Chinese)](../../.agents/instructions/distributed-state.md).
199
+
200
+ ### Input limits and HTTP capabilities
201
+
202
+ Object keys are limited to 1024 UTF-8 bytes and reject absolute paths, backslashes, control characters and dot segments. Each public HTTP batch delete accepts 1–1000 entries. `@h-ai/storage/schemas` exposes the shared browser-safe rules. Presign expiry is an integer from 1 to 604800 seconds (Node default 3600, HTTP default 900). Upload signing accepts only key/contentType/expiresIn; contentLength/maxSize/metadata/checksum are unsupported. Invalid input returns HTTP 400 and the typed client maps it to VALIDATION_FAILED. FileUpload limits guide file selection; applications must validate actual objects on the server. Node streaming/checksum/conditional-write options have no default HTTP endpoints. See the [capability and authorization matrix (Chinese)](README.md#存储输入约束与跨端能力).
203
+
204
+ ### Browser upload cancellation
205
+
206
+ `uploadWithPresignedUrl(url, data, { onProgress, abortController })` uses native XHR for progress and fetch when `onProgress` is omitted. An already aborted controller sends no request. Cancellation during setup or upload, external XHR abort, network/timeout errors, and progress callback exceptions settle once with a failure and remove this operation's listeners. Aborting after completion does not touch the old XHR. Cancellation only stops the local request; it cannot roll back an object already written remotely. Check the object state and authorize deletion separately.
207
+
208
+ ## Configuration
209
+
210
+ Lifecycle: init takes backend/business configuration; use the module only after success. Await close before releasing borrowed dependencies. Follow the quick-start example and public signatures for exact return values.
211
+
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.
150
213
 
151
214
  - **S3**: requires `bucket / region / accessKeyId / secretAccessKey`; optional `createBucket / endpoint / publicEndpoint / forcePathStyle / prefix / publicUrl`.
152
215
  - **Local**: requires `root`; optional `directoryMode / fileMode`.
@@ -157,6 +220,10 @@ MinIO and RustFS share the S3 provider with `type: 's3'`; no separate storage ty
157
220
 
158
221
  Use `createBucket: ${HAI_STORAGE_CREATEBUCKET:false}` in YAML for environment control; direct `storage.init()` input requires a boolean.
159
222
 
223
+ Set `prefix: my-app/uploads/` in `config/_storage.yml`, or set `HAI_STORAGE_PREFIX=my-app/uploads/` in a Hai app that registers `StorageConfigSchema`. Uploading `report.pdf` stores it under `my-app/uploads/report.pdf`. The startup probe and default listing both query `my-app/uploads/`, with or without the trailing slash in the configuration. An empty prefix uses the bucket root. Changing the prefix leaves existing objects at their current keys.
224
+
225
+ To scope application credentials, limit object permissions to the object ARN pattern `arn:aws:s3:::my-bucket/my-app/uploads/*` and apply the `s3:prefix` condition `my-app/uploads/*` to `s3:ListBucket`. Use a pre-created bucket with `createBucket: false` to avoid the bucket-level `HeadBucket` check used in bucket creation mode.
226
+
160
227
  ```ts
161
228
  const initResult = await storage.init({
162
229
  type: 's3',
@@ -173,7 +240,7 @@ if (!initResult.success) {
173
240
  }
174
241
  ```
175
242
 
176
- #### Internal connections and browser access
243
+ ### Internal connections and browser access
177
244
 
178
245
  `endpoint` handles server-side CRUD, bucket creation and health checks. Optional `publicEndpoint` selects the external address for `presign.getUrl/putUrl`; omission preserves endpoint-based signing. It accepts a proxy directory such as `https://app.example.com/delivery/files`. Path segments allow letters, numbers, underscores, dots and hyphens, excluding dot segments, repeated slashes and encoded separators. Do not include the bucket, credentials, query or fragment. `publicUrl` constructs public-resource URLs without authorizing private objects.
179
246
 
@@ -228,7 +295,13 @@ do {
228
295
  } while (continuationToken)
229
296
  ```
230
297
 
231
- ### Error handling
298
+ The host loads and validates configuration before passing it to this module. Keep secrets in private server environment files. Select the backend, capacity, deadlines and logging for the actual use case; reinitialize and check the result after changes.
299
+
300
+ [Per-option purpose, use cases and examples](../../docs/usage/configuration/data.md).
301
+
302
+ ## Error Handling
303
+
304
+ Check result.success for domain operations. Handle failures with the module error definitions and safe messages; retain codes, correlation IDs and sanitized causes in server logs. Synchronous tools, factories, stream iteration and lifecycle calls follow their own signatures. A completed Promise alone is not business success. See each feature for recovery limits.
232
305
 
233
306
  ```ts
234
307
  import { HaiStorageError, storage } from '@h-ai/storage'
@@ -253,19 +326,7 @@ Common error codes:
253
326
  | `HaiStorageError.INVALID_LIST_OPTIONS` | `hai:storage:019` | Invalid local list options or cursor, including mismatched query scope |
254
327
  | `HaiStorageError.CONFIG_ERROR` | `hai:storage:012` | Invalid configuration |
255
328
 
256
- ## Production shared state
257
-
258
- Production requires shared S3 or a compatible service; local is for development/explicit tests. Applications own object authorization and conditional read-modify-write commits. Initialization checks apply when NODE_ENV or HAI_CORE_ENV is production; only explicit HAI_E2E=1/HAI_E2E_MOCK=1 tests are exempt. [State, concurrency and recovery (Chinese)](../../.agents/instructions/distributed-state.md)。
259
-
260
- ## Input limits and HTTP capabilities
261
-
262
- Object keys are limited to 1024 UTF-8 bytes and reject absolute paths, backslashes, control characters and dot segments. Each public HTTP batch delete accepts 1–1000 entries. `@h-ai/storage/schemas` exposes the shared browser-safe rules. Presign expiry is an integer from 1 to 604800 seconds (Node default 3600, HTTP default 900). Upload signing accepts only key/contentType/expiresIn; contentLength/maxSize/metadata/checksum are unsupported. Invalid input returns HTTP 400 and the typed client maps it to VALIDATION_FAILED. FileUpload limits guide file selection; applications must validate actual objects on the server. Node streaming/checksum/conditional-write options have no default HTTP endpoints. See the [capability and authorization matrix (Chinese)](README.md#存储输入约束与跨端能力).
263
-
264
- ## Browser upload cancellation
265
-
266
- `uploadWithPresignedUrl(url, data, { onProgress, abortController })` uses native XHR for progress and fetch when `onProgress` is omitted. An already aborted controller sends no request. Cancellation during setup or upload, external XHR abort, network/timeout errors, and progress callback exceptions settle once with a failure and remove this operation's listeners. Aborting after completion does not touch the old XHR. Cancellation only stops the local request; it cannot roll back an object already written remotely. Check the object state and authorize deletion separately.
267
-
268
- ## Development and testing
329
+ ## Development
269
330
 
270
331
  ```bash
271
332
  pnpm --filter @h-ai/storage test
package/README.ja.md CHANGED
@@ -4,34 +4,11 @@
4
4
 
5
5
  [フレームワークのホーム](../../README.ja.md) · 関連モジュール:[@h-ai/core](../core/README.ja.md)
6
6
 
7
- ## 概要
8
-
9
7
  共通の `storage` 入口で S3 互換ストレージとローカルファイルシステムを操作するオブジェクトストレージモジュールです。
10
8
 
11
- 接続はヘルスチェック成功後に公開します。初期化失敗時は候補リソースを解放し、解放にも失敗した場合は初期化エラーの `error.ext.cleanupPending` が `true` になります。`storage.close()` は `Promise<HaiResult<void>>` を返し、初期化を待機して並行する終了処理をまとめます。解放開始後は `isInitialized` が `false` となり、設定と操作は利用できません。解放に失敗したリソースは保持され、次の `close()` または `init()` で再試行します。解放が成功するまで代替接続は作成しません。終了結果を必ず確認してください。
12
-
13
- ```ts
14
- const closed = await storage.close()
15
- if (!closed.success) {
16
- // 障害への対処後に一度再試行し、再失敗はアプリへ報告します。
17
- const retried = await storage.close()
18
- if (!retried.success)
19
- throw new Error(retried.error.message)
20
- }
21
- ```
22
-
23
- S3 の通常クライアントと、リクエスト再試行を無効化した一括削除クライアントは個別に解放します。一方の失敗でも他方は解放し、解放済みクライアントは再度破棄しません。
24
-
25
- ## 主な機能
26
-
27
- - S3 プロトコル(AWS S3 / MinIO / RustFS / OSS など)
28
- - ローカルファイルシステム
29
-
30
- ## 使い始める
31
-
32
- ### クイックスタート
9
+ ## クイックスタート
33
10
 
34
- #### Node.js サーバー
11
+ ### Node.js サーバー
35
12
 
36
13
  ```ts
37
14
  import { storage } from '@h-ai/storage'
@@ -83,6 +60,72 @@ await uploadWithPresignedUrl(signed.data.url, file)
83
60
 
84
61
  > 注:`@h-ai/storage` に HTTP ルートは組み込まれていません。署名付き URL API はアプリケーション層で実装してください。
85
62
 
63
+ ## 機能
64
+
65
+ 接続はヘルスチェック成功後に公開します。初期化失敗時は候補リソースを解放し、解放にも失敗した場合は初期化エラーの `error.ext.cleanupPending` が `true` になります。`storage.close()` は `Promise<HaiResult<void>>` を返し、初期化を待機して並行する終了処理をまとめます。解放開始後は `isInitialized` が `false` となり、設定と操作は利用できません。解放に失敗したリソースは保持され、次の `close()` または `init()` で再試行します。解放が成功するまで代替接続は作成しません。終了結果を必ず確認してください。
66
+
67
+ ```ts
68
+ const closed = await storage.close()
69
+ if (!closed.success) {
70
+ // 障害への対処後に一度再試行し、再失敗はアプリへ報告します。
71
+ const retried = await storage.close()
72
+ if (!retried.success)
73
+ throw new Error(retried.error.message)
74
+ }
75
+ ```
76
+
77
+ S3 の通常クライアントと、リクエスト再試行を無効化した一括削除クライアントは個別に解放します。一方の失敗でも他方は解放し、解放済みクライアントは再度破棄しません。
78
+
79
+ ### 操作と入出力
80
+
81
+ 本節の完全な初期化手順を先に実行します。例の業務値はホストが検証・認可し、非同期領域結果は success を確認してから data を読みます。void はデータなし完了、null は不存在、同期ファクトリー/ツールは表の型を直接返します。
82
+
83
+ #### storage.file
84
+
85
+ 元のキー・バイト/ストリーム・オプションで操作。put/copy/head はメタデータ、get は Buffer、getStream は非同期バイト列、exists は boolean、delete は void、deleteMany は項目別進捗。
86
+
87
+ | 入口 | 入力引数 | 成功結果 | 呼出し例 |
88
+ | ------------------------- | --------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------- |
89
+ | `storage.file.put` | key, data, options | `FileMetadata` | `await storage.file.put('report.txt', 'text', { contentType: 'text/plain' })` |
90
+ | `storage.file.get` | key, options | `Buffer<ArrayBufferLike>` | `await storage.file.get('report.txt')` |
91
+ | `storage.file.getStream` | key, options | `AsyncIterable<Uint8Array<ArrayBufferLike>>` | `await storage.file.getStream('report.txt')` |
92
+ | `storage.file.head` | key | `FileMetadata` | `await storage.file.head('report.txt')` |
93
+ | `storage.file.exists` | key | `boolean` | `await storage.file.exists('report.txt')` |
94
+ | `storage.file.copy` | sourceKey, destKey, options | `FileMetadata` | `await storage.file.copy('report.txt', 'archive/report.txt')` |
95
+ | `storage.file.delete` | key | `void` | `await storage.file.delete('report.txt')` |
96
+ | `storage.file.deleteMany` | keys | `StorageDeleteResult` | `await storage.file.deleteMany(['report.txt', 'archive/report.txt'])` |
97
+
98
+ #### storage.dir
99
+
100
+ 接頭辞で一覧/削除。list は prefix とページ指定からファイル/継続情報、delete は prefix から void。同時追加は防がない。
101
+
102
+ | 入口 | 入力引数 | 成功結果 | 呼出し例 |
103
+ | -------------------- | -------- | ------------ | ------------------------------------------------------------- |
104
+ | `storage.dir.list` | options | `ListResult` | `await storage.dir.list({ prefix: 'reports/', maxKeys: 20 })` |
105
+ | `storage.dir.delete` | prefix | `void` | `await storage.dir.delete('reports/')` |
106
+
107
+ #### storage.presign
108
+
109
+ S3 直接転送。キーと期限/応答指定から URL HaiResult、publicUrl は同期 URL/null を返しアクセス権は付与しない。
110
+
111
+ | 入口 | 入力引数 | 成功結果 | 呼出し例 |
112
+ | --------------------------- | ------------ | ---------------- | ------------------------------------------------------------------------------------------- |
113
+ | `storage.presign.getUrl` | key, options | `string` | `await storage.presign.getUrl('report.txt', { expiresIn: 300 })` |
114
+ | `storage.presign.putUrl` | key, options | `string` | `await storage.presign.putUrl('report.txt', { expiresIn: 300, contentType: 'text/plain' })` |
115
+ | `storage.presign.publicUrl` | key | `string \| null` | `storage.presign.publicUrl('report.txt')` |
116
+
117
+ #### storage.checksum
118
+
119
+ 初期化不要の完全性確認。データとアルゴリズム/符号化から摘要、expected との比較は boolean。ストリームは一度消費。
120
+
121
+ | 入口 | 入力引数 | 成功結果 | 呼出し例 |
122
+ | --------------------------- | ----------------------- | --------- | ------------------------------------------------------ |
123
+ | `storage.checksum.generate` | data, options | `string` | `await storage.checksum.generate('text')` |
124
+ | `storage.checksum.verify` | data, expected, options | `boolean` | `await storage.checksum.verify(bytes, expectedDigest)` |
125
+
126
+ - S3 プロトコル(AWS S3 / MinIO / RustFS / OSS など)
127
+ - ローカルファイルシステム
128
+
86
129
  ### HTTP API 契約
87
130
 
88
131
  公開 HTTP API は `@h-ai/api-contract` で定義し、`@h-ai/serv` の `createStorageProcedures({ storage, authorize })` で本モジュールの機能に接続します。
@@ -112,6 +155,10 @@ if (!result.success)
112
155
 
113
156
  `{ ifNoneMatch: '*' }` は標準 S3 条件付き書き込み、またはローカルの原子的公開で上書きを拒否し、競合時に `ALREADY_EXISTS` を返します。非対応の S3 サービスではエラーとし、上書きに切り替えません。失敗・中止時は一時ファイルや未完了パートを削除しますが、公開済みオブジェクトは中止で取り消せません。
114
157
 
158
+ Local はファイル横の予約ディレクトリ `.hai-storage-meta/` に内容の識別情報と結び付いた世代メタデータを準備し、内容ファイルの原子的公開をコミット点にします。準備失敗時は内容を作成・上書きしません。既定値にも空の記録を保存し、古いオブジェクトの属性を継承しません。公開後の既知の旧世代の削除失敗は診断だけに記録します。並行書き込みやクラッシュで安全な回収を確認できない内部記録が残る場合があり、推測で削除しません。各パス要素で `.meta.json`、`.upload.tmp`、`.hai-storage-meta` を予約します。世代記録が唯一のメタデータ形式で、有効な記録がなければ明示的に失敗します。再構築時は信頼する内容と業務メタデータを `file.put()` で保存します。内容と内部記録を一緒にバックアップし、この形式を迂回した直接書込を混在させないでください。
159
+
160
+ S3 `file.copy()` はコピー元の各パス要素をエンコードし、日本語・空白・URL 区切り文字をオブジェクトキーの一部として保持します。呼び出し側では事前エンコードせず元の key を渡します。
161
+
115
162
  `file.deleteMany(keys)` は `StorageDeleteResult` を返します。成功時の data は `{ deletedKeys, failures: [] }`、削除未完了の項目がある場合は `OPERATION_FAILED` と `error.ext?.deleteMany` に進捗を返します(未初期化などの事前エラーには進捗がない場合があります)。重複入力は一度だけ処理し、返す key に設定 prefix を含めません。S3 は最大 1000 件ずつ送信し、すべての `Deleted` / `Errors` を照合します。HTTP 200 でも全件成功とは限らず、欠落・重複・不明な応答は `unknown` になります。ローカル削除の並行数は最大 32 件です。
116
163
 
117
164
  明示的な `InternalError / ServiceUnavailable / SlowDown / RequestTimeout` の項目だけ最大 3 回試行し、成功済み・権限エラーは再送しません。`DeleteObjects` 専用クライアントは SDK のリクエスト再試行を無効化します。通信が中断した項目は直ちに `unknown`、後続の未送信バッチは `notAttempted` とし、不明な結果を再送しません。`retryable` は障害の分類であり、再試行前に不明な結果と並行書き込みを確認してください。`dir.delete()` は削除エラーを伝播しますが、並行して追加されるオブジェクトは防げません。S3 の削除は VersionId を指定しないため、バージョニングが有効な場合は削除マーカーを作成することがあり、過去のバージョンは消去しません。
@@ -146,7 +193,23 @@ checksum の中止時は次のチャンクを待つ処理を終了し、入力
146
193
  - `getMimeType(extension)`
147
194
  - `formatFileSize(bytes)`
148
195
 
149
- ### 設定
196
+ ### 本番の共有状態
197
+
198
+ 本番は共有 S3 または互換サービスを使います。local は開発・明示的テスト用です。オブジェクト認可と読取後更新の条件付きコミットはアプリが担当します。 NODE_ENV または HAI_CORE_ENV が production の場合、初期化成功前に検証します。明示的な HAI_E2E=1/HAI_E2E_MOCK=1 テストのみ除外します。 [状態・並行制御・復旧範囲(中国語)](../../.agents/instructions/distributed-state.md).
199
+
200
+ ### 入力制約と HTTP 機能
201
+
202
+ キーは UTF-8 で最大 1024 バイトで、絶対パス、バックスラッシュ、制御文字、ドットセグメントを拒否します。公開 HTTP 一括削除は一度に 1–1000 件です。共通規則はブラウザー対応の `@h-ai/storage/schemas` から参照できます。署名有効期限は整数 1–604800 秒(Node 既定 3600、HTTP 既定 900)。アップロード署名の入力は key/contentType/expiresIn のみで、contentLength/maxSize/metadata/checksum は未対応です。不正入力は HTTP 400、typed client では VALIDATION_FAILED です。FileUpload の制限は選択時の補助であり、実際のオブジェクトはサーバーで検証してください。Node のストリーム/checksum/条件付き書き込みには既定の HTTP エンドポイントがありません。[機能と認可の対応表(中国語)](README.md#存储输入约束与跨端能力)を参照してください。
203
+
204
+ ### ブラウザーアップロードのキャンセル
205
+
206
+ `uploadWithPresignedUrl(url, data, { onProgress, abortController })` は進捗通知にネイティブXHRを使用し、`onProgress` を省略するとfetchを使用します。事前にキャンセルされた場合は送信しません。準備中・送信中のキャンセル、外部からのXHR abort、通信・タイムアウト、進捗コールバックの例外は一度だけ失敗として確定し、この操作のリスナーを解除します。完了後のキャンセルは古いXHRを操作しません。キャンセルはローカル要求の停止であり、リモートに保存済みのオブジェクトを取り消せません。状態を確認してから、別途認可して削除してください。
207
+
208
+ ## 設定
209
+
210
+ ライフサイクル。init にバックエンド/業務設定を渡し成功後に使用します。借用依存先の解放前に close の完了を待ち、正確な戻り値は例と公開署名に従います。
211
+
212
+ Local は key 内のシンボリックリンクと junction を拒否し、予約拡張子 `.meta.json` / `.upload.tmp` は大文字小文字を区別せず保護します。put 上書きは旧メタデータを置換し、copy は既定で元を継承します。ブラウザー入口にサーバー単一インスタンスは含まれません。
150
213
 
151
214
  - **S3**:`bucket / region / accessKeyId / secretAccessKey` は必須。`createBucket / endpoint / publicEndpoint / forcePathStyle / prefix / publicUrl` は任意。
152
215
  - **Local**:`root` は必須。`directoryMode / fileMode` は任意。
@@ -157,6 +220,10 @@ MinIO と RustFS は S3 Provider を共有し、`type: 's3'` を使います。
157
220
 
158
221
  YAML では `createBucket: ${HAI_STORAGE_CREATEBUCKET:false}` で環境変数を参照し、`storage.init()` への直接入力は真偽値を使います。
159
222
 
223
+ `config/_storage.yml` に `prefix: my-app/uploads/` を設定します。`StorageConfigSchema` を登録した Hai アプリでは、`HAI_STORAGE_PREFIX=my-app/uploads/` でも指定できます。`report.pdf` をアップロードすると、オブジェクトキーは `my-app/uploads/report.pdf` になります。起動時の接続確認と既定の一覧取得は、設定値の末尾にスラッシュがなくても `my-app/uploads/` を検索します。空のプレフィックスはバケットのルートを使います。設定を変更しても既存オブジェクトのキーは変わりません。
224
+
225
+ アプリの認証情報を制限するには、オブジェクト操作の対象を `arn:aws:s3:::my-bucket/my-app/uploads/*` に絞り、`s3:ListBucket` には `s3:prefix` 条件で `my-app/uploads/*` を指定します。作成済みのバケットを使い、`createBucket: false` にすると、作成モードで行われるバケット単位の `HeadBucket` 確認を省けます。
226
+
160
227
  ```ts
161
228
  const initResult = await storage.init({
162
229
  type: 's3',
@@ -173,7 +240,7 @@ if (!initResult.success) {
173
240
  }
174
241
  ```
175
242
 
176
- #### 内部接続とブラウザーからの直接アクセス
243
+ ### 内部接続とブラウザーからの直接アクセス
177
244
 
178
245
  `endpoint` はサーバーの CRUD、バケット作成、接続確認に使います。`publicEndpoint` は `presign.getUrl/putUrl` の外部アドレスで既定値はなく、省略時は endpoint を使います。`https://app.example.com/delivery/files` のようなプロキシパスを指定できます。パスには英数字、アンダースコア、ドット、ハイフンを使用でき、`.`/`..`、連続スラッシュ、符号化された区切りは使用できません。バケット、認証情報、クエリ、フラグメントは含めません。`publicUrl` は公開 URL の組み立て用で、私有オブジェクトを認可しません。
179
246
 
@@ -228,7 +295,13 @@ do {
228
295
  } while (continuationToken)
229
296
  ```
230
297
 
231
- ### エラー処理
298
+ 設定はホストが読込・検証してから渡します。秘密はサーバーの非公開環境に保存し、利用場面に応じてバックエンド・容量・期限・ログを選びます。変更後は再初期化の結果を確認してください。
299
+
300
+ [項目別の用途・適用場面・例](../../docs/usage/configuration/data.md).
301
+
302
+ ## エラー処理
303
+
304
+ 領域操作は result.success を確認し、失敗時はモジュールのエラー定義と安全な説明を使います。コード・関連 ID・秘匿した原因をサーバーログに残します。同期ツール・ファクトリー・ストリーム反復・ライフサイクルは各署名に従い、Promise 完了だけで成功と判断しません。回復条件は各機能を参照してください。
232
305
 
233
306
  ```ts
234
307
  import { HaiStorageError, storage } from '@h-ai/storage'
@@ -253,18 +326,6 @@ if (!result.success && result.error.code === HaiStorageError.NOT_INITIALIZED.cod
253
326
  | `HaiStorageError.INVALID_LIST_OPTIONS` | `hai:storage:019` | ローカル一覧の引数・カーソルが不正、または検索範囲が不一致 |
254
327
  | `HaiStorageError.CONFIG_ERROR` | `hai:storage:012` | 設定エラー |
255
328
 
256
- ## 本番の共有状態
257
-
258
- 本番は共有 S3 または互換サービスを使います。local は開発・明示的テスト用です。オブジェクト認可と読取後更新の条件付きコミットはアプリが担当します。 NODE_ENV または HAI_CORE_ENV が production の場合、初期化成功前に検証します。明示的な HAI_E2E=1/HAI_E2E_MOCK=1 テストのみ除外します。 [状態・並行制御・復旧範囲(中国語)](../../.agents/instructions/distributed-state.md)。
259
-
260
- ## 入力制約と HTTP 機能
261
-
262
- キーは UTF-8 で最大 1024 バイトで、絶対パス、バックスラッシュ、制御文字、ドットセグメントを拒否します。公開 HTTP 一括削除は一度に 1–1000 件です。共通規則はブラウザー対応の `@h-ai/storage/schemas` から参照できます。署名有効期限は整数 1–604800 秒(Node 既定 3600、HTTP 既定 900)。アップロード署名の入力は key/contentType/expiresIn のみで、contentLength/maxSize/metadata/checksum は未対応です。不正入力は HTTP 400、typed client では VALIDATION_FAILED です。FileUpload の制限は選択時の補助であり、実際のオブジェクトはサーバーで検証してください。Node のストリーム/checksum/条件付き書き込みには既定の HTTP エンドポイントがありません。[機能と認可の対応表(中国語)](README.md#存储输入约束与跨端能力)を参照してください。
263
-
264
- ## ブラウザーアップロードのキャンセル
265
-
266
- `uploadWithPresignedUrl(url, data, { onProgress, abortController })` は進捗通知にネイティブXHRを使用し、`onProgress` を省略するとfetchを使用します。事前にキャンセルされた場合は送信しません。準備中・送信中のキャンセル、外部からのXHR abort、通信・タイムアウト、進捗コールバックの例外は一度だけ失敗として確定し、この操作のリスナーを解除します。完了後のキャンセルは古いXHRを操作しません。キャンセルはローカル要求の停止であり、リモートに保存済みのオブジェクトを取り消せません。状態を確認してから、別途認可して削除してください。
267
-
268
329
  ## 開発とテスト
269
330
 
270
331
  ```bash