@h-ai/storage 0.1.0-alpha.27 → 0.1.0-alpha.29

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.md CHANGED
@@ -93,6 +93,34 @@ const result = await apiClient.storage.presignedUrls.createUpload({ key: 'avatar
93
93
  - **S3**:`bucket / region / accessKeyId / secretAccessKey` 必填,可选 `endpoint / forcePathStyle / prefix / publicUrl`
94
94
  - **Local**:`root` 必填,可选 `directoryMode / fileMode`
95
95
 
96
+ ### 操作日志
97
+
98
+ 可在存储配置中通过 `operationLog` 开启文件、目录和预签名 URL 操作日志。日志在 Local/S3 Provider 的真实操作处输出,不在 `storage-main.ts` 中做统一包装;文件内容不会写入日志,上传数据只记录字节长度。
99
+
100
+ ```yaml
101
+ # config/_storage.yml
102
+ type: local
103
+ root: ./data/uploads
104
+ operationLog:
105
+ read: false # file.get/head/exists, dir.list, presign.getUrl/publicUrl
106
+ write: false # file.put/delete/deleteMany/copy, dir.delete, presign.putUrl
107
+ maxLength: 1000 # 参数序列化后的最大输出长度,超出会截断
108
+ level: debug # info | debug | trace,默认 debug
109
+ ```
110
+
111
+ ```ts
112
+ await storage.init({
113
+ type: 'local',
114
+ root: './data/uploads',
115
+ operationLog: {
116
+ read: true,
117
+ write: true,
118
+ maxLength: 500,
119
+ level: 'debug',
120
+ },
121
+ })
122
+ ```
123
+
96
124
  `storage.presign.putUrl()` 当前仅支持 `contentType` 与 `expiresIn` 等签名参数,不提供 `maxSize` 约束。
97
125
 
98
126
  本地存储的 `storage.dir.list()` 会递归扫描匹配前缀下的文件,仅支持 `maxKeys` 截断,不支持 `continuationToken` 真分页;大目录场景建议优先使用更窄的 `prefix`,或改用 S3 Provider 获取原生分页能力。
package/dist/browser.d.ts CHANGED
@@ -22,6 +22,17 @@ declare const StorageTypeSchema: z.ZodEnum<{
22
22
  }>;
23
23
  /** 存储类型 */
24
24
  type StorageType = z.infer<typeof StorageTypeSchema>;
25
+ declare const StorageOperationLogConfigSchema: z.ZodObject<{
26
+ read: z.ZodDefault<z.ZodBoolean>;
27
+ write: z.ZodDefault<z.ZodBoolean>;
28
+ maxLength: z.ZodDefault<z.ZodNumber>;
29
+ level: z.ZodDefault<z.ZodEnum<{
30
+ info: "info";
31
+ debug: "debug";
32
+ trace: "trace";
33
+ }>>;
34
+ }, z.core.$strip>;
35
+ type StorageOperationLogConfig = z.infer<typeof StorageOperationLogConfigSchema>;
25
36
  /**
26
37
  * S3 存储配置 Schema
27
38
  *
@@ -29,6 +40,16 @@ type StorageType = z.infer<typeof StorageTypeSchema>;
29
40
  * 必填字段:bucket、region、accessKeyId、secretAccessKey。
30
41
  */
31
42
  declare const S3ConfigSchema: z.ZodObject<{
43
+ operationLog: z.ZodOptional<z.ZodObject<{
44
+ read: z.ZodDefault<z.ZodBoolean>;
45
+ write: z.ZodDefault<z.ZodBoolean>;
46
+ maxLength: z.ZodDefault<z.ZodNumber>;
47
+ level: z.ZodDefault<z.ZodEnum<{
48
+ info: "info";
49
+ debug: "debug";
50
+ trace: "trace";
51
+ }>>;
52
+ }, z.core.$strip>>;
32
53
  type: z.ZodLiteral<"s3">;
33
54
  bucket: z.ZodString;
34
55
  region: z.ZodString;
@@ -47,6 +68,16 @@ type S3Config = z.infer<typeof S3ConfigSchema>;
47
68
  * 基于本地文件系统,仅后端可用。必填字段:root。
48
69
  */
49
70
  declare const LocalConfigSchema: z.ZodObject<{
71
+ operationLog: z.ZodOptional<z.ZodObject<{
72
+ read: z.ZodDefault<z.ZodBoolean>;
73
+ write: z.ZodDefault<z.ZodBoolean>;
74
+ maxLength: z.ZodDefault<z.ZodNumber>;
75
+ level: z.ZodDefault<z.ZodEnum<{
76
+ info: "info";
77
+ debug: "debug";
78
+ trace: "trace";
79
+ }>>;
80
+ }, z.core.$strip>>;
50
81
  type: z.ZodLiteral<"local">;
51
82
  root: z.ZodString;
52
83
  directoryMode: z.ZodDefault<z.ZodNumber>;
@@ -89,6 +120,16 @@ type LocalConfig = z.infer<typeof LocalConfigSchema>;
89
120
  * ```
90
121
  */
91
122
  declare const StorageConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
123
+ operationLog: z.ZodOptional<z.ZodObject<{
124
+ read: z.ZodDefault<z.ZodBoolean>;
125
+ write: z.ZodDefault<z.ZodBoolean>;
126
+ maxLength: z.ZodDefault<z.ZodNumber>;
127
+ level: z.ZodDefault<z.ZodEnum<{
128
+ info: "info";
129
+ debug: "debug";
130
+ trace: "trace";
131
+ }>>;
132
+ }, z.core.$strip>>;
92
133
  type: z.ZodLiteral<"s3">;
93
134
  bucket: z.ZodString;
94
135
  region: z.ZodString;
@@ -99,6 +140,16 @@ declare const StorageConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
99
140
  prefix: z.ZodDefault<z.ZodString>;
100
141
  publicUrl: z.ZodOptional<z.ZodString>;
101
142
  }, z.core.$strip>, z.ZodObject<{
143
+ operationLog: z.ZodOptional<z.ZodObject<{
144
+ read: z.ZodDefault<z.ZodBoolean>;
145
+ write: z.ZodDefault<z.ZodBoolean>;
146
+ maxLength: z.ZodDefault<z.ZodNumber>;
147
+ level: z.ZodDefault<z.ZodEnum<{
148
+ info: "info";
149
+ debug: "debug";
150
+ trace: "trace";
151
+ }>>;
152
+ }, z.core.$strip>>;
102
153
  type: z.ZodLiteral<"local">;
103
154
  root: z.ZodString;
104
155
  directoryMode: z.ZodDefault<z.ZodNumber>;
@@ -500,4 +551,4 @@ interface StorageProvider {
500
551
  readonly presign: PresignOperations;
501
552
  }
502
553
 
503
- export { type CopyOptions, type DirOperations, type DownloadOptions, type FileMetadata, type FileOperations, HaiStorageError, type ListOptions, type ListResult, type LocalConfig, LocalConfigSchema, type PresignOperations, type PresignOptions, PresignOptionsSchema, type PresignUploadOptions, PresignUploadOptionsSchema, type S3Config, S3ConfigSchema, type StorageConfig, type StorageConfigInput, StorageConfigSchema, type StorageFunctions, type StorageProvider, type StorageType, StorageTypeSchema, type UploadOptions };
554
+ export { type CopyOptions, type DirOperations, type DownloadOptions, type FileMetadata, type FileOperations, HaiStorageError, type ListOptions, type ListResult, type LocalConfig, LocalConfigSchema, type PresignOperations, type PresignOptions, PresignOptionsSchema, type PresignUploadOptions, PresignUploadOptionsSchema, type S3Config, S3ConfigSchema, type StorageConfig, type StorageConfigInput, StorageConfigSchema, type StorageFunctions, type StorageOperationLogConfig, StorageOperationLogConfigSchema, type StorageProvider, type StorageType, StorageTypeSchema, type UploadOptions };
package/dist/browser.js CHANGED
@@ -1,4 +1,4 @@
1
- export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageTypeSchema } from './chunk-JNLYVWRV.js';
1
+ export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema } from './chunk-4XD6ZH33.js';
2
2
  export { downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, uploadWithPresignedUrl } from './chunk-U427BQIQ.js';
3
3
  //# sourceMappingURL=browser.js.map
4
4
  //# sourceMappingURL=browser.js.map
@@ -3,6 +3,15 @@ import { z } from 'zod';
3
3
  import { core } from '@h-ai/core';
4
4
 
5
5
  var StorageTypeSchema = z.enum(["s3", "local"]);
6
+ var StorageOperationLogConfigSchema = z.object({
7
+ read: z.boolean().default(false),
8
+ write: z.boolean().default(false),
9
+ maxLength: z.number().int().min(0).default(1e3),
10
+ level: z.enum(["info", "debug", "trace"]).default("debug")
11
+ });
12
+ var OperationLogConfigFieldSchema = {
13
+ operationLog: StorageOperationLogConfigSchema.optional()
14
+ };
6
15
  var S3ConfigSchema = z.object({
7
16
  /** 存储类型 */
8
17
  type: z.literal("s3"),
@@ -21,7 +30,8 @@ var S3ConfigSchema = z.object({
21
30
  /** 路径前缀(所有操作都会加上此前缀) */
22
31
  prefix: z.string().default(""),
23
32
  /** 公开访问基础 URL(用于生成公开 URL) */
24
- publicUrl: z.string().url().optional()
33
+ publicUrl: z.string().url().optional(),
34
+ ...OperationLogConfigFieldSchema
25
35
  });
26
36
  var LocalConfigSchema = z.object({
27
37
  /** 存储类型 */
@@ -31,7 +41,8 @@ var LocalConfigSchema = z.object({
31
41
  /** 目录创建权限 */
32
42
  directoryMode: z.number().default(493),
33
43
  /** 文件创建权限 */
34
- fileMode: z.number().default(420)
44
+ fileMode: z.number().default(420),
45
+ ...OperationLogConfigFieldSchema
35
46
  });
36
47
  var StorageConfigSchema = z.discriminatedUnion("type", [
37
48
  S3ConfigSchema,
@@ -69,6 +80,6 @@ var StorageErrorInfo = {
69
80
  };
70
81
  var HaiStorageError = core.error.buildHaiErrorsDef("storage", StorageErrorInfo);
71
82
 
72
- export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageTypeSchema };
73
- //# sourceMappingURL=chunk-JNLYVWRV.js.map
74
- //# sourceMappingURL=chunk-JNLYVWRV.js.map
83
+ export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema };
84
+ //# sourceMappingURL=chunk-4XD6ZH33.js.map
85
+ //# sourceMappingURL=chunk-4XD6ZH33.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-4XD6ZH33.js","sourcesContent":["/**\n * @h-ai/storage — 错误码 + 配置 Schema\n * @module storage-config\n */\n\nimport { z } from 'zod'\nimport { storageM } from './storage-i18n.js'\n\n// ─── 配置 Schema ───\n\n/**\n * 存储类型枚举\n *\n * 支持的存储类型:\n * - `s3` - S3 协议对象存储(兼容 AWS S3、MinIO、阿里云 OSS 等)\n * - `local` - 本地文件存储(仅后端)\n */\nexport const StorageTypeSchema = z.enum(['s3', 'local'])\n\n/** 存储类型 */\nexport type StorageType = z.infer<typeof StorageTypeSchema>\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\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、阿里云 OSS 等)。\n * 必填字段:bucket、region、accessKeyId、secretAccessKey。\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 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、阿里云 OSS 等) */\n endpoint: z.string().url().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/** S3 配置类型(parse 后,所有字段已填充默认值) */\nexport type S3Config = z.infer<typeof S3ConfigSchema>\n\n/**\n * 本地存储配置 Schema\n *\n * 基于本地文件系统,仅后端可用。必填字段:root。\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/** 本地配置类型(parse 后,所有字段已填充默认值) */\nexport type LocalConfig = z.infer<typeof LocalConfigSchema>\n\n/**\n * 统一存储配置 Schema\n *\n * 支持 S3 和本地文件两种类型的配置验证。\n *\n * @example\n * ```ts\n * // S3 配置\n * const s3Config = StorageConfigSchema.parse({\n * type: 's3',\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * accessKeyId: 'AKIAXXXXXXXX',\n * secretAccessKey: 'xxxxx'\n * })\n *\n * // MinIO 配置\n * const minioConfig = StorageConfigSchema.parse({\n * type: 's3',\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * endpoint: 'http://localhost:9000',\n * accessKeyId: 'minioadmin',\n * secretAccessKey: 'minioadmin',\n * forcePathStyle: true\n * })\n *\n * // 本地存储配置\n * const localConfig = StorageConfigSchema.parse({\n * type: 'local',\n * root: '/data/uploads'\n * })\n * ```\n */\nexport const StorageConfigSchema = z.discriminatedUnion('type', [\n S3ConfigSchema,\n LocalConfigSchema,\n])\n\n/** 存储配置类型(parse 后的完整配置) */\nexport type StorageConfig = z.infer<typeof StorageConfigSchema>\n\n/** 存储配置输入类型(parse 前,允许省略带默认值的字段) */\nexport type StorageConfigInput = z.input<typeof StorageConfigSchema>\n\n// ─── 签名 URL 配置 ───\n\n/**\n * 签名 URL 配置 Schema\n *\n * 用于控制 `storage.presign.getUrl()` 生成的下载签名 URL 参数。\n *\n * @example\n * ```ts\n * const url = await storage.presign.getUrl('report.pdf', {\n * expiresIn: 600, // 10 分钟过期\n * responseContentDisposition: 'attachment; filename=\"report.pdf\"',\n * })\n * ```\n */\nexport const PresignOptionsSchema = z.object({\n /** 过期时间(秒),范围 1~604800(7 天),默认 3600(1 小时) */\n expiresIn: z.number().min(1).max(604800).default(3600), // 最长 7 天\n\n /** 响应内容类型(用于下载时设置 Content-Type) */\n responseContentType: z.string().optional(),\n\n /** 响应内容处置(用于设置下载文件名) */\n responseContentDisposition: z.string().optional(),\n})\n\n/** 签名 URL 配置类型(parse 后) */\nexport type PresignOptions = z.infer<typeof PresignOptionsSchema>\n\n/**\n * 上传签名 URL 配置 Schema\n *\n * 继承 PresignOptionsSchema 并扩展上传专属参数,用于 `storage.presign.putUrl()`。\n *\n * @example\n * ```ts\n * const url = await storage.presign.putUrl('uploads/avatar.png', {\n * contentType: 'image/png',\n * expiresIn: 300,\n * })\n * ```\n */\nexport const PresignUploadOptionsSchema = PresignOptionsSchema.extend({\n /** 上传内容类型(必须与实际上传匹配) */\n contentType: z.string().default('application/octet-stream'),\n})\n\n/** 上传签名 URL 配置类型(parse 后) */\nexport type PresignUploadOptions = z.infer<typeof PresignUploadOptionsSchema>\n","/**\n * @h-ai/storage — 公共类型定义\n *\n * 包含:\n * - 错误类型(StorageError)\n * - 文件元数据(FileMetadata)\n * - 列表结果(ListResult)\n * - 文件操作接口(FileOperations)\n * - 目录操作接口(DirOperations)\n * - 签名 URL 操作接口(PresignOperations)\n * - 函数接口(StorageFunctions)\n * - Provider 接口(StorageProvider)\n * @module storage-types\n */\n\nimport type { ErrorInfo, 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} as const satisfies ErrorInfo\n\nexport const HaiStorageError = core.error.buildHaiErrorsDef('storage', StorageErrorInfo)\n\n// ─── 文件元数据 ───\n\n/**\n * 文件元数据接口\n *\n * 表示存储中单个文件的元信息,由 put/head/copy/list 等操作返回。\n */\nexport interface FileMetadata {\n /** 文件键/路径(不含配置 prefix,对用户透明) */\n key: string\n /** 文件大小(字节) */\n size: number\n /** 内容类型(MIME),未指定时根据扩展名自动推断 */\n contentType: string\n /** 最后修改时间 */\n lastModified: Date\n /** ETag(用于缓存验证,本地存储为 MD5,S3 由服务端生成) */\n etag?: string\n /** 自定义元数据键值对 */\n metadata?: Record<string, string>\n}\n\n/**\n * 列表选项\n *\n * 用于控制 `storage.dir.list()` 的过滤、分页和目录模拟行为。\n */\nexport interface ListOptions {\n /** 前缀过滤(只返回以此前缀开头的文件) */\n prefix?: string\n /**\n * 分页标记(从上次 ListResult.nextContinuationToken 获取)\n *\n * 注意:本地存储目前不支持此字段,仅 S3 Provider 有效。\n */\n continuationToken?: string\n /** 每页最大数量(默认 1000) */\n maxKeys?: number\n /** 分隔符(用于模拟目录结构,通常为 '/';传入后子目录作为 commonPrefixes 返回) */\n delimiter?: string\n}\n\n/**\n * 列表结果\n *\n * 包含当前页的文件列表、目录前缀、分页信息。\n */\nexport interface ListResult {\n /** 当前页文件元数据列表 */\n files: FileMetadata[]\n /** 公共前缀(使用 delimiter 时由子目录路径组成) */\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 */\nexport interface UploadOptions {\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 */\nexport interface DownloadOptions {\n /** 范围起始位置(字节,含);不指定时从文件开头读取 */\n rangeStart?: number\n /** 范围结束位置(字节,含);不指定时读取到文件末尾 */\n rangeEnd?: number\n}\n\n/**\n * 复制选项\n *\n * 用于控制 `storage.file.copy()` 的目标文件元数据。\n * 提供任意字段时,S3 Provider 会使用 MetadataDirective='REPLACE' 覆盖源文件元数据。\n */\nexport interface CopyOptions {\n /** 目标文件的内容类型(不指定时继承源文件) */\n contentType?: string\n /** 目标文件的自定义元数据(不指定时继承源文件) */\n metadata?: Record<string, string>\n}\n\n// ─── 操作接口 ───\n\n/**\n * 文件操作接口\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: Buffer | Uint8Array | string, 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 * }\n * ```\n */\n get: (key: string, options?: DownloadOptions) => Promise<HaiResult<Buffer>>\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 * await storage.file.delete('uploads/a.txt')\n * ```\n */\n delete: (key: string) => Promise<HaiResult<void>>\n\n /**\n * 批量删除文件\n *\n * @param keys - 文件键/路径列表\n * @returns 删除结果\n * @example\n * ```ts\n * await storage.file.deleteMany(['a.txt', 'b.txt'])\n * ```\n */\n deleteMany: (keys: string[]) => Promise<HaiResult<void>>\n\n /**\n * 复制文件\n *\n * @param sourceKey - 源文件键\n * @param destKey - 目标文件键\n * @param options - 复制选项\n * @returns 目标文件元数据\n * @example\n * ```ts\n * await storage.file.copy('a.txt', 'b.txt')\n * ```\n */\n copy: (sourceKey: string, destKey: string, options?: CopyOptions) => Promise<HaiResult<FileMetadata>>\n}\n\n/**\n * 目录操作接口\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 * await storage.dir.delete('uploads/tmp/')\n * ```\n */\n delete: (prefix: string) => Promise<HaiResult<void>>\n}\n\n/**\n * 签名 URL 操作接口\n *\n * 提供生成临时授权 URL 的功能,支持前端直接上传下载。\n */\nexport interface PresignOperations {\n /**\n * 生成下载签名 URL\n *\n * @param key - 文件键/路径\n * @param options - 签名选项\n * @returns 签名 URL\n * @example\n * ```ts\n * const result = await storage.presign.getUrl('a.txt', { expiresIn: 60 })\n * ```\n */\n getUrl: (key: string, options?: PresignOptions) => Promise<HaiResult<string>>\n\n /**\n * 生成上传签名 URL\n *\n * @param key - 文件键/路径\n * @param options - 上传签名选项\n * @returns 签名 URL\n * @example\n * ```ts\n * const result = await storage.presign.putUrl('a.txt', { contentType: 'text/plain' })\n * ```\n */\n putUrl: (key: string, options?: PresignUploadOptions) => Promise<HaiResult<string>>\n\n /**\n * 获取公开访问 URL(如果配置了 publicUrl)\n *\n * @param key - 文件键/路径\n * @returns 公开 URL,如果未配置 publicUrl 则返回 null\n * @example\n * ```ts\n * const url = storage.presign.publicUrl('a.txt')\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 */\nexport interface StorageFunctions {\n /**\n * 初始化存储连接\n *\n * 已有连接时会先 close 再重新初始化。\n *\n * @param config - 存储配置(支持省略带默认值的字段)\n * @returns 成功 ok(undefined);失败返回 err(含 StorageError)\n */\n init: (config: StorageConfigInput) => Promise<HaiResult<void>>\n\n /**\n * 关闭存储连接并释放资源\n *\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\n// ─── Provider 接口 ───\n\n/**\n * 存储 Provider 接口\n *\n * 由各具体存储实现(S3、Local)实现此接口。\n */\nexport interface StorageProvider {\n /** Provider 名称标识(如 's3'、'local') */\n readonly name: string\n\n /**\n * 建立与存储服务的连接\n *\n * @param config - 经 Schema 校验后的完整配置\n * @returns 连接结果\n */\n connect: (config: StorageConfig) => Promise<HaiResult<void>>\n\n /** 关闭连接并释放资源 */\n close: () => Promise<void>\n\n /** 检查是否已连接 */\n isConnected: () => boolean\n\n /** 文件操作接口 */\n readonly file: FileOperations\n\n /** 目录操作接口 */\n readonly dir: DirOperations\n\n /** 签名 URL 操作接口 */\n readonly presign: PresignOperations\n}\n"]}
package/dist/node.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { ClientDownloadOptions, ClientUploadOptions, UploadProgress, downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, uploadWithPresignedUrl } from './client/index.js';
2
2
  import { StorageFunctions } from './browser.js';
3
- export { CopyOptions, DirOperations, DownloadOptions, FileMetadata, FileOperations, HaiStorageError, ListOptions, ListResult, LocalConfig, LocalConfigSchema, PresignOperations, PresignOptions, PresignOptionsSchema, PresignUploadOptions, PresignUploadOptionsSchema, S3Config, S3ConfigSchema, StorageConfig, StorageConfigInput, StorageConfigSchema, StorageProvider, StorageType, StorageTypeSchema, UploadOptions } from './browser.js';
3
+ export { CopyOptions, DirOperations, DownloadOptions, FileMetadata, FileOperations, HaiStorageError, ListOptions, ListResult, LocalConfig, LocalConfigSchema, PresignOperations, PresignOptions, PresignOptionsSchema, PresignUploadOptions, PresignUploadOptionsSchema, S3Config, S3ConfigSchema, StorageConfig, StorageConfigInput, StorageConfigSchema, StorageOperationLogConfig, StorageOperationLogConfigSchema, StorageProvider, StorageType, StorageTypeSchema, UploadOptions } from './browser.js';
4
4
  import 'zod';
5
5
  import '@h-ai/core';
6
6
  import 'node:buffer';
package/dist/node.js CHANGED
@@ -1,5 +1,5 @@
1
- import { HaiStorageError, StorageConfigSchema } from './chunk-JNLYVWRV.js';
2
- export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageTypeSchema } from './chunk-JNLYVWRV.js';
1
+ import { HaiStorageError, StorageConfigSchema } from './chunk-4XD6ZH33.js';
2
+ export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema } from './chunk-4XD6ZH33.js';
3
3
  import { storageM, MIME_TYPES, MIME_TYPE_DEFAULT } from './chunk-U427BQIQ.js';
4
4
  export { downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, uploadWithPresignedUrl } from './chunk-U427BQIQ.js';
5
5
  import { core, ok, err } from '@h-ai/core';
@@ -11,6 +11,45 @@ import * as path from 'path';
11
11
  import { S3Client, ListObjectsV2Command, PutObjectCommand, GetObjectCommand, CopyObjectCommand, DeleteObjectsCommand, DeleteObjectCommand, HeadObjectCommand } from '@aws-sdk/client-s3';
12
12
  import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
13
13
 
14
+ // src/providers/storage-operation-log.ts
15
+ function normalizePayloadValue(value) {
16
+ if (value instanceof Uint8Array) {
17
+ return { byteLength: value.byteLength };
18
+ }
19
+ return value;
20
+ }
21
+ function stringifyOperationPayload(payload, maxLength) {
22
+ if (maxLength <= 0) {
23
+ return "";
24
+ }
25
+ try {
26
+ const text = JSON.stringify(payload, (_key, value) => {
27
+ if (typeof value === "bigint") {
28
+ return value.toString();
29
+ }
30
+ if (typeof value === "function") {
31
+ return "[Function]";
32
+ }
33
+ return normalizePayloadValue(value);
34
+ });
35
+ return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;
36
+ } catch {
37
+ const text = String(payload);
38
+ return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;
39
+ }
40
+ }
41
+ function logStorageOperation(logger4, config, category, operation, payload) {
42
+ if (!config?.[category]) {
43
+ return;
44
+ }
45
+ logger4[config.level]("Storage operation executed", {
46
+ category,
47
+ operation,
48
+ payload: stringifyOperationPayload(payload, config.maxLength)
49
+ });
50
+ }
51
+
52
+ // src/providers/storage-provider-local.ts
14
53
  var logger = core.logger.child({ module: "storage", scope: "provider-local" });
15
54
  var PATH_SEGMENT_SPLIT_REGEX = /[\\/]+/;
16
55
  function toStorageError(error, key) {
@@ -103,6 +142,9 @@ function createLocalProvider() {
103
142
  function fullPath(key) {
104
143
  return safePath(getConfig().root, key);
105
144
  }
145
+ function logOperation(category, operation, payload) {
146
+ logStorageOperation(logger, config?.operationLog, category, operation, payload);
147
+ }
106
148
  function toBuffer(data) {
107
149
  if (typeof data === "string") {
108
150
  return Buffer.from(data);
@@ -123,6 +165,7 @@ function createLocalProvider() {
123
165
  try {
124
166
  const filePath = fullPath(key);
125
167
  const buffer = toBuffer(data);
168
+ logOperation("write", "file.put", { key, byteLength: buffer.length, options });
126
169
  logger.debug("Putting file", { key, size: buffer.length });
127
170
  const dir2 = path.dirname(filePath);
128
171
  await fsp.mkdir(dir2, { recursive: true, mode: getConfig().directoryMode });
@@ -158,6 +201,7 @@ function createLocalProvider() {
158
201
  }
159
202
  try {
160
203
  const filePath = fullPath(key);
204
+ logOperation("read", "file.get", { key, options });
161
205
  if (options.rangeStart !== void 0 || options.rangeEnd !== void 0) {
162
206
  const stat2 = await fsp.stat(filePath);
163
207
  const start = options.rangeStart ?? 0;
@@ -185,6 +229,7 @@ function createLocalProvider() {
185
229
  }
186
230
  try {
187
231
  const filePath = fullPath(key);
232
+ logOperation("read", "file.head", { key });
188
233
  const stat2 = await fsp.stat(filePath);
189
234
  if (stat2.isDirectory()) {
190
235
  return err(
@@ -221,6 +266,7 @@ function createLocalProvider() {
221
266
  }
222
267
  try {
223
268
  const filePath = fullPath(key);
269
+ logOperation("read", "file.exists", { key });
224
270
  await fsp.access(filePath, fs.constants.F_OK);
225
271
  return ok(true);
226
272
  } catch (error) {
@@ -239,6 +285,7 @@ function createLocalProvider() {
239
285
  }
240
286
  try {
241
287
  const filePath = fullPath(key);
288
+ logOperation("write", "file.delete", { key });
242
289
  logger.debug("Deleting file", { key });
243
290
  await fsp.unlink(filePath);
244
291
  try {
@@ -255,6 +302,7 @@ function createLocalProvider() {
255
302
  }
256
303
  },
257
304
  async deleteMany(keys) {
305
+ logOperation("write", "file.deleteMany", { count: keys.length, keys });
258
306
  const results = await Promise.allSettled(keys.map((key) => file.delete(key)));
259
307
  const errors = [];
260
308
  for (const result of results) {
@@ -282,6 +330,7 @@ function createLocalProvider() {
282
330
  try {
283
331
  const sourcePath = fullPath(sourceKey);
284
332
  const destPath = fullPath(destKey);
333
+ logOperation("write", "file.copy", { sourceKey, destKey, options });
285
334
  logger.debug("Copying file", { sourceKey, destKey });
286
335
  await fsp.mkdir(path.dirname(destPath), { recursive: true, mode: getConfig().directoryMode });
287
336
  await fsp.copyFile(sourcePath, destPath);
@@ -314,6 +363,7 @@ function createLocalProvider() {
314
363
  const prefix = options.prefix || "";
315
364
  const delimiter = options.delimiter || "";
316
365
  const maxKeys = options.maxKeys || 1e3;
366
+ logOperation("read", "dir.list", { options });
317
367
  const basePath = safePath(cfg.root, prefix);
318
368
  const files = [];
319
369
  const commonPrefixes = /* @__PURE__ */ new Set();
@@ -361,6 +411,7 @@ function createLocalProvider() {
361
411
  async delete(prefix) {
362
412
  try {
363
413
  const dirPath = fullPath(prefix);
414
+ logOperation("write", "dir.delete", { prefix });
364
415
  await fsp.rm(dirPath, { recursive: true, force: true });
365
416
  return ok(void 0);
366
417
  } catch (error) {
@@ -373,18 +424,21 @@ function createLocalProvider() {
373
424
  };
374
425
  const presign = {
375
426
  async getUrl(key, options) {
427
+ logOperation("read", "presign.getUrl", { key, options });
376
428
  const expiresIn = options?.expiresIn || 3600;
377
429
  const expires = Math.floor(Date.now() / 1e3) + expiresIn;
378
430
  const signature = crypto.createHash("sha256").update(`${key}:${expires}`).digest("hex").slice(0, 16);
379
431
  return ok(`local://${key}?expires=${expires}&signature=${signature}`);
380
432
  },
381
433
  async putUrl(key, options) {
434
+ logOperation("write", "presign.putUrl", { key, options });
382
435
  const expiresIn = options?.expiresIn || 3600;
383
436
  const expires = Math.floor(Date.now() / 1e3) + expiresIn;
384
437
  const signature = crypto.createHash("sha256").update(`put:${key}:${expires}`).digest("hex").slice(0, 16);
385
438
  return ok(`local://${key}?action=put&expires=${expires}&signature=${signature}`);
386
439
  },
387
- publicUrl(_key) {
440
+ publicUrl(key) {
441
+ logOperation("read", "presign.publicUrl", { key });
388
442
  return null;
389
443
  }
390
444
  };
@@ -506,6 +560,9 @@ function createS3Provider() {
506
560
  function fullKey(key) {
507
561
  return withPrefix(key, getConfig().prefix);
508
562
  }
563
+ function logOperation(category, operation, payload) {
564
+ logStorageOperation(logger2, config?.operationLog, category, operation, payload);
565
+ }
509
566
  const file = {
510
567
  async put(key, data, options = {}) {
511
568
  try {
@@ -513,6 +570,7 @@ function createS3Provider() {
513
570
  const s3Config = getConfig();
514
571
  const fullPath = fullKey(key);
515
572
  const body = typeof data === "string" ? Buffer.from(data) : data;
573
+ logOperation("write", "file.put", { key, fullPath, byteLength: body.length, options });
516
574
  await s3Client.send(new PutObjectCommand({
517
575
  Bucket: s3Config.bucket,
518
576
  Key: fullPath,
@@ -541,6 +599,7 @@ function createS3Provider() {
541
599
  const s3Client = getClient();
542
600
  const s3Config = getConfig();
543
601
  const fullPath = fullKey(key);
602
+ logOperation("read", "file.get", { key, fullPath, options });
544
603
  let Range;
545
604
  if (options.rangeStart !== void 0 || options.rangeEnd !== void 0) {
546
605
  const start = options.rangeStart ?? 0;
@@ -573,6 +632,7 @@ function createS3Provider() {
573
632
  const s3Client = getClient();
574
633
  const s3Config = getConfig();
575
634
  const fullPath = fullKey(key);
635
+ logOperation("read", "file.head", { key, fullPath });
576
636
  const response = await s3Client.send(new HeadObjectCommand({
577
637
  Bucket: s3Config.bucket,
578
638
  Key: fullPath
@@ -590,6 +650,7 @@ function createS3Provider() {
590
650
  }
591
651
  },
592
652
  async exists(key) {
653
+ logOperation("read", "file.exists", { key });
593
654
  const result = await file.head(key);
594
655
  if (result.success) {
595
656
  return ok(true);
@@ -604,6 +665,7 @@ function createS3Provider() {
604
665
  const s3Client = getClient();
605
666
  const s3Config = getConfig();
606
667
  const fullPath = fullKey(key);
668
+ logOperation("write", "file.delete", { key, fullPath });
607
669
  await s3Client.send(new DeleteObjectCommand({
608
670
  Bucket: s3Config.bucket,
609
671
  Key: fullPath
@@ -620,6 +682,7 @@ function createS3Provider() {
620
682
  if (keys.length === 0) {
621
683
  return ok(void 0);
622
684
  }
685
+ logOperation("write", "file.deleteMany", { count: keys.length, keys });
623
686
  await s3Client.send(new DeleteObjectsCommand({
624
687
  Bucket: s3Config.bucket,
625
688
  Delete: {
@@ -637,6 +700,7 @@ function createS3Provider() {
637
700
  const s3Config = getConfig();
638
701
  const sourceFullPath = fullKey(sourceKey);
639
702
  const destFullPath = fullKey(destKey);
703
+ logOperation("write", "file.copy", { sourceKey, destKey, sourceFullPath, destFullPath, options });
640
704
  await s3Client.send(new CopyObjectCommand({
641
705
  Bucket: s3Config.bucket,
642
706
  CopySource: `${s3Config.bucket}/${sourceFullPath}`,
@@ -656,6 +720,7 @@ function createS3Provider() {
656
720
  try {
657
721
  const s3Client = getClient();
658
722
  const s3Config = getConfig();
723
+ logOperation("read", "dir.list", { options });
659
724
  let prefix = s3Config.prefix || "";
660
725
  if (options.prefix) {
661
726
  prefix = withPrefix(options.prefix, s3Config.prefix);
@@ -688,6 +753,7 @@ function createS3Provider() {
688
753
  },
689
754
  async delete(prefix) {
690
755
  try {
756
+ logOperation("write", "dir.delete", { prefix });
691
757
  let continuationToken;
692
758
  do {
693
759
  const listResult = await dir.list({
@@ -719,6 +785,7 @@ function createS3Provider() {
719
785
  const s3Client = getClient();
720
786
  const s3Config = getConfig();
721
787
  const fullPath = fullKey(key);
788
+ logOperation("read", "presign.getUrl", { key, fullPath, options });
722
789
  const command = new GetObjectCommand({
723
790
  Bucket: s3Config.bucket,
724
791
  Key: fullPath,
@@ -742,6 +809,7 @@ function createS3Provider() {
742
809
  const s3Client = getClient();
743
810
  const s3Config = getConfig();
744
811
  const fullPath = fullKey(key);
812
+ logOperation("write", "presign.putUrl", { key, fullPath, options });
745
813
  const command = new PutObjectCommand({
746
814
  Bucket: s3Config.bucket,
747
815
  Key: fullPath,
@@ -765,6 +833,7 @@ function createS3Provider() {
765
833
  return null;
766
834
  }
767
835
  const fullPath = fullKey(key);
836
+ logOperation("read", "presign.publicUrl", { key, fullPath });
768
837
  return `${s3Config.publicUrl.replace(TRAILING_SLASHES_REGEX, "")}/${fullPath}`;
769
838
  }
770
839
  };
package/dist/node.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/providers/storage-provider-local.ts","../src/providers/storage-provider-s3.ts","../src/storage-main.ts"],"names":["getMimeType","stat","dir","resolve","logger","core","toStorageError","Buffer","ok","err"],"mappings":"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","file":"node.js","sourcesContent":["/**\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, StorageProvider } from '../storage-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'\n\nimport * as path from 'node:path'\nimport { core, err, ok } from '@h-ai/core'\n\nimport { storageM } from '../storage-i18n.js'\nimport { MIME_TYPE_DEFAULT, MIME_TYPES } from '../storage-mime.js'\nimport {\n\n HaiStorageError,\n\n} from '../storage-types.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 === '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 return key.endsWith('.meta.json')\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 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 不支持 continuationToken 分页(仅支持 maxKeys 截断)\n * - presign 返回虚拟 `local://` URL,应用层需自行处理\n * - publicUrl 始终返回 null\n *\n * @returns Local Provider 实例\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 function fullPath(key: string): string {\n return safePath(getConfig().root, key)\n }\n\n /**\n * 将输入数据统一转为 Buffer\n *\n * @param data - 字符串、Buffer 或 Uint8Array\n * @returns Buffer 实例\n */\n function toBuffer(data: Buffer | Uint8Array | string): Buffer {\n if (typeof data === 'string') {\n return Buffer.from(data)\n }\n if (data instanceof Buffer) {\n return data\n }\n return Buffer.from(data)\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 try {\n const filePath = fullPath(key)\n const buffer = toBuffer(data)\n logger.debug('Putting file', { key, size: buffer.length })\n\n // 确保目录存在\n const dir = path.dirname(filePath)\n await fsp.mkdir(dir, { recursive: true, mode: getConfig().directoryMode })\n\n // 写入文件\n await fsp.writeFile(filePath, buffer, { mode: getConfig().fileMode })\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) {\n const metaPath = `${filePath}.meta.json`\n await fsp.writeFile(metaPath, JSON.stringify({\n contentType: options.contentType,\n metadata: options.metadata,\n }))\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 },\n\n async get(key, options = {}): Promise<HaiResult<Buffer>> {\n if (isMetaFile(key)) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_metaFileAccess', { params: { key } }),\n )\n }\n try {\n const filePath = fullPath(key)\n\n // 检查范围请求\n if (options.rangeStart !== undefined || options.rangeEnd !== undefined) {\n const stat = await fsp.stat(filePath)\n const start = options.rangeStart ?? 0\n const end = options.rangeEnd !== undefined ? options.rangeEnd : stat.size - 1\n\n // 使用流读取指定范围\n return new Promise((resolve) => {\n const chunks: Buffer[] = []\n const stream = fs.createReadStream(filePath, { start, end })\n\n stream.on('data', (chunk: Buffer) => chunks.push(chunk))\n stream.on('end', () => resolve(ok(Buffer.concat(chunks))))\n stream.on('error', error => resolve(err(toStorageError(error, key))))\n })\n }\n\n const data = await fsp.readFile(filePath)\n return ok(data)\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 = fullPath(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 = `${filePath}.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 = fullPath(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 = fullPath(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<HaiResult<void>> {\n // 并行删除,避免 await-in-loop(N+1)\n const results = await Promise.allSettled(keys.map(key => file.delete(key)))\n\n const errors: HaiError[] = []\n for (const result of results) {\n if (result.status === 'fulfilled' && !result.value.success) {\n errors.push(result.value.error)\n }\n }\n\n if (errors.length > 0) {\n return err(\n HaiStorageError.OPERATION_FAILED,\n storageM('storage_deleteManyFailed', { params: { count: errors.length } }),\n errors,\n )\n }\n\n return ok(undefined)\n },\n\n async copy(sourceKey, destKey, options = {}): Promise<HaiResult<FileMetadata>> {\n if (isMetaFile(sourceKey) || isMetaFile(destKey)) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_metaFileAccess', { params: { key: sourceKey } }),\n sourceKey,\n )\n }\n try {\n const sourcePath = fullPath(sourceKey)\n const destPath = fullPath(destKey)\n logger.debug('Copying file', { sourceKey, destKey })\n\n // 确保目标目录存在\n await fsp.mkdir(path.dirname(destPath), { recursive: true, mode: getConfig().directoryMode })\n\n // 复制文件\n await fsp.copyFile(sourcePath, destPath)\n\n // 获取新文件的元数据(无需读取文件内容,使用 stat 计算 ETag)\n const stat = await fsp.stat(destPath)\n\n const metadata: FileMetadata = {\n key: destKey,\n size: stat.size,\n contentType: options.contentType || getMimeType(destKey),\n lastModified: stat.mtime,\n etag: calculateEtag(stat),\n metadata: options.metadata,\n }\n\n // 存储自定义元数据\n if (options.contentType || options.metadata) {\n const metaPath = `${destPath}.meta.json`\n await fsp.writeFile(metaPath, JSON.stringify({\n contentType: options.contentType,\n metadata: options.metadata,\n }))\n }\n\n return ok(metadata)\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 cfg = getConfig()\n const prefix = options.prefix || ''\n const delimiter = options.delimiter || ''\n const maxKeys = options.maxKeys || 1000\n\n const basePath = safePath(cfg.root, prefix)\n const files: FileMetadata[] = []\n const commonPrefixes = new Set<string>()\n\n // 递归读取目录\n async function readDir(dirPath: string): Promise<void> {\n let entries: fs.Dirent[]\n try {\n entries = await fsp.readdir(dirPath, { withFileTypes: true })\n }\n catch {\n return // 目录不存在,返回空\n }\n\n for (const entry of entries) {\n if (files.length >= maxKeys)\n return\n\n const fullEntryPath = path.join(dirPath, entry.name)\n const key = path.relative(cfg.root, fullEntryPath).replace(/\\\\/g, '/')\n\n // 跳过元数据文件\n if (entry.name.endsWith('.meta.json'))\n continue\n\n if (entry.isDirectory()) {\n if (delimiter) {\n // 有分隔符时,目录作为公共前缀\n commonPrefixes.add(`${key}/`)\n }\n else {\n // 无分隔符时,递归遍历\n await readDir(fullEntryPath)\n }\n }\n else if (entry.isFile()) {\n const stat = await fsp.stat(fullEntryPath)\n files.push({\n key,\n size: stat.size,\n contentType: getMimeType(key),\n lastModified: stat.mtime,\n })\n }\n }\n }\n\n await readDir(basePath)\n\n return ok({\n files,\n commonPrefixes: Array.from(commonPrefixes),\n isTruncated: files.length >= maxKeys,\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 = fullPath(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 // 本地存储不支持真正的签名 URL\n // 返回一个带签名参数的虚拟 URL,应用层需要自行处理\n const expiresIn = options?.expiresIn || 3600\n const expires = Math.floor(Date.now() / 1000) + expiresIn\n const signature = crypto\n .createHash('sha256')\n .update(`${key}:${expires}`)\n .digest('hex')\n .slice(0, 16)\n\n return ok(`local://${key}?expires=${expires}&signature=${signature}`)\n },\n\n async putUrl(key, options?): Promise<HaiResult<string>> {\n // 同上,返回虚拟 URL\n const expiresIn = options?.expiresIn || 3600\n const expires = Math.floor(Date.now() / 1000) + expiresIn\n const signature = crypto\n .createHash('sha256')\n .update(`put:${key}:${expires}`)\n .digest('hex')\n .slice(0, 16)\n\n return ok(`local://${key}?action=put&expires=${expires}&signature=${signature}`)\n },\n\n publicUrl(_key): string | null {\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 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、阿里云 OSS 等兼容 S3 协议的存储服务。\n * @module storage-provider-s3\n */\n\nimport type { HaiError, HaiResult } from '@h-ai/core'\nimport type { S3Config, StorageConfig } from '../storage-config.js'\nimport type { DirOperations, FileMetadata, FileOperations, ListResult, PresignOperations, StorageProvider } from '../storage-types.js'\nimport { Buffer } from 'node:buffer'\nimport {\n CopyObjectCommand,\n DeleteObjectCommand,\n DeleteObjectsCommand,\n GetObjectCommand,\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 { storageM } from '../storage-i18n.js'\nimport {\n\n HaiStorageError,\n\n} from '../storage-types.js'\n\nconst logger = core.logger.child({ module: 'storage', scope: 'provider-s3' })\nconst TRAILING_SLASHES_REGEX = /\\/+$/\nconst LEADING_SLASHES_REGEX = /^\\/+/\n\n/**\n * 脱敏 S3 endpoint,避免日志暴露认证信息\n */\nfunction sanitizeEndpointUrl(url: string): string {\n try {\n const parsed = new URL(url)\n if (parsed.username)\n parsed.username = '***'\n if (parsed.password)\n parsed.password = '***'\n return parsed.toString()\n }\n catch {\n return '(invalid url)'\n }\n}\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 // 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 并通过 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 */\nexport function createS3Provider(): StorageProvider {\n let client: S3Client | null = null\n let config: S3Config | null = null\n\n // -------------------------------------------------------------------------\n // 内部辅助\n // -------------------------------------------------------------------------\n\n /** 获取当前 S3Client,未初始化时抛异常 */\n function getClient(): S3Client {\n if (!client) {\n throw new Error(storageM('storage_s3ClientNotInitialized'))\n }\n return client\n }\n\n /** 获取当前 S3 配置,未初始化时抛异常 */\n function getConfig(): S3Config {\n if (!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 // -------------------------------------------------------------------------\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 const body = typeof data === 'string' ? Buffer.from(data) : data\n\n await s3Client.send(new PutObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n Body: body,\n ContentType: options.contentType,\n Metadata: options.metadata,\n CacheControl: options.cacheControl,\n ContentDisposition: options.contentDisposition,\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: body.length,\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 try {\n const s3Client = getClient()\n const s3Config = getConfig()\n const fullPath = fullKey(key)\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 }))\n\n if (!response.Body) {\n return err(\n HaiStorageError.OPERATION_FAILED,\n storageM('storage_responseBodyEmpty'),\n )\n }\n\n // 将流转换为 Buffer\n const chunks: Uint8Array[] = []\n const stream = response.Body as AsyncIterable<Uint8Array>\n for await (const chunk of stream) {\n chunks.push(chunk)\n }\n\n return ok(Buffer.concat(chunks))\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\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 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\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<HaiResult<void>> {\n try {\n const s3Client = getClient()\n const s3Config = getConfig()\n\n if (keys.length === 0) {\n return ok(undefined)\n }\n\n await s3Client.send(new DeleteObjectsCommand({\n Bucket: s3Config.bucket,\n Delete: {\n Objects: keys.map(key => ({ Key: fullKey(key) })),\n },\n }))\n\n return ok(undefined)\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\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\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 // 分页列出并逐批删除,每批最多 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 s3Client = getClient()\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n\n const command = new GetObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n ResponseContentType: options?.responseContentType,\n ResponseContentDisposition: options?.responseContentDisposition,\n })\n\n const url = await getSignedUrl(s3Client, command, {\n expiresIn: options?.expiresIn || 3600,\n })\n\n return ok(url)\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 s3Client = getClient()\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n\n const command = new PutObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n ContentType: options?.contentType || 'application/octet-stream',\n })\n\n const url = await getSignedUrl(s3Client, command, {\n expiresIn: options?.expiresIn || 3600,\n })\n\n return ok(url)\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 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 config = cfg\n logger.info('Connecting S3 provider', {\n bucket: cfg.bucket,\n region: cfg.region,\n endpoint: cfg.endpoint ? sanitizeEndpointUrl(cfg.endpoint) : undefined,\n })\n\n client = new S3Client({\n region: cfg.region,\n endpoint: cfg.endpoint,\n forcePathStyle: cfg.forcePathStyle,\n credentials: {\n accessKeyId: cfg.accessKeyId,\n secretAccessKey: cfg.secretAccessKey,\n },\n })\n\n // 测试连接:尝试列出一个对象\n await client.send(new ListObjectsV2Command({\n Bucket: cfg.bucket,\n MaxKeys: 1,\n }))\n\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 config = null\n client = null\n return err(toStorageError(error))\n }\n },\n\n async close(): Promise<void> {\n if (client) {\n client.destroy()\n client = null\n config = null\n logger.info('S3 provider disconnected')\n }\n },\n\n isConnected(): boolean {\n return client !== null && config !== null\n },\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 { StorageConfig, StorageConfigInput } from './storage-config.js'\nimport type { DirOperations, FileOperations, PresignOperations, StorageFunctions, StorageProvider } from './storage-types.js'\n\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 { 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 工厂 ───\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()` 初始化,传入 S3 或本地存储配置。\n * 初始化后通过 `storage.file`、`storage.dir`、`storage.presign` 访问各操作接口。\n *\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * // 初始化\n * await storage.init({ type: 'local', root: '/data/uploads' })\n *\n * // 上传文件\n * await storage.file.put('hello.txt', 'Hello World')\n *\n * // 关闭连接\n * await storage.close()\n * ```\n */\nexport const storage: StorageFunctions = {\n /**\n * 初始化存储连接。\n *\n * 如果当前已有活跃连接,会先自动 close 再重新初始化。\n * 配置会通过 Zod Schema 校验;校验失败或连接异常会返回 `CONNECTION_FAILED`。\n *\n * @param config 存储配置(S3 或本地),支持省略带默认值的字段。\n * @returns 成功时返回 ok(undefined);失败时返回包含错误码和消息的 err。\n */\n async init(config: StorageConfigInput): Promise<HaiResult<void>> {\n // 并发初始化防护:避免多次 init 同时执行导致 Provider 泄漏\n if (initInProgress) {\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\n try {\n if (currentProvider) {\n logger.warn('Storage module is already initialized, reinitializing')\n const closeResult = await storage.close()\n if (!closeResult.success) {\n return closeResult\n }\n }\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\n const provider = createProvider(parsed)\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 connectResult\n }\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 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 }\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 },\n\n /**\n * 关闭存储连接并释放资源\n *\n * 关闭后 file/dir/presign 操作将返回 NOT_INITIALIZED 错误。\n * 重复调用不会报错。\n */\n async close(): Promise<HaiResult<void>> {\n if (!currentProvider) {\n currentConfig = null\n logger.info('Storage module already closed, skipping')\n return ok(undefined)\n }\n\n logger.info('Closing storage module')\n\n try {\n await currentProvider.close()\n logger.info('Storage module closed')\n return ok(undefined)\n }\n catch (error) {\n logger.error('Storage module close failed', { error })\n return 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 currentProvider = null\n currentConfig = null\n }\n },\n}\n"]}
1
+ {"version":3,"sources":["../src/providers/storage-operation-log.ts","../src/providers/storage-provider-local.ts","../src/providers/storage-provider-s3.ts","../src/storage-main.ts"],"names":["logger","getMimeType","stat","dir","resolve","core","toStorageError","Buffer","ok","err"],"mappings":"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","file":"node.js","sourcesContent":["import type { StorageOperationLogConfig } from '../storage-config.js'\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\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\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, StorageProvider } from '../storage-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'\n\nimport * as path from 'node:path'\nimport { core, err, ok } from '@h-ai/core'\n\nimport { storageM } from '../storage-i18n.js'\nimport { MIME_TYPE_DEFAULT, MIME_TYPES } from '../storage-mime.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 === '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 return key.endsWith('.meta.json')\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 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 不支持 continuationToken 分页(仅支持 maxKeys 截断)\n * - presign 返回虚拟 `local://` URL,应用层需自行处理\n * - publicUrl 始终返回 null\n *\n * @returns Local Provider 实例\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 function fullPath(key: string): string {\n return safePath(getConfig().root, key)\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 * 将输入数据统一转为 Buffer\n *\n * @param data - 字符串、Buffer 或 Uint8Array\n * @returns Buffer 实例\n */\n function toBuffer(data: Buffer | Uint8Array | string): Buffer {\n if (typeof data === 'string') {\n return Buffer.from(data)\n }\n if (data instanceof Buffer) {\n return data\n }\n return Buffer.from(data)\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 try {\n const filePath = fullPath(key)\n const buffer = toBuffer(data)\n logOperation('write', 'file.put', { key, byteLength: buffer.length, options })\n logger.debug('Putting file', { key, size: buffer.length })\n\n // 确保目录存在\n const dir = path.dirname(filePath)\n await fsp.mkdir(dir, { recursive: true, mode: getConfig().directoryMode })\n\n // 写入文件\n await fsp.writeFile(filePath, buffer, { mode: getConfig().fileMode })\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) {\n const metaPath = `${filePath}.meta.json`\n await fsp.writeFile(metaPath, JSON.stringify({\n contentType: options.contentType,\n metadata: options.metadata,\n }))\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 },\n\n async get(key, options = {}): Promise<HaiResult<Buffer>> {\n if (isMetaFile(key)) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_metaFileAccess', { params: { key } }),\n )\n }\n try {\n const filePath = fullPath(key)\n logOperation('read', 'file.get', { key, options })\n\n // 检查范围请求\n if (options.rangeStart !== undefined || options.rangeEnd !== undefined) {\n const stat = await fsp.stat(filePath)\n const start = options.rangeStart ?? 0\n const end = options.rangeEnd !== undefined ? options.rangeEnd : stat.size - 1\n\n // 使用流读取指定范围\n return new Promise((resolve) => {\n const chunks: Buffer[] = []\n const stream = fs.createReadStream(filePath, { start, end })\n\n stream.on('data', (chunk: Buffer) => chunks.push(chunk))\n stream.on('end', () => resolve(ok(Buffer.concat(chunks))))\n stream.on('error', error => resolve(err(toStorageError(error, key))))\n })\n }\n\n const data = await fsp.readFile(filePath)\n return ok(data)\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 = 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 = `${filePath}.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 = 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 = 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<HaiResult<void>> {\n logOperation('write', 'file.deleteMany', { count: keys.length, keys })\n // 并行删除,避免 await-in-loop(N+1)\n const results = await Promise.allSettled(keys.map(key => file.delete(key)))\n\n const errors: HaiError[] = []\n for (const result of results) {\n if (result.status === 'fulfilled' && !result.value.success) {\n errors.push(result.value.error)\n }\n }\n\n if (errors.length > 0) {\n return err(\n HaiStorageError.OPERATION_FAILED,\n storageM('storage_deleteManyFailed', { params: { count: errors.length } }),\n errors,\n )\n }\n\n return ok(undefined)\n },\n\n async copy(sourceKey, destKey, options = {}): Promise<HaiResult<FileMetadata>> {\n if (isMetaFile(sourceKey) || isMetaFile(destKey)) {\n return err(\n HaiStorageError.INVALID_PATH,\n storageM('storage_metaFileAccess', { params: { key: sourceKey } }),\n sourceKey,\n )\n }\n try {\n const sourcePath = fullPath(sourceKey)\n const destPath = fullPath(destKey)\n logOperation('write', 'file.copy', { sourceKey, destKey, options })\n logger.debug('Copying file', { sourceKey, destKey })\n\n // 确保目标目录存在\n await fsp.mkdir(path.dirname(destPath), { recursive: true, mode: getConfig().directoryMode })\n\n // 复制文件\n await fsp.copyFile(sourcePath, destPath)\n\n // 获取新文件的元数据(无需读取文件内容,使用 stat 计算 ETag)\n const stat = await fsp.stat(destPath)\n\n const metadata: FileMetadata = {\n key: destKey,\n size: stat.size,\n contentType: options.contentType || getMimeType(destKey),\n lastModified: stat.mtime,\n etag: calculateEtag(stat),\n metadata: options.metadata,\n }\n\n // 存储自定义元数据\n if (options.contentType || options.metadata) {\n const metaPath = `${destPath}.meta.json`\n await fsp.writeFile(metaPath, JSON.stringify({\n contentType: options.contentType,\n metadata: options.metadata,\n }))\n }\n\n return ok(metadata)\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 cfg = getConfig()\n const prefix = options.prefix || ''\n const delimiter = options.delimiter || ''\n const maxKeys = options.maxKeys || 1000\n logOperation('read', 'dir.list', { options })\n\n const basePath = safePath(cfg.root, prefix)\n const files: FileMetadata[] = []\n const commonPrefixes = new Set<string>()\n\n // 递归读取目录\n async function readDir(dirPath: string): Promise<void> {\n let entries: fs.Dirent[]\n try {\n entries = await fsp.readdir(dirPath, { withFileTypes: true })\n }\n catch {\n return // 目录不存在,返回空\n }\n\n for (const entry of entries) {\n if (files.length >= maxKeys)\n return\n\n const fullEntryPath = path.join(dirPath, entry.name)\n const key = path.relative(cfg.root, fullEntryPath).replace(/\\\\/g, '/')\n\n // 跳过元数据文件\n if (entry.name.endsWith('.meta.json'))\n continue\n\n if (entry.isDirectory()) {\n if (delimiter) {\n // 有分隔符时,目录作为公共前缀\n commonPrefixes.add(`${key}/`)\n }\n else {\n // 无分隔符时,递归遍历\n await readDir(fullEntryPath)\n }\n }\n else if (entry.isFile()) {\n const stat = await fsp.stat(fullEntryPath)\n files.push({\n key,\n size: stat.size,\n contentType: getMimeType(key),\n lastModified: stat.mtime,\n })\n }\n }\n }\n\n await readDir(basePath)\n\n return ok({\n files,\n commonPrefixes: Array.from(commonPrefixes),\n isTruncated: files.length >= maxKeys,\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 = 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 // 本地存储不支持真正的签名 URL\n // 返回一个带签名参数的虚拟 URL,应用层需要自行处理\n const expiresIn = options?.expiresIn || 3600\n const expires = Math.floor(Date.now() / 1000) + expiresIn\n const signature = crypto\n .createHash('sha256')\n .update(`${key}:${expires}`)\n .digest('hex')\n .slice(0, 16)\n\n return ok(`local://${key}?expires=${expires}&signature=${signature}`)\n },\n\n async putUrl(key, options?): Promise<HaiResult<string>> {\n // 同上,返回虚拟 URL\n logOperation('write', 'presign.putUrl', { key, options })\n const expiresIn = options?.expiresIn || 3600\n const expires = Math.floor(Date.now() / 1000) + expiresIn\n const signature = crypto\n .createHash('sha256')\n .update(`put:${key}:${expires}`)\n .digest('hex')\n .slice(0, 16)\n\n return ok(`local://${key}?action=put&expires=${expires}&signature=${signature}`)\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 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、阿里云 OSS 等兼容 S3 协议的存储服务。\n * @module storage-provider-s3\n */\n\nimport type { HaiError, HaiResult } from '@h-ai/core'\nimport type { S3Config, StorageConfig } from '../storage-config.js'\nimport type { DirOperations, FileMetadata, FileOperations, ListResult, PresignOperations, StorageProvider } from '../storage-types.js'\nimport { Buffer } from 'node:buffer'\nimport {\n CopyObjectCommand,\n DeleteObjectCommand,\n DeleteObjectsCommand,\n GetObjectCommand,\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 { storageM } from '../storage-i18n.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\n/**\n * 脱敏 S3 endpoint,避免日志暴露认证信息\n */\nfunction sanitizeEndpointUrl(url: string): string {\n try {\n const parsed = new URL(url)\n if (parsed.username)\n parsed.username = '***'\n if (parsed.password)\n parsed.password = '***'\n return parsed.toString()\n }\n catch {\n return '(invalid url)'\n }\n}\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 // 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 并通过 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 */\nexport function createS3Provider(): StorageProvider {\n let client: S3Client | null = null\n let config: S3Config | null = null\n\n // -------------------------------------------------------------------------\n // 内部辅助\n // -------------------------------------------------------------------------\n\n /** 获取当前 S3Client,未初始化时抛异常 */\n function getClient(): S3Client {\n if (!client) {\n throw new Error(storageM('storage_s3ClientNotInitialized'))\n }\n return client\n }\n\n /** 获取当前 S3 配置,未初始化时抛异常 */\n function getConfig(): S3Config {\n if (!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 const body = typeof data === 'string' ? Buffer.from(data) : data\n logOperation('write', 'file.put', { key, fullPath, byteLength: body.length, options })\n\n await s3Client.send(new PutObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n Body: body,\n ContentType: options.contentType,\n Metadata: options.metadata,\n CacheControl: options.cacheControl,\n ContentDisposition: options.contentDisposition,\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: body.length,\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 try {\n const s3Client = getClient()\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n logOperation('read', 'file.get', { 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 }))\n\n if (!response.Body) {\n return err(\n HaiStorageError.OPERATION_FAILED,\n storageM('storage_responseBodyEmpty'),\n )\n }\n\n // 将流转换为 Buffer\n const chunks: Uint8Array[] = []\n const stream = response.Body as AsyncIterable<Uint8Array>\n for await (const chunk of stream) {\n chunks.push(chunk)\n }\n\n return ok(Buffer.concat(chunks))\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<HaiResult<void>> {\n try {\n const s3Client = getClient()\n const s3Config = getConfig()\n\n if (keys.length === 0) {\n return ok(undefined)\n }\n\n logOperation('write', 'file.deleteMany', { count: keys.length, keys })\n\n await s3Client.send(new DeleteObjectsCommand({\n Bucket: s3Config.bucket,\n Delete: {\n Objects: keys.map(key => ({ Key: fullKey(key) })),\n },\n }))\n\n return ok(undefined)\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 s3Client = getClient()\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n logOperation('read', 'presign.getUrl', { key, fullPath, options })\n\n const command = new GetObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n ResponseContentType: options?.responseContentType,\n ResponseContentDisposition: options?.responseContentDisposition,\n })\n\n const url = await getSignedUrl(s3Client, command, {\n expiresIn: options?.expiresIn || 3600,\n })\n\n return ok(url)\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 s3Client = getClient()\n const s3Config = getConfig()\n const fullPath = fullKey(key)\n logOperation('write', 'presign.putUrl', { key, fullPath, options })\n\n const command = new PutObjectCommand({\n Bucket: s3Config.bucket,\n Key: fullPath,\n ContentType: options?.contentType || 'application/octet-stream',\n })\n\n const url = await getSignedUrl(s3Client, command, {\n expiresIn: options?.expiresIn || 3600,\n })\n\n return ok(url)\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 config = cfg\n logger.info('Connecting S3 provider', {\n bucket: cfg.bucket,\n region: cfg.region,\n endpoint: cfg.endpoint ? sanitizeEndpointUrl(cfg.endpoint) : undefined,\n })\n\n client = new S3Client({\n region: cfg.region,\n endpoint: cfg.endpoint,\n forcePathStyle: cfg.forcePathStyle,\n credentials: {\n accessKeyId: cfg.accessKeyId,\n secretAccessKey: cfg.secretAccessKey,\n },\n })\n\n // 测试连接:尝试列出一个对象\n await client.send(new ListObjectsV2Command({\n Bucket: cfg.bucket,\n MaxKeys: 1,\n }))\n\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 config = null\n client = null\n return err(toStorageError(error))\n }\n },\n\n async close(): Promise<void> {\n if (client) {\n client.destroy()\n client = null\n config = null\n logger.info('S3 provider disconnected')\n }\n },\n\n isConnected(): boolean {\n return client !== null && config !== null\n },\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 { StorageConfig, StorageConfigInput } from './storage-config.js'\nimport type { DirOperations, FileOperations, PresignOperations, StorageFunctions, StorageProvider } from './storage-types.js'\n\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 { 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 工厂 ───\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()` 初始化,传入 S3 或本地存储配置。\n * 初始化后通过 `storage.file`、`storage.dir`、`storage.presign` 访问各操作接口。\n *\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * // 初始化\n * await storage.init({ type: 'local', root: '/data/uploads' })\n *\n * // 上传文件\n * await storage.file.put('hello.txt', 'Hello World')\n *\n * // 关闭连接\n * await storage.close()\n * ```\n */\nexport const storage: StorageFunctions = {\n /**\n * 初始化存储连接。\n *\n * 如果当前已有活跃连接,会先自动 close 再重新初始化。\n * 配置会通过 Zod Schema 校验;校验失败或连接异常会返回 `CONNECTION_FAILED`。\n *\n * @param config 存储配置(S3 或本地),支持省略带默认值的字段。\n * @returns 成功时返回 ok(undefined);失败时返回包含错误码和消息的 err。\n */\n async init(config: StorageConfigInput): Promise<HaiResult<void>> {\n // 并发初始化防护:避免多次 init 同时执行导致 Provider 泄漏\n if (initInProgress) {\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\n try {\n if (currentProvider) {\n logger.warn('Storage module is already initialized, reinitializing')\n const closeResult = await storage.close()\n if (!closeResult.success) {\n return closeResult\n }\n }\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\n const provider = createProvider(parsed)\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 connectResult\n }\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 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 }\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 },\n\n /**\n * 关闭存储连接并释放资源\n *\n * 关闭后 file/dir/presign 操作将返回 NOT_INITIALIZED 错误。\n * 重复调用不会报错。\n */\n async close(): Promise<HaiResult<void>> {\n if (!currentProvider) {\n currentConfig = null\n logger.info('Storage module already closed, skipping')\n return ok(undefined)\n }\n\n logger.info('Closing storage module')\n\n try {\n await currentProvider.close()\n logger.info('Storage module closed')\n return ok(undefined)\n }\n catch (error) {\n logger.error('Storage module close failed', { error })\n return 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 currentProvider = null\n currentConfig = null\n }\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": "0.1.0-alpha.27",
4
+ "version": "0.1.0-alpha.29",
5
5
  "description": "Hai Framework object storage module for S3-compatible and local backends.",
6
6
  "license": "Apache-2.0",
7
7
  "exports": {
@@ -29,7 +29,7 @@
29
29
  "@aws-sdk/client-s3": "^3.723.0",
30
30
  "@aws-sdk/s3-request-presigner": "^3.723.0",
31
31
  "zod": "^4.4.3",
32
- "@h-ai/core": "0.1.0-alpha.27"
32
+ "@h-ai/core": "0.1.0-alpha.29"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^25.9.1",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/storage-config.ts","../src/storage-types.ts"],"names":[],"mappings":"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","file":"chunk-JNLYVWRV.js","sourcesContent":["/**\n * @h-ai/storage — 错误码 + 配置 Schema\n * @module storage-config\n */\n\nimport { z } from 'zod'\nimport { storageM } from './storage-i18n.js'\n\n// ─── 配置 Schema ───\n\n/**\n * 存储类型枚举\n *\n * 支持的存储类型:\n * - `s3` - S3 协议对象存储(兼容 AWS S3、MinIO、阿里云 OSS 等)\n * - `local` - 本地文件存储(仅后端)\n */\nexport const StorageTypeSchema = z.enum(['s3', 'local'])\n\n/** 存储类型 */\nexport type StorageType = z.infer<typeof StorageTypeSchema>\n\n/**\n * S3 存储配置 Schema\n *\n * 支持标准 AWS S3 以及兼容 S3 协议的存储服务(MinIO、阿里云 OSS 等)。\n * 必填字段:bucket、region、accessKeyId、secretAccessKey。\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 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、阿里云 OSS 等) */\n endpoint: z.string().url().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\n/** S3 配置类型(parse 后,所有字段已填充默认值) */\nexport type S3Config = z.infer<typeof S3ConfigSchema>\n\n/**\n * 本地存储配置 Schema\n *\n * 基于本地文件系统,仅后端可用。必填字段:root。\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\n/** 本地配置类型(parse 后,所有字段已填充默认值) */\nexport type LocalConfig = z.infer<typeof LocalConfigSchema>\n\n/**\n * 统一存储配置 Schema\n *\n * 支持 S3 和本地文件两种类型的配置验证。\n *\n * @example\n * ```ts\n * // S3 配置\n * const s3Config = StorageConfigSchema.parse({\n * type: 's3',\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * accessKeyId: 'AKIAXXXXXXXX',\n * secretAccessKey: 'xxxxx'\n * })\n *\n * // MinIO 配置\n * const minioConfig = StorageConfigSchema.parse({\n * type: 's3',\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * endpoint: 'http://localhost:9000',\n * accessKeyId: 'minioadmin',\n * secretAccessKey: 'minioadmin',\n * forcePathStyle: true\n * })\n *\n * // 本地存储配置\n * const localConfig = StorageConfigSchema.parse({\n * type: 'local',\n * root: '/data/uploads'\n * })\n * ```\n */\nexport const StorageConfigSchema = z.discriminatedUnion('type', [\n S3ConfigSchema,\n LocalConfigSchema,\n])\n\n/** 存储配置类型(parse 后的完整配置) */\nexport type StorageConfig = z.infer<typeof StorageConfigSchema>\n\n/** 存储配置输入类型(parse 前,允许省略带默认值的字段) */\nexport type StorageConfigInput = z.input<typeof StorageConfigSchema>\n\n// ─── 签名 URL 配置 ───\n\n/**\n * 签名 URL 配置 Schema\n *\n * 用于控制 `storage.presign.getUrl()` 生成的下载签名 URL 参数。\n *\n * @example\n * ```ts\n * const url = await storage.presign.getUrl('report.pdf', {\n * expiresIn: 600, // 10 分钟过期\n * responseContentDisposition: 'attachment; filename=\"report.pdf\"',\n * })\n * ```\n */\nexport const PresignOptionsSchema = z.object({\n /** 过期时间(秒),范围 1~604800(7 天),默认 3600(1 小时) */\n expiresIn: z.number().min(1).max(604800).default(3600), // 最长 7 天\n\n /** 响应内容类型(用于下载时设置 Content-Type) */\n responseContentType: z.string().optional(),\n\n /** 响应内容处置(用于设置下载文件名) */\n responseContentDisposition: z.string().optional(),\n})\n\n/** 签名 URL 配置类型(parse 后) */\nexport type PresignOptions = z.infer<typeof PresignOptionsSchema>\n\n/**\n * 上传签名 URL 配置 Schema\n *\n * 继承 PresignOptionsSchema 并扩展上传专属参数,用于 `storage.presign.putUrl()`。\n *\n * @example\n * ```ts\n * const url = await storage.presign.putUrl('uploads/avatar.png', {\n * contentType: 'image/png',\n * expiresIn: 300,\n * })\n * ```\n */\nexport const PresignUploadOptionsSchema = PresignOptionsSchema.extend({\n /** 上传内容类型(必须与实际上传匹配) */\n contentType: z.string().default('application/octet-stream'),\n})\n\n/** 上传签名 URL 配置类型(parse 后) */\nexport type PresignUploadOptions = z.infer<typeof PresignUploadOptionsSchema>\n","/**\n * @h-ai/storage — 公共类型定义\n *\n * 包含:\n * - 错误类型(StorageError)\n * - 文件元数据(FileMetadata)\n * - 列表结果(ListResult)\n * - 文件操作接口(FileOperations)\n * - 目录操作接口(DirOperations)\n * - 签名 URL 操作接口(PresignOperations)\n * - 函数接口(StorageFunctions)\n * - Provider 接口(StorageProvider)\n * @module storage-types\n */\n\nimport type { ErrorInfo, 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} as const satisfies ErrorInfo\n\nexport const HaiStorageError = core.error.buildHaiErrorsDef('storage', StorageErrorInfo)\n\n// ─── 文件元数据 ───\n\n/**\n * 文件元数据接口\n *\n * 表示存储中单个文件的元信息,由 put/head/copy/list 等操作返回。\n */\nexport interface FileMetadata {\n /** 文件键/路径(不含配置 prefix,对用户透明) */\n key: string\n /** 文件大小(字节) */\n size: number\n /** 内容类型(MIME),未指定时根据扩展名自动推断 */\n contentType: string\n /** 最后修改时间 */\n lastModified: Date\n /** ETag(用于缓存验证,本地存储为 MD5,S3 由服务端生成) */\n etag?: string\n /** 自定义元数据键值对 */\n metadata?: Record<string, string>\n}\n\n/**\n * 列表选项\n *\n * 用于控制 `storage.dir.list()` 的过滤、分页和目录模拟行为。\n */\nexport interface ListOptions {\n /** 前缀过滤(只返回以此前缀开头的文件) */\n prefix?: string\n /**\n * 分页标记(从上次 ListResult.nextContinuationToken 获取)\n *\n * 注意:本地存储目前不支持此字段,仅 S3 Provider 有效。\n */\n continuationToken?: string\n /** 每页最大数量(默认 1000) */\n maxKeys?: number\n /** 分隔符(用于模拟目录结构,通常为 '/';传入后子目录作为 commonPrefixes 返回) */\n delimiter?: string\n}\n\n/**\n * 列表结果\n *\n * 包含当前页的文件列表、目录前缀、分页信息。\n */\nexport interface ListResult {\n /** 当前页文件元数据列表 */\n files: FileMetadata[]\n /** 公共前缀(使用 delimiter 时由子目录路径组成) */\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 */\nexport interface UploadOptions {\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 */\nexport interface DownloadOptions {\n /** 范围起始位置(字节,含);不指定时从文件开头读取 */\n rangeStart?: number\n /** 范围结束位置(字节,含);不指定时读取到文件末尾 */\n rangeEnd?: number\n}\n\n/**\n * 复制选项\n *\n * 用于控制 `storage.file.copy()` 的目标文件元数据。\n * 提供任意字段时,S3 Provider 会使用 MetadataDirective='REPLACE' 覆盖源文件元数据。\n */\nexport interface CopyOptions {\n /** 目标文件的内容类型(不指定时继承源文件) */\n contentType?: string\n /** 目标文件的自定义元数据(不指定时继承源文件) */\n metadata?: Record<string, string>\n}\n\n// ─── 操作接口 ───\n\n/**\n * 文件操作接口\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: Buffer | Uint8Array | string, 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 * }\n * ```\n */\n get: (key: string, options?: DownloadOptions) => Promise<HaiResult<Buffer>>\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 * await storage.file.delete('uploads/a.txt')\n * ```\n */\n delete: (key: string) => Promise<HaiResult<void>>\n\n /**\n * 批量删除文件\n *\n * @param keys - 文件键/路径列表\n * @returns 删除结果\n * @example\n * ```ts\n * await storage.file.deleteMany(['a.txt', 'b.txt'])\n * ```\n */\n deleteMany: (keys: string[]) => Promise<HaiResult<void>>\n\n /**\n * 复制文件\n *\n * @param sourceKey - 源文件键\n * @param destKey - 目标文件键\n * @param options - 复制选项\n * @returns 目标文件元数据\n * @example\n * ```ts\n * await storage.file.copy('a.txt', 'b.txt')\n * ```\n */\n copy: (sourceKey: string, destKey: string, options?: CopyOptions) => Promise<HaiResult<FileMetadata>>\n}\n\n/**\n * 目录操作接口\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 * await storage.dir.delete('uploads/tmp/')\n * ```\n */\n delete: (prefix: string) => Promise<HaiResult<void>>\n}\n\n/**\n * 签名 URL 操作接口\n *\n * 提供生成临时授权 URL 的功能,支持前端直接上传下载。\n */\nexport interface PresignOperations {\n /**\n * 生成下载签名 URL\n *\n * @param key - 文件键/路径\n * @param options - 签名选项\n * @returns 签名 URL\n * @example\n * ```ts\n * const result = await storage.presign.getUrl('a.txt', { expiresIn: 60 })\n * ```\n */\n getUrl: (key: string, options?: PresignOptions) => Promise<HaiResult<string>>\n\n /**\n * 生成上传签名 URL\n *\n * @param key - 文件键/路径\n * @param options - 上传签名选项\n * @returns 签名 URL\n * @example\n * ```ts\n * const result = await storage.presign.putUrl('a.txt', { contentType: 'text/plain' })\n * ```\n */\n putUrl: (key: string, options?: PresignUploadOptions) => Promise<HaiResult<string>>\n\n /**\n * 获取公开访问 URL(如果配置了 publicUrl)\n *\n * @param key - 文件键/路径\n * @returns 公开 URL,如果未配置 publicUrl 则返回 null\n * @example\n * ```ts\n * const url = storage.presign.publicUrl('a.txt')\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 */\nexport interface StorageFunctions {\n /**\n * 初始化存储连接\n *\n * 已有连接时会先 close 再重新初始化。\n *\n * @param config - 存储配置(支持省略带默认值的字段)\n * @returns 成功 ok(undefined);失败返回 err(含 StorageError)\n */\n init: (config: StorageConfigInput) => Promise<HaiResult<void>>\n\n /**\n * 关闭存储连接并释放资源\n *\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\n// ─── Provider 接口 ───\n\n/**\n * 存储 Provider 接口\n *\n * 由各具体存储实现(S3、Local)实现此接口。\n */\nexport interface StorageProvider {\n /** Provider 名称标识(如 's3'、'local') */\n readonly name: string\n\n /**\n * 建立与存储服务的连接\n *\n * @param config - 经 Schema 校验后的完整配置\n * @returns 连接结果\n */\n connect: (config: StorageConfig) => Promise<HaiResult<void>>\n\n /** 关闭连接并释放资源 */\n close: () => Promise<void>\n\n /** 检查是否已连接 */\n isConnected: () => boolean\n\n /** 文件操作接口 */\n readonly file: FileOperations\n\n /** 目录操作接口 */\n readonly dir: DirOperations\n\n /** 签名 URL 操作接口 */\n readonly presign: PresignOperations\n}\n"]}