@h-ai/storage 1.0.0-beta.11 → 1.0.0-beta.13
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 +4 -0
- package/README.ja.md +4 -0
- package/README.md +4 -0
- package/dist/browser.js +1 -1
- package/dist/{chunk-ANAESRWS.js → chunk-JSTMXNYI.js} +3 -3
- package/dist/chunk-JSTMXNYI.js.map +1 -0
- package/dist/node.js +5 -8
- package/dist/node.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-ANAESRWS.js.map +0 -1
package/README.en.md
CHANGED
|
@@ -159,6 +159,10 @@ MinIO and RustFS share the S3 provider with `type: 's3'`; no separate storage ty
|
|
|
159
159
|
|
|
160
160
|
Use `createBucket: ${HAI_STORAGE_CREATEBUCKET:false}` in YAML for environment control; direct `storage.init()` input requires a boolean.
|
|
161
161
|
|
|
162
|
+
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.
|
|
163
|
+
|
|
164
|
+
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.
|
|
165
|
+
|
|
162
166
|
```ts
|
|
163
167
|
const initResult = await storage.init({
|
|
164
168
|
type: 's3',
|
package/README.ja.md
CHANGED
|
@@ -159,6 +159,10 @@ MinIO と RustFS は S3 Provider を共有し、`type: 's3'` を使います。
|
|
|
159
159
|
|
|
160
160
|
YAML では `createBucket: ${HAI_STORAGE_CREATEBUCKET:false}` で環境変数を参照し、`storage.init()` への直接入力は真偽値を使います。
|
|
161
161
|
|
|
162
|
+
`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/` を検索します。空のプレフィックスはバケットのルートを使います。設定を変更しても既存オブジェクトのキーは変わりません。
|
|
163
|
+
|
|
164
|
+
アプリの認証情報を制限するには、オブジェクト操作の対象を `arn:aws:s3:::my-bucket/my-app/uploads/*` に絞り、`s3:ListBucket` には `s3:prefix` 条件で `my-app/uploads/*` を指定します。作成済みのバケットを使い、`createBucket: false` にすると、作成モードで行われるバケット単位の `HeadBucket` 確認を省けます。
|
|
165
|
+
|
|
162
166
|
```ts
|
|
163
167
|
const initResult = await storage.init({
|
|
164
168
|
type: 's3',
|
package/README.md
CHANGED
|
@@ -159,6 +159,10 @@ MinIO、RustFS 共用 S3 Provider,使用 `type: 's3'`,无需单独的存储
|
|
|
159
159
|
|
|
160
160
|
YAML 显式引用 `createBucket: ${HAI_STORAGE_CREATEBUCKET:false}` 后可由环境变量控制;直接传入 `storage.init()` 时使用布尔值。
|
|
161
161
|
|
|
162
|
+
在 `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/`,配置值可省略末尾的斜杠。空前缀使用桶根,修改前缀不会搬迁已有对象。
|
|
163
|
+
|
|
164
|
+
要限制应用凭据的访问范围,将对象权限限定到 `arn:aws:s3:::my-bucket/my-app/uploads/*`,并对 `s3:ListBucket` 设置 `s3:prefix` 条件 `my-app/uploads/*`。使用预先创建的桶并保持 `createBucket: false`,避免建桶模式触发桶级 `HeadBucket` 检查。
|
|
165
|
+
|
|
162
166
|
```ts
|
|
163
167
|
const initResult = await storage.init({
|
|
164
168
|
type: 's3',
|
package/dist/browser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema } from './chunk-
|
|
1
|
+
export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema } from './chunk-JSTMXNYI.js';
|
|
2
2
|
export { downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, uploadWithPresignedUrl } from './chunk-TLZY4IP6.js';
|
|
3
3
|
export { STORAGE_DELETE_MAX_KEYS, STORAGE_KEY_MAX_BYTES, StorageDeleteKeysSchema, StorageKeySchema, StoragePrefixSchema, StoragePresignExpiresInSchema, getStorageKeyIssue } from './chunk-P65WYXJG.js';
|
|
4
4
|
//# sourceMappingURL=browser.js.map
|
|
@@ -41,7 +41,7 @@ var S3ConfigSchema = z.object({
|
|
|
41
41
|
}, storageM("storage_config_publicEndpointInvalid")).optional(),
|
|
42
42
|
/** 强制使用路径风格 URL(某些 S3 兼容服务需要) */
|
|
43
43
|
forcePathStyle: z.boolean().default(false),
|
|
44
|
-
/**
|
|
44
|
+
/** S3 桶内对象键前缀,例如 my-app/uploads/,留空使用桶根 */
|
|
45
45
|
prefix: z.string().default(""),
|
|
46
46
|
/** 公开访问基础 URL(用于生成公开 URL) */
|
|
47
47
|
publicUrl: z.string().url().optional(),
|
|
@@ -99,5 +99,5 @@ var StorageErrorInfo = {
|
|
|
99
99
|
var HaiStorageError = core.error.buildHaiErrorsDef("storage", StorageErrorInfo);
|
|
100
100
|
|
|
101
101
|
export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema };
|
|
102
|
-
//# sourceMappingURL=chunk-
|
|
103
|
-
//# sourceMappingURL=chunk-
|
|
102
|
+
//# sourceMappingURL=chunk-JSTMXNYI.js.map
|
|
103
|
+
//# sourceMappingURL=chunk-JSTMXNYI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/storage-config.ts","../src/storage-types.ts"],"names":[],"mappings":"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","file":"chunk-JSTMXNYI.js","sourcesContent":["/**\n * @h-ai/storage — 存储配置 Schema\n * @module storage-config\n */\n\nimport { z } from 'zod'\nimport { storageM } from './storage-i18n.js'\nimport { StoragePresignExpiresInSchema } from './storage-schemas.js'\n\n// ─── 配置 Schema ───\n\n/**\n * 存储类型枚举\n *\n * 支持的存储类型:\n * - `s3` - S3 协议对象存储(兼容 AWS S3、MinIO、RustFS、阿里云 OSS 等)\n * - `local` - 本地文件存储(仅后端)\n * @example\n * ```ts\n * import { StorageTypeSchema } from '@h-ai/storage'\n *\n * const value = StorageTypeSchema.parse('local')\n * void value\n * ```\n */\nexport const StorageTypeSchema = z.enum(['s3', 'local'])\n\n/**\n * 存储类型\n * @example\n * ```ts\n * import type { StorageType } from '@h-ai/storage'\n *\n * const value: StorageType = 's3'\n * void value\n * ```\n */\nexport type StorageType = z.infer<typeof StorageTypeSchema>\n\n/**\n * StorageOperationLogConfigSchema 的数据约束与操作契约。\n * @example\n * ```ts\n * import { StorageOperationLogConfigSchema } from '@h-ai/storage'\n *\n * const value = StorageOperationLogConfigSchema.parse({ read: false, write: true, maxLength: 500, level: 'debug' })\n * void value\n * ```\n */\nexport const StorageOperationLogConfigSchema = z.object({\n read: z.boolean().default(false),\n write: z.boolean().default(false),\n maxLength: z.number().int().min(0).default(1000),\n level: z.enum(['info', 'debug', 'trace']).default('debug'),\n})\n\n/**\n * StorageOperationLogConfig 的数据约束与操作契约。\n * @example\n * ```ts\n * import { StorageOperationLogConfigSchema, type StorageOperationLogConfig } from '@h-ai/storage'\n *\n * const value: StorageOperationLogConfig = StorageOperationLogConfigSchema.parse({ read: false, write: true, maxLength: 500, level: 'debug' })\n * void value\n * ```\n */\nexport type StorageOperationLogConfig = z.infer<typeof StorageOperationLogConfigSchema>\n\nconst OperationLogConfigFieldSchema = {\n operationLog: StorageOperationLogConfigSchema.optional(),\n}\n\n/**\n * S3 存储配置 Schema\n *\n * 支持标准 AWS S3 以及兼容 S3 协议的存储服务(MinIO、RustFS、阿里云 OSS 等)。\n * 必填字段:bucket、region、accessKeyId、secretAccessKey。\n * @example\n * ```ts\n * import { S3ConfigSchema } from '@h-ai/storage'\n *\n * const value = S3ConfigSchema.parse({ type: 's3', bucket: 'uploads', region: 'us-east-1', accessKeyId: process.env.HAI_STORAGE_ACCESS_KEY_ID, secretAccessKey: process.env.HAI_STORAGE_SECRET_ACCESS_KEY })\n * void value\n * ```\n */\nexport const S3ConfigSchema = z.object({\n /** 存储类型 */\n type: z.literal('s3'),\n\n /** 存储桶名称 */\n bucket: z.string().min(1, storageM('storage_config_bucketRequired')),\n\n /** 初始化时检查并创建缺失的桶;默认关闭,启用后凭据须有建桶权限。 */\n createBucket: z.boolean().default(false),\n\n /** 区域 */\n region: z.string().min(1, storageM('storage_config_regionRequired')),\n\n /** Access Key ID */\n accessKeyId: z.string().min(1, storageM('storage_config_accessKeyIdRequired')),\n\n /** Secret Access Key */\n secretAccessKey: z.string().min(1, storageM('storage_config_secretAccessKeyRequired')),\n\n /** 自定义端点(用于 MinIO、RustFS、阿里云 OSS 等) */\n endpoint: z.string().url().optional(),\n\n /** 浏览器预签名端点,可含代理目录(如 /delivery/files),不含桶名、凭据或查询;代理须去除目录并保留 Host。 */\n publicEndpoint: z.string().refine((value) => {\n // 必须先检查原始文本;URL 校验会移除制表符和换行,不能在标准化后再检查。\n // eslint-disable-next-line no-control-regex -- 在入口显式拒绝控制字符,避免校验与代理解释不一致。\n return !/[\\u0000-\\u0020\\u007F\\\\%]/.test(value) && !/(?:^|\\/)\\.{1,2}(?:\\/|$)/.test(value)\n }, storageM('storage_config_publicEndpointInvalid')).url().refine((value) => {\n try {\n const url = new URL(value)\n return ['http:', 'https:'].includes(url.protocol) && !url.username && !url.password\n && /^\\/(?:[\\w.-]+(?:\\/[\\w.-]+)*\\/?)?$/.test(url.pathname) && !url.search && !url.hash\n }\n catch {\n return false\n }\n }, storageM('storage_config_publicEndpointInvalid')).optional(),\n\n /** 强制使用路径风格 URL(某些 S3 兼容服务需要) */\n forcePathStyle: z.boolean().default(false),\n\n /** S3 桶内对象键前缀,例如 my-app/uploads/,留空使用桶根 */\n prefix: z.string().default(''),\n\n /** 公开访问基础 URL(用于生成公开 URL) */\n publicUrl: z.string().url().optional(),\n\n ...OperationLogConfigFieldSchema,\n})\n\n/**\n * S3 配置类型(parse 后,带默认值的字段已填充,optional 字段仍可缺省)\n * @example\n * ```ts\n * import { S3ConfigSchema, type S3Config } from '@h-ai/storage'\n *\n * const value: S3Config = S3ConfigSchema.parse({ type: 's3', bucket: 'uploads', region: 'us-east-1', accessKeyId: process.env.HAI_STORAGE_ACCESS_KEY_ID, secretAccessKey: process.env.HAI_STORAGE_SECRET_ACCESS_KEY })\n * void value\n * ```\n */\nexport type S3Config = z.infer<typeof S3ConfigSchema>\n\n/**\n * 本地存储配置 Schema\n *\n * 基于本地文件系统,仅后端可用。必填字段:root。\n * @example\n * ```ts\n * import { LocalConfigSchema } from '@h-ai/storage'\n *\n * const value = LocalConfigSchema.parse({ type: 'local', root: './data/uploads' })\n * void value\n * ```\n */\nexport const LocalConfigSchema = z.object({\n /** 存储类型 */\n type: z.literal('local'),\n\n /** 根目录路径 */\n root: z.string().min(1, storageM('storage_config_rootRequired')),\n\n /** 目录创建权限 */\n directoryMode: z.number().default(0o755),\n\n /** 文件创建权限 */\n fileMode: z.number().default(0o644),\n\n ...OperationLogConfigFieldSchema,\n})\n\n/**\n * 本地配置类型(parse 后,带默认值的字段已填充,optional 字段仍可缺省)\n * @example\n * ```ts\n * import { LocalConfigSchema, type LocalConfig } from '@h-ai/storage'\n *\n * const value: LocalConfig = LocalConfigSchema.parse({ type: 'local', root: './data/uploads' })\n * void value\n * ```\n */\nexport type LocalConfig = z.infer<typeof LocalConfigSchema>\n\n/**\n * 统一存储配置 Schema\n *\n * 支持 S3 和本地文件两种类型的配置验证。\n * @example\n * ```ts\n * import { StorageConfigSchema } from '@h-ai/storage'\n *\n * const value = StorageConfigSchema.parse({ type: 'local', root: './data/uploads' })\n * void value\n * ```\n */\nexport const StorageConfigSchema = z.discriminatedUnion('type', [\n S3ConfigSchema,\n LocalConfigSchema,\n])\n\n/**\n * 存储配置类型(parse 后的完整配置)\n * @example\n * ```ts\n * import { StorageConfigSchema, type StorageConfig } from '@h-ai/storage'\n *\n * const value: StorageConfig = StorageConfigSchema.parse({ type: 'local', root: './data/uploads' })\n * void value\n * ```\n */\nexport type StorageConfig = z.infer<typeof StorageConfigSchema>\n\n/**\n * 存储配置输入类型(parse 前,允许省略带默认值的字段)\n * @example\n * ```ts\n * import type { StorageConfigInput } from '@h-ai/storage'\n *\n * const value: StorageConfigInput = { type: 'local', root: './data/uploads' }\n * void value\n * ```\n */\nexport type StorageConfigInput = z.input<typeof StorageConfigSchema>\n\n// ─── 签名 URL 配置 ───\n\n/**\n * 签名 URL 配置 Schema\n *\n * 用于控制 `storage.presign.getUrl()` 生成的下载签名 URL 参数。\n * @example\n * ```ts\n * import { PresignOptionsSchema } from '@h-ai/storage'\n *\n * const value = PresignOptionsSchema.parse({ expiresIn: 60 })\n * void value\n * ```\n */\nexport const PresignOptionsSchema = z.object({\n /** 过期时间(秒),范围 1~604800(7 天),默认 3600(1 小时) */\n expiresIn: StoragePresignExpiresInSchema.default(3600), // 最长 7 天\n\n /** 响应内容类型(用于下载时设置 Content-Type) */\n responseContentType: z.string().optional(),\n\n /** 响应内容处置(用于设置下载文件名) */\n responseContentDisposition: z.string().optional(),\n})\n\n/**\n * 签名 URL 输入选项,允许省略带默认值的字段。\n * @example\n * ```ts\n * import type { PresignOptions } from '@h-ai/storage'\n *\n * const value: PresignOptions = { expiresIn: 60, responseContentType: 'text/plain' }\n * void value\n * ```\n */\nexport type PresignOptions = z.input<typeof PresignOptionsSchema>\n\n/**\n * 上传签名 URL 配置 Schema\n *\n * 继承 PresignOptionsSchema 并扩展上传专属参数,用于 `storage.presign.putUrl()`。\n * @example\n * ```ts\n * import { PresignUploadOptionsSchema } from '@h-ai/storage'\n *\n * const value = PresignUploadOptionsSchema.parse({ contentType: 'text/plain' })\n * void value\n * ```\n */\nexport const PresignUploadOptionsSchema = PresignOptionsSchema.extend({\n /** 上传内容类型(必须与实际上传匹配) */\n contentType: z.string().default('application/octet-stream'),\n})\n\n/**\n * 上传签名 URL 输入选项,省略 contentType 时使用 application/octet-stream。\n * @example\n * ```ts\n * import type { PresignUploadOptions } from '@h-ai/storage'\n *\n * const value: PresignUploadOptions = { expiresIn: 60, contentType: 'text/plain' }\n * void value\n * ```\n */\nexport type PresignUploadOptions = z.input<typeof PresignUploadOptionsSchema>\n","/**\n * @h-ai/storage — 公共类型定义\n *\n * 包含:\n * - 错误定义(HaiStorageError)\n * - 文件元数据(FileMetadata)\n * - 列表结果(ListResult)\n * - 文件操作接口(FileOperations)\n * - 目录操作接口(DirOperations)\n * - 签名 URL 操作接口(PresignOperations)\n * - 函数接口(StorageFunctions)\n * @module storage-types\n */\n\nimport type { ErrorInfo, HaiError, HaiResult } from '@h-ai/core'\nimport type { Buffer } from 'node:buffer'\nimport type { PresignOptions, PresignUploadOptions, StorageConfig, StorageConfigInput } from './storage-config.js'\nimport { core } from '@h-ai/core'\n\n// ─── 错误定义(照 @h-ai/core 范式) ───\n\nconst StorageErrorInfo = {\n CONNECTION_FAILED: '001:500',\n OPERATION_FAILED: '002:500',\n NOT_FOUND: '003:404',\n ALREADY_EXISTS: '004:409',\n PERMISSION_DENIED: '005:403',\n QUOTA_EXCEEDED: '006:413',\n INVALID_PATH: '007:400',\n IO_ERROR: '008:500',\n NETWORK_ERROR: '009:502',\n NOT_INITIALIZED: '010:500',\n UNSUPPORTED_TYPE: '011:400',\n CONFIG_ERROR: '012:500',\n PRESIGN_FAILED: '013:500',\n UPLOAD_FAILED: '014:500',\n DOWNLOAD_FAILED: '015:500',\n CHECKSUM_FAILED: '016:500',\n INVALID_CHECKSUM: '017:400',\n UNSUPPORTED_OPERATION: '018:400',\n INVALID_LIST_OPTIONS: '019:400',\n} as const satisfies ErrorInfo\n\n/**\n * HaiStorageError 的数据约束与操作契约。\n * @example\n * ```ts\n * import { HaiStorageError } from '@h-ai/storage'\n * import { err } from '@h-ai/core'\n *\n * const result = err(HaiStorageError.NOT_FOUND, 'File missing')\n * if (!result.success) void result.error.code\n * ```\n */\nexport const HaiStorageError = core.error.buildHaiErrorsDef('storage', StorageErrorInfo)\n\n/**\n * 批量删除中未确认完成的一项;key 始终使用调用方视角,不含 Provider prefix。\n * @example\n * ```ts\n * import type { StorageDeleteFailure } from '@h-ai/storage'\n *\n * const value: StorageDeleteFailure = { key: 'private.txt', code: 'AccessDenied', retryable: false, outcome: 'failed' }\n * void value\n * ```\n */\nexport interface StorageDeleteFailure {\n /** 调用方提交的文件键。 */\n key: string\n /** 渠道/框架错误标识,不包含渠道原始 Message。 */\n code: string\n /** 是否属于可重试的瞬时故障;未知结果重试前仍需考虑并发上传/版本语义。 */\n retryable: boolean\n /** failed 为已知失败,unknown 为已发送但结果未知,notAttempted 为尚未发送。 */\n outcome: 'failed' | 'unknown' | 'notAttempted'\n}\n\n/**\n * 批量删除进度;任一失败不能宣称全部删除完成。\n * @example\n * ```ts\n * import type { StorageDeleteSummary } from '@h-ai/storage'\n *\n * const value: StorageDeleteSummary = { deletedKeys: ['a.txt'], failures: [] }\n * void value\n * ```\n */\nexport interface StorageDeleteSummary {\n /** 已确认删除或本来就不存在的唯一文件键。 */\n deletedKeys: string[]\n /** 失败、未知及尚未尝试的唯一文件键。 */\n failures: StorageDeleteFailure[]\n}\n\n/**\n * 批量删除结果;失败仍遵守 HaiResult,部分进度置于 error.ext.deleteMany。\n * @example\n * ```ts\n * import { storage, type StorageDeleteResult } from '@h-ai/storage'\n *\n * async function remove() {\n * const result: StorageDeleteResult = await storage.file.deleteMany(['a.txt', 'b.txt'])\n * return result.success ? result.data : result.error.ext?.deleteMany\n * }\n * ```\n */\nexport type StorageDeleteResult\n = | { success: true, data: StorageDeleteSummary }\n | { success: false, error: HaiError & { ext?: Record<string, unknown> & { deleteMany?: StorageDeleteSummary } } }\n\n// ─── 文件元数据 ───\n\n/**\n * 文件元数据接口\n *\n * 表示存储中单个文件的元信息,由 put/head/copy/list 等操作返回。\n * @example\n * ```ts\n * import type { FileMetadata } from '@h-ai/storage'\n *\n * const value: FileMetadata = { key: 'a.txt', size: 5, contentType: 'text/plain', lastModified: new Date() }\n * void value\n * ```\n */\nexport interface FileMetadata {\n /** 文件键/路径(不含配置 prefix,对用户透明) */\n key: string\n /** 文件大小(字节) */\n size: number\n /** 内容类型(MIME),未指定时根据扩展名自动推断 */\n contentType: string\n /** 最后修改时间 */\n lastModified: Date\n /** ETag(缓存验证标记;本地为 size-mtime,S3 由服务端生成,不等同于内容 checksum) */\n etag?: string\n /** 自定义元数据键值对 */\n metadata?: Record<string, string>\n}\n\n/**\n * 列表选项\n *\n * 用于控制 `storage.dir.list()` 的过滤、分页和目录模拟行为。\n * @example\n * ```ts\n * import type { ListOptions } from '@h-ai/storage'\n *\n * const value: ListOptions = { prefix: 'reports/', delimiter: '/', maxKeys: 100 }\n * void value\n * ```\n */\nexport interface ListOptions {\n /** 前缀过滤(只返回以此前缀开头的文件) */\n prefix?: string\n /**\n * 分页标记(从上次 ListResult.nextContinuationToken 获取)\n *\n * Local 游标绑定根目录、prefix 和 delimiter,可跨实例使用;不得跨查询或 provider 混用。\n */\n continuationToken?: string\n /** 每页逻辑条目上限(1–1000,默认 1000);files 和 commonPrefixes 合计计数。 */\n maxKeys?: number\n /** 分隔符,通常为 '/';从 prefix 之后首次出现的位置折叠 commonPrefixes,空字符串表示不折叠。 */\n delimiter?: string\n}\n\n/**\n * 列表结果\n *\n * 包含当前页的文件列表、目录前缀、分页信息。\n * @example\n * ```ts\n * import type { ListResult } from '@h-ai/storage'\n *\n * const value: ListResult = { files: [], commonPrefixes: ['reports/'], isTruncated: false }\n * void value\n * ```\n */\nexport interface ListResult {\n /** 当前页文件元数据列表 */\n files: FileMetadata[]\n /** 公共前缀:由实际匹配文件按 delimiter 折叠,不包含 Local 空物理目录。 */\n commonPrefixes: string[]\n /** 下一页标记;无后续页时为 undefined */\n nextContinuationToken?: string\n /** 是否被截断(true 表示还有更多数据,可用 nextContinuationToken 继续拉取) */\n isTruncated: boolean\n}\n\n// ─── 上传下载选项 ───\n\n/**\n * 上传选项\n *\n * 用于控制 `storage.file.put()` 的元数据与缓存行为。\n * @example\n * ```ts\n * import type { UploadOptions } from '@h-ai/storage'\n *\n * const value: UploadOptions = { ifNoneMatch: '*', contentType: 'text/plain', signal: new AbortController().signal }\n * void value\n * ```\n */\nexport interface UploadOptions {\n /** 仅当 key 不存在时原子写入;S3 使用标准 If-None-Match,后端不支持时返回错误。 */\n ifNoneMatch?: '*'\n /** 取消上传;失败或取消时清理未完成的临时文件/分片。 */\n signal?: AbortSignal\n /** 内容类型(不指定时根据文件扩展名自动推断) */\n contentType?: string\n /** 自定义元数据键值对(存储后可通过 head 获取) */\n metadata?: Record<string, string>\n /** Cache-Control 响应头(例如 'max-age=3600') */\n cacheControl?: string\n /** Content-Disposition 响应头(例如 'attachment; filename=\"a.txt\"') */\n contentDisposition?: string\n}\n\n/**\n * 下载选项\n *\n * 用于控制 `storage.file.get()` 的范围读取。\n * 两个字段均为字节偏移(含),与 HTTP Range 语义一致。\n * @example\n * ```ts\n * import type { DownloadOptions } from '@h-ai/storage'\n *\n * const value: DownloadOptions = { rangeStart: 0, rangeEnd: 99 }\n * void value\n * ```\n */\nexport interface DownloadOptions {\n /** 取消读取。 */\n signal?: AbortSignal\n /** 范围起始位置(字节,含);不指定时从文件开头读取 */\n rangeStart?: number\n /** 范围结束位置(字节,含);不指定时读取到文件末尾 */\n rangeEnd?: number\n}\n\n/**\n * 复制选项\n *\n * 用于控制 `storage.file.copy()` 的目标文件元数据。\n * 提供任意字段时,S3 Provider 会使用 MetadataDirective='REPLACE' 覆盖源文件元数据。\n * @example\n * ```ts\n * import type { CopyOptions } from '@h-ai/storage'\n *\n * const value: CopyOptions = { contentType: 'text/plain', metadata: { category: 'reports' } }\n * void value\n * ```\n */\nexport interface CopyOptions {\n /** 目标文件的内容类型(不指定时继承源文件) */\n contentType?: string\n /** 目标文件的自定义元数据(不指定时继承源文件) */\n metadata?: Record<string, string>\n}\n\n// ─── 操作接口 ───\n\n/**\n * 文件内容;异步字节流支持 Node Readable 等,不将整个文件载入内存。\n * @example\n * ```ts\n * import type { StorageData } from '@h-ai/storage'\n *\n * const value: StorageData = new TextEncoder().encode('hello')\n * void value\n * ```\n */\nexport type StorageData = Buffer | Uint8Array | string | AsyncIterable<Uint8Array>\n\n/**\n * 常见 checksum 算法;MD5/SHA-1 仅用于兼容性完整性校验,不用于安全认证。\n * @example\n * ```ts\n * import type { ChecksumAlgorithm } from '@h-ai/storage'\n *\n * const value: ChecksumAlgorithm = 'sha256'\n * void value\n * ```\n */\nexport type ChecksumAlgorithm = 'sha256' | 'sha384' | 'sha512' | 'sha1' | 'md5'\n\n/**\n * checksum 计算选项;默认 SHA-256、小写 hex。\n * @example\n * ```ts\n * import type { ChecksumOptions } from '@h-ai/storage'\n *\n * const value: ChecksumOptions = { algorithm: 'sha256', encoding: 'hex' }\n * void value\n * ```\n */\nexport interface ChecksumOptions {\n algorithm?: ChecksumAlgorithm\n encoding?: 'hex' | 'base64'\n signal?: AbortSignal\n}\n\n/**\n * Node 端无状态 checksum 工具,不依赖 storage.init;流输入仅消费一次。\n * @example\n * ```ts\n * import type { ChecksumOperations } from '@h-ai/storage'\n *\n * async function inspect(operations: ChecksumOperations) {\n * const result = await operations.generate('hello')\n * if (!result.success) return result.error.message\n * return result.data\n * }\n * ```\n */\nexport interface ChecksumOperations {\n generate: (data: StorageData, options?: ChecksumOptions) => Promise<HaiResult<string>>\n verify: (data: StorageData, expected: string, options?: ChecksumOptions) => Promise<HaiResult<boolean>>\n}\n\n/**\n * 文件操作接口\n *\n * 提供文件的读写操作。\n * @example\n * ```ts\n * import type { FileOperations } from '@h-ai/storage'\n *\n * async function inspect(operations: FileOperations) {\n * const result = await operations.getStream('reports/a.txt')\n * if (!result.success) return result.error.message\n * for await (const chunk of result.data) void chunk.byteLength\n * }\n * ```\n */\nexport interface FileOperations {\n /**\n * 上传文件\n *\n * @param key - 文件键/路径\n * @param data - 文件内容(Buffer、Uint8Array、字符串或异步字节流)\n * @param options - 上传选项\n * @returns 上传结果包含文件元数据\n * @example\n * ```ts\n * const result = await storage.file.put('uploads/a.txt', 'hello')\n * if (result.success) {\n * result.data.key\n * }\n * ```\n */\n put: (key: string, data: StorageData, options?: UploadOptions) => Promise<HaiResult<FileMetadata>>\n\n /**\n * 下载文件\n *\n * @param key - 文件键/路径\n * @param options - 下载选项\n * @returns 文件内容\n * @example\n * ```ts\n * const result = await storage.file.get('uploads/a.txt')\n * if (result.success) {\n * const buffer = result.data\n * void buffer\n * }\n * ```\n */\n get: (key: string, options?: DownloadOptions) => Promise<HaiResult<Buffer>>\n\n /**\n * 读取字节流,支持 Range 与取消;消费方负责及时迭代或关闭流。\n * @throws 迭代期间的 I/O/取消错误;初始请求失败通过 HaiResult 返回。\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const result = await storage.file.getStream('reports/a.txt')\n * if (!result.success) throw new Error(result.error.message)\n * try {\n * for await (const chunk of result.data) void chunk.byteLength\n * } catch (error) {\n * // 初始请求成功后,迭代仍可能因 I/O/取消失败。\n * throw error\n * }\n * ```\n */\n getStream: (key: string, options?: DownloadOptions) => Promise<HaiResult<AsyncIterable<Uint8Array>>>\n\n /**\n * 获取文件元数据\n *\n * @param key - 文件键/路径\n * @returns 文件元数据\n * @example\n * ```ts\n * const result = await storage.file.head('uploads/a.txt')\n * if (result.success) {\n * result.data.size\n * }\n * ```\n */\n head: (key: string) => Promise<HaiResult<FileMetadata>>\n\n /**\n * 检查文件是否存在\n *\n * @param key - 文件键/路径\n * @returns 是否存在\n * @example\n * ```ts\n * const result = await storage.file.exists('uploads/a.txt')\n * if (result.success) {\n * result.data\n * }\n * ```\n */\n exists: (key: string) => Promise<HaiResult<boolean>>\n\n /**\n * 删除文件\n *\n * @param key - 文件键/路径\n * @returns 删除结果\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const result = await storage.file.delete('uploads/a.txt')\n * if (!result.success) throw new Error(result.error.message)\n * ```\n */\n delete: (key: string) => Promise<HaiResult<void>>\n\n /**\n * 批量删除文件\n *\n * @param keys - 文件键/路径列表\n * @returns 全部成功时返回进度;任一失败时 success=false,进度在 error.ext.deleteMany\n * @example\n * ```ts\n * const result = await storage.file.deleteMany(['a.txt', 'b.txt'])\n * const progress = result.success ? result.data : result.error.ext?.deleteMany\n * void progress\n * ```\n */\n deleteMany: (keys: string[]) => Promise<StorageDeleteResult>\n\n /**\n * 复制文件\n *\n * @param sourceKey - 源文件键\n * @param destKey - 目标文件键\n * @param options - 复制选项\n * @returns 目标文件元数据\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const result = await storage.file.copy('a.txt', 'b.txt')\n * if (!result.success) throw new Error(result.error.message)\n * void result.data.key\n * ```\n */\n copy: (sourceKey: string, destKey: string, options?: CopyOptions) => Promise<HaiResult<FileMetadata>>\n}\n\n/**\n * 目录操作接口\n *\n * 提供目录(前缀)的列表操作。\n * @example\n * ```ts\n * import type { DirOperations } from '@h-ai/storage'\n *\n * async function inspect(operations: DirOperations) {\n * const page = await operations.list({ prefix: 'reports/', maxKeys: 100 })\n * if (!page.success) return page.error.message\n * return page.data.files\n * }\n * ```\n */\nexport interface DirOperations {\n /**\n * 列出目录内容\n *\n * @param options - 列表选项\n * @returns 文件和目录列表\n * @example\n * ```ts\n * const result = await storage.dir.list({ prefix: 'uploads/' })\n * if (result.success) {\n * result.data.files\n * }\n * ```\n */\n list: (options?: ListOptions) => Promise<HaiResult<ListResult>>\n\n /**\n * 删除目录(删除指定前缀下的所有文件)\n *\n * @param prefix - 目录前缀\n * @returns 删除结果\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const result = await storage.dir.delete('uploads/tmp/')\n * if (!result.success) throw new Error(result.error.message)\n * ```\n */\n delete: (prefix: string) => Promise<HaiResult<void>>\n}\n\n/**\n * 签名 URL 操作接口\n *\n * S3 provider 提供临时授权 URL,支持前端直接上传下载。\n * local 的 getUrl/putUrl 返回 UNSUPPORTED_OPERATION,publicUrl 返回 null。\n * @example\n * ```ts\n * import type { PresignOperations } from '@h-ai/storage'\n *\n * async function inspect(operations: PresignOperations) {\n * const signed = await operations.putUrl('reports/a.txt', { contentType: 'text/plain', expiresIn: 300 })\n * if (!signed.success) return signed.error.message\n * return signed.data\n * }\n * ```\n */\nexport interface PresignOperations {\n /**\n * 生成 GET 下载签名 URL;使用 publicEndpoint,缺省回退 endpoint/AWS 端点。\n * publicEndpoint 可含代理目录,返回值已带此前缀;代理须去除它并保留 S3 URI/Host/query,调用方不得自行改写。\n *\n * @param key - 文件键/路径\n * @param options - 签名选项\n * @returns 成功时为可访问的 S3 签名 URL;local 返回 UNSUPPORTED_OPERATION。\n * @example\n * ```ts\n * const result = await storage.presign.getUrl('a.txt', { expiresIn: 60 })\n * if (!result.success) throw new Error(result.error.message)\n * const downloadUrl = result.data\n * void downloadUrl\n * ```\n */\n getUrl: (key: string, options?: PresignOptions) => Promise<HaiResult<string>>\n\n /**\n * 生成 PUT 上传签名 URL;publicEndpoint 目录与 GET 相同处理,服务端 CRUD 仍使用内部 endpoint。\n *\n * @param key - 文件键/路径\n * @param options - 上传签名选项\n * @returns 成功时为可访问的 S3 签名 URL;local 返回 UNSUPPORTED_OPERATION。\n * @example\n * ```ts\n * const result = await storage.presign.putUrl('a.txt', { contentType: 'text/plain' })\n * if (!result.success) throw new Error(result.error.message)\n * const uploadUrl = result.data\n * void uploadUrl\n * ```\n */\n putUrl: (key: string, options?: PresignUploadOptions) => Promise<HaiResult<string>>\n\n /**\n * 获取公开访问 URL(如果配置了 publicUrl)\n *\n * @param key - 文件键/路径\n * @returns 公开 URL;S3 未配置 publicUrl 或 local provider 均返回 null。\n * @example\n * ```ts\n * const url = storage.presign.publicUrl('a.txt')\n * void url\n * ```\n */\n publicUrl: (key: string) => string | null\n}\n\n// ─── 函数接口 ───\n\n/**\n * 存储函数接口\n *\n * 统一的存储访问入口:\n * - `storage.init()` — 初始化存储连接\n * - `storage.close()` — 关闭连接并释放资源\n * - `storage.file` — 文件操作(上传/下载/删除/复制等)\n * - `storage.dir` — 目录操作(列表/删除)\n * - `storage.presign` — 签名 URL 操作\n * - `storage.config` — 当前脱敏配置快照(未初始化时为 null)\n * - `storage.isInitialized` — 初始化状态\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const initialized = await storage.init({ type: 'local', root: './data/uploads' })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const uploaded = await storage.file.put('reports/a.txt', 'hello', { ifNoneMatch: '*' })\n * if (!uploaded.success) throw new Error(uploaded.error.message)\n * const downloaded = await storage.file.get('reports/a.txt')\n * if (!downloaded.success) throw new Error(downloaded.error.message)\n * void downloaded.data\n * } finally {\n * const closed = await storage.close()\n * if (!closed.success) throw new Error(closed.error.message)\n * }\n * ```\n */\nexport interface StorageFunctions {\n /** 无状态 checksum 生成与校验,Node 专用,无需初始化。 */\n readonly checksum: ChecksumOperations\n /**\n * 初始化存储连接\n *\n * 已有连接时会先 close 再重新初始化。\n *\n * @param config - 存储配置(支持省略带默认值的字段)\n * @returns 成功 ok(undefined);失败返回 err(含 HaiStorageError 定义的错误信息)\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const initialized = await storage.init({ type: 'local', root: './data/uploads' })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * const closed = await storage.close()\n * if (!closed.success) throw new Error(closed.error.message)\n * ```\n */\n init: (config: StorageConfigInput) => Promise<HaiResult<void>>\n\n /**\n * 关闭存储连接并释放资源\n *\n * 等待初始化并合并并发关闭;失败时操作不可用,资源保留供 close/init 重试。\n * @returns 所有资源释放成功才返回成功;未初始化时同样成功。\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const closed = await storage.close()\n * if (!closed.success) throw new Error(closed.error.message)\n * ```\n */\n close: () => Promise<HaiResult<void>>\n\n /** 当前脱敏后的存储配置快照;未初始化或已关闭时为 null */\n readonly config: StorageConfig | null\n\n /** 是否已完成初始化 */\n readonly isInitialized: boolean\n\n /** 文件操作接口(put / get / head / exists / delete / deleteMany / copy) */\n readonly file: FileOperations\n\n /** 目录操作接口(list / delete) */\n readonly dir: DirOperations\n\n /** 签名 URL 操作接口(getUrl / putUrl / publicUrl) */\n readonly presign: PresignOperations\n}\n"]}
|
package/dist/node.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { HaiStorageError, StorageConfigSchema, PresignUploadOptionsSchema, PresignOptionsSchema } from './chunk-
|
|
2
|
-
export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema } from './chunk-
|
|
1
|
+
import { HaiStorageError, StorageConfigSchema, PresignUploadOptionsSchema, PresignOptionsSchema } from './chunk-JSTMXNYI.js';
|
|
2
|
+
export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema } from './chunk-JSTMXNYI.js';
|
|
3
3
|
import { storageM, MIME_TYPES, MIME_TYPE_DEFAULT } from './chunk-TLZY4IP6.js';
|
|
4
4
|
export { downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, uploadWithPresignedUrl } from './chunk-TLZY4IP6.js';
|
|
5
5
|
export { STORAGE_DELETE_MAX_KEYS, STORAGE_KEY_MAX_BYTES, StorageDeleteKeysSchema, StorageKeySchema, StoragePrefixSchema, StoragePresignExpiresInSchema, getStorageKeyIssue } from './chunk-P65WYXJG.js';
|
|
@@ -631,7 +631,7 @@ function withPrefix(key, prefix) {
|
|
|
631
631
|
function withoutPrefix(key, prefix) {
|
|
632
632
|
if (!prefix)
|
|
633
633
|
return key;
|
|
634
|
-
const normalizedPrefix =
|
|
634
|
+
const normalizedPrefix = withPrefix("", prefix);
|
|
635
635
|
if (key.startsWith(normalizedPrefix)) {
|
|
636
636
|
return key.slice(normalizedPrefix.length);
|
|
637
637
|
}
|
|
@@ -939,13 +939,9 @@ function createS3Provider() {
|
|
|
939
939
|
const s3Client = getClient();
|
|
940
940
|
const s3Config = getConfig();
|
|
941
941
|
logOperation("read", "dir.list", { options });
|
|
942
|
-
let prefix = s3Config.prefix || "";
|
|
943
|
-
if (options.prefix) {
|
|
944
|
-
prefix = withPrefix(options.prefix, s3Config.prefix);
|
|
945
|
-
}
|
|
946
942
|
const response = await s3Client.send(new ListObjectsV2Command({
|
|
947
943
|
Bucket: s3Config.bucket,
|
|
948
|
-
Prefix: prefix || void 0,
|
|
944
|
+
Prefix: withPrefix(options.prefix ?? "", s3Config.prefix) || void 0,
|
|
949
945
|
Delimiter: options.delimiter,
|
|
950
946
|
MaxKeys: options.maxKeys,
|
|
951
947
|
ContinuationToken: options.continuationToken
|
|
@@ -1121,6 +1117,7 @@ function createS3Provider() {
|
|
|
1121
1117
|
}
|
|
1122
1118
|
await client.send(new ListObjectsV2Command({
|
|
1123
1119
|
Bucket: cfg.bucket,
|
|
1120
|
+
Prefix: withPrefix("", cfg.prefix) || void 0,
|
|
1124
1121
|
MaxKeys: 1
|
|
1125
1122
|
}));
|
|
1126
1123
|
config = cfg;
|
package/dist/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/storage-delete.ts","../src/storage-stream.ts","../src/providers/storage-operation-log.ts","../src/providers/storage-provider-local.ts","../src/providers/storage-provider-s3.ts","../src/storage-checksum.ts","../src/storage-main.ts"],"names":["logger","getMimeType","stat","dir","ok","Buffer","core","toStorageError","Readable","err","resolve","createHash","process"],"mappings":"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","file":"node.js","sourcesContent":["/** Local/S3 共用的批量删除结果构造。 */\nimport type { StorageDeleteResult, StorageDeleteSummary } from './storage-types.js'\nimport { ok } from '@h-ai/core'\nimport { storageM } from './storage-i18n.js'\nimport { HaiStorageError } from './storage-types.js'\n\n/**\n * 仅在所有对象均确认删除时返回成功;失败时保留安全、可跨端传输的部分进度。\n * @param summary - 已确认成功与未完成的项目\n * @returns 批量删除结果,不抛业务异常\n * @example 模块内部上下文\n * ```ts\n * const result = toStorageDeleteResult({ deletedKeys: ['a.txt'], failures: [] })\n * if (!result.success) throw new Error(result.error.message)\n * void result.data.deletedKeys\n * ```\n */\nexport function toStorageDeleteResult(summary: StorageDeleteSummary): StorageDeleteResult {\n if (summary.failures.length === 0)\n return ok(summary)\n return {\n success: false,\n error: {\n ...HaiStorageError.OPERATION_FAILED,\n message: storageM('storage_deleteManyFailed', { params: { count: summary.failures.length } }),\n ext: { deleteMany: summary },\n },\n }\n}\n","/**\n * @h-ai/storage — 字节流规范化\n *\n * 供存储 Provider 与 checksum 共用,保持消费有界且支持取消。\n */\nimport type { StorageData } from './storage-types.js'\nimport { Buffer } from 'node:buffer'\nimport { storageM } from './storage-i18n.js'\n\n/**\n * 规范化字节输入;取消时终止等待并关闭迭代器,拒绝对象模式流中的非字节块。\n * @param data - 待迭代的字符串、字节数组或异步字节流。\n * @param signal - 可选取消信号;取消时迭代抛出原因。\n * @returns 逐块产生字节的异步可迭代对象;不缓冲整个输入。\n * @example 模块内部上下文\n * ```ts\n * const controller = new AbortController()\n * for await (const chunk of toByteStream('hello', controller.signal)) {\n * void chunk.byteLength\n * } // 迭代可能因取消或 I/O 抛错,应由上层捕获。\n * ```\n */\nexport async function* toByteStream(data: StorageData, signal?: AbortSignal): AsyncIterable<Uint8Array> {\n signal?.throwIfAborted()\n if (typeof data === 'string' || data instanceof Uint8Array) {\n yield typeof data === 'string' ? Buffer.from(data) : data\n return\n }\n const iterator = data[Symbol.asyncIterator]()\n let completed = false\n try {\n while (true) {\n signal?.throwIfAborted()\n let result: IteratorResult<Uint8Array>\n let onAbort: (() => void) | undefined\n try {\n // 每块独立等待并释放监听,避免在长流上累积共享 Promise 的回调。\n const cancelled = signal\n ? new Promise<never>((_, reject) => {\n onAbort = () => reject(signal.reason)\n signal.addEventListener('abort', onAbort, { once: true })\n })\n : undefined\n const next = iterator.next()\n result = await (cancelled ? Promise.race([next, cancelled]) : next)\n }\n finally {\n if (onAbort)\n signal?.removeEventListener('abort', onAbort)\n }\n if (result.done) {\n completed = true\n break\n }\n if (!(result.value instanceof Uint8Array))\n throw new TypeError(storageM('storage_streamInvalidChunk'))\n yield result.value\n }\n signal?.throwIfAborted()\n }\n finally {\n if (!completed && iterator.return) {\n // 不可取消的源可能把 return 排在挂起的 next 后面;取消结果不能继续等待源释放。\n if (signal?.aborted)\n void Promise.resolve(iterator.return()).catch(() => {})\n else\n await iterator.return()\n }\n }\n}\n","import type { StorageOperationLogConfig } from '../storage-config.js'\n\n/**\n * 存储 Provider 所需的最小结构化日志接口。\n * @example 模块内部上下文\n * ```ts\n * function emit(logger: StorageOperationLogger) {\n * logger.debug('Storage operation executed', { count: 1 })\n * }\n * ```\n */\ninterface StorageOperationLogger {\n info: (message: string, context?: Record<string, unknown>) => void\n debug: (message: string, context?: Record<string, unknown>) => void\n trace: (message: string, context?: Record<string, unknown>) => void\n}\n\n/**\n * 选择存储操作日志 read/write 开关的内部分类。\n * @example 模块内部上下文\n * ```ts\n * const category: StorageOperationCategory = 'write'\n * void category\n * ```\n */\ntype StorageOperationCategory = 'read' | 'write'\n\nfunction normalizePayloadValue(value: unknown): unknown {\n if (value instanceof Uint8Array) {\n return { byteLength: value.byteLength }\n }\n return value\n}\n\nfunction stringifyOperationPayload(payload: unknown, maxLength: number): string {\n if (maxLength <= 0) {\n return ''\n }\n\n try {\n const text = JSON.stringify(payload, (_key, value: unknown) => {\n if (typeof value === 'bigint') {\n return value.toString()\n }\n if (typeof value === 'function') {\n return '[Function]'\n }\n return normalizePayloadValue(value)\n })\n return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text\n }\n catch {\n const text = String(payload)\n return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text\n }\n}\n\n/**\n * 按配置输出有界操作摘要;字节内容只记录长度,不接管连接生命周期。\n * @param logger - 当前 Provider 的结构化日志器。\n * @param config - 已解析的日志开关和长度配置;未设置时不输出。\n * @param category - read 或 write,对应日志开关。\n * @param operation - 稳定的操作名称。\n * @param payload - 诊断摘要;不传入凭据或原始文件内容。\n * @example 模块内部上下文\n * ```ts\n * function emit(logger: StorageOperationLogger) {\n * logStorageOperation(logger, { read: false, write: true, level: 'debug', maxLength: 100 }, 'write', 'file.put', { bytes: 5 })\n * }\n * ```\n */\nexport function logStorageOperation(\n logger: StorageOperationLogger,\n config: StorageOperationLogConfig | undefined,\n category: StorageOperationCategory,\n operation: string,\n payload: unknown,\n): void {\n if (!config?.[category]) {\n return\n }\n\n logger[config.level]('Storage operation executed', {\n category,\n operation,\n payload: stringifyOperationPayload(payload, config.maxLength),\n })\n}\n","/**\n * @h-ai/storage — Local Provider 实现\n *\n * 基于本地文件系统的存储 Provider。\n * @module storage-provider-local\n */\n\nimport type { HaiError, HaiResult } from '@h-ai/core'\nimport type { LocalConfig, StorageConfig } from '../storage-config.js'\nimport type { DirOperations, FileMetadata, FileOperations, ListResult, PresignOperations, StorageDeleteResult, StorageDeleteSummary } from '../storage-types.js'\nimport type { StorageProvider } from './storage-provider-types.js'\nimport { Buffer } from 'node:buffer'\nimport * as crypto from 'node:crypto'\nimport * as fs from 'node:fs'\nimport * as fsp from 'node:fs/promises'\nimport * as path from 'node:path'\nimport process from 'node:process'\nimport { Readable } from 'node:stream'\n\nimport { pipeline } from 'node:stream/promises'\nimport { core, err, ok } from '@h-ai/core'\n\nimport { toStorageDeleteResult } from '../storage-delete.js'\nimport { storageM } from '../storage-i18n.js'\nimport { MIME_TYPE_DEFAULT, MIME_TYPES } from '../storage-mime.js'\nimport { toByteStream } from '../storage-stream.js'\nimport {\n\n HaiStorageError,\n\n} from '../storage-types.js'\nimport { logStorageOperation } from './storage-operation-log.js'\n\nconst logger = core.logger.child({ module: 'storage', scope: 'provider-local' })\nconst PATH_SEGMENT_SPLIT_REGEX = /[\\\\/]+/\n\n// ─── 辅助函数 ───\n\n/**\n * 将 Node.js 文件系统错误转换为 StorageError\n *\n * 映射规则:\n * - ENOENT → NOT_FOUND\n * - EACCES / EPERM → PERMISSION_DENIED\n * - ENOSPC → QUOTA_EXCEEDED\n * - EISDIR → INVALID_PATH\n * - 其他 → IO_ERROR\n *\n * @param error - 原始错误对象\n * @param key - 相关文件键(可选,用于错误消息)\n * @returns 统一的 StorageError\n */\nfunction toStorageError(error: unknown, key?: string): HaiError {\n const e = error as NodeJS.ErrnoException\n\n if (e.code === 'EEXIST') {\n return { ...HaiStorageError.ALREADY_EXISTS, message: storageM('storage_fileAlreadyExists', { params: { key: key ?? '' } }), cause: error }\n }\n\n if (e.code === 'ENOENT') {\n return {\n ...HaiStorageError.NOT_FOUND,\n message: storageM('storage_fileNotFound', { params: { key: key ?? '' } }),\n cause: error,\n }\n }\n\n if (e.code === 'EACCES' || e.code === 'EPERM') {\n return {\n ...HaiStorageError.PERMISSION_DENIED,\n message: storageM('storage_permissionDenied', { params: { key: key ?? '' } }),\n cause: error,\n }\n }\n\n if (e.code === 'ENOSPC') {\n return {\n ...HaiStorageError.QUOTA_EXCEEDED,\n message: storageM('storage_diskSpaceInsufficient'),\n cause: error,\n }\n }\n\n if (e.code === 'EISDIR') {\n return {\n ...HaiStorageError.INVALID_PATH,\n message: storageM('storage_pathIsDir', { params: { key: key ?? '' } }),\n cause: error,\n }\n }\n\n if (e.code === 'EINVAL') {\n return {\n ...HaiStorageError.INVALID_PATH,\n message: e.message || storageM('storage_pathTraversal'),\n cause: error,\n }\n }\n\n return {\n code: HaiStorageError.IO_ERROR.code,\n message: storageM('storage_ioError', { params: { error: e.message ?? '' } }),\n cause: error,\n }\n}\n\n/**\n * 根据文件扩展名获取 MIME 类型\n *\n * 使用共享 MIME_TYPES 映射表,未匹配时返回 'application/octet-stream'。\n *\n * @param filePath - 文件路径或文件名\n * @returns MIME 类型字符串\n */\nfunction getMimeType(filePath: string): string {\n const ext = path.extname(filePath).toLowerCase().replace('.', '')\n return MIME_TYPES[ext] || MIME_TYPE_DEFAULT\n}\n\n/**\n * 根据文件 stat 信息计算 ETag\n *\n * 使用 \"size-mtime\" 格式,与 head 操作保持一致,无需读取文件内容。\n *\n * @param stat - 文件的 fs.Stats 对象\n * @returns 带双引号的 ETag 字符串,如 '\"a-18f7ec3b4c0\"'\n */\nfunction calculateEtag(stat: fs.Stats): string {\n return `\"${stat.size.toString(16)}-${stat.mtimeMs.toString(16)}\"`\n}\n\n/**\n * 检查 key 是否指向内部元数据文件(.meta.json)\n *\n * 拦截外部对 .meta.json 文件的直接访问,防止元数据泄漏。\n */\nfunction isMetaFile(key: string): boolean {\n const normalized = key.toLowerCase()\n return normalized.endsWith('.meta.json') || normalized.endsWith('.upload.tmp')\n}\n\n/**\n * 创建非法路径错误(用于统一映射为 INVALID_PATH)\n */\nfunction createInvalidPathError(): NodeJS.ErrnoException {\n const error = new Error(storageM('storage_pathTraversal')) as NodeJS.ErrnoException\n error.code = 'EINVAL'\n return error\n}\n\n/**\n * 判断路径片段中是否包含路径穿越标记(..)\n */\nfunction hasPathTraversalSegment(key: string): boolean {\n const segments = key.split(PATH_SEGMENT_SPLIT_REGEX).filter(Boolean)\n return segments.includes('..')\n}\n\n/**\n * 安全的路径解析,防止路径穿越攻击\n *\n * 处理流程:\n * 1. 拒绝绝对路径、空字节、`..` 路径穿越片段\n * 2. resolve(root, key) 得到完整路径\n * 3. 校验完整路径必须位于 root 内部(或等于 root)\n *\n * @param root - 存储根目录绝对路径\n * @param key - 用户提供的文件键\n * @returns 安全的完整文件路径\n * @throws 当解析后路径超出 root 范围时抛出异常\n */\nfunction safePath(root: string, key: string): string {\n // 拒绝绝对路径输入,防止 Windows 盘符绕过(如 D:\\etc\\passwd)\n if (path.isAbsolute(key)) {\n throw createInvalidPathError()\n }\n\n // 拒绝空字节与路径穿越片段\n if (key.includes('\\0') || hasPathTraversalSegment(key)\n || (process.platform === 'win32' && key.split(PATH_SEGMENT_SPLIT_REGEX).some(segment => /[:. ]$/.test(segment) || segment.includes(':')))) {\n throw createInvalidPathError()\n }\n\n const normalized = path.normalize(key)\n const fullPath = path.resolve(root, normalized)\n\n // 确保路径在 root 目录下\n const realRoot = path.resolve(root)\n const inRoot = fullPath === realRoot || fullPath.startsWith(`${realRoot}${path.sep}`)\n\n if (!inRoot) {\n throw createInvalidPathError()\n }\n\n return fullPath\n}\n\n// ─── Local Provider 实现 ───\n\n/**\n * 创建本地存储 Provider\n *\n * 基于本地文件系统实现的 StorageProvider。\n * connect 时会自动创建根目录;文件操作会自动创建中间目录。\n *\n * 已知限制:\n * - list 使用绑定查询范围的无状态游标;按 UTF-8 key 排序,每次仍需扫描并排序匹配目录\n * - presign.getUrl/putUrl 明确不支持,publicUrl 返回 null;HTTP 访问由应用鉴权接口提供\n * - publicUrl 始终返回 null\n *\n * @returns Local Provider 实例\n * @example 模块内部上下文\n * ```ts\n * const provider = createLocalProvider()\n * await provider.close() // 工厂尚未连接;connect 的失败结果由调用方处理。\n * ```\n */\nexport function createLocalProvider(): StorageProvider {\n let config: LocalConfig | null = null\n let connected = false\n\n // -------------------------------------------------------------------------\n // 辅助方法\n // -------------------------------------------------------------------------\n\n /** 获取当前 Local 配置,未初始化时抛异常 */\n function getConfig(): LocalConfig {\n if (!config) {\n throw new Error(storageM('storage_localNotInitialized'))\n }\n return config\n }\n\n /** 将用户 key 转为安全的绝对路径(经 safePath 防穿越处理) */\n async function fullPath(key: string): Promise<string> {\n const root = getConfig().root\n const target = safePath(root, key)\n // 逐段检查现有路径,拒绝目录 junction 和文件符号链接;不存在的后缀由本次操作创建。\n let current = root\n for (const segment of path.relative(root, target).split(path.sep).filter(Boolean)) {\n current = path.join(current, segment)\n try {\n if ((await fsp.lstat(current)).isSymbolicLink())\n throw createInvalidPathError()\n }\n catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT')\n break\n throw error\n }\n }\n return target\n }\n\n function logOperation(category: 'read' | 'write', operation: string, payload: unknown): void {\n logStorageOperation(logger, config?.operationLog, category, operation, payload)\n }\n\n // -------------------------------------------------------------------------\n // 文件操作实现\n // -------------------------------------------------------------------------\n\n const file: FileOperations = {\n async put(key, data, options = {}): Promise<HaiResult<FileMetadata>> {\n if (isMetaFile(key)) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_metaFileAccess', { params: { key } }),\n )\n }\n let temporaryPath: string | undefined\n try {\n const filePath = await fullPath(key)\n const metaPath = await fullPath(`${key}.meta.json`)\n logOperation('write', 'file.put', { key, options })\n\n // 确保目录存在\n const dir = path.dirname(filePath)\n await fsp.mkdir(dir, { recursive: true, mode: getConfig().directoryMode })\n\n // 先流式写入同目录临时文件,失败不破坏已有对象;发布采用原子文件系统操作。\n temporaryPath = `${filePath}.${crypto.randomUUID()}.upload.tmp`\n await pipeline(\n Readable.from(toByteStream(data, options.signal), { objectMode: false }),\n fs.createWriteStream(temporaryPath, { flags: 'wx', mode: getConfig().fileMode }),\n { signal: options.signal },\n )\n options.signal?.throwIfAborted()\n if (options.ifNoneMatch === '*')\n await fsp.link(temporaryPath, filePath)\n else\n await fsp.rename(temporaryPath, filePath)\n\n // 获取元数据\n const stat = await fsp.stat(filePath)\n const metadata: FileMetadata = {\n key,\n size: stat.size,\n contentType: options.contentType || getMimeType(key),\n lastModified: stat.mtime,\n etag: calculateEtag(stat),\n metadata: options.metadata,\n }\n\n // 存储自定义元数据(使用扩展属性或元数据文件)\n if (options.metadata || options.contentType) {\n await fsp.writeFile(metaPath, JSON.stringify({\n contentType: options.contentType,\n metadata: options.metadata,\n }))\n }\n else {\n await fsp.rm(metaPath, { force: true })\n }\n\n return ok(metadata)\n }\n catch (error) {\n logger.warn('Put file failed', { key, error })\n return err(toStorageError(error, key))\n }\n finally {\n if (temporaryPath)\n await fsp.unlink(temporaryPath).catch(() => {})\n }\n },\n\n async get(key, options = {}): Promise<HaiResult<Buffer>> {\n const streamed = await file.getStream(key, options)\n if (!streamed.success)\n return streamed\n try {\n const chunks: Uint8Array[] = []\n for await (const chunk of streamed.data)\n chunks.push(chunk)\n return ok(Buffer.concat(chunks))\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async getStream(key, options = {}) {\n if (isMetaFile(key)) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_metaFileAccess', { params: { key } }),\n )\n }\n try {\n const filePath = await fullPath(key)\n logOperation('read', 'file.getStream', { key, options })\n options.signal?.throwIfAborted()\n const stat = await fsp.stat(filePath)\n if (stat.isDirectory())\n return err(HaiStorageError.INVALID_PATH, storageM('storage_pathIsDir', { params: { key } }))\n // 延迟打开,未消费的返回值不会占用文件句柄;迭代错误交由流消费者处理。\n async function* read(): AsyncIterable<Uint8Array> {\n const stream = fs.createReadStream(filePath, { start: options.rangeStart, end: options.rangeEnd, signal: options.signal })\n for await (const chunk of stream)\n yield chunk\n }\n return ok(read())\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async head(key): Promise<HaiResult<FileMetadata>> {\n if (isMetaFile(key)) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_metaFileAccess', { params: { key } }),\n )\n }\n try {\n const filePath = await fullPath(key)\n logOperation('read', 'file.head', { key })\n const stat = await fsp.stat(filePath)\n\n if (stat.isDirectory()) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_pathIsDir', { params: { key } }),\n )\n }\n\n // 尝试读取元数据文件\n let customMetadata: { contentType?: string, metadata?: Record<string, string> } = {}\n try {\n const metaPath = await fullPath(`${key}.meta.json`)\n const metaContent = await fsp.readFile(metaPath, 'utf-8')\n customMetadata = JSON.parse(metaContent)\n }\n catch {\n // 元数据文件不存在,忽略\n }\n\n // 基于文件大小和修改时间生成 ETag(无需读取整个文件)\n const etag = `\"${stat.size.toString(16)}-${stat.mtimeMs.toString(16)}\"`\n\n return ok({\n key,\n size: stat.size,\n contentType: customMetadata.contentType || getMimeType(key),\n lastModified: stat.mtime,\n etag,\n metadata: customMetadata.metadata,\n })\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async exists(key): Promise<HaiResult<boolean>> {\n if (isMetaFile(key)) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_metaFileAccess', { params: { key } }),\n )\n }\n try {\n const filePath = await fullPath(key)\n logOperation('read', 'file.exists', { key })\n await fsp.access(filePath, fs.constants.F_OK)\n return ok(true)\n }\n catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return ok(false)\n }\n return err(toStorageError(error, key))\n }\n },\n\n async delete(key): Promise<HaiResult<void>> {\n if (isMetaFile(key)) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_metaFileAccess', { params: { key } }),\n )\n }\n try {\n const filePath = await fullPath(key)\n logOperation('write', 'file.delete', { key })\n logger.debug('Deleting file', { key })\n await fsp.unlink(filePath)\n\n // 尝试删除元数据文件\n try {\n await fsp.unlink(`${filePath}.meta.json`)\n }\n catch {\n // 忽略\n }\n\n return ok(undefined)\n }\n catch (error) {\n // 如果文件不存在,视为删除成功\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return ok(undefined)\n }\n logger.warn('Delete file failed', { key, error })\n return err(toStorageError(error, key))\n }\n },\n\n async deleteMany(keys): Promise<StorageDeleteResult> {\n logOperation('write', 'file.deleteMany', { count: keys.length, keys })\n const summary: StorageDeleteSummary = { deletedKeys: [], failures: [] }\n const uniqueKeys = [...new Set(keys)]\n // 有界并行;reject 也必须形成失败项,不能被 Promise.allSettled 静默丢弃。\n for (let offset = 0; offset < uniqueKeys.length; offset += 32) {\n const batch = uniqueKeys.slice(offset, offset + 32)\n const results = await Promise.allSettled(batch.map(key => file.delete(key)))\n for (const [index, result] of results.entries()) {\n const key = batch[index]\n if (result.status === 'rejected')\n summary.failures.push({ key, code: 'UnexpectedDeleteFailure', retryable: false, outcome: 'failed' })\n else if (!result.value.success)\n summary.failures.push({ key, code: String(result.value.error.code), retryable: false, outcome: 'failed' })\n else\n summary.deletedKeys.push(key)\n }\n }\n return toStorageDeleteResult(summary)\n },\n\n async copy(sourceKey, destKey, options = {}): Promise<HaiResult<FileMetadata>> {\n logOperation('write', 'file.copy', { sourceKey, destKey, options })\n const source = await file.head(sourceKey)\n if (!source.success)\n return source\n const stream = await file.getStream(sourceKey)\n if (!stream.success)\n return stream\n return file.put(destKey, stream.data, {\n contentType: options.contentType ?? source.data.contentType,\n metadata: options.metadata ?? source.data.metadata,\n })\n },\n }\n\n // -------------------------------------------------------------------------\n // 目录操作实现\n // -------------------------------------------------------------------------\n\n const dir: DirOperations = {\n async list(options = {}): Promise<HaiResult<ListResult>> {\n try {\n const cfg = getConfig()\n const prefix = options.prefix === undefined ? '' : options.prefix\n const delimiter = options.delimiter === undefined ? '' : options.delimiter\n const maxKeys = options.maxKeys === undefined ? 1000 : options.maxKeys\n if (typeof prefix !== 'string' || typeof delimiter !== 'string' || !Number.isInteger(maxKeys) || maxKeys < 1 || maxKeys > 1000\n || Buffer.from(prefix).toString('utf8') !== prefix || Buffer.from(delimiter).toString('utf8') !== delimiter) {\n return err(HaiStorageError.INVALID_LIST_OPTIONS, storageM('storage_listOptionsInvalid'))\n }\n logOperation('read', 'dir.list', { options })\n\n // 前缀只是 key 字符串,不能当作目录打开;从规范根目录遍历,子目录符号链接不会被跟随。\n safePath(cfg.root, prefix)\n const root = await fsp.realpath(cfg.root)\n const scope = crypto.createHash('sha256').update(JSON.stringify([root, prefix, delimiter])).digest('hex')\n let after = ''\n if (options.continuationToken !== undefined) {\n const token = options.continuationToken\n try {\n if (typeof token !== 'string' || token.length === 0 || token.length > 262_144)\n throw new Error('Invalid cursor encoding')\n const bytes = Buffer.from(token, 'base64url')\n if (bytes.toString('base64url') !== token)\n throw new Error('Invalid cursor encoding')\n const decoded: unknown = JSON.parse(bytes.toString('utf8'))\n if (!Array.isArray(decoded) || decoded.length !== 3 || decoded[0] !== 1 || decoded[1] !== scope\n || typeof decoded[2] !== 'string' || decoded[2].length === 0 || !decoded[2].startsWith(prefix)) {\n throw new Error('Invalid cursor scope')\n }\n after = decoded[2]\n }\n catch {\n return err(HaiStorageError.INVALID_LIST_OPTIONS, storageM('storage_listCursorInvalid'))\n }\n }\n const files: FileMetadata[] = []\n const commonPrefixes: string[] = []\n const compare = (left: string, right: string) => Buffer.compare(Buffer.from(left), Buffer.from(right))\n\n // 按 name + '/' 排序目录,再深度遍历,可保持完整文件 key 的字节序(a.txt 在 a/x 之前)。\n async function* readDir(dirPath: string, directoryKey: string): AsyncGenerator<{ key: string, filePath: string }> {\n const entries = await fsp.readdir(dirPath, { withFileTypes: true })\n entries.sort((left, right) => compare(left.name + (left.isDirectory() ? '/' : ''), right.name + (right.isDirectory() ? '/' : '')))\n for (const entry of entries) {\n if (isMetaFile(entry.name))\n continue\n const key = directoryKey + entry.name\n const filePath = path.join(dirPath, entry.name)\n if (entry.isDirectory()) {\n const childPrefix = `${key}/`\n if (prefix.startsWith(childPrefix) || childPrefix.startsWith(prefix)) {\n yield* readDir(filePath, childPrefix)\n }\n }\n else if (entry.isFile() && key.startsWith(prefix)) {\n yield { key, filePath }\n }\n }\n }\n\n let previous = ''\n let last = ''\n let count = 0\n let isTruncated = false\n for await (const entry of readDir(root, '')) {\n const index = delimiter ? entry.key.indexOf(delimiter, prefix.length) : -1\n const logicalKey = index < 0 ? entry.key : entry.key.slice(0, index + delimiter.length)\n if (logicalKey === previous)\n continue\n previous = logicalKey\n if (compare(logicalKey, after) <= 0)\n continue\n // 多读取一个逻辑条目证明存在下一页;恰好一页时不伪造 continuation。\n if (count === maxKeys) {\n isTruncated = true\n break\n }\n if (index >= 0) {\n commonPrefixes.push(logicalKey)\n }\n else {\n const stat = await fsp.stat(entry.filePath)\n files.push({ key: entry.key, size: stat.size, contentType: getMimeType(entry.key), lastModified: stat.mtime })\n }\n count++\n last = logicalKey\n }\n return ok({\n files,\n commonPrefixes,\n isTruncated,\n ...(isTruncated ? { nextContinuationToken: Buffer.from(JSON.stringify([1, scope, last])).toString('base64url') } : {}),\n })\n }\n catch (error) {\n return err(toStorageError(error))\n }\n },\n\n async delete(prefix): Promise<HaiResult<void>> {\n try {\n const dirPath = await fullPath(prefix)\n logOperation('write', 'dir.delete', { prefix })\n\n // 递归删除目录\n await fsp.rm(dirPath, { recursive: true, force: true })\n\n return ok(undefined)\n }\n catch (error) {\n // 如果目录不存在,视为删除成功\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return ok(undefined)\n }\n return err(toStorageError(error, prefix))\n }\n },\n }\n\n // -------------------------------------------------------------------------\n // 签名 URL 操作实现\n // -------------------------------------------------------------------------\n\n const presign: PresignOperations = {\n async getUrl(key, options?): Promise<HaiResult<string>> {\n logOperation('read', 'presign.getUrl', { key, options })\n return err(HaiStorageError.UNSUPPORTED_OPERATION, storageM('storage_localPresignUnsupported'))\n },\n\n async putUrl(key, options?): Promise<HaiResult<string>> {\n logOperation('write', 'presign.putUrl', { key, options })\n return err(HaiStorageError.UNSUPPORTED_OPERATION, storageM('storage_localPresignUnsupported'))\n },\n\n publicUrl(key): string | null {\n logOperation('read', 'presign.publicUrl', { key })\n // 本地存储不支持公开 URL\n return null\n },\n }\n\n // -------------------------------------------------------------------------\n // Provider 接口实现\n // -------------------------------------------------------------------------\n\n return {\n name: 'local',\n\n file,\n dir,\n presign,\n\n async connect(cfg: StorageConfig): Promise<HaiResult<void>> {\n if (cfg.type !== 'local') {\n return err(\n HaiStorageError.CONFIG_ERROR,\n storageM('storage_localConfigTypeError'),\n )\n }\n\n try {\n config = cfg\n logger.info('Connecting local provider', { root: cfg.root })\n\n // 确保根目录存在\n await fsp.mkdir(cfg.root, { recursive: true, mode: cfg.directoryMode })\n\n config = { ...cfg, root: await fsp.realpath(cfg.root) }\n connected = true\n logger.info('Local provider connected', { root: cfg.root })\n return ok(undefined)\n }\n catch (error) {\n logger.error('Local provider connect failed', { error })\n config = null\n return err(toStorageError(error))\n }\n },\n\n async close(): Promise<void> {\n logger.info('Local provider disconnected')\n config = null\n connected = false\n },\n\n isConnected(): boolean {\n return connected\n },\n }\n}\n","/**\n * @h-ai/storage — S3 Provider 实现\n *\n * 基于 AWS SDK v3 实现的 S3 协议存储 Provider。\n * 支持 AWS S3、MinIO、RustFS、阿里云 OSS 等兼容 S3 协议的存储服务。\n * @module storage-provider-s3\n */\n\nimport type { BucketLocationConstraint, S3ClientConfig } from '@aws-sdk/client-s3'\nimport type { HaiError, HaiResult } from '@h-ai/core'\nimport type { S3Config, StorageConfig } from '../storage-config.js'\nimport type { DirOperations, FileMetadata, FileOperations, ListResult, PresignOperations, StorageDeleteFailure, StorageDeleteResult, StorageDeleteSummary } from '../storage-types.js'\nimport type { StorageProvider } from './storage-provider-types.js'\nimport { Buffer } from 'node:buffer'\nimport { Readable } from 'node:stream'\nimport {\n CopyObjectCommand,\n CreateBucketCommand,\n DeleteObjectCommand,\n DeleteObjectsCommand,\n GetObjectCommand,\n HeadBucketCommand,\n HeadObjectCommand,\n ListObjectsV2Command,\n PutObjectCommand,\n S3Client,\n} from '@aws-sdk/client-s3'\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner'\n\nimport { core, err, ok } from '@h-ai/core'\n\nimport { PresignOptionsSchema, PresignUploadOptionsSchema } from '../storage-config.js'\nimport { toStorageDeleteResult } from '../storage-delete.js'\nimport { storageM } from '../storage-i18n.js'\nimport { toByteStream } from '../storage-stream.js'\nimport {\n\n HaiStorageError,\n\n} from '../storage-types.js'\nimport { logStorageOperation } from './storage-operation-log.js'\n\nconst logger = core.logger.child({ module: 'storage', scope: 'provider-s3' })\nconst TRAILING_SLASHES_REGEX = /\\/+$/\nconst LEADING_SLASHES_REGEX = /^\\/+/\n/** S3 单次 DeleteObjects 最多接受 1000 项;不并发放大删除请求。 */\nconst DELETE_BATCH_SIZE = 1000\n/** 只重试服务明确报告的瞬时逐项错误,最多三次,不重发已成功或权限失败项。 */\nconst RETRYABLE_DELETE_CODES = new Set(['InternalError', 'ServiceUnavailable', 'SlowDown', 'RequestTimeout'])\n\n// ─── 辅助函数 ───\n\n/**\n * 将 S3 SDK 错误转换为 StorageError\n *\n * 映射规则:\n * - NoSuchKey / NotFound / 404 → NOT_FOUND\n * - AccessDenied → PERMISSION_DENIED\n * - NoSuchBucket → CONFIG_ERROR\n * - NetworkError / ECONNREFUSED → NETWORK_ERROR\n * - 其他 → OPERATION_FAILED\n *\n * @param error - AWS SDK 抛出的原始错误\n * @param key - 相关文件键(可选,用于错误消息)\n * @returns 统一的 StorageError\n */\nfunction toStorageError(error: unknown, key?: string): HaiError {\n const e = error as { name?: string, message?: string, Code?: string }\n\n if (e.name === 'PreconditionFailed' || (e as { $metadata?: { httpStatusCode?: number } }).$metadata?.httpStatusCode === 412)\n return { ...HaiStorageError.ALREADY_EXISTS, message: storageM('storage_fileAlreadyExists', { params: { key: key ?? '' } }), cause: error }\n\n // S3 特定错误处理\n if (e.name === 'NoSuchKey' || e.Code === 'NoSuchKey'\n || e.name === 'NotFound' || e.Code === 'NotFound'\n || e.name === '404' || (e as { $metadata?: { httpStatusCode?: number } }).$metadata?.httpStatusCode === 404) {\n return {\n ...HaiStorageError.NOT_FOUND,\n message: storageM('storage_fileNotFound', { params: { key: key ?? '' } }),\n cause: error,\n }\n }\n\n if (e.name === 'AccessDenied' || e.Code === 'AccessDenied') {\n return {\n ...HaiStorageError.PERMISSION_DENIED,\n message: storageM('storage_permissionDenied', { params: { key: key ?? '' } }),\n cause: error,\n }\n }\n\n if (e.name === 'NoSuchBucket' || e.Code === 'NoSuchBucket') {\n return {\n ...HaiStorageError.CONFIG_ERROR,\n message: storageM('storage_bucketNotExist'),\n cause: error,\n }\n }\n\n // 网络相关错误\n if (e.name === 'NetworkError' || e.name?.includes('ECONNREFUSED')) {\n return {\n ...HaiStorageError.NETWORK_ERROR,\n message: storageM('storage_networkError'),\n cause: error,\n }\n }\n\n return {\n ...HaiStorageError.OPERATION_FAILED,\n message: storageM('storage_operationFailed', { params: { error: e.message ?? '' } }),\n cause: error,\n }\n}\n\n/**\n * 为 key 添加配置中的路径前缀\n *\n * @param key - 用户提供的文件键\n * @param prefix - 配置中的前缀(可能为空)\n * @returns 拼接后的完整 S3 key\n */\nfunction withPrefix(key: string, prefix: string): string {\n if (!prefix)\n return key\n return `${prefix.replace(TRAILING_SLASHES_REGEX, '')}/${key.replace(LEADING_SLASHES_REGEX, '')}`\n}\n\n/**\n * 从 S3 key 中移除配置前缀,还原为用户视角的文件键\n *\n * @param key - 包含前缀的完整 S3 key\n * @param prefix - 配置中的前缀\n * @returns 去除前缀后的文件键\n */\nfunction withoutPrefix(key: string, prefix: string): string {\n if (!prefix)\n return key\n const normalizedPrefix = `${prefix.replace(TRAILING_SLASHES_REGEX, '')}/`\n if (key.startsWith(normalizedPrefix)) {\n return key.slice(normalizedPrefix.length)\n }\n return key\n}\n\n// ─── S3 Provider 实现 ───\n\n/**\n * 创建 S3 存储 Provider\n *\n * 基于 AWS SDK v3 实现的 StorageProvider。\n * connect 时创建 S3Client,按 createBucket 可选检查/创建桶,再通过 ListObjectsV2 验证连接。\n * 支持配置 prefix 重定向所有 key,对用户透明。\n *\n * 实现说明:\n * - copy 时若指定了 contentType 或 metadata,使用 MetadataDirective='REPLACE'\n * - dir.delete 通过先 list 再批量删除实现\n * - presign 使用 @aws-sdk/s3-request-presigner 签名\n *\n * @returns S3 Provider 实例\n * @example 模块内部上下文\n * ```ts\n * const provider = createS3Provider()\n * await provider.close() // 客户端由 connect 分配;close 释放本地 SDK 资源。\n * ```\n */\nexport function createS3Provider(): StorageProvider {\n let client: S3Client | null = null\n let deleteClient: S3Client | null = null\n let presignClient: S3Client | null = null\n let publicBasePath = ''\n let config: S3Config | null = null\n let ready = false\n let closing: Promise<void> | null = null\n\n /** 所有自有客户端独立释放;失败的客户端保留供重试,成功的客户端不重复释放。 */\n function closeClients(): Promise<void> {\n ready = false\n config = null\n publicBasePath = ''\n if (closing)\n return closing\n closing = Promise.resolve().then(() => {\n const failures: unknown[] = []\n for (const kind of ['normal', 'delete', 'presign'] as const) {\n const owned = kind === 'normal' ? client : kind === 'delete' ? deleteClient : presignClient\n if (!owned)\n continue\n try {\n owned.destroy()\n if (kind === 'normal')\n client = null\n else if (kind === 'delete')\n deleteClient = null\n else presignClient = null\n }\n catch (error) {\n failures.push(error)\n }\n }\n if (failures.length > 0)\n throw new AggregateError(failures, 'S3 client cleanup failed')\n }).finally(() => { closing = null })\n return closing\n }\n\n // -------------------------------------------------------------------------\n // 内部辅助\n // -------------------------------------------------------------------------\n\n /** 预签名仅使用外部端点;CRUD 与健康检查仍使用内部端点,批量删除禁用 SDK 重试。 */\n function getClient(operation?: 'deleteMany' | 'presign'): S3Client {\n const selected = operation === 'deleteMany' ? deleteClient : operation === 'presign' ? presignClient ?? client : client\n if (!ready || !selected) {\n throw new Error(storageM('storage_s3ClientNotInitialized'))\n }\n return selected\n }\n\n /**\n * 只添加入口代理目录,保留原始签名路径编码与查询;Nginx 转发时去除同一目录。\n * @param url - S3 客户端按公开 origin 生成的签名地址。\n * @param basePath - 已校验且无末尾斜杠的入口目录,空字符串保持地址不变。\n * @returns 添加入口目录后的浏览器地址,不重新编码路径或查询。\n * @example 模块内部上下文\n * ```ts\n * const url = publicPresignedUrl('https://files.example.com/private/report.pdf', '/delivery/files')\n * void url\n * ```\n */\n function publicPresignedUrl(url: string, basePath: string): string {\n if (!basePath)\n return url\n const pathOffset = url.indexOf('/', url.indexOf('://') + 3)\n return `${url.slice(0, pathOffset)}${basePath}${url.slice(pathOffset)}`\n }\n\n /** 获取当前 S3 配置,未初始化时抛异常 */\n function getConfig(): S3Config {\n if (!ready || !config) {\n throw new Error(storageM('storage_s3ConfigNotInitialized'))\n }\n return config\n }\n\n /** 将用户 key 拼接配置前缀,得到完整 S3 对象键 */\n function fullKey(key: string): string {\n return withPrefix(key, getConfig().prefix)\n }\n\n function logOperation(category: 'read' | 'write', operation: string, payload: unknown): void {\n logStorageOperation(logger, config?.operationLog, category, operation, payload)\n }\n\n // -------------------------------------------------------------------------\n // 文件操作实现\n // -------------------------------------------------------------------------\n\n const file: FileOperations = {\n async put(key, data, options = {}): Promise<HaiResult<FileMetadata>> {\n try {\n const s3Client = getClient()\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n\n options.signal?.throwIfAborted()\n const body = typeof data === 'string' ? Buffer.from(data) : data\n const buffered = body instanceof Uint8Array\n let size = buffered ? body.byteLength : 0\n const params = {\n Bucket: s3Config.bucket,\n Key: fullPath,\n ContentType: options.contentType,\n Metadata: options.metadata,\n CacheControl: options.cacheControl,\n ContentDisposition: options.contentDisposition,\n IfNoneMatch: options.ifNoneMatch,\n }\n logOperation('write', 'file.put', { key, fullPath, byteLength: buffered ? size : undefined, options })\n if (buffered) {\n await s3Client.send(new PutObjectCommand({ ...params, Body: body }), { abortSignal: options.signal })\n }\n else {\n async function* counted(): AsyncIterable<Uint8Array> {\n for await (const chunk of toByteStream(data, options.signal)) {\n size += chunk.byteLength\n yield chunk\n }\n }\n const stream = Readable.from(counted(), { objectMode: false })\n const controller = new AbortController()\n const abort = () => controller.abort()\n options.signal?.addEventListener('abort', abort, { once: true })\n try {\n if (options.signal?.aborted)\n abort()\n // 标准 S3 multipart;SDK 管理分片与失败清理,最多缓冲两片。\n const { Upload } = await import('@aws-sdk/lib-storage')\n await new Upload({ client: s3Client, params: { ...params, Body: stream }, queueSize: 2, leavePartsOnError: false, abortController: controller }).done()\n }\n finally {\n options.signal?.removeEventListener('abort', abort)\n stream.destroy()\n }\n }\n\n // 获取上传后的元数据\n const headResult = await file.head(key)\n if (headResult.success) {\n return headResult\n }\n\n // 如果获取元数据失败,返回基本信息\n return ok({\n key,\n size,\n contentType: options.contentType || 'application/octet-stream',\n lastModified: new Date(),\n })\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async get(key, options = {}): Promise<HaiResult<Buffer>> {\n const streamed = await file.getStream(key, options)\n if (!streamed.success)\n return streamed\n try {\n const chunks: Uint8Array[] = []\n for await (const chunk of streamed.data)\n chunks.push(chunk)\n return ok(Buffer.concat(chunks))\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async getStream(key, options = {}) {\n try {\n const s3Client = getClient()\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n logOperation('read', 'file.getStream', { key, fullPath, options })\n\n let Range: string | undefined\n if (options.rangeStart !== undefined || options.rangeEnd !== undefined) {\n const start = options.rangeStart ?? 0\n const end = options.rangeEnd !== undefined ? options.rangeEnd : ''\n Range = `bytes=${start}-${end}`\n }\n\n const response = await s3Client.send(new GetObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n Range,\n }), { abortSignal: options.signal })\n\n if (!response.Body) {\n return err(\n HaiStorageError.OPERATION_FAILED,\n storageM('storage_responseBodyEmpty'),\n )\n }\n\n // Node SDK 的 Body 是字节可迭代流;SDK 类型联合也包含其他运行时的 Blob。\n const stream = response.Body as AsyncIterable<Uint8Array>\n return ok(stream)\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async head(key): Promise<HaiResult<FileMetadata>> {\n try {\n const s3Client = getClient()\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n logOperation('read', 'file.head', { key, fullPath })\n\n const response = await s3Client.send(new HeadObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n }))\n\n return ok({\n key,\n size: response.ContentLength || 0,\n contentType: response.ContentType || 'application/octet-stream',\n lastModified: response.LastModified || new Date(),\n etag: response.ETag,\n metadata: response.Metadata,\n })\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async exists(key): Promise<HaiResult<boolean>> {\n logOperation('read', 'file.exists', { key })\n const result = await file.head(key)\n if (result.success) {\n return ok(true)\n }\n if (result.error.code === HaiStorageError.NOT_FOUND.code) {\n return ok(false)\n }\n return err(result.error)\n },\n\n async delete(key): Promise<HaiResult<void>> {\n try {\n const s3Client = getClient()\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n logOperation('write', 'file.delete', { key, fullPath })\n\n await s3Client.send(new DeleteObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n }))\n\n return ok(undefined)\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async deleteMany(keys): Promise<StorageDeleteResult> {\n try {\n const s3Client = getClient('deleteMany')\n const s3Config = getConfig()\n logOperation('write', 'file.deleteMany', { count: keys.length, keys })\n const uniqueKeys = [...new Set(keys)]\n const summary: StorageDeleteSummary = { deletedKeys: [], failures: [] }\n for (let offset = 0; offset < uniqueKeys.length; offset += DELETE_BATCH_SIZE) {\n let pending = uniqueKeys.slice(offset, offset + DELETE_BATCH_SIZE)\n for (let attempt = 0; pending.length > 0 && attempt < 3; attempt++) {\n let response\n try {\n response = await s3Client.send(new DeleteObjectsCommand({\n Bucket: s3Config.bucket,\n Delete: { Objects: [...new Set(pending.map(fullKey))].map(Key => ({ Key })), Quiet: false },\n }))\n }\n catch (cause) {\n // 已发送请求的结果可能未知,尚未发送的后续批次不能被标记为已删除。\n const failure = (cause && typeof cause === 'object' ? cause : {}) as { name?: string, $metadata?: { httpStatusCode?: number } }\n const status = failure.$metadata?.httpStatusCode\n const code = failure.name && /^[\\w.-]{1,80}$/.test(failure.name) ? failure.name : 'RequestFailed'\n const retryable = status === undefined || status === 408 || status === 429 || status >= 500\n const outcome: StorageDeleteFailure['outcome'] = status !== undefined && status >= 400 && status < 500 && status !== 408 ? 'failed' : 'unknown'\n summary.failures.push(...pending.map(key => ({ key, code, retryable, outcome })))\n summary.failures.push(...uniqueKeys.slice(offset + DELETE_BATCH_SIZE).map(key => ({ key, code, retryable, outcome: 'notAttempted' as const })))\n return toStorageDeleteResult(summary)\n }\n const outcomes = new Map<string, string | null>()\n const requested = new Set(pending.map(fullKey))\n let malformed = false\n // 非 quiet 响应中每个请求键必须恰好出现一次;遗漏、重复和越界都不能宣称成功。\n const items = [\n ...(response.Deleted ?? []).map(item => ({ key: item.Key, code: null })),\n ...(response.Errors ?? []).map(item => ({ key: item.Key, code: item.Code && /^[\\w.-]{1,80}$/.test(item.Code) ? item.Code : 'UnknownError' })),\n ]\n for (const item of items) {\n if (!item.key || !requested.has(item.key) || outcomes.has(item.key)) {\n malformed = true\n break\n }\n outcomes.set(item.key, item.code)\n }\n if (malformed || outcomes.size !== requested.size) {\n summary.failures.push(...pending.map(key => ({ key, code: 'InvalidDeleteResponse', retryable: false, outcome: 'unknown' as const })))\n summary.failures.push(...uniqueKeys.slice(offset + DELETE_BATCH_SIZE).map(key => ({ key, code: 'InvalidDeleteResponse', retryable: false, outcome: 'notAttempted' as const })))\n return toStorageDeleteResult(summary)\n }\n const retry: string[] = []\n for (const key of pending) {\n const code = outcomes.get(fullKey(key))\n if (code === null)\n summary.deletedKeys.push(key)\n else if (code && RETRYABLE_DELETE_CODES.has(code) && attempt < 2)\n retry.push(key)\n else\n summary.failures.push({ key, code: code ?? 'UnknownError', retryable: !!code && RETRYABLE_DELETE_CODES.has(code), outcome: 'failed' })\n }\n pending = retry\n if (pending.length > 0)\n await new Promise(resolve => setTimeout(resolve, 100 * 2 ** attempt))\n }\n }\n return toStorageDeleteResult(summary)\n }\n catch (error) {\n return err(toStorageError(error))\n }\n },\n\n async copy(sourceKey, destKey, options = {}): Promise<HaiResult<FileMetadata>> {\n try {\n const s3Client = getClient()\n const s3Config = getConfig()\n const sourceFullPath = fullKey(sourceKey)\n const destFullPath = fullKey(destKey)\n logOperation('write', 'file.copy', { sourceKey, destKey, sourceFullPath, destFullPath, options })\n\n await s3Client.send(new CopyObjectCommand({\n Bucket: s3Config.bucket,\n CopySource: `${s3Config.bucket}/${sourceFullPath}`,\n Key: destFullPath,\n ContentType: options.contentType,\n Metadata: options.metadata,\n MetadataDirective: (options.metadata || options.contentType) ? 'REPLACE' : 'COPY',\n }))\n\n return file.head(destKey)\n }\n catch (error) {\n return err(toStorageError(error, sourceKey))\n }\n },\n }\n\n // -------------------------------------------------------------------------\n // 目录操作实现\n // -------------------------------------------------------------------------\n\n const dir: DirOperations = {\n async list(options = {}): Promise<HaiResult<ListResult>> {\n try {\n const s3Client = getClient()\n const s3Config = getConfig()\n logOperation('read', 'dir.list', { options })\n\n // 构建完整前缀\n let prefix = s3Config.prefix || ''\n if (options.prefix) {\n prefix = withPrefix(options.prefix, s3Config.prefix)\n }\n\n const response = await s3Client.send(new ListObjectsV2Command({\n Bucket: s3Config.bucket,\n Prefix: prefix || undefined,\n Delimiter: options.delimiter,\n MaxKeys: options.maxKeys,\n ContinuationToken: options.continuationToken,\n }))\n\n const files: FileMetadata[] = (response.Contents || []).map(item => ({\n key: withoutPrefix(item.Key || '', s3Config.prefix),\n size: item.Size || 0,\n contentType: 'application/octet-stream', // ListObjects 不返回 ContentType\n lastModified: item.LastModified || new Date(),\n etag: item.ETag,\n }))\n\n const commonPrefixes = (response.CommonPrefixes || [])\n .map(p => withoutPrefix(p.Prefix || '', s3Config.prefix))\n .filter(p => p.length > 0)\n\n return ok({\n files,\n commonPrefixes,\n nextContinuationToken: response.NextContinuationToken,\n isTruncated: response.IsTruncated || false,\n })\n }\n catch (error) {\n return err(toStorageError(error))\n }\n },\n\n async delete(prefix): Promise<HaiResult<void>> {\n try {\n logOperation('write', 'dir.delete', { prefix })\n // 分页列出并逐批删除,每批最多 1000 个对象\n let continuationToken: string | undefined\n\n do {\n const listResult = await dir.list({\n prefix,\n continuationToken,\n maxKeys: 1000,\n })\n\n if (!listResult.success) {\n return err(listResult.error)\n }\n\n const keys = listResult.data.files.map(f => f.key)\n if (keys.length > 0) {\n const deleteResult = await file.deleteMany(keys)\n if (!deleteResult.success) {\n return deleteResult\n }\n }\n\n continuationToken = listResult.data.nextContinuationToken\n } while (continuationToken)\n\n return ok(undefined)\n }\n catch (error) {\n return err(toStorageError(error))\n }\n },\n }\n\n // -------------------------------------------------------------------------\n // 签名 URL 操作实现\n // -------------------------------------------------------------------------\n\n const presign: PresignOperations = {\n async getUrl(key, options?): Promise<HaiResult<string>> {\n try {\n const parsed = PresignOptionsSchema.parse(options ?? {})\n const s3Client = getClient('presign')\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n logOperation('read', 'presign.getUrl', { key, fullPath, options })\n const basePath = publicBasePath\n\n const command = new GetObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n ResponseContentType: parsed.responseContentType,\n ResponseContentDisposition: parsed.responseContentDisposition,\n })\n\n const url = await getSignedUrl(s3Client, command, {\n expiresIn: parsed.expiresIn,\n })\n\n return ok(publicPresignedUrl(url, basePath))\n }\n catch (error) {\n return err(\n HaiStorageError.PRESIGN_FAILED,\n storageM('storage_presignUrlFailed'),\n error,\n )\n }\n },\n\n async putUrl(key, options?): Promise<HaiResult<string>> {\n try {\n const parsed = PresignUploadOptionsSchema.parse(options ?? {})\n const s3Client = getClient('presign')\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n logOperation('write', 'presign.putUrl', { key, fullPath, options })\n const basePath = publicBasePath\n\n const command = new PutObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n ContentType: parsed.contentType,\n })\n\n const url = await getSignedUrl(s3Client, command, {\n expiresIn: parsed.expiresIn,\n })\n\n return ok(publicPresignedUrl(url, basePath))\n }\n catch (error) {\n return err(\n HaiStorageError.PRESIGN_FAILED,\n storageM('storage_presignUploadUrlFailed'),\n error,\n )\n }\n },\n\n publicUrl(key): string | null {\n const s3Config = getConfig()\n if (!s3Config.publicUrl) {\n return null\n }\n const fullPath = fullKey(key)\n logOperation('read', 'presign.publicUrl', { key, fullPath })\n return `${s3Config.publicUrl.replace(TRAILING_SLASHES_REGEX, '')}/${fullPath}`\n },\n }\n\n // -------------------------------------------------------------------------\n // Provider 接口实现\n // -------------------------------------------------------------------------\n\n return {\n name: 's3',\n\n file,\n dir,\n presign,\n\n async connect(cfg: StorageConfig): Promise<HaiResult<void>> {\n if (cfg.type !== 's3') {\n return err(\n HaiStorageError.CONFIG_ERROR,\n storageM('storage_s3ConfigTypeError'),\n )\n }\n\n try {\n await closeClients()\n\n config = cfg\n logger.info('Connecting S3 provider', {\n bucket: cfg.bucket,\n region: cfg.region,\n endpoint: cfg.endpoint ? core.sanitize.sanitizeUrl(cfg.endpoint) : undefined,\n })\n\n const clientConfig: S3ClientConfig = {\n region: cfg.region,\n endpoint: cfg.endpoint,\n forcePathStyle: cfg.forcePathStyle,\n // 不强制兼容服务实现 AWS checksum 扩展;校验工具按实际字节独立计算。\n requestChecksumCalculation: 'WHEN_REQUIRED',\n responseChecksumValidation: 'WHEN_REQUIRED',\n credentials: {\n accessKeyId: cfg.accessKeyId,\n secretAccessKey: cfg.secretAccessKey,\n },\n }\n client = new S3Client(clientConfig)\n // 回执丢失时重放可能删除并发新版本;只允许上层对明确的逐项瞬时错误重试。\n deleteClient = new S3Client({ ...clientConfig, maxAttempts: 1 })\n // 签名阶段不联网;不得用外部地址执行内部健康检查,也不能在签名后改写 Host。\n if (cfg.publicEndpoint) {\n const endpoint = new URL(cfg.publicEndpoint)\n publicBasePath = endpoint.pathname.replace(TRAILING_SLASHES_REGEX, '')\n // 含目录的网关使用桶路径;签名针对网关还原后的 S3 URI,而非入口前缀。\n presignClient = new S3Client({ ...clientConfig, endpoint: endpoint.origin, forcePathStyle: publicBasePath ? true : cfg.forcePathStyle })\n }\n\n if (cfg.createBucket) {\n try {\n await client.send(new HeadBucketCommand({ Bucket: cfg.bucket }))\n }\n catch (error) {\n // HeadBucket 只有明确 404 才证明缺失;403、网络错误与服务异常必须阻止启动。\n if (!(error && typeof error === 'object' && '$metadata' in error\n && error.$metadata && typeof error.$metadata === 'object'\n && 'httpStatusCode' in error.$metadata && error.$metadata.httpStatusCode === 404)) {\n throw error\n }\n try {\n await client.send(new CreateBucketCommand({\n Bucket: cfg.bucket,\n // AWS us-east-1 要省略 LocationConstraint;兼容服务可使用自定义区域名。\n ...(cfg.region === 'us-east-1' ? {} : { CreateBucketConfiguration: { LocationConstraint: cfg.region as BucketLocationConstraint } }),\n }))\n logger.info('S3 bucket created', { bucket: cfg.bucket })\n }\n catch (createError) {\n // 并发应用仅接受本账号已拥有的桶,并重新核验访问;全局名称冲突不能当成功。\n if (!(createError instanceof Error && createError.name === 'BucketAlreadyOwnedByYou'))\n throw createError\n await client.send(new HeadBucketCommand({ Bucket: cfg.bucket }))\n }\n }\n }\n\n // 测试连接:尝试列出一个对象\n await client.send(new ListObjectsV2Command({\n Bucket: cfg.bucket,\n MaxKeys: 1,\n }))\n\n config = cfg\n ready = true\n logger.info('S3 provider connected', { bucket: cfg.bucket })\n return ok(undefined)\n }\n catch (error) {\n logger.error('S3 provider connect failed', { error })\n const original = toStorageError(error)\n try {\n await closeClients()\n return err(original)\n }\n catch (cleanupError) {\n return err({ ...original, ext: { ...original.ext, cleanupPending: true, cleanupError } })\n }\n }\n },\n\n close: closeClients,\n\n isConnected(): boolean {\n return ready && client !== null && deleteClient !== null && config !== null\n },\n }\n}\n","/** @h-ai/storage — Node 字节内容 checksum 生成与校验,无存储生命周期依赖。 */\nimport type { HaiResult } from '@h-ai/core'\nimport type { ChecksumOperations, ChecksumOptions, StorageData } from './storage-types.js'\nimport { Buffer } from 'node:buffer'\nimport { createHash, timingSafeEqual } from 'node:crypto'\nimport { err, ok } from '@h-ai/core'\nimport { storageM } from './storage-i18n.js'\nimport { toByteStream } from './storage-stream.js'\nimport { HaiStorageError } from './storage-types.js'\n\nconst HEX_CHECKSUM = /^[\\da-f]+$/i\nconst BASE64_CHECKSUM = /^(?:[A-Z\\d+/]{4})*(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?$/i\nconst ALGORITHMS = new Set(['sha256', 'sha384', 'sha512', 'sha1', 'md5'])\n\nasync function generate(data: StorageData, options: ChecksumOptions = {}): Promise<HaiResult<string>> {\n try {\n const algorithm = options.algorithm ?? 'sha256'\n if (!ALGORITHMS.has(algorithm))\n return err(HaiStorageError.INVALID_CHECKSUM, storageM('storage_checksumInvalid'))\n const hash = createHash(algorithm)\n for await (const chunk of toByteStream(data, options.signal))\n hash.update(chunk)\n return ok(hash.digest(options.encoding ?? 'hex'))\n }\n catch (error) {\n return err(HaiStorageError.CHECKSUM_FAILED, storageM('storage_checksumFailed'), error)\n }\n}\n\n/**\n * Node 端无状态字节校验工具;无需初始化,流只能消费一次。\n * @example 模块内部上下文\n * ```ts\n * const calculated = await checksum.generate('hello', { algorithm: 'sha256' })\n * if (!calculated.success) throw new Error(calculated.error.message)\n * const checked = await checksum.verify('hello', calculated.data)\n * if (!checked.success) throw new Error(checked.error.message)\n * void checked.data\n * ```\n */\nexport const checksum: ChecksumOperations = {\n generate,\n async verify(data, expected, options = {}) {\n const encoding = options.encoding ?? 'hex'\n const valid = encoding === 'hex'\n ? expected.length % 2 === 0 && HEX_CHECKSUM.test(expected)\n : BASE64_CHECKSUM.test(expected) && expected.length > 0\n if (!valid)\n return err(HaiStorageError.INVALID_CHECKSUM, storageM('storage_checksumInvalid'))\n const calculated = await generate(data, options)\n if (!calculated.success)\n return calculated\n const actualBytes = Buffer.from(calculated.data, encoding)\n const expectedBytes = Buffer.from(expected, encoding)\n return ok(actualBytes.length === expectedBytes.length && timingSafeEqual(actualBytes, expectedBytes))\n },\n}\n","/**\n * @h-ai/storage — 存储服务主入口\n *\n * 提供统一的 `storage` 对象,管理运行时状态与生命周期。\n * @module storage-main\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { StorageProvider } from './providers/storage-provider-types.js'\nimport type { StorageConfig, StorageConfigInput } from './storage-config.js'\nimport type { DirOperations, FileOperations, PresignOperations, StorageFunctions } from './storage-types.js'\n\nimport process from 'node:process'\nimport { core, err, ok } from '@h-ai/core'\n\nimport { createLocalProvider } from './providers/storage-provider-local.js'\nimport { createS3Provider } from './providers/storage-provider-s3.js'\nimport { checksum } from './storage-checksum.js'\nimport { StorageConfigSchema } from './storage-config.js'\nimport { storageM } from './storage-i18n.js'\nimport {\n\n HaiStorageError,\n\n} from './storage-types.js'\n\nconst logger = core.logger.child({ module: 'storage', scope: 'main' })\n\n// ─── 内部状态 ───\n\n/** 当前使用的存储 Provider 实例(init 后非空,close 后置空) */\nlet currentProvider: StorageProvider | null = null\n\n/** 当前解析后的存储配置(init 后非空,close 后置空) */\nlet currentConfig: StorageConfig | null = null\n\n/** 并发初始化防护标志;防止多次 init() 同时执行导致资源泄漏 */\nlet initInProgress = false\n\n/** 尚未释放的候选/旧 Provider;绝不用于业务操作,清理成功后才能替换。 */\nlet pendingProvider: StorageProvider | null = null\n/** 初始化完成屏障,供 close 等待候选资源归属确定。 */\nlet initCompletion: Promise<void> | null = null\n/** 合并并发关闭,避免同一连接重复释放。 */\nlet closingPromise: Promise<HaiResult<void>> | null = null\n\n/** 停止发布资源并重试清理;失败时保留唯一所有权供下一次 close/init 重试。 */\nasync function closeOwnedProvider(): Promise<HaiResult<void>> {\n if (currentProvider) {\n pendingProvider = currentProvider\n currentProvider = null\n }\n currentConfig = null\n if (!pendingProvider)\n return ok(undefined)\n try {\n await pendingProvider.close()\n pendingProvider = null\n return ok(undefined)\n }\n catch (error) {\n logger.error('Provider cleanup failed; retaining resource for retry', { error })\n return err({ ...HaiStorageError.CONNECTION_FAILED, message: storageM('storage_operationFailed', {\n params: { error: error instanceof Error ? error.message : String(error) },\n }), cause: error, ext: { cleanupPending: true } })\n }\n}\n\n/** 初始化失败后释放候选;保留原始错误,并明确指出清理尚未完成。 */\nasync function failInitialization(result: HaiResult<void>): Promise<HaiResult<void>> {\n const cleanup = await closeOwnedProvider()\n if (result.success)\n return result\n if (cleanup.success) {\n const ext = { ...result.error.ext }\n delete ext.cleanupPending\n delete ext.cleanupError\n return err({ ...result.error, ext: Object.keys(ext).length > 0 ? ext : undefined })\n }\n return err({ ...result.error, ext: { ...result.error.ext, cleanupPending: true, cleanupError: cleanup.error } })\n}\n\n// ─── Provider 工厂 ───\n\n/**\n * 根据配置类型创建对应的存储 Provider\n *\n * @param config - 经 Schema 校验后的存储配置\n * @returns 对应类型的 Provider 实例(尚未连接)\n * @throws 理论上仅在出现未覆盖分支时抛出异常(正常情况下由 Schema 保证不会发生)\n */\nfunction createProvider(config: StorageConfig): StorageProvider {\n switch (config.type) {\n case 's3': return createS3Provider()\n case 'local': return createLocalProvider()\n default:\n throw new Error(storageM('storage_unsupportedType', { params: { type: (config as StorageConfig).type } }))\n }\n}\n\n// ─── 未初始化占位 ───\n\n/**\n * 创建未初始化状态的错误工具包\n *\n * 当 storage 未调用 init() 就直接使用 file/dir/presign 操作时,\n * 所有方法调用都会返回 NOT_INITIALIZED 错误。\n */\nconst notInitialized = core.module.createNotInitializedKit(\n HaiStorageError.NOT_INITIALIZED,\n () => storageM('storage_notInitialized'),\n)\n\n/** 未初始化时的文件操作占位代理 */\nconst notInitializedFile = notInitialized.proxy<FileOperations>()\n\n/** 未初始化时的目录操作占位代理 */\nconst notInitializedDir = notInitialized.proxy<DirOperations>()\n\n/**\n * 未初始化时的签名 URL 操作占位代理\n *\n * publicUrl 始终返回 null;其余方法返回 NOT_INITIALIZED 错误。\n */\nconst notInitializedPresignBase = notInitialized.proxy<PresignOperations>()\nconst notInitializedPresign = new Proxy(\n notInitializedPresignBase,\n {\n get: (target, prop, receiver) => prop === 'publicUrl' ? () => null : Reflect.get(target, prop, receiver),\n },\n)\n\n// ─── 存储服务对象 ───\n\n/**\n * 存储服务单例\n *\n * 文件、目录及签名操作前须调用 `storage.init()`;checksum 为无状态工具,无需初始化。\n * 初始化后通过 `storage.file`、`storage.dir`、`storage.presign` 访问各操作接口。\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const initialized = await storage.init({ type: 'local', root: './data/uploads' })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const uploaded = await storage.file.put('reports/a.txt', 'hello', { ifNoneMatch: '*' })\n * if (!uploaded.success) throw new Error(uploaded.error.message)\n * const downloaded = await storage.file.get('reports/a.txt')\n * if (!downloaded.success) throw new Error(downloaded.error.message)\n * void downloaded.data\n * } finally {\n * const closed = await storage.close()\n * if (!closed.success) throw new Error(closed.error.message)\n * }\n * ```\n */\nexport const storage: StorageFunctions = {\n checksum,\n /**\n * 初始化存储连接。\n *\n * 如果当前已有活跃连接,会先自动 close 再重新初始化。\n * 配置通过 Zod Schema 校验;校验、连接或清理失败均返回 HaiResult,不发布失败候选。\n *\n * @param config 存储配置(S3 或本地),支持省略带默认值的字段。\n * @returns 成功时返回 ok(undefined);失败时返回包含错误码和消息的 err。\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const initialized = await storage.init({ type: 'local', root: './data/uploads' })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * const closed = await storage.close()\n * if (!closed.success) throw new Error(closed.error.message)\n * ```\n */\n async init(config: StorageConfigInput): Promise<HaiResult<void>> {\n // 并发初始化防护:避免多次 init 同时执行导致 Provider 泄漏\n if (initInProgress || closingPromise) {\n logger.warn('Storage init already in progress, skipping concurrent call')\n return err(\n HaiStorageError.OPERATION_FAILED,\n storageM('storage_operationFailed', { params: { error: 'Concurrent initialization detected' } }),\n )\n }\n initInProgress = true\n let resolveInit!: () => void\n initCompletion = new Promise<void>((resolve) => {\n resolveInit = resolve\n })\n\n try {\n const cleanup = await closeOwnedProvider()\n if (!cleanup.success)\n return cleanup\n\n logger.info('Initializing storage module')\n\n const parseResult = StorageConfigSchema.safeParse(config)\n if (!parseResult.success) {\n logger.error('Storage config validation failed', { error: parseResult.error.message })\n return err(\n HaiStorageError.CONFIG_ERROR,\n storageM('storage_configError', { params: { error: parseResult.error.message } }),\n parseResult.error,\n )\n }\n const parsed = parseResult.data\n if (core.module.requiresSharedState(process.env) && (parsed.type === 'local'))\n return err(HaiStorageError.CONFIG_ERROR, storageM('storage_sharedBackendRequired'))\n\n const provider = createProvider(parsed)\n pendingProvider = provider\n const connectResult = await provider.connect(parsed)\n if (!connectResult.success) {\n logger.error('Storage module initialization failed', {\n code: connectResult.error.code,\n message: connectResult.error.message,\n })\n return await failInitialization(connectResult)\n }\n pendingProvider = null\n currentProvider = provider\n currentConfig = parsed\n logger.info('Storage module initialized')\n return ok(undefined)\n }\n catch (error) {\n logger.error('Storage module initialization failed', { error })\n return await failInitialization(err(\n HaiStorageError.CONNECTION_FAILED,\n storageM('storage_operationFailed', {\n params: { error: error instanceof Error ? error.message : String(error) },\n }),\n error,\n ))\n }\n finally {\n initInProgress = false\n initCompletion = null\n resolveInit()\n }\n },\n\n /** 文件操作接口。未初始化时所有方法返回 NOT_INITIALIZED 错误 */\n get file(): FileOperations { return currentProvider?.file ?? notInitializedFile },\n\n /** 目录操作接口。未初始化时所有方法返回 NOT_INITIALIZED 错误 */\n get dir(): DirOperations { return currentProvider?.dir ?? notInitializedDir },\n\n /** 签名 URL 操作接口。未初始化时 publicUrl 返回 null,其余返回错误 */\n get presign(): PresignOperations { return currentProvider?.presign ?? notInitializedPresign },\n\n /** 当前脱敏后的存储配置快照;未初始化或已关闭时为 null */\n get config() {\n return currentConfig ? core.sanitize.sanitizeSensitiveFields(currentConfig) : null\n },\n\n /** 是否已完成初始化 */\n get isInitialized() { return currentProvider !== null && currentProvider.isConnected() },\n\n /**\n * 关闭存储连接并释放资源\n *\n * 关闭后 file/dir/presign 操作将返回 NOT_INITIALIZED 错误。\n * 等待初始化并合并并发关闭;失败时保留资源供 close/init 重试。\n * @returns 所有客户端释放成功才返回成功。\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const closed = await storage.close()\n * if (!closed.success) throw new Error(closed.error.message)\n * ```\n */\n async close(): Promise<HaiResult<void>> {\n if (!closingPromise) {\n closingPromise = (async () => {\n if (initCompletion)\n await initCompletion\n return await closeOwnedProvider()\n })().finally(() => { closingPromise = null })\n }\n return await closingPromise\n },\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/storage-delete.ts","../src/storage-stream.ts","../src/providers/storage-operation-log.ts","../src/providers/storage-provider-local.ts","../src/providers/storage-provider-s3.ts","../src/storage-checksum.ts","../src/storage-main.ts"],"names":["logger","getMimeType","stat","dir","ok","Buffer","core","toStorageError","Readable","err","resolve","createHash","process"],"mappings":"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","file":"node.js","sourcesContent":["/** Local/S3 共用的批量删除结果构造。 */\nimport type { StorageDeleteResult, StorageDeleteSummary } from './storage-types.js'\nimport { ok } from '@h-ai/core'\nimport { storageM } from './storage-i18n.js'\nimport { HaiStorageError } from './storage-types.js'\n\n/**\n * 仅在所有对象均确认删除时返回成功;失败时保留安全、可跨端传输的部分进度。\n * @param summary - 已确认成功与未完成的项目\n * @returns 批量删除结果,不抛业务异常\n * @example 模块内部上下文\n * ```ts\n * const result = toStorageDeleteResult({ deletedKeys: ['a.txt'], failures: [] })\n * if (!result.success) throw new Error(result.error.message)\n * void result.data.deletedKeys\n * ```\n */\nexport function toStorageDeleteResult(summary: StorageDeleteSummary): StorageDeleteResult {\n if (summary.failures.length === 0)\n return ok(summary)\n return {\n success: false,\n error: {\n ...HaiStorageError.OPERATION_FAILED,\n message: storageM('storage_deleteManyFailed', { params: { count: summary.failures.length } }),\n ext: { deleteMany: summary },\n },\n }\n}\n","/**\n * @h-ai/storage — 字节流规范化\n *\n * 供存储 Provider 与 checksum 共用,保持消费有界且支持取消。\n */\nimport type { StorageData } from './storage-types.js'\nimport { Buffer } from 'node:buffer'\nimport { storageM } from './storage-i18n.js'\n\n/**\n * 规范化字节输入;取消时终止等待并关闭迭代器,拒绝对象模式流中的非字节块。\n * @param data - 待迭代的字符串、字节数组或异步字节流。\n * @param signal - 可选取消信号;取消时迭代抛出原因。\n * @returns 逐块产生字节的异步可迭代对象;不缓冲整个输入。\n * @example 模块内部上下文\n * ```ts\n * const controller = new AbortController()\n * for await (const chunk of toByteStream('hello', controller.signal)) {\n * void chunk.byteLength\n * } // 迭代可能因取消或 I/O 抛错,应由上层捕获。\n * ```\n */\nexport async function* toByteStream(data: StorageData, signal?: AbortSignal): AsyncIterable<Uint8Array> {\n signal?.throwIfAborted()\n if (typeof data === 'string' || data instanceof Uint8Array) {\n yield typeof data === 'string' ? Buffer.from(data) : data\n return\n }\n const iterator = data[Symbol.asyncIterator]()\n let completed = false\n try {\n while (true) {\n signal?.throwIfAborted()\n let result: IteratorResult<Uint8Array>\n let onAbort: (() => void) | undefined\n try {\n // 每块独立等待并释放监听,避免在长流上累积共享 Promise 的回调。\n const cancelled = signal\n ? new Promise<never>((_, reject) => {\n onAbort = () => reject(signal.reason)\n signal.addEventListener('abort', onAbort, { once: true })\n })\n : undefined\n const next = iterator.next()\n result = await (cancelled ? Promise.race([next, cancelled]) : next)\n }\n finally {\n if (onAbort)\n signal?.removeEventListener('abort', onAbort)\n }\n if (result.done) {\n completed = true\n break\n }\n if (!(result.value instanceof Uint8Array))\n throw new TypeError(storageM('storage_streamInvalidChunk'))\n yield result.value\n }\n signal?.throwIfAborted()\n }\n finally {\n if (!completed && iterator.return) {\n // 不可取消的源可能把 return 排在挂起的 next 后面;取消结果不能继续等待源释放。\n if (signal?.aborted)\n void Promise.resolve(iterator.return()).catch(() => {})\n else\n await iterator.return()\n }\n }\n}\n","import type { StorageOperationLogConfig } from '../storage-config.js'\n\n/**\n * 存储 Provider 所需的最小结构化日志接口。\n * @example 模块内部上下文\n * ```ts\n * function emit(logger: StorageOperationLogger) {\n * logger.debug('Storage operation executed', { count: 1 })\n * }\n * ```\n */\ninterface StorageOperationLogger {\n info: (message: string, context?: Record<string, unknown>) => void\n debug: (message: string, context?: Record<string, unknown>) => void\n trace: (message: string, context?: Record<string, unknown>) => void\n}\n\n/**\n * 选择存储操作日志 read/write 开关的内部分类。\n * @example 模块内部上下文\n * ```ts\n * const category: StorageOperationCategory = 'write'\n * void category\n * ```\n */\ntype StorageOperationCategory = 'read' | 'write'\n\nfunction normalizePayloadValue(value: unknown): unknown {\n if (value instanceof Uint8Array) {\n return { byteLength: value.byteLength }\n }\n return value\n}\n\nfunction stringifyOperationPayload(payload: unknown, maxLength: number): string {\n if (maxLength <= 0) {\n return ''\n }\n\n try {\n const text = JSON.stringify(payload, (_key, value: unknown) => {\n if (typeof value === 'bigint') {\n return value.toString()\n }\n if (typeof value === 'function') {\n return '[Function]'\n }\n return normalizePayloadValue(value)\n })\n return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text\n }\n catch {\n const text = String(payload)\n return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text\n }\n}\n\n/**\n * 按配置输出有界操作摘要;字节内容只记录长度,不接管连接生命周期。\n * @param logger - 当前 Provider 的结构化日志器。\n * @param config - 已解析的日志开关和长度配置;未设置时不输出。\n * @param category - read 或 write,对应日志开关。\n * @param operation - 稳定的操作名称。\n * @param payload - 诊断摘要;不传入凭据或原始文件内容。\n * @example 模块内部上下文\n * ```ts\n * function emit(logger: StorageOperationLogger) {\n * logStorageOperation(logger, { read: false, write: true, level: 'debug', maxLength: 100 }, 'write', 'file.put', { bytes: 5 })\n * }\n * ```\n */\nexport function logStorageOperation(\n logger: StorageOperationLogger,\n config: StorageOperationLogConfig | undefined,\n category: StorageOperationCategory,\n operation: string,\n payload: unknown,\n): void {\n if (!config?.[category]) {\n return\n }\n\n logger[config.level]('Storage operation executed', {\n category,\n operation,\n payload: stringifyOperationPayload(payload, config.maxLength),\n })\n}\n","/**\n * @h-ai/storage — Local Provider 实现\n *\n * 基于本地文件系统的存储 Provider。\n * @module storage-provider-local\n */\n\nimport type { HaiError, HaiResult } from '@h-ai/core'\nimport type { LocalConfig, StorageConfig } from '../storage-config.js'\nimport type { DirOperations, FileMetadata, FileOperations, ListResult, PresignOperations, StorageDeleteResult, StorageDeleteSummary } from '../storage-types.js'\nimport type { StorageProvider } from './storage-provider-types.js'\nimport { Buffer } from 'node:buffer'\nimport * as crypto from 'node:crypto'\nimport * as fs from 'node:fs'\nimport * as fsp from 'node:fs/promises'\nimport * as path from 'node:path'\nimport process from 'node:process'\nimport { Readable } from 'node:stream'\n\nimport { pipeline } from 'node:stream/promises'\nimport { core, err, ok } from '@h-ai/core'\n\nimport { toStorageDeleteResult } from '../storage-delete.js'\nimport { storageM } from '../storage-i18n.js'\nimport { MIME_TYPE_DEFAULT, MIME_TYPES } from '../storage-mime.js'\nimport { toByteStream } from '../storage-stream.js'\nimport {\n\n HaiStorageError,\n\n} from '../storage-types.js'\nimport { logStorageOperation } from './storage-operation-log.js'\n\nconst logger = core.logger.child({ module: 'storage', scope: 'provider-local' })\nconst PATH_SEGMENT_SPLIT_REGEX = /[\\\\/]+/\n\n// ─── 辅助函数 ───\n\n/**\n * 将 Node.js 文件系统错误转换为 StorageError\n *\n * 映射规则:\n * - ENOENT → NOT_FOUND\n * - EACCES / EPERM → PERMISSION_DENIED\n * - ENOSPC → QUOTA_EXCEEDED\n * - EISDIR → INVALID_PATH\n * - 其他 → IO_ERROR\n *\n * @param error - 原始错误对象\n * @param key - 相关文件键(可选,用于错误消息)\n * @returns 统一的 StorageError\n */\nfunction toStorageError(error: unknown, key?: string): HaiError {\n const e = error as NodeJS.ErrnoException\n\n if (e.code === 'EEXIST') {\n return { ...HaiStorageError.ALREADY_EXISTS, message: storageM('storage_fileAlreadyExists', { params: { key: key ?? '' } }), cause: error }\n }\n\n if (e.code === 'ENOENT') {\n return {\n ...HaiStorageError.NOT_FOUND,\n message: storageM('storage_fileNotFound', { params: { key: key ?? '' } }),\n cause: error,\n }\n }\n\n if (e.code === 'EACCES' || e.code === 'EPERM') {\n return {\n ...HaiStorageError.PERMISSION_DENIED,\n message: storageM('storage_permissionDenied', { params: { key: key ?? '' } }),\n cause: error,\n }\n }\n\n if (e.code === 'ENOSPC') {\n return {\n ...HaiStorageError.QUOTA_EXCEEDED,\n message: storageM('storage_diskSpaceInsufficient'),\n cause: error,\n }\n }\n\n if (e.code === 'EISDIR') {\n return {\n ...HaiStorageError.INVALID_PATH,\n message: storageM('storage_pathIsDir', { params: { key: key ?? '' } }),\n cause: error,\n }\n }\n\n if (e.code === 'EINVAL') {\n return {\n ...HaiStorageError.INVALID_PATH,\n message: e.message || storageM('storage_pathTraversal'),\n cause: error,\n }\n }\n\n return {\n code: HaiStorageError.IO_ERROR.code,\n message: storageM('storage_ioError', { params: { error: e.message ?? '' } }),\n cause: error,\n }\n}\n\n/**\n * 根据文件扩展名获取 MIME 类型\n *\n * 使用共享 MIME_TYPES 映射表,未匹配时返回 'application/octet-stream'。\n *\n * @param filePath - 文件路径或文件名\n * @returns MIME 类型字符串\n */\nfunction getMimeType(filePath: string): string {\n const ext = path.extname(filePath).toLowerCase().replace('.', '')\n return MIME_TYPES[ext] || MIME_TYPE_DEFAULT\n}\n\n/**\n * 根据文件 stat 信息计算 ETag\n *\n * 使用 \"size-mtime\" 格式,与 head 操作保持一致,无需读取文件内容。\n *\n * @param stat - 文件的 fs.Stats 对象\n * @returns 带双引号的 ETag 字符串,如 '\"a-18f7ec3b4c0\"'\n */\nfunction calculateEtag(stat: fs.Stats): string {\n return `\"${stat.size.toString(16)}-${stat.mtimeMs.toString(16)}\"`\n}\n\n/**\n * 检查 key 是否指向内部元数据文件(.meta.json)\n *\n * 拦截外部对 .meta.json 文件的直接访问,防止元数据泄漏。\n */\nfunction isMetaFile(key: string): boolean {\n const normalized = key.toLowerCase()\n return normalized.endsWith('.meta.json') || normalized.endsWith('.upload.tmp')\n}\n\n/**\n * 创建非法路径错误(用于统一映射为 INVALID_PATH)\n */\nfunction createInvalidPathError(): NodeJS.ErrnoException {\n const error = new Error(storageM('storage_pathTraversal')) as NodeJS.ErrnoException\n error.code = 'EINVAL'\n return error\n}\n\n/**\n * 判断路径片段中是否包含路径穿越标记(..)\n */\nfunction hasPathTraversalSegment(key: string): boolean {\n const segments = key.split(PATH_SEGMENT_SPLIT_REGEX).filter(Boolean)\n return segments.includes('..')\n}\n\n/**\n * 安全的路径解析,防止路径穿越攻击\n *\n * 处理流程:\n * 1. 拒绝绝对路径、空字节、`..` 路径穿越片段\n * 2. resolve(root, key) 得到完整路径\n * 3. 校验完整路径必须位于 root 内部(或等于 root)\n *\n * @param root - 存储根目录绝对路径\n * @param key - 用户提供的文件键\n * @returns 安全的完整文件路径\n * @throws 当解析后路径超出 root 范围时抛出异常\n */\nfunction safePath(root: string, key: string): string {\n // 拒绝绝对路径输入,防止 Windows 盘符绕过(如 D:\\etc\\passwd)\n if (path.isAbsolute(key)) {\n throw createInvalidPathError()\n }\n\n // 拒绝空字节与路径穿越片段\n if (key.includes('\\0') || hasPathTraversalSegment(key)\n || (process.platform === 'win32' && key.split(PATH_SEGMENT_SPLIT_REGEX).some(segment => /[:. ]$/.test(segment) || segment.includes(':')))) {\n throw createInvalidPathError()\n }\n\n const normalized = path.normalize(key)\n const fullPath = path.resolve(root, normalized)\n\n // 确保路径在 root 目录下\n const realRoot = path.resolve(root)\n const inRoot = fullPath === realRoot || fullPath.startsWith(`${realRoot}${path.sep}`)\n\n if (!inRoot) {\n throw createInvalidPathError()\n }\n\n return fullPath\n}\n\n// ─── Local Provider 实现 ───\n\n/**\n * 创建本地存储 Provider\n *\n * 基于本地文件系统实现的 StorageProvider。\n * connect 时会自动创建根目录;文件操作会自动创建中间目录。\n *\n * 已知限制:\n * - list 使用绑定查询范围的无状态游标;按 UTF-8 key 排序,每次仍需扫描并排序匹配目录\n * - presign.getUrl/putUrl 明确不支持,publicUrl 返回 null;HTTP 访问由应用鉴权接口提供\n * - publicUrl 始终返回 null\n *\n * @returns Local Provider 实例\n * @example 模块内部上下文\n * ```ts\n * const provider = createLocalProvider()\n * await provider.close() // 工厂尚未连接;connect 的失败结果由调用方处理。\n * ```\n */\nexport function createLocalProvider(): StorageProvider {\n let config: LocalConfig | null = null\n let connected = false\n\n // -------------------------------------------------------------------------\n // 辅助方法\n // -------------------------------------------------------------------------\n\n /** 获取当前 Local 配置,未初始化时抛异常 */\n function getConfig(): LocalConfig {\n if (!config) {\n throw new Error(storageM('storage_localNotInitialized'))\n }\n return config\n }\n\n /** 将用户 key 转为安全的绝对路径(经 safePath 防穿越处理) */\n async function fullPath(key: string): Promise<string> {\n const root = getConfig().root\n const target = safePath(root, key)\n // 逐段检查现有路径,拒绝目录 junction 和文件符号链接;不存在的后缀由本次操作创建。\n let current = root\n for (const segment of path.relative(root, target).split(path.sep).filter(Boolean)) {\n current = path.join(current, segment)\n try {\n if ((await fsp.lstat(current)).isSymbolicLink())\n throw createInvalidPathError()\n }\n catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT')\n break\n throw error\n }\n }\n return target\n }\n\n function logOperation(category: 'read' | 'write', operation: string, payload: unknown): void {\n logStorageOperation(logger, config?.operationLog, category, operation, payload)\n }\n\n // -------------------------------------------------------------------------\n // 文件操作实现\n // -------------------------------------------------------------------------\n\n const file: FileOperations = {\n async put(key, data, options = {}): Promise<HaiResult<FileMetadata>> {\n if (isMetaFile(key)) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_metaFileAccess', { params: { key } }),\n )\n }\n let temporaryPath: string | undefined\n try {\n const filePath = await fullPath(key)\n const metaPath = await fullPath(`${key}.meta.json`)\n logOperation('write', 'file.put', { key, options })\n\n // 确保目录存在\n const dir = path.dirname(filePath)\n await fsp.mkdir(dir, { recursive: true, mode: getConfig().directoryMode })\n\n // 先流式写入同目录临时文件,失败不破坏已有对象;发布采用原子文件系统操作。\n temporaryPath = `${filePath}.${crypto.randomUUID()}.upload.tmp`\n await pipeline(\n Readable.from(toByteStream(data, options.signal), { objectMode: false }),\n fs.createWriteStream(temporaryPath, { flags: 'wx', mode: getConfig().fileMode }),\n { signal: options.signal },\n )\n options.signal?.throwIfAborted()\n if (options.ifNoneMatch === '*')\n await fsp.link(temporaryPath, filePath)\n else\n await fsp.rename(temporaryPath, filePath)\n\n // 获取元数据\n const stat = await fsp.stat(filePath)\n const metadata: FileMetadata = {\n key,\n size: stat.size,\n contentType: options.contentType || getMimeType(key),\n lastModified: stat.mtime,\n etag: calculateEtag(stat),\n metadata: options.metadata,\n }\n\n // 存储自定义元数据(使用扩展属性或元数据文件)\n if (options.metadata || options.contentType) {\n await fsp.writeFile(metaPath, JSON.stringify({\n contentType: options.contentType,\n metadata: options.metadata,\n }))\n }\n else {\n await fsp.rm(metaPath, { force: true })\n }\n\n return ok(metadata)\n }\n catch (error) {\n logger.warn('Put file failed', { key, error })\n return err(toStorageError(error, key))\n }\n finally {\n if (temporaryPath)\n await fsp.unlink(temporaryPath).catch(() => {})\n }\n },\n\n async get(key, options = {}): Promise<HaiResult<Buffer>> {\n const streamed = await file.getStream(key, options)\n if (!streamed.success)\n return streamed\n try {\n const chunks: Uint8Array[] = []\n for await (const chunk of streamed.data)\n chunks.push(chunk)\n return ok(Buffer.concat(chunks))\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async getStream(key, options = {}) {\n if (isMetaFile(key)) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_metaFileAccess', { params: { key } }),\n )\n }\n try {\n const filePath = await fullPath(key)\n logOperation('read', 'file.getStream', { key, options })\n options.signal?.throwIfAborted()\n const stat = await fsp.stat(filePath)\n if (stat.isDirectory())\n return err(HaiStorageError.INVALID_PATH, storageM('storage_pathIsDir', { params: { key } }))\n // 延迟打开,未消费的返回值不会占用文件句柄;迭代错误交由流消费者处理。\n async function* read(): AsyncIterable<Uint8Array> {\n const stream = fs.createReadStream(filePath, { start: options.rangeStart, end: options.rangeEnd, signal: options.signal })\n for await (const chunk of stream)\n yield chunk\n }\n return ok(read())\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async head(key): Promise<HaiResult<FileMetadata>> {\n if (isMetaFile(key)) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_metaFileAccess', { params: { key } }),\n )\n }\n try {\n const filePath = await fullPath(key)\n logOperation('read', 'file.head', { key })\n const stat = await fsp.stat(filePath)\n\n if (stat.isDirectory()) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_pathIsDir', { params: { key } }),\n )\n }\n\n // 尝试读取元数据文件\n let customMetadata: { contentType?: string, metadata?: Record<string, string> } = {}\n try {\n const metaPath = await fullPath(`${key}.meta.json`)\n const metaContent = await fsp.readFile(metaPath, 'utf-8')\n customMetadata = JSON.parse(metaContent)\n }\n catch {\n // 元数据文件不存在,忽略\n }\n\n // 基于文件大小和修改时间生成 ETag(无需读取整个文件)\n const etag = `\"${stat.size.toString(16)}-${stat.mtimeMs.toString(16)}\"`\n\n return ok({\n key,\n size: stat.size,\n contentType: customMetadata.contentType || getMimeType(key),\n lastModified: stat.mtime,\n etag,\n metadata: customMetadata.metadata,\n })\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async exists(key): Promise<HaiResult<boolean>> {\n if (isMetaFile(key)) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_metaFileAccess', { params: { key } }),\n )\n }\n try {\n const filePath = await fullPath(key)\n logOperation('read', 'file.exists', { key })\n await fsp.access(filePath, fs.constants.F_OK)\n return ok(true)\n }\n catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return ok(false)\n }\n return err(toStorageError(error, key))\n }\n },\n\n async delete(key): Promise<HaiResult<void>> {\n if (isMetaFile(key)) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_metaFileAccess', { params: { key } }),\n )\n }\n try {\n const filePath = await fullPath(key)\n logOperation('write', 'file.delete', { key })\n logger.debug('Deleting file', { key })\n await fsp.unlink(filePath)\n\n // 尝试删除元数据文件\n try {\n await fsp.unlink(`${filePath}.meta.json`)\n }\n catch {\n // 忽略\n }\n\n return ok(undefined)\n }\n catch (error) {\n // 如果文件不存在,视为删除成功\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return ok(undefined)\n }\n logger.warn('Delete file failed', { key, error })\n return err(toStorageError(error, key))\n }\n },\n\n async deleteMany(keys): Promise<StorageDeleteResult> {\n logOperation('write', 'file.deleteMany', { count: keys.length, keys })\n const summary: StorageDeleteSummary = { deletedKeys: [], failures: [] }\n const uniqueKeys = [...new Set(keys)]\n // 有界并行;reject 也必须形成失败项,不能被 Promise.allSettled 静默丢弃。\n for (let offset = 0; offset < uniqueKeys.length; offset += 32) {\n const batch = uniqueKeys.slice(offset, offset + 32)\n const results = await Promise.allSettled(batch.map(key => file.delete(key)))\n for (const [index, result] of results.entries()) {\n const key = batch[index]\n if (result.status === 'rejected')\n summary.failures.push({ key, code: 'UnexpectedDeleteFailure', retryable: false, outcome: 'failed' })\n else if (!result.value.success)\n summary.failures.push({ key, code: String(result.value.error.code), retryable: false, outcome: 'failed' })\n else\n summary.deletedKeys.push(key)\n }\n }\n return toStorageDeleteResult(summary)\n },\n\n async copy(sourceKey, destKey, options = {}): Promise<HaiResult<FileMetadata>> {\n logOperation('write', 'file.copy', { sourceKey, destKey, options })\n const source = await file.head(sourceKey)\n if (!source.success)\n return source\n const stream = await file.getStream(sourceKey)\n if (!stream.success)\n return stream\n return file.put(destKey, stream.data, {\n contentType: options.contentType ?? source.data.contentType,\n metadata: options.metadata ?? source.data.metadata,\n })\n },\n }\n\n // -------------------------------------------------------------------------\n // 目录操作实现\n // -------------------------------------------------------------------------\n\n const dir: DirOperations = {\n async list(options = {}): Promise<HaiResult<ListResult>> {\n try {\n const cfg = getConfig()\n const prefix = options.prefix === undefined ? '' : options.prefix\n const delimiter = options.delimiter === undefined ? '' : options.delimiter\n const maxKeys = options.maxKeys === undefined ? 1000 : options.maxKeys\n if (typeof prefix !== 'string' || typeof delimiter !== 'string' || !Number.isInteger(maxKeys) || maxKeys < 1 || maxKeys > 1000\n || Buffer.from(prefix).toString('utf8') !== prefix || Buffer.from(delimiter).toString('utf8') !== delimiter) {\n return err(HaiStorageError.INVALID_LIST_OPTIONS, storageM('storage_listOptionsInvalid'))\n }\n logOperation('read', 'dir.list', { options })\n\n // 前缀只是 key 字符串,不能当作目录打开;从规范根目录遍历,子目录符号链接不会被跟随。\n safePath(cfg.root, prefix)\n const root = await fsp.realpath(cfg.root)\n const scope = crypto.createHash('sha256').update(JSON.stringify([root, prefix, delimiter])).digest('hex')\n let after = ''\n if (options.continuationToken !== undefined) {\n const token = options.continuationToken\n try {\n if (typeof token !== 'string' || token.length === 0 || token.length > 262_144)\n throw new Error('Invalid cursor encoding')\n const bytes = Buffer.from(token, 'base64url')\n if (bytes.toString('base64url') !== token)\n throw new Error('Invalid cursor encoding')\n const decoded: unknown = JSON.parse(bytes.toString('utf8'))\n if (!Array.isArray(decoded) || decoded.length !== 3 || decoded[0] !== 1 || decoded[1] !== scope\n || typeof decoded[2] !== 'string' || decoded[2].length === 0 || !decoded[2].startsWith(prefix)) {\n throw new Error('Invalid cursor scope')\n }\n after = decoded[2]\n }\n catch {\n return err(HaiStorageError.INVALID_LIST_OPTIONS, storageM('storage_listCursorInvalid'))\n }\n }\n const files: FileMetadata[] = []\n const commonPrefixes: string[] = []\n const compare = (left: string, right: string) => Buffer.compare(Buffer.from(left), Buffer.from(right))\n\n // 按 name + '/' 排序目录,再深度遍历,可保持完整文件 key 的字节序(a.txt 在 a/x 之前)。\n async function* readDir(dirPath: string, directoryKey: string): AsyncGenerator<{ key: string, filePath: string }> {\n const entries = await fsp.readdir(dirPath, { withFileTypes: true })\n entries.sort((left, right) => compare(left.name + (left.isDirectory() ? '/' : ''), right.name + (right.isDirectory() ? '/' : '')))\n for (const entry of entries) {\n if (isMetaFile(entry.name))\n continue\n const key = directoryKey + entry.name\n const filePath = path.join(dirPath, entry.name)\n if (entry.isDirectory()) {\n const childPrefix = `${key}/`\n if (prefix.startsWith(childPrefix) || childPrefix.startsWith(prefix)) {\n yield* readDir(filePath, childPrefix)\n }\n }\n else if (entry.isFile() && key.startsWith(prefix)) {\n yield { key, filePath }\n }\n }\n }\n\n let previous = ''\n let last = ''\n let count = 0\n let isTruncated = false\n for await (const entry of readDir(root, '')) {\n const index = delimiter ? entry.key.indexOf(delimiter, prefix.length) : -1\n const logicalKey = index < 0 ? entry.key : entry.key.slice(0, index + delimiter.length)\n if (logicalKey === previous)\n continue\n previous = logicalKey\n if (compare(logicalKey, after) <= 0)\n continue\n // 多读取一个逻辑条目证明存在下一页;恰好一页时不伪造 continuation。\n if (count === maxKeys) {\n isTruncated = true\n break\n }\n if (index >= 0) {\n commonPrefixes.push(logicalKey)\n }\n else {\n const stat = await fsp.stat(entry.filePath)\n files.push({ key: entry.key, size: stat.size, contentType: getMimeType(entry.key), lastModified: stat.mtime })\n }\n count++\n last = logicalKey\n }\n return ok({\n files,\n commonPrefixes,\n isTruncated,\n ...(isTruncated ? { nextContinuationToken: Buffer.from(JSON.stringify([1, scope, last])).toString('base64url') } : {}),\n })\n }\n catch (error) {\n return err(toStorageError(error))\n }\n },\n\n async delete(prefix): Promise<HaiResult<void>> {\n try {\n const dirPath = await fullPath(prefix)\n logOperation('write', 'dir.delete', { prefix })\n\n // 递归删除目录\n await fsp.rm(dirPath, { recursive: true, force: true })\n\n return ok(undefined)\n }\n catch (error) {\n // 如果目录不存在,视为删除成功\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return ok(undefined)\n }\n return err(toStorageError(error, prefix))\n }\n },\n }\n\n // -------------------------------------------------------------------------\n // 签名 URL 操作实现\n // -------------------------------------------------------------------------\n\n const presign: PresignOperations = {\n async getUrl(key, options?): Promise<HaiResult<string>> {\n logOperation('read', 'presign.getUrl', { key, options })\n return err(HaiStorageError.UNSUPPORTED_OPERATION, storageM('storage_localPresignUnsupported'))\n },\n\n async putUrl(key, options?): Promise<HaiResult<string>> {\n logOperation('write', 'presign.putUrl', { key, options })\n return err(HaiStorageError.UNSUPPORTED_OPERATION, storageM('storage_localPresignUnsupported'))\n },\n\n publicUrl(key): string | null {\n logOperation('read', 'presign.publicUrl', { key })\n // 本地存储不支持公开 URL\n return null\n },\n }\n\n // -------------------------------------------------------------------------\n // Provider 接口实现\n // -------------------------------------------------------------------------\n\n return {\n name: 'local',\n\n file,\n dir,\n presign,\n\n async connect(cfg: StorageConfig): Promise<HaiResult<void>> {\n if (cfg.type !== 'local') {\n return err(\n HaiStorageError.CONFIG_ERROR,\n storageM('storage_localConfigTypeError'),\n )\n }\n\n try {\n config = cfg\n logger.info('Connecting local provider', { root: cfg.root })\n\n // 确保根目录存在\n await fsp.mkdir(cfg.root, { recursive: true, mode: cfg.directoryMode })\n\n config = { ...cfg, root: await fsp.realpath(cfg.root) }\n connected = true\n logger.info('Local provider connected', { root: cfg.root })\n return ok(undefined)\n }\n catch (error) {\n logger.error('Local provider connect failed', { error })\n config = null\n return err(toStorageError(error))\n }\n },\n\n async close(): Promise<void> {\n logger.info('Local provider disconnected')\n config = null\n connected = false\n },\n\n isConnected(): boolean {\n return connected\n },\n }\n}\n","/**\n * @h-ai/storage — S3 Provider 实现\n *\n * 基于 AWS SDK v3 实现的 S3 协议存储 Provider。\n * 支持 AWS S3、MinIO、RustFS、阿里云 OSS 等兼容 S3 协议的存储服务。\n * @module storage-provider-s3\n */\n\nimport type { BucketLocationConstraint, S3ClientConfig } from '@aws-sdk/client-s3'\nimport type { HaiError, HaiResult } from '@h-ai/core'\nimport type { S3Config, StorageConfig } from '../storage-config.js'\nimport type { DirOperations, FileMetadata, FileOperations, ListResult, PresignOperations, StorageDeleteFailure, StorageDeleteResult, StorageDeleteSummary } from '../storage-types.js'\nimport type { StorageProvider } from './storage-provider-types.js'\nimport { Buffer } from 'node:buffer'\nimport { Readable } from 'node:stream'\nimport {\n CopyObjectCommand,\n CreateBucketCommand,\n DeleteObjectCommand,\n DeleteObjectsCommand,\n GetObjectCommand,\n HeadBucketCommand,\n HeadObjectCommand,\n ListObjectsV2Command,\n PutObjectCommand,\n S3Client,\n} from '@aws-sdk/client-s3'\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner'\n\nimport { core, err, ok } from '@h-ai/core'\n\nimport { PresignOptionsSchema, PresignUploadOptionsSchema } from '../storage-config.js'\nimport { toStorageDeleteResult } from '../storage-delete.js'\nimport { storageM } from '../storage-i18n.js'\nimport { toByteStream } from '../storage-stream.js'\nimport {\n\n HaiStorageError,\n\n} from '../storage-types.js'\nimport { logStorageOperation } from './storage-operation-log.js'\n\nconst logger = core.logger.child({ module: 'storage', scope: 'provider-s3' })\nconst TRAILING_SLASHES_REGEX = /\\/+$/\nconst LEADING_SLASHES_REGEX = /^\\/+/\n/** S3 单次 DeleteObjects 最多接受 1000 项;不并发放大删除请求。 */\nconst DELETE_BATCH_SIZE = 1000\n/** 只重试服务明确报告的瞬时逐项错误,最多三次,不重发已成功或权限失败项。 */\nconst RETRYABLE_DELETE_CODES = new Set(['InternalError', 'ServiceUnavailable', 'SlowDown', 'RequestTimeout'])\n\n// ─── 辅助函数 ───\n\n/**\n * 将 S3 SDK 错误转换为 StorageError\n *\n * 映射规则:\n * - NoSuchKey / NotFound / 404 → NOT_FOUND\n * - AccessDenied → PERMISSION_DENIED\n * - NoSuchBucket → CONFIG_ERROR\n * - NetworkError / ECONNREFUSED → NETWORK_ERROR\n * - 其他 → OPERATION_FAILED\n *\n * @param error - AWS SDK 抛出的原始错误\n * @param key - 相关文件键(可选,用于错误消息)\n * @returns 统一的 StorageError\n */\nfunction toStorageError(error: unknown, key?: string): HaiError {\n const e = error as { name?: string, message?: string, Code?: string }\n\n if (e.name === 'PreconditionFailed' || (e as { $metadata?: { httpStatusCode?: number } }).$metadata?.httpStatusCode === 412)\n return { ...HaiStorageError.ALREADY_EXISTS, message: storageM('storage_fileAlreadyExists', { params: { key: key ?? '' } }), cause: error }\n\n // S3 特定错误处理\n if (e.name === 'NoSuchKey' || e.Code === 'NoSuchKey'\n || e.name === 'NotFound' || e.Code === 'NotFound'\n || e.name === '404' || (e as { $metadata?: { httpStatusCode?: number } }).$metadata?.httpStatusCode === 404) {\n return {\n ...HaiStorageError.NOT_FOUND,\n message: storageM('storage_fileNotFound', { params: { key: key ?? '' } }),\n cause: error,\n }\n }\n\n if (e.name === 'AccessDenied' || e.Code === 'AccessDenied') {\n return {\n ...HaiStorageError.PERMISSION_DENIED,\n message: storageM('storage_permissionDenied', { params: { key: key ?? '' } }),\n cause: error,\n }\n }\n\n if (e.name === 'NoSuchBucket' || e.Code === 'NoSuchBucket') {\n return {\n ...HaiStorageError.CONFIG_ERROR,\n message: storageM('storage_bucketNotExist'),\n cause: error,\n }\n }\n\n // 网络相关错误\n if (e.name === 'NetworkError' || e.name?.includes('ECONNREFUSED')) {\n return {\n ...HaiStorageError.NETWORK_ERROR,\n message: storageM('storage_networkError'),\n cause: error,\n }\n }\n\n return {\n ...HaiStorageError.OPERATION_FAILED,\n message: storageM('storage_operationFailed', { params: { error: e.message ?? '' } }),\n cause: error,\n }\n}\n\n/**\n * 为 key 添加配置中的路径前缀\n *\n * @param key - 用户提供的文件键\n * @param prefix - 配置中的前缀(可能为空)\n * @returns 拼接后的完整 S3 key\n */\nfunction withPrefix(key: string, prefix: string): string {\n if (!prefix)\n return key\n return `${prefix.replace(TRAILING_SLASHES_REGEX, '')}/${key.replace(LEADING_SLASHES_REGEX, '')}`\n}\n\n/**\n * 从 S3 key 中移除配置前缀,还原为用户视角的文件键\n *\n * @param key - 包含前缀的完整 S3 key\n * @param prefix - 配置中的前缀\n * @returns 去除前缀后的文件键\n */\nfunction withoutPrefix(key: string, prefix: string): string {\n if (!prefix)\n return key\n const normalizedPrefix = withPrefix('', prefix)\n if (key.startsWith(normalizedPrefix)) {\n return key.slice(normalizedPrefix.length)\n }\n return key\n}\n\n// ─── S3 Provider 实现 ───\n\n/**\n * 创建 S3 存储 Provider\n *\n * 基于 AWS SDK v3 实现的 StorageProvider。\n * connect 时创建 S3Client,按 createBucket 可选检查/创建桶,再通过 ListObjectsV2 验证连接。\n * 支持配置 prefix 重定向所有 key,对用户透明。\n *\n * 实现说明:\n * - copy 时若指定了 contentType 或 metadata,使用 MetadataDirective='REPLACE'\n * - dir.delete 通过先 list 再批量删除实现\n * - presign 使用 @aws-sdk/s3-request-presigner 签名\n *\n * @returns S3 Provider 实例\n * @example 模块内部上下文\n * ```ts\n * const provider = createS3Provider()\n * await provider.close() // 客户端由 connect 分配;close 释放本地 SDK 资源。\n * ```\n */\nexport function createS3Provider(): StorageProvider {\n let client: S3Client | null = null\n let deleteClient: S3Client | null = null\n let presignClient: S3Client | null = null\n let publicBasePath = ''\n let config: S3Config | null = null\n let ready = false\n let closing: Promise<void> | null = null\n\n /** 所有自有客户端独立释放;失败的客户端保留供重试,成功的客户端不重复释放。 */\n function closeClients(): Promise<void> {\n ready = false\n config = null\n publicBasePath = ''\n if (closing)\n return closing\n closing = Promise.resolve().then(() => {\n const failures: unknown[] = []\n for (const kind of ['normal', 'delete', 'presign'] as const) {\n const owned = kind === 'normal' ? client : kind === 'delete' ? deleteClient : presignClient\n if (!owned)\n continue\n try {\n owned.destroy()\n if (kind === 'normal')\n client = null\n else if (kind === 'delete')\n deleteClient = null\n else presignClient = null\n }\n catch (error) {\n failures.push(error)\n }\n }\n if (failures.length > 0)\n throw new AggregateError(failures, 'S3 client cleanup failed')\n }).finally(() => { closing = null })\n return closing\n }\n\n // -------------------------------------------------------------------------\n // 内部辅助\n // -------------------------------------------------------------------------\n\n /** 预签名仅使用外部端点;CRUD 与健康检查仍使用内部端点,批量删除禁用 SDK 重试。 */\n function getClient(operation?: 'deleteMany' | 'presign'): S3Client {\n const selected = operation === 'deleteMany' ? deleteClient : operation === 'presign' ? presignClient ?? client : client\n if (!ready || !selected) {\n throw new Error(storageM('storage_s3ClientNotInitialized'))\n }\n return selected\n }\n\n /**\n * 只添加入口代理目录,保留原始签名路径编码与查询;Nginx 转发时去除同一目录。\n * @param url - S3 客户端按公开 origin 生成的签名地址。\n * @param basePath - 已校验且无末尾斜杠的入口目录,空字符串保持地址不变。\n * @returns 添加入口目录后的浏览器地址,不重新编码路径或查询。\n * @example 模块内部上下文\n * ```ts\n * const url = publicPresignedUrl('https://files.example.com/private/report.pdf', '/delivery/files')\n * void url\n * ```\n */\n function publicPresignedUrl(url: string, basePath: string): string {\n if (!basePath)\n return url\n const pathOffset = url.indexOf('/', url.indexOf('://') + 3)\n return `${url.slice(0, pathOffset)}${basePath}${url.slice(pathOffset)}`\n }\n\n /** 获取当前 S3 配置,未初始化时抛异常 */\n function getConfig(): S3Config {\n if (!ready || !config) {\n throw new Error(storageM('storage_s3ConfigNotInitialized'))\n }\n return config\n }\n\n /** 将用户 key 拼接配置前缀,得到完整 S3 对象键 */\n function fullKey(key: string): string {\n return withPrefix(key, getConfig().prefix)\n }\n\n function logOperation(category: 'read' | 'write', operation: string, payload: unknown): void {\n logStorageOperation(logger, config?.operationLog, category, operation, payload)\n }\n\n // -------------------------------------------------------------------------\n // 文件操作实现\n // -------------------------------------------------------------------------\n\n const file: FileOperations = {\n async put(key, data, options = {}): Promise<HaiResult<FileMetadata>> {\n try {\n const s3Client = getClient()\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n\n options.signal?.throwIfAborted()\n const body = typeof data === 'string' ? Buffer.from(data) : data\n const buffered = body instanceof Uint8Array\n let size = buffered ? body.byteLength : 0\n const params = {\n Bucket: s3Config.bucket,\n Key: fullPath,\n ContentType: options.contentType,\n Metadata: options.metadata,\n CacheControl: options.cacheControl,\n ContentDisposition: options.contentDisposition,\n IfNoneMatch: options.ifNoneMatch,\n }\n logOperation('write', 'file.put', { key, fullPath, byteLength: buffered ? size : undefined, options })\n if (buffered) {\n await s3Client.send(new PutObjectCommand({ ...params, Body: body }), { abortSignal: options.signal })\n }\n else {\n async function* counted(): AsyncIterable<Uint8Array> {\n for await (const chunk of toByteStream(data, options.signal)) {\n size += chunk.byteLength\n yield chunk\n }\n }\n const stream = Readable.from(counted(), { objectMode: false })\n const controller = new AbortController()\n const abort = () => controller.abort()\n options.signal?.addEventListener('abort', abort, { once: true })\n try {\n if (options.signal?.aborted)\n abort()\n // 标准 S3 multipart;SDK 管理分片与失败清理,最多缓冲两片。\n const { Upload } = await import('@aws-sdk/lib-storage')\n await new Upload({ client: s3Client, params: { ...params, Body: stream }, queueSize: 2, leavePartsOnError: false, abortController: controller }).done()\n }\n finally {\n options.signal?.removeEventListener('abort', abort)\n stream.destroy()\n }\n }\n\n // 获取上传后的元数据\n const headResult = await file.head(key)\n if (headResult.success) {\n return headResult\n }\n\n // 如果获取元数据失败,返回基本信息\n return ok({\n key,\n size,\n contentType: options.contentType || 'application/octet-stream',\n lastModified: new Date(),\n })\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async get(key, options = {}): Promise<HaiResult<Buffer>> {\n const streamed = await file.getStream(key, options)\n if (!streamed.success)\n return streamed\n try {\n const chunks: Uint8Array[] = []\n for await (const chunk of streamed.data)\n chunks.push(chunk)\n return ok(Buffer.concat(chunks))\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async getStream(key, options = {}) {\n try {\n const s3Client = getClient()\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n logOperation('read', 'file.getStream', { key, fullPath, options })\n\n let Range: string | undefined\n if (options.rangeStart !== undefined || options.rangeEnd !== undefined) {\n const start = options.rangeStart ?? 0\n const end = options.rangeEnd !== undefined ? options.rangeEnd : ''\n Range = `bytes=${start}-${end}`\n }\n\n const response = await s3Client.send(new GetObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n Range,\n }), { abortSignal: options.signal })\n\n if (!response.Body) {\n return err(\n HaiStorageError.OPERATION_FAILED,\n storageM('storage_responseBodyEmpty'),\n )\n }\n\n // Node SDK 的 Body 是字节可迭代流;SDK 类型联合也包含其他运行时的 Blob。\n const stream = response.Body as AsyncIterable<Uint8Array>\n return ok(stream)\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async head(key): Promise<HaiResult<FileMetadata>> {\n try {\n const s3Client = getClient()\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n logOperation('read', 'file.head', { key, fullPath })\n\n const response = await s3Client.send(new HeadObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n }))\n\n return ok({\n key,\n size: response.ContentLength || 0,\n contentType: response.ContentType || 'application/octet-stream',\n lastModified: response.LastModified || new Date(),\n etag: response.ETag,\n metadata: response.Metadata,\n })\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async exists(key): Promise<HaiResult<boolean>> {\n logOperation('read', 'file.exists', { key })\n const result = await file.head(key)\n if (result.success) {\n return ok(true)\n }\n if (result.error.code === HaiStorageError.NOT_FOUND.code) {\n return ok(false)\n }\n return err(result.error)\n },\n\n async delete(key): Promise<HaiResult<void>> {\n try {\n const s3Client = getClient()\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n logOperation('write', 'file.delete', { key, fullPath })\n\n await s3Client.send(new DeleteObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n }))\n\n return ok(undefined)\n }\n catch (error) {\n return err(toStorageError(error, key))\n }\n },\n\n async deleteMany(keys): Promise<StorageDeleteResult> {\n try {\n const s3Client = getClient('deleteMany')\n const s3Config = getConfig()\n logOperation('write', 'file.deleteMany', { count: keys.length, keys })\n const uniqueKeys = [...new Set(keys)]\n const summary: StorageDeleteSummary = { deletedKeys: [], failures: [] }\n for (let offset = 0; offset < uniqueKeys.length; offset += DELETE_BATCH_SIZE) {\n let pending = uniqueKeys.slice(offset, offset + DELETE_BATCH_SIZE)\n for (let attempt = 0; pending.length > 0 && attempt < 3; attempt++) {\n let response\n try {\n response = await s3Client.send(new DeleteObjectsCommand({\n Bucket: s3Config.bucket,\n Delete: { Objects: [...new Set(pending.map(fullKey))].map(Key => ({ Key })), Quiet: false },\n }))\n }\n catch (cause) {\n // 已发送请求的结果可能未知,尚未发送的后续批次不能被标记为已删除。\n const failure = (cause && typeof cause === 'object' ? cause : {}) as { name?: string, $metadata?: { httpStatusCode?: number } }\n const status = failure.$metadata?.httpStatusCode\n const code = failure.name && /^[\\w.-]{1,80}$/.test(failure.name) ? failure.name : 'RequestFailed'\n const retryable = status === undefined || status === 408 || status === 429 || status >= 500\n const outcome: StorageDeleteFailure['outcome'] = status !== undefined && status >= 400 && status < 500 && status !== 408 ? 'failed' : 'unknown'\n summary.failures.push(...pending.map(key => ({ key, code, retryable, outcome })))\n summary.failures.push(...uniqueKeys.slice(offset + DELETE_BATCH_SIZE).map(key => ({ key, code, retryable, outcome: 'notAttempted' as const })))\n return toStorageDeleteResult(summary)\n }\n const outcomes = new Map<string, string | null>()\n const requested = new Set(pending.map(fullKey))\n let malformed = false\n // 非 quiet 响应中每个请求键必须恰好出现一次;遗漏、重复和越界都不能宣称成功。\n const items = [\n ...(response.Deleted ?? []).map(item => ({ key: item.Key, code: null })),\n ...(response.Errors ?? []).map(item => ({ key: item.Key, code: item.Code && /^[\\w.-]{1,80}$/.test(item.Code) ? item.Code : 'UnknownError' })),\n ]\n for (const item of items) {\n if (!item.key || !requested.has(item.key) || outcomes.has(item.key)) {\n malformed = true\n break\n }\n outcomes.set(item.key, item.code)\n }\n if (malformed || outcomes.size !== requested.size) {\n summary.failures.push(...pending.map(key => ({ key, code: 'InvalidDeleteResponse', retryable: false, outcome: 'unknown' as const })))\n summary.failures.push(...uniqueKeys.slice(offset + DELETE_BATCH_SIZE).map(key => ({ key, code: 'InvalidDeleteResponse', retryable: false, outcome: 'notAttempted' as const })))\n return toStorageDeleteResult(summary)\n }\n const retry: string[] = []\n for (const key of pending) {\n const code = outcomes.get(fullKey(key))\n if (code === null)\n summary.deletedKeys.push(key)\n else if (code && RETRYABLE_DELETE_CODES.has(code) && attempt < 2)\n retry.push(key)\n else\n summary.failures.push({ key, code: code ?? 'UnknownError', retryable: !!code && RETRYABLE_DELETE_CODES.has(code), outcome: 'failed' })\n }\n pending = retry\n if (pending.length > 0)\n await new Promise(resolve => setTimeout(resolve, 100 * 2 ** attempt))\n }\n }\n return toStorageDeleteResult(summary)\n }\n catch (error) {\n return err(toStorageError(error))\n }\n },\n\n async copy(sourceKey, destKey, options = {}): Promise<HaiResult<FileMetadata>> {\n try {\n const s3Client = getClient()\n const s3Config = getConfig()\n const sourceFullPath = fullKey(sourceKey)\n const destFullPath = fullKey(destKey)\n logOperation('write', 'file.copy', { sourceKey, destKey, sourceFullPath, destFullPath, options })\n\n await s3Client.send(new CopyObjectCommand({\n Bucket: s3Config.bucket,\n CopySource: `${s3Config.bucket}/${sourceFullPath}`,\n Key: destFullPath,\n ContentType: options.contentType,\n Metadata: options.metadata,\n MetadataDirective: (options.metadata || options.contentType) ? 'REPLACE' : 'COPY',\n }))\n\n return file.head(destKey)\n }\n catch (error) {\n return err(toStorageError(error, sourceKey))\n }\n },\n }\n\n // -------------------------------------------------------------------------\n // 目录操作实现\n // -------------------------------------------------------------------------\n\n const dir: DirOperations = {\n async list(options = {}): Promise<HaiResult<ListResult>> {\n try {\n const s3Client = getClient()\n const s3Config = getConfig()\n logOperation('read', 'dir.list', { options })\n\n const response = await s3Client.send(new ListObjectsV2Command({\n Bucket: s3Config.bucket,\n Prefix: withPrefix(options.prefix ?? '', s3Config.prefix) || undefined,\n Delimiter: options.delimiter,\n MaxKeys: options.maxKeys,\n ContinuationToken: options.continuationToken,\n }))\n\n const files: FileMetadata[] = (response.Contents || []).map(item => ({\n key: withoutPrefix(item.Key || '', s3Config.prefix),\n size: item.Size || 0,\n contentType: 'application/octet-stream', // ListObjects 不返回 ContentType\n lastModified: item.LastModified || new Date(),\n etag: item.ETag,\n }))\n\n const commonPrefixes = (response.CommonPrefixes || [])\n .map(p => withoutPrefix(p.Prefix || '', s3Config.prefix))\n .filter(p => p.length > 0)\n\n return ok({\n files,\n commonPrefixes,\n nextContinuationToken: response.NextContinuationToken,\n isTruncated: response.IsTruncated || false,\n })\n }\n catch (error) {\n return err(toStorageError(error))\n }\n },\n\n async delete(prefix): Promise<HaiResult<void>> {\n try {\n logOperation('write', 'dir.delete', { prefix })\n // 分页列出并逐批删除,每批最多 1000 个对象\n let continuationToken: string | undefined\n\n do {\n const listResult = await dir.list({\n prefix,\n continuationToken,\n maxKeys: 1000,\n })\n\n if (!listResult.success) {\n return err(listResult.error)\n }\n\n const keys = listResult.data.files.map(f => f.key)\n if (keys.length > 0) {\n const deleteResult = await file.deleteMany(keys)\n if (!deleteResult.success) {\n return deleteResult\n }\n }\n\n continuationToken = listResult.data.nextContinuationToken\n } while (continuationToken)\n\n return ok(undefined)\n }\n catch (error) {\n return err(toStorageError(error))\n }\n },\n }\n\n // -------------------------------------------------------------------------\n // 签名 URL 操作实现\n // -------------------------------------------------------------------------\n\n const presign: PresignOperations = {\n async getUrl(key, options?): Promise<HaiResult<string>> {\n try {\n const parsed = PresignOptionsSchema.parse(options ?? {})\n const s3Client = getClient('presign')\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n logOperation('read', 'presign.getUrl', { key, fullPath, options })\n const basePath = publicBasePath\n\n const command = new GetObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n ResponseContentType: parsed.responseContentType,\n ResponseContentDisposition: parsed.responseContentDisposition,\n })\n\n const url = await getSignedUrl(s3Client, command, {\n expiresIn: parsed.expiresIn,\n })\n\n return ok(publicPresignedUrl(url, basePath))\n }\n catch (error) {\n return err(\n HaiStorageError.PRESIGN_FAILED,\n storageM('storage_presignUrlFailed'),\n error,\n )\n }\n },\n\n async putUrl(key, options?): Promise<HaiResult<string>> {\n try {\n const parsed = PresignUploadOptionsSchema.parse(options ?? {})\n const s3Client = getClient('presign')\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n logOperation('write', 'presign.putUrl', { key, fullPath, options })\n const basePath = publicBasePath\n\n const command = new PutObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n ContentType: parsed.contentType,\n })\n\n const url = await getSignedUrl(s3Client, command, {\n expiresIn: parsed.expiresIn,\n })\n\n return ok(publicPresignedUrl(url, basePath))\n }\n catch (error) {\n return err(\n HaiStorageError.PRESIGN_FAILED,\n storageM('storage_presignUploadUrlFailed'),\n error,\n )\n }\n },\n\n publicUrl(key): string | null {\n const s3Config = getConfig()\n if (!s3Config.publicUrl) {\n return null\n }\n const fullPath = fullKey(key)\n logOperation('read', 'presign.publicUrl', { key, fullPath })\n return `${s3Config.publicUrl.replace(TRAILING_SLASHES_REGEX, '')}/${fullPath}`\n },\n }\n\n // -------------------------------------------------------------------------\n // Provider 接口实现\n // -------------------------------------------------------------------------\n\n return {\n name: 's3',\n\n file,\n dir,\n presign,\n\n async connect(cfg: StorageConfig): Promise<HaiResult<void>> {\n if (cfg.type !== 's3') {\n return err(\n HaiStorageError.CONFIG_ERROR,\n storageM('storage_s3ConfigTypeError'),\n )\n }\n\n try {\n await closeClients()\n\n config = cfg\n logger.info('Connecting S3 provider', {\n bucket: cfg.bucket,\n region: cfg.region,\n endpoint: cfg.endpoint ? core.sanitize.sanitizeUrl(cfg.endpoint) : undefined,\n })\n\n const clientConfig: S3ClientConfig = {\n region: cfg.region,\n endpoint: cfg.endpoint,\n forcePathStyle: cfg.forcePathStyle,\n // 不强制兼容服务实现 AWS checksum 扩展;校验工具按实际字节独立计算。\n requestChecksumCalculation: 'WHEN_REQUIRED',\n responseChecksumValidation: 'WHEN_REQUIRED',\n credentials: {\n accessKeyId: cfg.accessKeyId,\n secretAccessKey: cfg.secretAccessKey,\n },\n }\n client = new S3Client(clientConfig)\n // 回执丢失时重放可能删除并发新版本;只允许上层对明确的逐项瞬时错误重试。\n deleteClient = new S3Client({ ...clientConfig, maxAttempts: 1 })\n // 签名阶段不联网;不得用外部地址执行内部健康检查,也不能在签名后改写 Host。\n if (cfg.publicEndpoint) {\n const endpoint = new URL(cfg.publicEndpoint)\n publicBasePath = endpoint.pathname.replace(TRAILING_SLASHES_REGEX, '')\n // 含目录的网关使用桶路径;签名针对网关还原后的 S3 URI,而非入口前缀。\n presignClient = new S3Client({ ...clientConfig, endpoint: endpoint.origin, forcePathStyle: publicBasePath ? true : cfg.forcePathStyle })\n }\n\n if (cfg.createBucket) {\n try {\n await client.send(new HeadBucketCommand({ Bucket: cfg.bucket }))\n }\n catch (error) {\n // HeadBucket 只有明确 404 才证明缺失;403、网络错误与服务异常必须阻止启动。\n if (!(error && typeof error === 'object' && '$metadata' in error\n && error.$metadata && typeof error.$metadata === 'object'\n && 'httpStatusCode' in error.$metadata && error.$metadata.httpStatusCode === 404)) {\n throw error\n }\n try {\n await client.send(new CreateBucketCommand({\n Bucket: cfg.bucket,\n // AWS us-east-1 要省略 LocationConstraint;兼容服务可使用自定义区域名。\n ...(cfg.region === 'us-east-1' ? {} : { CreateBucketConfiguration: { LocationConstraint: cfg.region as BucketLocationConstraint } }),\n }))\n logger.info('S3 bucket created', { bucket: cfg.bucket })\n }\n catch (createError) {\n // 并发应用仅接受本账号已拥有的桶,并重新核验访问;全局名称冲突不能当成功。\n if (!(createError instanceof Error && createError.name === 'BucketAlreadyOwnedByYou'))\n throw createError\n await client.send(new HeadBucketCommand({ Bucket: cfg.bucket }))\n }\n }\n }\n\n // 使用与默认列表相同的前缀探测连接,支持限制列表前缀的凭据。\n await client.send(new ListObjectsV2Command({\n Bucket: cfg.bucket,\n Prefix: withPrefix('', cfg.prefix) || undefined,\n MaxKeys: 1,\n }))\n\n config = cfg\n ready = true\n logger.info('S3 provider connected', { bucket: cfg.bucket })\n return ok(undefined)\n }\n catch (error) {\n logger.error('S3 provider connect failed', { error })\n const original = toStorageError(error)\n try {\n await closeClients()\n return err(original)\n }\n catch (cleanupError) {\n return err({ ...original, ext: { ...original.ext, cleanupPending: true, cleanupError } })\n }\n }\n },\n\n close: closeClients,\n\n isConnected(): boolean {\n return ready && client !== null && deleteClient !== null && config !== null\n },\n }\n}\n","/** @h-ai/storage — Node 字节内容 checksum 生成与校验,无存储生命周期依赖。 */\nimport type { HaiResult } from '@h-ai/core'\nimport type { ChecksumOperations, ChecksumOptions, StorageData } from './storage-types.js'\nimport { Buffer } from 'node:buffer'\nimport { createHash, timingSafeEqual } from 'node:crypto'\nimport { err, ok } from '@h-ai/core'\nimport { storageM } from './storage-i18n.js'\nimport { toByteStream } from './storage-stream.js'\nimport { HaiStorageError } from './storage-types.js'\n\nconst HEX_CHECKSUM = /^[\\da-f]+$/i\nconst BASE64_CHECKSUM = /^(?:[A-Z\\d+/]{4})*(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?$/i\nconst ALGORITHMS = new Set(['sha256', 'sha384', 'sha512', 'sha1', 'md5'])\n\nasync function generate(data: StorageData, options: ChecksumOptions = {}): Promise<HaiResult<string>> {\n try {\n const algorithm = options.algorithm ?? 'sha256'\n if (!ALGORITHMS.has(algorithm))\n return err(HaiStorageError.INVALID_CHECKSUM, storageM('storage_checksumInvalid'))\n const hash = createHash(algorithm)\n for await (const chunk of toByteStream(data, options.signal))\n hash.update(chunk)\n return ok(hash.digest(options.encoding ?? 'hex'))\n }\n catch (error) {\n return err(HaiStorageError.CHECKSUM_FAILED, storageM('storage_checksumFailed'), error)\n }\n}\n\n/**\n * Node 端无状态字节校验工具;无需初始化,流只能消费一次。\n * @example 模块内部上下文\n * ```ts\n * const calculated = await checksum.generate('hello', { algorithm: 'sha256' })\n * if (!calculated.success) throw new Error(calculated.error.message)\n * const checked = await checksum.verify('hello', calculated.data)\n * if (!checked.success) throw new Error(checked.error.message)\n * void checked.data\n * ```\n */\nexport const checksum: ChecksumOperations = {\n generate,\n async verify(data, expected, options = {}) {\n const encoding = options.encoding ?? 'hex'\n const valid = encoding === 'hex'\n ? expected.length % 2 === 0 && HEX_CHECKSUM.test(expected)\n : BASE64_CHECKSUM.test(expected) && expected.length > 0\n if (!valid)\n return err(HaiStorageError.INVALID_CHECKSUM, storageM('storage_checksumInvalid'))\n const calculated = await generate(data, options)\n if (!calculated.success)\n return calculated\n const actualBytes = Buffer.from(calculated.data, encoding)\n const expectedBytes = Buffer.from(expected, encoding)\n return ok(actualBytes.length === expectedBytes.length && timingSafeEqual(actualBytes, expectedBytes))\n },\n}\n","/**\n * @h-ai/storage — 存储服务主入口\n *\n * 提供统一的 `storage` 对象,管理运行时状态与生命周期。\n * @module storage-main\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { StorageProvider } from './providers/storage-provider-types.js'\nimport type { StorageConfig, StorageConfigInput } from './storage-config.js'\nimport type { DirOperations, FileOperations, PresignOperations, StorageFunctions } from './storage-types.js'\n\nimport process from 'node:process'\nimport { core, err, ok } from '@h-ai/core'\n\nimport { createLocalProvider } from './providers/storage-provider-local.js'\nimport { createS3Provider } from './providers/storage-provider-s3.js'\nimport { checksum } from './storage-checksum.js'\nimport { StorageConfigSchema } from './storage-config.js'\nimport { storageM } from './storage-i18n.js'\nimport {\n\n HaiStorageError,\n\n} from './storage-types.js'\n\nconst logger = core.logger.child({ module: 'storage', scope: 'main' })\n\n// ─── 内部状态 ───\n\n/** 当前使用的存储 Provider 实例(init 后非空,close 后置空) */\nlet currentProvider: StorageProvider | null = null\n\n/** 当前解析后的存储配置(init 后非空,close 后置空) */\nlet currentConfig: StorageConfig | null = null\n\n/** 并发初始化防护标志;防止多次 init() 同时执行导致资源泄漏 */\nlet initInProgress = false\n\n/** 尚未释放的候选/旧 Provider;绝不用于业务操作,清理成功后才能替换。 */\nlet pendingProvider: StorageProvider | null = null\n/** 初始化完成屏障,供 close 等待候选资源归属确定。 */\nlet initCompletion: Promise<void> | null = null\n/** 合并并发关闭,避免同一连接重复释放。 */\nlet closingPromise: Promise<HaiResult<void>> | null = null\n\n/** 停止发布资源并重试清理;失败时保留唯一所有权供下一次 close/init 重试。 */\nasync function closeOwnedProvider(): Promise<HaiResult<void>> {\n if (currentProvider) {\n pendingProvider = currentProvider\n currentProvider = null\n }\n currentConfig = null\n if (!pendingProvider)\n return ok(undefined)\n try {\n await pendingProvider.close()\n pendingProvider = null\n return ok(undefined)\n }\n catch (error) {\n logger.error('Provider cleanup failed; retaining resource for retry', { error })\n return err({ ...HaiStorageError.CONNECTION_FAILED, message: storageM('storage_operationFailed', {\n params: { error: error instanceof Error ? error.message : String(error) },\n }), cause: error, ext: { cleanupPending: true } })\n }\n}\n\n/** 初始化失败后释放候选;保留原始错误,并明确指出清理尚未完成。 */\nasync function failInitialization(result: HaiResult<void>): Promise<HaiResult<void>> {\n const cleanup = await closeOwnedProvider()\n if (result.success)\n return result\n if (cleanup.success) {\n const ext = { ...result.error.ext }\n delete ext.cleanupPending\n delete ext.cleanupError\n return err({ ...result.error, ext: Object.keys(ext).length > 0 ? ext : undefined })\n }\n return err({ ...result.error, ext: { ...result.error.ext, cleanupPending: true, cleanupError: cleanup.error } })\n}\n\n// ─── Provider 工厂 ───\n\n/**\n * 根据配置类型创建对应的存储 Provider\n *\n * @param config - 经 Schema 校验后的存储配置\n * @returns 对应类型的 Provider 实例(尚未连接)\n * @throws 理论上仅在出现未覆盖分支时抛出异常(正常情况下由 Schema 保证不会发生)\n */\nfunction createProvider(config: StorageConfig): StorageProvider {\n switch (config.type) {\n case 's3': return createS3Provider()\n case 'local': return createLocalProvider()\n default:\n throw new Error(storageM('storage_unsupportedType', { params: { type: (config as StorageConfig).type } }))\n }\n}\n\n// ─── 未初始化占位 ───\n\n/**\n * 创建未初始化状态的错误工具包\n *\n * 当 storage 未调用 init() 就直接使用 file/dir/presign 操作时,\n * 所有方法调用都会返回 NOT_INITIALIZED 错误。\n */\nconst notInitialized = core.module.createNotInitializedKit(\n HaiStorageError.NOT_INITIALIZED,\n () => storageM('storage_notInitialized'),\n)\n\n/** 未初始化时的文件操作占位代理 */\nconst notInitializedFile = notInitialized.proxy<FileOperations>()\n\n/** 未初始化时的目录操作占位代理 */\nconst notInitializedDir = notInitialized.proxy<DirOperations>()\n\n/**\n * 未初始化时的签名 URL 操作占位代理\n *\n * publicUrl 始终返回 null;其余方法返回 NOT_INITIALIZED 错误。\n */\nconst notInitializedPresignBase = notInitialized.proxy<PresignOperations>()\nconst notInitializedPresign = new Proxy(\n notInitializedPresignBase,\n {\n get: (target, prop, receiver) => prop === 'publicUrl' ? () => null : Reflect.get(target, prop, receiver),\n },\n)\n\n// ─── 存储服务对象 ───\n\n/**\n * 存储服务单例\n *\n * 文件、目录及签名操作前须调用 `storage.init()`;checksum 为无状态工具,无需初始化。\n * 初始化后通过 `storage.file`、`storage.dir`、`storage.presign` 访问各操作接口。\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const initialized = await storage.init({ type: 'local', root: './data/uploads' })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const uploaded = await storage.file.put('reports/a.txt', 'hello', { ifNoneMatch: '*' })\n * if (!uploaded.success) throw new Error(uploaded.error.message)\n * const downloaded = await storage.file.get('reports/a.txt')\n * if (!downloaded.success) throw new Error(downloaded.error.message)\n * void downloaded.data\n * } finally {\n * const closed = await storage.close()\n * if (!closed.success) throw new Error(closed.error.message)\n * }\n * ```\n */\nexport const storage: StorageFunctions = {\n checksum,\n /**\n * 初始化存储连接。\n *\n * 如果当前已有活跃连接,会先自动 close 再重新初始化。\n * 配置通过 Zod Schema 校验;校验、连接或清理失败均返回 HaiResult,不发布失败候选。\n *\n * @param config 存储配置(S3 或本地),支持省略带默认值的字段。\n * @returns 成功时返回 ok(undefined);失败时返回包含错误码和消息的 err。\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const initialized = await storage.init({ type: 'local', root: './data/uploads' })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * const closed = await storage.close()\n * if (!closed.success) throw new Error(closed.error.message)\n * ```\n */\n async init(config: StorageConfigInput): Promise<HaiResult<void>> {\n // 并发初始化防护:避免多次 init 同时执行导致 Provider 泄漏\n if (initInProgress || closingPromise) {\n logger.warn('Storage init already in progress, skipping concurrent call')\n return err(\n HaiStorageError.OPERATION_FAILED,\n storageM('storage_operationFailed', { params: { error: 'Concurrent initialization detected' } }),\n )\n }\n initInProgress = true\n let resolveInit!: () => void\n initCompletion = new Promise<void>((resolve) => {\n resolveInit = resolve\n })\n\n try {\n const cleanup = await closeOwnedProvider()\n if (!cleanup.success)\n return cleanup\n\n logger.info('Initializing storage module')\n\n const parseResult = StorageConfigSchema.safeParse(config)\n if (!parseResult.success) {\n logger.error('Storage config validation failed', { error: parseResult.error.message })\n return err(\n HaiStorageError.CONFIG_ERROR,\n storageM('storage_configError', { params: { error: parseResult.error.message } }),\n parseResult.error,\n )\n }\n const parsed = parseResult.data\n if (core.module.requiresSharedState(process.env) && (parsed.type === 'local'))\n return err(HaiStorageError.CONFIG_ERROR, storageM('storage_sharedBackendRequired'))\n\n const provider = createProvider(parsed)\n pendingProvider = provider\n const connectResult = await provider.connect(parsed)\n if (!connectResult.success) {\n logger.error('Storage module initialization failed', {\n code: connectResult.error.code,\n message: connectResult.error.message,\n })\n return await failInitialization(connectResult)\n }\n pendingProvider = null\n currentProvider = provider\n currentConfig = parsed\n logger.info('Storage module initialized')\n return ok(undefined)\n }\n catch (error) {\n logger.error('Storage module initialization failed', { error })\n return await failInitialization(err(\n HaiStorageError.CONNECTION_FAILED,\n storageM('storage_operationFailed', {\n params: { error: error instanceof Error ? error.message : String(error) },\n }),\n error,\n ))\n }\n finally {\n initInProgress = false\n initCompletion = null\n resolveInit()\n }\n },\n\n /** 文件操作接口。未初始化时所有方法返回 NOT_INITIALIZED 错误 */\n get file(): FileOperations { return currentProvider?.file ?? notInitializedFile },\n\n /** 目录操作接口。未初始化时所有方法返回 NOT_INITIALIZED 错误 */\n get dir(): DirOperations { return currentProvider?.dir ?? notInitializedDir },\n\n /** 签名 URL 操作接口。未初始化时 publicUrl 返回 null,其余返回错误 */\n get presign(): PresignOperations { return currentProvider?.presign ?? notInitializedPresign },\n\n /** 当前脱敏后的存储配置快照;未初始化或已关闭时为 null */\n get config() {\n return currentConfig ? core.sanitize.sanitizeSensitiveFields(currentConfig) : null\n },\n\n /** 是否已完成初始化 */\n get isInitialized() { return currentProvider !== null && currentProvider.isConnected() },\n\n /**\n * 关闭存储连接并释放资源\n *\n * 关闭后 file/dir/presign 操作将返回 NOT_INITIALIZED 错误。\n * 等待初始化并合并并发关闭;失败时保留资源供 close/init 重试。\n * @returns 所有客户端释放成功才返回成功。\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const closed = await storage.close()\n * if (!closed.success) throw new Error(closed.error.message)\n * ```\n */\n async close(): Promise<HaiResult<void>> {\n if (!closingPromise) {\n closingPromise = (async () => {\n if (initCompletion)\n await initCompletion\n return await closeOwnedProvider()\n })().finally(() => { closingPromise = null })\n }\n return await closingPromise\n },\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-ai/storage",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.13",
|
|
5
5
|
"description": "Hai Framework object storage module for S3-compatible and local backends.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"exports": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@aws-sdk/client-s3": "^3.1149.0",
|
|
40
40
|
"@aws-sdk/lib-storage": "^3.1149.0",
|
|
41
41
|
"@aws-sdk/s3-request-presigner": "^3.1149.0",
|
|
42
|
-
"@h-ai/core": "1.0.0-beta.
|
|
42
|
+
"@h-ai/core": "1.0.0-beta.13",
|
|
43
43
|
"zod": "^4.6.5"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/storage-config.ts","../src/storage-types.ts"],"names":[],"mappings":"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","file":"chunk-ANAESRWS.js","sourcesContent":["/**\n * @h-ai/storage — 存储配置 Schema\n * @module storage-config\n */\n\nimport { z } from 'zod'\nimport { storageM } from './storage-i18n.js'\nimport { StoragePresignExpiresInSchema } from './storage-schemas.js'\n\n// ─── 配置 Schema ───\n\n/**\n * 存储类型枚举\n *\n * 支持的存储类型:\n * - `s3` - S3 协议对象存储(兼容 AWS S3、MinIO、RustFS、阿里云 OSS 等)\n * - `local` - 本地文件存储(仅后端)\n * @example\n * ```ts\n * import { StorageTypeSchema } from '@h-ai/storage'\n *\n * const value = StorageTypeSchema.parse('local')\n * void value\n * ```\n */\nexport const StorageTypeSchema = z.enum(['s3', 'local'])\n\n/**\n * 存储类型\n * @example\n * ```ts\n * import type { StorageType } from '@h-ai/storage'\n *\n * const value: StorageType = 's3'\n * void value\n * ```\n */\nexport type StorageType = z.infer<typeof StorageTypeSchema>\n\n/**\n * StorageOperationLogConfigSchema 的数据约束与操作契约。\n * @example\n * ```ts\n * import { StorageOperationLogConfigSchema } from '@h-ai/storage'\n *\n * const value = StorageOperationLogConfigSchema.parse({ read: false, write: true, maxLength: 500, level: 'debug' })\n * void value\n * ```\n */\nexport const StorageOperationLogConfigSchema = z.object({\n read: z.boolean().default(false),\n write: z.boolean().default(false),\n maxLength: z.number().int().min(0).default(1000),\n level: z.enum(['info', 'debug', 'trace']).default('debug'),\n})\n\n/**\n * StorageOperationLogConfig 的数据约束与操作契约。\n * @example\n * ```ts\n * import { StorageOperationLogConfigSchema, type StorageOperationLogConfig } from '@h-ai/storage'\n *\n * const value: StorageOperationLogConfig = StorageOperationLogConfigSchema.parse({ read: false, write: true, maxLength: 500, level: 'debug' })\n * void value\n * ```\n */\nexport type StorageOperationLogConfig = z.infer<typeof StorageOperationLogConfigSchema>\n\nconst OperationLogConfigFieldSchema = {\n operationLog: StorageOperationLogConfigSchema.optional(),\n}\n\n/**\n * S3 存储配置 Schema\n *\n * 支持标准 AWS S3 以及兼容 S3 协议的存储服务(MinIO、RustFS、阿里云 OSS 等)。\n * 必填字段:bucket、region、accessKeyId、secretAccessKey。\n * @example\n * ```ts\n * import { S3ConfigSchema } from '@h-ai/storage'\n *\n * const value = S3ConfigSchema.parse({ type: 's3', bucket: 'uploads', region: 'us-east-1', accessKeyId: process.env.HAI_STORAGE_ACCESS_KEY_ID, secretAccessKey: process.env.HAI_STORAGE_SECRET_ACCESS_KEY })\n * void value\n * ```\n */\nexport const S3ConfigSchema = z.object({\n /** 存储类型 */\n type: z.literal('s3'),\n\n /** 存储桶名称 */\n bucket: z.string().min(1, storageM('storage_config_bucketRequired')),\n\n /** 初始化时检查并创建缺失的桶;默认关闭,启用后凭据须有建桶权限。 */\n createBucket: z.boolean().default(false),\n\n /** 区域 */\n region: z.string().min(1, storageM('storage_config_regionRequired')),\n\n /** Access Key ID */\n accessKeyId: z.string().min(1, storageM('storage_config_accessKeyIdRequired')),\n\n /** Secret Access Key */\n secretAccessKey: z.string().min(1, storageM('storage_config_secretAccessKeyRequired')),\n\n /** 自定义端点(用于 MinIO、RustFS、阿里云 OSS 等) */\n endpoint: z.string().url().optional(),\n\n /** 浏览器预签名端点,可含代理目录(如 /delivery/files),不含桶名、凭据或查询;代理须去除目录并保留 Host。 */\n publicEndpoint: z.string().refine((value) => {\n // 必须先检查原始文本;URL 校验会移除制表符和换行,不能在标准化后再检查。\n // eslint-disable-next-line no-control-regex -- 在入口显式拒绝控制字符,避免校验与代理解释不一致。\n return !/[\\u0000-\\u0020\\u007F\\\\%]/.test(value) && !/(?:^|\\/)\\.{1,2}(?:\\/|$)/.test(value)\n }, storageM('storage_config_publicEndpointInvalid')).url().refine((value) => {\n try {\n const url = new URL(value)\n return ['http:', 'https:'].includes(url.protocol) && !url.username && !url.password\n && /^\\/(?:[\\w.-]+(?:\\/[\\w.-]+)*\\/?)?$/.test(url.pathname) && !url.search && !url.hash\n }\n catch {\n return false\n }\n }, storageM('storage_config_publicEndpointInvalid')).optional(),\n\n /** 强制使用路径风格 URL(某些 S3 兼容服务需要) */\n forcePathStyle: z.boolean().default(false),\n\n /** 路径前缀(所有操作都会加上此前缀) */\n prefix: z.string().default(''),\n\n /** 公开访问基础 URL(用于生成公开 URL) */\n publicUrl: z.string().url().optional(),\n\n ...OperationLogConfigFieldSchema,\n})\n\n/**\n * S3 配置类型(parse 后,带默认值的字段已填充,optional 字段仍可缺省)\n * @example\n * ```ts\n * import { S3ConfigSchema, type S3Config } from '@h-ai/storage'\n *\n * const value: S3Config = S3ConfigSchema.parse({ type: 's3', bucket: 'uploads', region: 'us-east-1', accessKeyId: process.env.HAI_STORAGE_ACCESS_KEY_ID, secretAccessKey: process.env.HAI_STORAGE_SECRET_ACCESS_KEY })\n * void value\n * ```\n */\nexport type S3Config = z.infer<typeof S3ConfigSchema>\n\n/**\n * 本地存储配置 Schema\n *\n * 基于本地文件系统,仅后端可用。必填字段:root。\n * @example\n * ```ts\n * import { LocalConfigSchema } from '@h-ai/storage'\n *\n * const value = LocalConfigSchema.parse({ type: 'local', root: './data/uploads' })\n * void value\n * ```\n */\nexport const LocalConfigSchema = z.object({\n /** 存储类型 */\n type: z.literal('local'),\n\n /** 根目录路径 */\n root: z.string().min(1, storageM('storage_config_rootRequired')),\n\n /** 目录创建权限 */\n directoryMode: z.number().default(0o755),\n\n /** 文件创建权限 */\n fileMode: z.number().default(0o644),\n\n ...OperationLogConfigFieldSchema,\n})\n\n/**\n * 本地配置类型(parse 后,带默认值的字段已填充,optional 字段仍可缺省)\n * @example\n * ```ts\n * import { LocalConfigSchema, type LocalConfig } from '@h-ai/storage'\n *\n * const value: LocalConfig = LocalConfigSchema.parse({ type: 'local', root: './data/uploads' })\n * void value\n * ```\n */\nexport type LocalConfig = z.infer<typeof LocalConfigSchema>\n\n/**\n * 统一存储配置 Schema\n *\n * 支持 S3 和本地文件两种类型的配置验证。\n * @example\n * ```ts\n * import { StorageConfigSchema } from '@h-ai/storage'\n *\n * const value = StorageConfigSchema.parse({ type: 'local', root: './data/uploads' })\n * void value\n * ```\n */\nexport const StorageConfigSchema = z.discriminatedUnion('type', [\n S3ConfigSchema,\n LocalConfigSchema,\n])\n\n/**\n * 存储配置类型(parse 后的完整配置)\n * @example\n * ```ts\n * import { StorageConfigSchema, type StorageConfig } from '@h-ai/storage'\n *\n * const value: StorageConfig = StorageConfigSchema.parse({ type: 'local', root: './data/uploads' })\n * void value\n * ```\n */\nexport type StorageConfig = z.infer<typeof StorageConfigSchema>\n\n/**\n * 存储配置输入类型(parse 前,允许省略带默认值的字段)\n * @example\n * ```ts\n * import type { StorageConfigInput } from '@h-ai/storage'\n *\n * const value: StorageConfigInput = { type: 'local', root: './data/uploads' }\n * void value\n * ```\n */\nexport type StorageConfigInput = z.input<typeof StorageConfigSchema>\n\n// ─── 签名 URL 配置 ───\n\n/**\n * 签名 URL 配置 Schema\n *\n * 用于控制 `storage.presign.getUrl()` 生成的下载签名 URL 参数。\n * @example\n * ```ts\n * import { PresignOptionsSchema } from '@h-ai/storage'\n *\n * const value = PresignOptionsSchema.parse({ expiresIn: 60 })\n * void value\n * ```\n */\nexport const PresignOptionsSchema = z.object({\n /** 过期时间(秒),范围 1~604800(7 天),默认 3600(1 小时) */\n expiresIn: StoragePresignExpiresInSchema.default(3600), // 最长 7 天\n\n /** 响应内容类型(用于下载时设置 Content-Type) */\n responseContentType: z.string().optional(),\n\n /** 响应内容处置(用于设置下载文件名) */\n responseContentDisposition: z.string().optional(),\n})\n\n/**\n * 签名 URL 输入选项,允许省略带默认值的字段。\n * @example\n * ```ts\n * import type { PresignOptions } from '@h-ai/storage'\n *\n * const value: PresignOptions = { expiresIn: 60, responseContentType: 'text/plain' }\n * void value\n * ```\n */\nexport type PresignOptions = z.input<typeof PresignOptionsSchema>\n\n/**\n * 上传签名 URL 配置 Schema\n *\n * 继承 PresignOptionsSchema 并扩展上传专属参数,用于 `storage.presign.putUrl()`。\n * @example\n * ```ts\n * import { PresignUploadOptionsSchema } from '@h-ai/storage'\n *\n * const value = PresignUploadOptionsSchema.parse({ contentType: 'text/plain' })\n * void value\n * ```\n */\nexport const PresignUploadOptionsSchema = PresignOptionsSchema.extend({\n /** 上传内容类型(必须与实际上传匹配) */\n contentType: z.string().default('application/octet-stream'),\n})\n\n/**\n * 上传签名 URL 输入选项,省略 contentType 时使用 application/octet-stream。\n * @example\n * ```ts\n * import type { PresignUploadOptions } from '@h-ai/storage'\n *\n * const value: PresignUploadOptions = { expiresIn: 60, contentType: 'text/plain' }\n * void value\n * ```\n */\nexport type PresignUploadOptions = z.input<typeof PresignUploadOptionsSchema>\n","/**\n * @h-ai/storage — 公共类型定义\n *\n * 包含:\n * - 错误定义(HaiStorageError)\n * - 文件元数据(FileMetadata)\n * - 列表结果(ListResult)\n * - 文件操作接口(FileOperations)\n * - 目录操作接口(DirOperations)\n * - 签名 URL 操作接口(PresignOperations)\n * - 函数接口(StorageFunctions)\n * @module storage-types\n */\n\nimport type { ErrorInfo, HaiError, HaiResult } from '@h-ai/core'\nimport type { Buffer } from 'node:buffer'\nimport type { PresignOptions, PresignUploadOptions, StorageConfig, StorageConfigInput } from './storage-config.js'\nimport { core } from '@h-ai/core'\n\n// ─── 错误定义(照 @h-ai/core 范式) ───\n\nconst StorageErrorInfo = {\n CONNECTION_FAILED: '001:500',\n OPERATION_FAILED: '002:500',\n NOT_FOUND: '003:404',\n ALREADY_EXISTS: '004:409',\n PERMISSION_DENIED: '005:403',\n QUOTA_EXCEEDED: '006:413',\n INVALID_PATH: '007:400',\n IO_ERROR: '008:500',\n NETWORK_ERROR: '009:502',\n NOT_INITIALIZED: '010:500',\n UNSUPPORTED_TYPE: '011:400',\n CONFIG_ERROR: '012:500',\n PRESIGN_FAILED: '013:500',\n UPLOAD_FAILED: '014:500',\n DOWNLOAD_FAILED: '015:500',\n CHECKSUM_FAILED: '016:500',\n INVALID_CHECKSUM: '017:400',\n UNSUPPORTED_OPERATION: '018:400',\n INVALID_LIST_OPTIONS: '019:400',\n} as const satisfies ErrorInfo\n\n/**\n * HaiStorageError 的数据约束与操作契约。\n * @example\n * ```ts\n * import { HaiStorageError } from '@h-ai/storage'\n * import { err } from '@h-ai/core'\n *\n * const result = err(HaiStorageError.NOT_FOUND, 'File missing')\n * if (!result.success) void result.error.code\n * ```\n */\nexport const HaiStorageError = core.error.buildHaiErrorsDef('storage', StorageErrorInfo)\n\n/**\n * 批量删除中未确认完成的一项;key 始终使用调用方视角,不含 Provider prefix。\n * @example\n * ```ts\n * import type { StorageDeleteFailure } from '@h-ai/storage'\n *\n * const value: StorageDeleteFailure = { key: 'private.txt', code: 'AccessDenied', retryable: false, outcome: 'failed' }\n * void value\n * ```\n */\nexport interface StorageDeleteFailure {\n /** 调用方提交的文件键。 */\n key: string\n /** 渠道/框架错误标识,不包含渠道原始 Message。 */\n code: string\n /** 是否属于可重试的瞬时故障;未知结果重试前仍需考虑并发上传/版本语义。 */\n retryable: boolean\n /** failed 为已知失败,unknown 为已发送但结果未知,notAttempted 为尚未发送。 */\n outcome: 'failed' | 'unknown' | 'notAttempted'\n}\n\n/**\n * 批量删除进度;任一失败不能宣称全部删除完成。\n * @example\n * ```ts\n * import type { StorageDeleteSummary } from '@h-ai/storage'\n *\n * const value: StorageDeleteSummary = { deletedKeys: ['a.txt'], failures: [] }\n * void value\n * ```\n */\nexport interface StorageDeleteSummary {\n /** 已确认删除或本来就不存在的唯一文件键。 */\n deletedKeys: string[]\n /** 失败、未知及尚未尝试的唯一文件键。 */\n failures: StorageDeleteFailure[]\n}\n\n/**\n * 批量删除结果;失败仍遵守 HaiResult,部分进度置于 error.ext.deleteMany。\n * @example\n * ```ts\n * import { storage, type StorageDeleteResult } from '@h-ai/storage'\n *\n * async function remove() {\n * const result: StorageDeleteResult = await storage.file.deleteMany(['a.txt', 'b.txt'])\n * return result.success ? result.data : result.error.ext?.deleteMany\n * }\n * ```\n */\nexport type StorageDeleteResult\n = | { success: true, data: StorageDeleteSummary }\n | { success: false, error: HaiError & { ext?: Record<string, unknown> & { deleteMany?: StorageDeleteSummary } } }\n\n// ─── 文件元数据 ───\n\n/**\n * 文件元数据接口\n *\n * 表示存储中单个文件的元信息,由 put/head/copy/list 等操作返回。\n * @example\n * ```ts\n * import type { FileMetadata } from '@h-ai/storage'\n *\n * const value: FileMetadata = { key: 'a.txt', size: 5, contentType: 'text/plain', lastModified: new Date() }\n * void value\n * ```\n */\nexport interface FileMetadata {\n /** 文件键/路径(不含配置 prefix,对用户透明) */\n key: string\n /** 文件大小(字节) */\n size: number\n /** 内容类型(MIME),未指定时根据扩展名自动推断 */\n contentType: string\n /** 最后修改时间 */\n lastModified: Date\n /** ETag(缓存验证标记;本地为 size-mtime,S3 由服务端生成,不等同于内容 checksum) */\n etag?: string\n /** 自定义元数据键值对 */\n metadata?: Record<string, string>\n}\n\n/**\n * 列表选项\n *\n * 用于控制 `storage.dir.list()` 的过滤、分页和目录模拟行为。\n * @example\n * ```ts\n * import type { ListOptions } from '@h-ai/storage'\n *\n * const value: ListOptions = { prefix: 'reports/', delimiter: '/', maxKeys: 100 }\n * void value\n * ```\n */\nexport interface ListOptions {\n /** 前缀过滤(只返回以此前缀开头的文件) */\n prefix?: string\n /**\n * 分页标记(从上次 ListResult.nextContinuationToken 获取)\n *\n * Local 游标绑定根目录、prefix 和 delimiter,可跨实例使用;不得跨查询或 provider 混用。\n */\n continuationToken?: string\n /** 每页逻辑条目上限(1–1000,默认 1000);files 和 commonPrefixes 合计计数。 */\n maxKeys?: number\n /** 分隔符,通常为 '/';从 prefix 之后首次出现的位置折叠 commonPrefixes,空字符串表示不折叠。 */\n delimiter?: string\n}\n\n/**\n * 列表结果\n *\n * 包含当前页的文件列表、目录前缀、分页信息。\n * @example\n * ```ts\n * import type { ListResult } from '@h-ai/storage'\n *\n * const value: ListResult = { files: [], commonPrefixes: ['reports/'], isTruncated: false }\n * void value\n * ```\n */\nexport interface ListResult {\n /** 当前页文件元数据列表 */\n files: FileMetadata[]\n /** 公共前缀:由实际匹配文件按 delimiter 折叠,不包含 Local 空物理目录。 */\n commonPrefixes: string[]\n /** 下一页标记;无后续页时为 undefined */\n nextContinuationToken?: string\n /** 是否被截断(true 表示还有更多数据,可用 nextContinuationToken 继续拉取) */\n isTruncated: boolean\n}\n\n// ─── 上传下载选项 ───\n\n/**\n * 上传选项\n *\n * 用于控制 `storage.file.put()` 的元数据与缓存行为。\n * @example\n * ```ts\n * import type { UploadOptions } from '@h-ai/storage'\n *\n * const value: UploadOptions = { ifNoneMatch: '*', contentType: 'text/plain', signal: new AbortController().signal }\n * void value\n * ```\n */\nexport interface UploadOptions {\n /** 仅当 key 不存在时原子写入;S3 使用标准 If-None-Match,后端不支持时返回错误。 */\n ifNoneMatch?: '*'\n /** 取消上传;失败或取消时清理未完成的临时文件/分片。 */\n signal?: AbortSignal\n /** 内容类型(不指定时根据文件扩展名自动推断) */\n contentType?: string\n /** 自定义元数据键值对(存储后可通过 head 获取) */\n metadata?: Record<string, string>\n /** Cache-Control 响应头(例如 'max-age=3600') */\n cacheControl?: string\n /** Content-Disposition 响应头(例如 'attachment; filename=\"a.txt\"') */\n contentDisposition?: string\n}\n\n/**\n * 下载选项\n *\n * 用于控制 `storage.file.get()` 的范围读取。\n * 两个字段均为字节偏移(含),与 HTTP Range 语义一致。\n * @example\n * ```ts\n * import type { DownloadOptions } from '@h-ai/storage'\n *\n * const value: DownloadOptions = { rangeStart: 0, rangeEnd: 99 }\n * void value\n * ```\n */\nexport interface DownloadOptions {\n /** 取消读取。 */\n signal?: AbortSignal\n /** 范围起始位置(字节,含);不指定时从文件开头读取 */\n rangeStart?: number\n /** 范围结束位置(字节,含);不指定时读取到文件末尾 */\n rangeEnd?: number\n}\n\n/**\n * 复制选项\n *\n * 用于控制 `storage.file.copy()` 的目标文件元数据。\n * 提供任意字段时,S3 Provider 会使用 MetadataDirective='REPLACE' 覆盖源文件元数据。\n * @example\n * ```ts\n * import type { CopyOptions } from '@h-ai/storage'\n *\n * const value: CopyOptions = { contentType: 'text/plain', metadata: { category: 'reports' } }\n * void value\n * ```\n */\nexport interface CopyOptions {\n /** 目标文件的内容类型(不指定时继承源文件) */\n contentType?: string\n /** 目标文件的自定义元数据(不指定时继承源文件) */\n metadata?: Record<string, string>\n}\n\n// ─── 操作接口 ───\n\n/**\n * 文件内容;异步字节流支持 Node Readable 等,不将整个文件载入内存。\n * @example\n * ```ts\n * import type { StorageData } from '@h-ai/storage'\n *\n * const value: StorageData = new TextEncoder().encode('hello')\n * void value\n * ```\n */\nexport type StorageData = Buffer | Uint8Array | string | AsyncIterable<Uint8Array>\n\n/**\n * 常见 checksum 算法;MD5/SHA-1 仅用于兼容性完整性校验,不用于安全认证。\n * @example\n * ```ts\n * import type { ChecksumAlgorithm } from '@h-ai/storage'\n *\n * const value: ChecksumAlgorithm = 'sha256'\n * void value\n * ```\n */\nexport type ChecksumAlgorithm = 'sha256' | 'sha384' | 'sha512' | 'sha1' | 'md5'\n\n/**\n * checksum 计算选项;默认 SHA-256、小写 hex。\n * @example\n * ```ts\n * import type { ChecksumOptions } from '@h-ai/storage'\n *\n * const value: ChecksumOptions = { algorithm: 'sha256', encoding: 'hex' }\n * void value\n * ```\n */\nexport interface ChecksumOptions {\n algorithm?: ChecksumAlgorithm\n encoding?: 'hex' | 'base64'\n signal?: AbortSignal\n}\n\n/**\n * Node 端无状态 checksum 工具,不依赖 storage.init;流输入仅消费一次。\n * @example\n * ```ts\n * import type { ChecksumOperations } from '@h-ai/storage'\n *\n * async function inspect(operations: ChecksumOperations) {\n * const result = await operations.generate('hello')\n * if (!result.success) return result.error.message\n * return result.data\n * }\n * ```\n */\nexport interface ChecksumOperations {\n generate: (data: StorageData, options?: ChecksumOptions) => Promise<HaiResult<string>>\n verify: (data: StorageData, expected: string, options?: ChecksumOptions) => Promise<HaiResult<boolean>>\n}\n\n/**\n * 文件操作接口\n *\n * 提供文件的读写操作。\n * @example\n * ```ts\n * import type { FileOperations } from '@h-ai/storage'\n *\n * async function inspect(operations: FileOperations) {\n * const result = await operations.getStream('reports/a.txt')\n * if (!result.success) return result.error.message\n * for await (const chunk of result.data) void chunk.byteLength\n * }\n * ```\n */\nexport interface FileOperations {\n /**\n * 上传文件\n *\n * @param key - 文件键/路径\n * @param data - 文件内容(Buffer、Uint8Array、字符串或异步字节流)\n * @param options - 上传选项\n * @returns 上传结果包含文件元数据\n * @example\n * ```ts\n * const result = await storage.file.put('uploads/a.txt', 'hello')\n * if (result.success) {\n * result.data.key\n * }\n * ```\n */\n put: (key: string, data: StorageData, options?: UploadOptions) => Promise<HaiResult<FileMetadata>>\n\n /**\n * 下载文件\n *\n * @param key - 文件键/路径\n * @param options - 下载选项\n * @returns 文件内容\n * @example\n * ```ts\n * const result = await storage.file.get('uploads/a.txt')\n * if (result.success) {\n * const buffer = result.data\n * void buffer\n * }\n * ```\n */\n get: (key: string, options?: DownloadOptions) => Promise<HaiResult<Buffer>>\n\n /**\n * 读取字节流,支持 Range 与取消;消费方负责及时迭代或关闭流。\n * @throws 迭代期间的 I/O/取消错误;初始请求失败通过 HaiResult 返回。\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const result = await storage.file.getStream('reports/a.txt')\n * if (!result.success) throw new Error(result.error.message)\n * try {\n * for await (const chunk of result.data) void chunk.byteLength\n * } catch (error) {\n * // 初始请求成功后,迭代仍可能因 I/O/取消失败。\n * throw error\n * }\n * ```\n */\n getStream: (key: string, options?: DownloadOptions) => Promise<HaiResult<AsyncIterable<Uint8Array>>>\n\n /**\n * 获取文件元数据\n *\n * @param key - 文件键/路径\n * @returns 文件元数据\n * @example\n * ```ts\n * const result = await storage.file.head('uploads/a.txt')\n * if (result.success) {\n * result.data.size\n * }\n * ```\n */\n head: (key: string) => Promise<HaiResult<FileMetadata>>\n\n /**\n * 检查文件是否存在\n *\n * @param key - 文件键/路径\n * @returns 是否存在\n * @example\n * ```ts\n * const result = await storage.file.exists('uploads/a.txt')\n * if (result.success) {\n * result.data\n * }\n * ```\n */\n exists: (key: string) => Promise<HaiResult<boolean>>\n\n /**\n * 删除文件\n *\n * @param key - 文件键/路径\n * @returns 删除结果\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const result = await storage.file.delete('uploads/a.txt')\n * if (!result.success) throw new Error(result.error.message)\n * ```\n */\n delete: (key: string) => Promise<HaiResult<void>>\n\n /**\n * 批量删除文件\n *\n * @param keys - 文件键/路径列表\n * @returns 全部成功时返回进度;任一失败时 success=false,进度在 error.ext.deleteMany\n * @example\n * ```ts\n * const result = await storage.file.deleteMany(['a.txt', 'b.txt'])\n * const progress = result.success ? result.data : result.error.ext?.deleteMany\n * void progress\n * ```\n */\n deleteMany: (keys: string[]) => Promise<StorageDeleteResult>\n\n /**\n * 复制文件\n *\n * @param sourceKey - 源文件键\n * @param destKey - 目标文件键\n * @param options - 复制选项\n * @returns 目标文件元数据\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const result = await storage.file.copy('a.txt', 'b.txt')\n * if (!result.success) throw new Error(result.error.message)\n * void result.data.key\n * ```\n */\n copy: (sourceKey: string, destKey: string, options?: CopyOptions) => Promise<HaiResult<FileMetadata>>\n}\n\n/**\n * 目录操作接口\n *\n * 提供目录(前缀)的列表操作。\n * @example\n * ```ts\n * import type { DirOperations } from '@h-ai/storage'\n *\n * async function inspect(operations: DirOperations) {\n * const page = await operations.list({ prefix: 'reports/', maxKeys: 100 })\n * if (!page.success) return page.error.message\n * return page.data.files\n * }\n * ```\n */\nexport interface DirOperations {\n /**\n * 列出目录内容\n *\n * @param options - 列表选项\n * @returns 文件和目录列表\n * @example\n * ```ts\n * const result = await storage.dir.list({ prefix: 'uploads/' })\n * if (result.success) {\n * result.data.files\n * }\n * ```\n */\n list: (options?: ListOptions) => Promise<HaiResult<ListResult>>\n\n /**\n * 删除目录(删除指定前缀下的所有文件)\n *\n * @param prefix - 目录前缀\n * @returns 删除结果\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const result = await storage.dir.delete('uploads/tmp/')\n * if (!result.success) throw new Error(result.error.message)\n * ```\n */\n delete: (prefix: string) => Promise<HaiResult<void>>\n}\n\n/**\n * 签名 URL 操作接口\n *\n * S3 provider 提供临时授权 URL,支持前端直接上传下载。\n * local 的 getUrl/putUrl 返回 UNSUPPORTED_OPERATION,publicUrl 返回 null。\n * @example\n * ```ts\n * import type { PresignOperations } from '@h-ai/storage'\n *\n * async function inspect(operations: PresignOperations) {\n * const signed = await operations.putUrl('reports/a.txt', { contentType: 'text/plain', expiresIn: 300 })\n * if (!signed.success) return signed.error.message\n * return signed.data\n * }\n * ```\n */\nexport interface PresignOperations {\n /**\n * 生成 GET 下载签名 URL;使用 publicEndpoint,缺省回退 endpoint/AWS 端点。\n * publicEndpoint 可含代理目录,返回值已带此前缀;代理须去除它并保留 S3 URI/Host/query,调用方不得自行改写。\n *\n * @param key - 文件键/路径\n * @param options - 签名选项\n * @returns 成功时为可访问的 S3 签名 URL;local 返回 UNSUPPORTED_OPERATION。\n * @example\n * ```ts\n * const result = await storage.presign.getUrl('a.txt', { expiresIn: 60 })\n * if (!result.success) throw new Error(result.error.message)\n * const downloadUrl = result.data\n * void downloadUrl\n * ```\n */\n getUrl: (key: string, options?: PresignOptions) => Promise<HaiResult<string>>\n\n /**\n * 生成 PUT 上传签名 URL;publicEndpoint 目录与 GET 相同处理,服务端 CRUD 仍使用内部 endpoint。\n *\n * @param key - 文件键/路径\n * @param options - 上传签名选项\n * @returns 成功时为可访问的 S3 签名 URL;local 返回 UNSUPPORTED_OPERATION。\n * @example\n * ```ts\n * const result = await storage.presign.putUrl('a.txt', { contentType: 'text/plain' })\n * if (!result.success) throw new Error(result.error.message)\n * const uploadUrl = result.data\n * void uploadUrl\n * ```\n */\n putUrl: (key: string, options?: PresignUploadOptions) => Promise<HaiResult<string>>\n\n /**\n * 获取公开访问 URL(如果配置了 publicUrl)\n *\n * @param key - 文件键/路径\n * @returns 公开 URL;S3 未配置 publicUrl 或 local provider 均返回 null。\n * @example\n * ```ts\n * const url = storage.presign.publicUrl('a.txt')\n * void url\n * ```\n */\n publicUrl: (key: string) => string | null\n}\n\n// ─── 函数接口 ───\n\n/**\n * 存储函数接口\n *\n * 统一的存储访问入口:\n * - `storage.init()` — 初始化存储连接\n * - `storage.close()` — 关闭连接并释放资源\n * - `storage.file` — 文件操作(上传/下载/删除/复制等)\n * - `storage.dir` — 目录操作(列表/删除)\n * - `storage.presign` — 签名 URL 操作\n * - `storage.config` — 当前脱敏配置快照(未初始化时为 null)\n * - `storage.isInitialized` — 初始化状态\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const initialized = await storage.init({ type: 'local', root: './data/uploads' })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const uploaded = await storage.file.put('reports/a.txt', 'hello', { ifNoneMatch: '*' })\n * if (!uploaded.success) throw new Error(uploaded.error.message)\n * const downloaded = await storage.file.get('reports/a.txt')\n * if (!downloaded.success) throw new Error(downloaded.error.message)\n * void downloaded.data\n * } finally {\n * const closed = await storage.close()\n * if (!closed.success) throw new Error(closed.error.message)\n * }\n * ```\n */\nexport interface StorageFunctions {\n /** 无状态 checksum 生成与校验,Node 专用,无需初始化。 */\n readonly checksum: ChecksumOperations\n /**\n * 初始化存储连接\n *\n * 已有连接时会先 close 再重新初始化。\n *\n * @param config - 存储配置(支持省略带默认值的字段)\n * @returns 成功 ok(undefined);失败返回 err(含 HaiStorageError 定义的错误信息)\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const initialized = await storage.init({ type: 'local', root: './data/uploads' })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * const closed = await storage.close()\n * if (!closed.success) throw new Error(closed.error.message)\n * ```\n */\n init: (config: StorageConfigInput) => Promise<HaiResult<void>>\n\n /**\n * 关闭存储连接并释放资源\n *\n * 等待初始化并合并并发关闭;失败时操作不可用,资源保留供 close/init 重试。\n * @returns 所有资源释放成功才返回成功;未初始化时同样成功。\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const closed = await storage.close()\n * if (!closed.success) throw new Error(closed.error.message)\n * ```\n */\n close: () => Promise<HaiResult<void>>\n\n /** 当前脱敏后的存储配置快照;未初始化或已关闭时为 null */\n readonly config: StorageConfig | null\n\n /** 是否已完成初始化 */\n readonly isInitialized: boolean\n\n /** 文件操作接口(put / get / head / exists / delete / deleteMany / copy) */\n readonly file: FileOperations\n\n /** 目录操作接口(list / delete) */\n readonly dir: DirOperations\n\n /** 签名 URL 操作接口(getUrl / putUrl / publicUrl) */\n readonly presign: PresignOperations\n}\n"]}
|