@h-ai/storage 0.1.0-alpha.67 → 0.1.0-alpha.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +85 -14
- package/README.ja.md +85 -14
- package/README.md +125 -14
- package/dist/browser.d.ts +399 -93
- package/dist/browser.js +3 -2
- package/dist/{chunk-KXE6OO5T.js → chunk-C7FR45VO.js} +8 -5
- package/dist/chunk-C7FR45VO.js.map +1 -0
- package/dist/chunk-I4BSES63.js +357 -0
- package/dist/chunk-I4BSES63.js.map +1 -0
- package/dist/chunk-P65WYXJG.js +33 -0
- package/dist/chunk-P65WYXJG.js.map +1 -0
- package/dist/client/index.d.ts +57 -48
- package/dist/client/index.js +1 -1
- package/dist/node.d.ts +15 -11
- package/dist/node.js +296 -136
- package/dist/node.js.map +1 -1
- package/dist/schemas.d.ts +103 -0
- package/dist/schemas.js +3 -0
- package/dist/schemas.js.map +1 -0
- package/package.json +7 -2
- package/dist/chunk-25GYTRA7.js +0 -274
- package/dist/chunk-25GYTRA7.js.map +0 -1
- package/dist/chunk-KXE6OO5T.js.map +0 -1
package/dist/browser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema } from './chunk-
|
|
2
|
-
export { downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, uploadWithPresignedUrl } from './chunk-
|
|
1
|
+
export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema } from './chunk-C7FR45VO.js';
|
|
2
|
+
export { downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, uploadWithPresignedUrl } from './chunk-I4BSES63.js';
|
|
3
|
+
export { STORAGE_DELETE_MAX_KEYS, STORAGE_KEY_MAX_BYTES, StorageDeleteKeysSchema, StorageKeySchema, StoragePrefixSchema, StoragePresignExpiresInSchema, getStorageKeyIssue } from './chunk-P65WYXJG.js';
|
|
3
4
|
//# sourceMappingURL=browser.js.map
|
|
4
5
|
//# sourceMappingURL=browser.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { storageM } from './chunk-
|
|
1
|
+
import { storageM } from './chunk-I4BSES63.js';
|
|
2
|
+
import { StoragePresignExpiresInSchema } from './chunk-P65WYXJG.js';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { core } from '@h-ai/core';
|
|
4
5
|
|
|
@@ -50,7 +51,7 @@ var StorageConfigSchema = z.discriminatedUnion("type", [
|
|
|
50
51
|
]);
|
|
51
52
|
var PresignOptionsSchema = z.object({
|
|
52
53
|
/** 过期时间(秒),范围 1~604800(7 天),默认 3600(1 小时) */
|
|
53
|
-
expiresIn:
|
|
54
|
+
expiresIn: StoragePresignExpiresInSchema.default(3600),
|
|
54
55
|
// 最长 7 天
|
|
55
56
|
/** 响应内容类型(用于下载时设置 Content-Type) */
|
|
56
57
|
responseContentType: z.string().optional(),
|
|
@@ -78,10 +79,12 @@ var StorageErrorInfo = {
|
|
|
78
79
|
UPLOAD_FAILED: "014:500",
|
|
79
80
|
DOWNLOAD_FAILED: "015:500",
|
|
80
81
|
CHECKSUM_FAILED: "016:500",
|
|
81
|
-
INVALID_CHECKSUM: "017:400"
|
|
82
|
+
INVALID_CHECKSUM: "017:400",
|
|
83
|
+
UNSUPPORTED_OPERATION: "018:400",
|
|
84
|
+
INVALID_LIST_OPTIONS: "019:400"
|
|
82
85
|
};
|
|
83
86
|
var HaiStorageError = core.error.buildHaiErrorsDef("storage", StorageErrorInfo);
|
|
84
87
|
|
|
85
88
|
export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema };
|
|
86
|
-
//# sourceMappingURL=chunk-
|
|
87
|
-
//# sourceMappingURL=chunk-
|
|
89
|
+
//# sourceMappingURL=chunk-C7FR45VO.js.map
|
|
90
|
+
//# sourceMappingURL=chunk-C7FR45VO.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-C7FR45VO.js","sourcesContent":["/**\n * @h-ai/storage — 存储配置 Schema\n * @module storage-config\n */\n\nimport { z } from 'zod'\nimport { storageM } from './storage-i18n.js'\nimport { StoragePresignExpiresInSchema } from './storage-schemas.js'\n\n// ─── 配置 Schema ───\n\n/**\n * 存储类型枚举\n *\n * 支持的存储类型:\n * - `s3` - S3 协议对象存储(兼容 AWS S3、MinIO、RustFS、阿里云 OSS 等)\n * - `local` - 本地文件存储(仅后端)\n * @example\n * ```ts\n * import { StorageTypeSchema } from '@h-ai/storage'\n *\n * const value = StorageTypeSchema.parse('local')\n * void value\n * ```\n */\nexport const StorageTypeSchema = z.enum(['s3', 'local'])\n\n/**\n * 存储类型\n * @example\n * ```ts\n * import type { StorageType } from '@h-ai/storage'\n *\n * const value: StorageType = 's3'\n * void value\n * ```\n */\nexport type StorageType = z.infer<typeof StorageTypeSchema>\n\n/**\n * StorageOperationLogConfigSchema 的数据约束与操作契约。\n * @example\n * ```ts\n * import { StorageOperationLogConfigSchema } from '@h-ai/storage'\n *\n * const value = StorageOperationLogConfigSchema.parse({ read: false, write: true, maxLength: 500, level: 'debug' })\n * void value\n * ```\n */\nexport const StorageOperationLogConfigSchema = z.object({\n read: z.boolean().default(false),\n write: z.boolean().default(false),\n maxLength: z.number().int().min(0).default(1000),\n level: z.enum(['info', 'debug', 'trace']).default('debug'),\n})\n\n/**\n * StorageOperationLogConfig 的数据约束与操作契约。\n * @example\n * ```ts\n * import { StorageOperationLogConfigSchema, type StorageOperationLogConfig } from '@h-ai/storage'\n *\n * const value: StorageOperationLogConfig = StorageOperationLogConfigSchema.parse({ read: false, write: true, maxLength: 500, level: 'debug' })\n * void value\n * ```\n */\nexport type StorageOperationLogConfig = z.infer<typeof StorageOperationLogConfigSchema>\n\nconst OperationLogConfigFieldSchema = {\n operationLog: StorageOperationLogConfigSchema.optional(),\n}\n\n/**\n * S3 存储配置 Schema\n *\n * 支持标准 AWS S3 以及兼容 S3 协议的存储服务(MinIO、RustFS、阿里云 OSS 等)。\n * 必填字段:bucket、region、accessKeyId、secretAccessKey。\n * @example\n * ```ts\n * import { S3ConfigSchema } from '@h-ai/storage'\n *\n * const value = S3ConfigSchema.parse({ type: 's3', bucket: 'uploads', region: 'us-east-1', accessKeyId: process.env.HAI_STORAGE_ACCESS_KEY_ID, secretAccessKey: process.env.HAI_STORAGE_SECRET_ACCESS_KEY })\n * void value\n * ```\n */\nexport const S3ConfigSchema = z.object({\n /** 存储类型 */\n type: z.literal('s3'),\n\n /** 存储桶名称 */\n bucket: z.string().min(1, storageM('storage_config_bucketRequired')),\n\n /** 区域 */\n region: z.string().min(1, storageM('storage_config_regionRequired')),\n\n /** Access Key ID */\n accessKeyId: z.string().min(1, storageM('storage_config_accessKeyIdRequired')),\n\n /** Secret Access Key */\n secretAccessKey: z.string().min(1, storageM('storage_config_secretAccessKeyRequired')),\n\n /** 自定义端点(用于 MinIO、RustFS、阿里云 OSS 等) */\n endpoint: z.string().url().optional(),\n\n /** 强制使用路径风格 URL(某些 S3 兼容服务需要) */\n forcePathStyle: z.boolean().default(false),\n\n /** 路径前缀(所有操作都会加上此前缀) */\n prefix: z.string().default(''),\n\n /** 公开访问基础 URL(用于生成公开 URL) */\n publicUrl: z.string().url().optional(),\n\n ...OperationLogConfigFieldSchema,\n})\n\n/**\n * S3 配置类型(parse 后,带默认值的字段已填充,optional 字段仍可缺省)\n * @example\n * ```ts\n * import { S3ConfigSchema, type S3Config } from '@h-ai/storage'\n *\n * const value: S3Config = S3ConfigSchema.parse({ type: 's3', bucket: 'uploads', region: 'us-east-1', accessKeyId: process.env.HAI_STORAGE_ACCESS_KEY_ID, secretAccessKey: process.env.HAI_STORAGE_SECRET_ACCESS_KEY })\n * void value\n * ```\n */\nexport type S3Config = z.infer<typeof S3ConfigSchema>\n\n/**\n * 本地存储配置 Schema\n *\n * 基于本地文件系统,仅后端可用。必填字段:root。\n * @example\n * ```ts\n * import { LocalConfigSchema } from '@h-ai/storage'\n *\n * const value = LocalConfigSchema.parse({ type: 'local', root: './data/uploads' })\n * void value\n * ```\n */\nexport const LocalConfigSchema = z.object({\n /** 存储类型 */\n type: z.literal('local'),\n\n /** 根目录路径 */\n root: z.string().min(1, storageM('storage_config_rootRequired')),\n\n /** 目录创建权限 */\n directoryMode: z.number().default(0o755),\n\n /** 文件创建权限 */\n fileMode: z.number().default(0o644),\n\n ...OperationLogConfigFieldSchema,\n})\n\n/**\n * 本地配置类型(parse 后,带默认值的字段已填充,optional 字段仍可缺省)\n * @example\n * ```ts\n * import { LocalConfigSchema, type LocalConfig } from '@h-ai/storage'\n *\n * const value: LocalConfig = LocalConfigSchema.parse({ type: 'local', root: './data/uploads' })\n * void value\n * ```\n */\nexport type LocalConfig = z.infer<typeof LocalConfigSchema>\n\n/**\n * 统一存储配置 Schema\n *\n * 支持 S3 和本地文件两种类型的配置验证。\n * @example\n * ```ts\n * import { StorageConfigSchema } from '@h-ai/storage'\n *\n * const value = StorageConfigSchema.parse({ type: 'local', root: './data/uploads' })\n * void value\n * ```\n */\nexport const StorageConfigSchema = z.discriminatedUnion('type', [\n S3ConfigSchema,\n LocalConfigSchema,\n])\n\n/**\n * 存储配置类型(parse 后的完整配置)\n * @example\n * ```ts\n * import { StorageConfigSchema, type StorageConfig } from '@h-ai/storage'\n *\n * const value: StorageConfig = StorageConfigSchema.parse({ type: 'local', root: './data/uploads' })\n * void value\n * ```\n */\nexport type StorageConfig = z.infer<typeof StorageConfigSchema>\n\n/**\n * 存储配置输入类型(parse 前,允许省略带默认值的字段)\n * @example\n * ```ts\n * import type { StorageConfigInput } from '@h-ai/storage'\n *\n * const value: StorageConfigInput = { type: 'local', root: './data/uploads' }\n * void value\n * ```\n */\nexport type StorageConfigInput = z.input<typeof StorageConfigSchema>\n\n// ─── 签名 URL 配置 ───\n\n/**\n * 签名 URL 配置 Schema\n *\n * 用于控制 `storage.presign.getUrl()` 生成的下载签名 URL 参数。\n * @example\n * ```ts\n * import { PresignOptionsSchema } from '@h-ai/storage'\n *\n * const value = PresignOptionsSchema.parse({ expiresIn: 60 })\n * void value\n * ```\n */\nexport const PresignOptionsSchema = z.object({\n /** 过期时间(秒),范围 1~604800(7 天),默认 3600(1 小时) */\n expiresIn: StoragePresignExpiresInSchema.default(3600), // 最长 7 天\n\n /** 响应内容类型(用于下载时设置 Content-Type) */\n responseContentType: z.string().optional(),\n\n /** 响应内容处置(用于设置下载文件名) */\n responseContentDisposition: z.string().optional(),\n})\n\n/**\n * 签名 URL 输入选项,允许省略带默认值的字段。\n * @example\n * ```ts\n * import type { PresignOptions } from '@h-ai/storage'\n *\n * const value: PresignOptions = { expiresIn: 60, responseContentType: 'text/plain' }\n * void value\n * ```\n */\nexport type PresignOptions = z.input<typeof PresignOptionsSchema>\n\n/**\n * 上传签名 URL 配置 Schema\n *\n * 继承 PresignOptionsSchema 并扩展上传专属参数,用于 `storage.presign.putUrl()`。\n * @example\n * ```ts\n * import { PresignUploadOptionsSchema } from '@h-ai/storage'\n *\n * const value = PresignUploadOptionsSchema.parse({ contentType: 'text/plain' })\n * void value\n * ```\n */\nexport const PresignUploadOptionsSchema = PresignOptionsSchema.extend({\n /** 上传内容类型(必须与实际上传匹配) */\n contentType: z.string().default('application/octet-stream'),\n})\n\n/**\n * 上传签名 URL 输入选项,省略 contentType 时使用 application/octet-stream。\n * @example\n * ```ts\n * import type { PresignUploadOptions } from '@h-ai/storage'\n *\n * const value: PresignUploadOptions = { expiresIn: 60, contentType: 'text/plain' }\n * void value\n * ```\n */\nexport type PresignUploadOptions = z.input<typeof PresignUploadOptionsSchema>\n","/**\n * @h-ai/storage — 公共类型定义\n *\n * 包含:\n * - 错误定义(HaiStorageError)\n * - 文件元数据(FileMetadata)\n * - 列表结果(ListResult)\n * - 文件操作接口(FileOperations)\n * - 目录操作接口(DirOperations)\n * - 签名 URL 操作接口(PresignOperations)\n * - 函数接口(StorageFunctions)\n * @module storage-types\n */\n\nimport type { ErrorInfo, HaiError, HaiResult } from '@h-ai/core'\nimport type { Buffer } from 'node:buffer'\nimport type { PresignOptions, PresignUploadOptions, StorageConfig, StorageConfigInput } from './storage-config.js'\nimport { core } from '@h-ai/core'\n\n// ─── 错误定义(照 @h-ai/core 范式) ───\n\nconst StorageErrorInfo = {\n CONNECTION_FAILED: '001:500',\n OPERATION_FAILED: '002:500',\n NOT_FOUND: '003:404',\n ALREADY_EXISTS: '004:409',\n PERMISSION_DENIED: '005:403',\n QUOTA_EXCEEDED: '006:413',\n INVALID_PATH: '007:400',\n IO_ERROR: '008:500',\n NETWORK_ERROR: '009:502',\n NOT_INITIALIZED: '010:500',\n UNSUPPORTED_TYPE: '011:400',\n CONFIG_ERROR: '012:500',\n PRESIGN_FAILED: '013:500',\n UPLOAD_FAILED: '014:500',\n DOWNLOAD_FAILED: '015:500',\n CHECKSUM_FAILED: '016:500',\n INVALID_CHECKSUM: '017:400',\n UNSUPPORTED_OPERATION: '018:400',\n INVALID_LIST_OPTIONS: '019:400',\n} as const satisfies ErrorInfo\n\n/**\n * HaiStorageError 的数据约束与操作契约。\n * @example\n * ```ts\n * import { HaiStorageError } from '@h-ai/storage'\n * import { err } from '@h-ai/core'\n *\n * const result = err(HaiStorageError.NOT_FOUND, 'File missing')\n * if (!result.success) void result.error.code\n * ```\n */\nexport const HaiStorageError = core.error.buildHaiErrorsDef('storage', StorageErrorInfo)\n\n/**\n * 批量删除中未确认完成的一项;key 始终使用调用方视角,不含 Provider prefix。\n * @example\n * ```ts\n * import type { StorageDeleteFailure } from '@h-ai/storage'\n *\n * const value: StorageDeleteFailure = { key: 'private.txt', code: 'AccessDenied', retryable: false, outcome: 'failed' }\n * void value\n * ```\n */\nexport interface StorageDeleteFailure {\n /** 调用方提交的文件键。 */\n key: string\n /** 渠道/框架错误标识,不包含渠道原始 Message。 */\n code: string\n /** 是否属于可重试的瞬时故障;未知结果重试前仍需考虑并发上传/版本语义。 */\n retryable: boolean\n /** failed 为已知失败,unknown 为已发送但结果未知,notAttempted 为尚未发送。 */\n outcome: 'failed' | 'unknown' | 'notAttempted'\n}\n\n/**\n * 批量删除进度;任一失败不能宣称全部删除完成。\n * @example\n * ```ts\n * import type { StorageDeleteSummary } from '@h-ai/storage'\n *\n * const value: StorageDeleteSummary = { deletedKeys: ['a.txt'], failures: [] }\n * void value\n * ```\n */\nexport interface StorageDeleteSummary {\n /** 已确认删除或本来就不存在的唯一文件键。 */\n deletedKeys: string[]\n /** 失败、未知及尚未尝试的唯一文件键。 */\n failures: StorageDeleteFailure[]\n}\n\n/**\n * 批量删除结果;失败仍遵守 HaiResult,部分进度置于 error.ext.deleteMany。\n * @example\n * ```ts\n * import { storage, type StorageDeleteResult } from '@h-ai/storage'\n *\n * async function remove() {\n * const result: StorageDeleteResult = await storage.file.deleteMany(['a.txt', 'b.txt'])\n * return result.success ? result.data : result.error.ext?.deleteMany\n * }\n * ```\n */\nexport type StorageDeleteResult\n = | { success: true, data: StorageDeleteSummary }\n | { success: false, error: HaiError & { ext?: Record<string, unknown> & { deleteMany?: StorageDeleteSummary } } }\n\n// ─── 文件元数据 ───\n\n/**\n * 文件元数据接口\n *\n * 表示存储中单个文件的元信息,由 put/head/copy/list 等操作返回。\n * @example\n * ```ts\n * import type { FileMetadata } from '@h-ai/storage'\n *\n * const value: FileMetadata = { key: 'a.txt', size: 5, contentType: 'text/plain', lastModified: new Date() }\n * void value\n * ```\n */\nexport interface FileMetadata {\n /** 文件键/路径(不含配置 prefix,对用户透明) */\n key: string\n /** 文件大小(字节) */\n size: number\n /** 内容类型(MIME),未指定时根据扩展名自动推断 */\n contentType: string\n /** 最后修改时间 */\n lastModified: Date\n /** ETag(缓存验证标记;本地为 size-mtime,S3 由服务端生成,不等同于内容 checksum) */\n etag?: string\n /** 自定义元数据键值对 */\n metadata?: Record<string, string>\n}\n\n/**\n * 列表选项\n *\n * 用于控制 `storage.dir.list()` 的过滤、分页和目录模拟行为。\n * @example\n * ```ts\n * import type { ListOptions } from '@h-ai/storage'\n *\n * const value: ListOptions = { prefix: 'reports/', delimiter: '/', maxKeys: 100 }\n * void value\n * ```\n */\nexport interface ListOptions {\n /** 前缀过滤(只返回以此前缀开头的文件) */\n prefix?: string\n /**\n * 分页标记(从上次 ListResult.nextContinuationToken 获取)\n *\n * Local 游标绑定根目录、prefix 和 delimiter,可跨实例使用;不得跨查询或 provider 混用。\n */\n continuationToken?: string\n /** 每页逻辑条目上限(1–1000,默认 1000);files 和 commonPrefixes 合计计数。 */\n maxKeys?: number\n /** 分隔符,通常为 '/';从 prefix 之后首次出现的位置折叠 commonPrefixes,空字符串表示不折叠。 */\n delimiter?: string\n}\n\n/**\n * 列表结果\n *\n * 包含当前页的文件列表、目录前缀、分页信息。\n * @example\n * ```ts\n * import type { ListResult } from '@h-ai/storage'\n *\n * const value: ListResult = { files: [], commonPrefixes: ['reports/'], isTruncated: false }\n * void value\n * ```\n */\nexport interface ListResult {\n /** 当前页文件元数据列表 */\n files: FileMetadata[]\n /** 公共前缀:由实际匹配文件按 delimiter 折叠,不包含 Local 空物理目录。 */\n commonPrefixes: string[]\n /** 下一页标记;无后续页时为 undefined */\n nextContinuationToken?: string\n /** 是否被截断(true 表示还有更多数据,可用 nextContinuationToken 继续拉取) */\n isTruncated: boolean\n}\n\n// ─── 上传下载选项 ───\n\n/**\n * 上传选项\n *\n * 用于控制 `storage.file.put()` 的元数据与缓存行为。\n * @example\n * ```ts\n * import type { UploadOptions } from '@h-ai/storage'\n *\n * const value: UploadOptions = { ifNoneMatch: '*', contentType: 'text/plain', signal: new AbortController().signal }\n * void value\n * ```\n */\nexport interface UploadOptions {\n /** 仅当 key 不存在时原子写入;S3 使用标准 If-None-Match,后端不支持时返回错误。 */\n ifNoneMatch?: '*'\n /** 取消上传;失败或取消时清理未完成的临时文件/分片。 */\n signal?: AbortSignal\n /** 内容类型(不指定时根据文件扩展名自动推断) */\n contentType?: string\n /** 自定义元数据键值对(存储后可通过 head 获取) */\n metadata?: Record<string, string>\n /** Cache-Control 响应头(例如 'max-age=3600') */\n cacheControl?: string\n /** Content-Disposition 响应头(例如 'attachment; filename=\"a.txt\"') */\n contentDisposition?: string\n}\n\n/**\n * 下载选项\n *\n * 用于控制 `storage.file.get()` 的范围读取。\n * 两个字段均为字节偏移(含),与 HTTP Range 语义一致。\n * @example\n * ```ts\n * import type { DownloadOptions } from '@h-ai/storage'\n *\n * const value: DownloadOptions = { rangeStart: 0, rangeEnd: 99 }\n * void value\n * ```\n */\nexport interface DownloadOptions {\n /** 取消读取。 */\n signal?: AbortSignal\n /** 范围起始位置(字节,含);不指定时从文件开头读取 */\n rangeStart?: number\n /** 范围结束位置(字节,含);不指定时读取到文件末尾 */\n rangeEnd?: number\n}\n\n/**\n * 复制选项\n *\n * 用于控制 `storage.file.copy()` 的目标文件元数据。\n * 提供任意字段时,S3 Provider 会使用 MetadataDirective='REPLACE' 覆盖源文件元数据。\n * @example\n * ```ts\n * import type { CopyOptions } from '@h-ai/storage'\n *\n * const value: CopyOptions = { contentType: 'text/plain', metadata: { category: 'reports' } }\n * void value\n * ```\n */\nexport interface CopyOptions {\n /** 目标文件的内容类型(不指定时继承源文件) */\n contentType?: string\n /** 目标文件的自定义元数据(不指定时继承源文件) */\n metadata?: Record<string, string>\n}\n\n// ─── 操作接口 ───\n\n/**\n * 文件内容;异步字节流支持 Node Readable 等,不将整个文件载入内存。\n * @example\n * ```ts\n * import type { StorageData } from '@h-ai/storage'\n *\n * const value: StorageData = new TextEncoder().encode('hello')\n * void value\n * ```\n */\nexport type StorageData = Buffer | Uint8Array | string | AsyncIterable<Uint8Array>\n\n/**\n * 常见 checksum 算法;MD5/SHA-1 仅用于兼容性完整性校验,不用于安全认证。\n * @example\n * ```ts\n * import type { ChecksumAlgorithm } from '@h-ai/storage'\n *\n * const value: ChecksumAlgorithm = 'sha256'\n * void value\n * ```\n */\nexport type ChecksumAlgorithm = 'sha256' | 'sha384' | 'sha512' | 'sha1' | 'md5'\n\n/**\n * checksum 计算选项;默认 SHA-256、小写 hex。\n * @example\n * ```ts\n * import type { ChecksumOptions } from '@h-ai/storage'\n *\n * const value: ChecksumOptions = { algorithm: 'sha256', encoding: 'hex' }\n * void value\n * ```\n */\nexport interface ChecksumOptions {\n algorithm?: ChecksumAlgorithm\n encoding?: 'hex' | 'base64'\n signal?: AbortSignal\n}\n\n/**\n * Node 端无状态 checksum 工具,不依赖 storage.init;流输入仅消费一次。\n * @example\n * ```ts\n * import type { ChecksumOperations } from '@h-ai/storage'\n *\n * async function inspect(operations: ChecksumOperations) {\n * const result = await operations.generate('hello')\n * if (!result.success) return result.error.message\n * return result.data\n * }\n * ```\n */\nexport interface ChecksumOperations {\n generate: (data: StorageData, options?: ChecksumOptions) => Promise<HaiResult<string>>\n verify: (data: StorageData, expected: string, options?: ChecksumOptions) => Promise<HaiResult<boolean>>\n}\n\n/**\n * 文件操作接口\n *\n * 提供文件的读写操作。\n * @example\n * ```ts\n * import type { FileOperations } from '@h-ai/storage'\n *\n * async function inspect(operations: FileOperations) {\n * const result = await operations.getStream('reports/a.txt')\n * if (!result.success) return result.error.message\n * for await (const chunk of result.data) void chunk.byteLength\n * }\n * ```\n */\nexport interface FileOperations {\n /**\n * 上传文件\n *\n * @param key - 文件键/路径\n * @param data - 文件内容(Buffer、Uint8Array、字符串或异步字节流)\n * @param options - 上传选项\n * @returns 上传结果包含文件元数据\n * @example\n * ```ts\n * const result = await storage.file.put('uploads/a.txt', 'hello')\n * if (result.success) {\n * result.data.key\n * }\n * ```\n */\n put: (key: string, data: StorageData, options?: UploadOptions) => Promise<HaiResult<FileMetadata>>\n\n /**\n * 下载文件\n *\n * @param key - 文件键/路径\n * @param options - 下载选项\n * @returns 文件内容\n * @example\n * ```ts\n * const result = await storage.file.get('uploads/a.txt')\n * if (result.success) {\n * const buffer = result.data\n * void buffer\n * }\n * ```\n */\n get: (key: string, options?: DownloadOptions) => Promise<HaiResult<Buffer>>\n\n /**\n * 读取字节流,支持 Range 与取消;消费方负责及时迭代或关闭流。\n * @throws 迭代期间的 I/O/取消错误;初始请求失败通过 HaiResult 返回。\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const result = await storage.file.getStream('reports/a.txt')\n * if (!result.success) throw new Error(result.error.message)\n * try {\n * for await (const chunk of result.data) void chunk.byteLength\n * } catch (error) {\n * // 初始请求成功后,迭代仍可能因 I/O/取消失败。\n * throw error\n * }\n * ```\n */\n getStream: (key: string, options?: DownloadOptions) => Promise<HaiResult<AsyncIterable<Uint8Array>>>\n\n /**\n * 获取文件元数据\n *\n * @param key - 文件键/路径\n * @returns 文件元数据\n * @example\n * ```ts\n * const result = await storage.file.head('uploads/a.txt')\n * if (result.success) {\n * result.data.size\n * }\n * ```\n */\n head: (key: string) => Promise<HaiResult<FileMetadata>>\n\n /**\n * 检查文件是否存在\n *\n * @param key - 文件键/路径\n * @returns 是否存在\n * @example\n * ```ts\n * const result = await storage.file.exists('uploads/a.txt')\n * if (result.success) {\n * result.data\n * }\n * ```\n */\n exists: (key: string) => Promise<HaiResult<boolean>>\n\n /**\n * 删除文件\n *\n * @param key - 文件键/路径\n * @returns 删除结果\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const result = await storage.file.delete('uploads/a.txt')\n * if (!result.success) throw new Error(result.error.message)\n * ```\n */\n delete: (key: string) => Promise<HaiResult<void>>\n\n /**\n * 批量删除文件\n *\n * @param keys - 文件键/路径列表\n * @returns 全部成功时返回进度;任一失败时 success=false,进度在 error.ext.deleteMany\n * @example\n * ```ts\n * const result = await storage.file.deleteMany(['a.txt', 'b.txt'])\n * const progress = result.success ? result.data : result.error.ext?.deleteMany\n * void progress\n * ```\n */\n deleteMany: (keys: string[]) => Promise<StorageDeleteResult>\n\n /**\n * 复制文件\n *\n * @param sourceKey - 源文件键\n * @param destKey - 目标文件键\n * @param options - 复制选项\n * @returns 目标文件元数据\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const result = await storage.file.copy('a.txt', 'b.txt')\n * if (!result.success) throw new Error(result.error.message)\n * void result.data.key\n * ```\n */\n copy: (sourceKey: string, destKey: string, options?: CopyOptions) => Promise<HaiResult<FileMetadata>>\n}\n\n/**\n * 目录操作接口\n *\n * 提供目录(前缀)的列表操作。\n * @example\n * ```ts\n * import type { DirOperations } from '@h-ai/storage'\n *\n * async function inspect(operations: DirOperations) {\n * const page = await operations.list({ prefix: 'reports/', maxKeys: 100 })\n * if (!page.success) return page.error.message\n * return page.data.files\n * }\n * ```\n */\nexport interface DirOperations {\n /**\n * 列出目录内容\n *\n * @param options - 列表选项\n * @returns 文件和目录列表\n * @example\n * ```ts\n * const result = await storage.dir.list({ prefix: 'uploads/' })\n * if (result.success) {\n * result.data.files\n * }\n * ```\n */\n list: (options?: ListOptions) => Promise<HaiResult<ListResult>>\n\n /**\n * 删除目录(删除指定前缀下的所有文件)\n *\n * @param prefix - 目录前缀\n * @returns 删除结果\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const result = await storage.dir.delete('uploads/tmp/')\n * if (!result.success) throw new Error(result.error.message)\n * ```\n */\n delete: (prefix: string) => Promise<HaiResult<void>>\n}\n\n/**\n * 签名 URL 操作接口\n *\n * S3 provider 提供临时授权 URL,支持前端直接上传下载。\n * local 的 getUrl/putUrl 返回 UNSUPPORTED_OPERATION,publicUrl 返回 null。\n * @example\n * ```ts\n * import type { PresignOperations } from '@h-ai/storage'\n *\n * async function inspect(operations: PresignOperations) {\n * const signed = await operations.putUrl('reports/a.txt', { contentType: 'text/plain', expiresIn: 300 })\n * if (!signed.success) return signed.error.message\n * return signed.data\n * }\n * ```\n */\nexport interface PresignOperations {\n /**\n * 生成下载签名 URL\n *\n * @param key - 文件键/路径\n * @param options - 签名选项\n * @returns 成功时为可访问的 S3 签名 URL;local 返回 UNSUPPORTED_OPERATION。\n * @example\n * ```ts\n * const result = await storage.presign.getUrl('a.txt', { expiresIn: 60 })\n * if (!result.success) throw new Error(result.error.message)\n * const downloadUrl = result.data\n * void downloadUrl\n * ```\n */\n getUrl: (key: string, options?: PresignOptions) => Promise<HaiResult<string>>\n\n /**\n * 生成上传签名 URL\n *\n * @param key - 文件键/路径\n * @param options - 上传签名选项\n * @returns 成功时为可访问的 S3 签名 URL;local 返回 UNSUPPORTED_OPERATION。\n * @example\n * ```ts\n * const result = await storage.presign.putUrl('a.txt', { contentType: 'text/plain' })\n * if (!result.success) throw new Error(result.error.message)\n * const uploadUrl = result.data\n * void uploadUrl\n * ```\n */\n putUrl: (key: string, options?: PresignUploadOptions) => Promise<HaiResult<string>>\n\n /**\n * 获取公开访问 URL(如果配置了 publicUrl)\n *\n * @param key - 文件键/路径\n * @returns 公开 URL;S3 未配置 publicUrl 或 local provider 均返回 null。\n * @example\n * ```ts\n * const url = storage.presign.publicUrl('a.txt')\n * void url\n * ```\n */\n publicUrl: (key: string) => string | null\n}\n\n// ─── 函数接口 ───\n\n/**\n * 存储函数接口\n *\n * 统一的存储访问入口:\n * - `storage.init()` — 初始化存储连接\n * - `storage.close()` — 关闭连接并释放资源\n * - `storage.file` — 文件操作(上传/下载/删除/复制等)\n * - `storage.dir` — 目录操作(列表/删除)\n * - `storage.presign` — 签名 URL 操作\n * - `storage.config` — 当前脱敏配置快照(未初始化时为 null)\n * - `storage.isInitialized` — 初始化状态\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const initialized = await storage.init({ type: 'local', root: './data/uploads' })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const uploaded = await storage.file.put('reports/a.txt', 'hello', { ifNoneMatch: '*' })\n * if (!uploaded.success) throw new Error(uploaded.error.message)\n * const downloaded = await storage.file.get('reports/a.txt')\n * if (!downloaded.success) throw new Error(downloaded.error.message)\n * void downloaded.data\n * } finally {\n * const closed = await storage.close()\n * if (!closed.success) throw new Error(closed.error.message)\n * }\n * ```\n */\nexport interface StorageFunctions {\n /** 无状态 checksum 生成与校验,Node 专用,无需初始化。 */\n readonly checksum: ChecksumOperations\n /**\n * 初始化存储连接\n *\n * 已有连接时会先 close 再重新初始化。\n *\n * @param config - 存储配置(支持省略带默认值的字段)\n * @returns 成功 ok(undefined);失败返回 err(含 HaiStorageError 定义的错误信息)\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const initialized = await storage.init({ type: 'local', root: './data/uploads' })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * const closed = await storage.close()\n * if (!closed.success) throw new Error(closed.error.message)\n * ```\n */\n init: (config: StorageConfigInput) => Promise<HaiResult<void>>\n\n /**\n * 关闭存储连接并释放资源\n *\n * 等待初始化并合并并发关闭;失败时操作不可用,资源保留供 close/init 重试。\n * @returns 所有资源释放成功才返回成功;未初始化时同样成功。\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n *\n * const closed = await storage.close()\n * if (!closed.success) throw new Error(closed.error.message)\n * ```\n */\n close: () => Promise<HaiResult<void>>\n\n /** 当前脱敏后的存储配置快照;未初始化或已关闭时为 null */\n readonly config: StorageConfig | null\n\n /** 是否已完成初始化 */\n readonly isInitialized: boolean\n\n /** 文件操作接口(put / get / head / exists / delete / deleteMany / copy) */\n readonly file: FileOperations\n\n /** 目录操作接口(list / delete) */\n readonly dir: DirOperations\n\n /** 签名 URL 操作接口(getUrl / putUrl / publicUrl) */\n readonly presign: PresignOperations\n}\n"]}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { core } from '@h-ai/core';
|
|
2
|
+
|
|
3
|
+
// src/storage-i18n.ts
|
|
4
|
+
|
|
5
|
+
// messages/en-US.json
|
|
6
|
+
var en_US_default = {
|
|
7
|
+
$schema: "https://inlang.com/schema/inlang-message-format",
|
|
8
|
+
storage_notInitialized: "Storage not initialized, please call storage.init() first",
|
|
9
|
+
storage_bucketNotExist: "Bucket does not exist",
|
|
10
|
+
storage_networkError: "Network error",
|
|
11
|
+
storage_responseBodyEmpty: "Response body is empty",
|
|
12
|
+
storage_presignUrlFailed: "Failed to generate presigned URL",
|
|
13
|
+
storage_presignUploadUrlFailed: "Failed to generate presigned upload URL",
|
|
14
|
+
storage_localPresignUnsupported: "Local storage does not support presigned URLs; upload or download through an authenticated application file endpoint",
|
|
15
|
+
storage_listOptionsInvalid: "Invalid list options: maxKeys must be an integer between 1 and 1000, and prefix and delimiter must be well-formed Unicode strings",
|
|
16
|
+
storage_listCursorInvalid: "Invalid list cursor or mismatched root, prefix or delimiter",
|
|
17
|
+
storage_s3ConfigTypeError: "Config type error: S3 provider only supports type=s3",
|
|
18
|
+
storage_diskSpaceInsufficient: "Insufficient disk space",
|
|
19
|
+
storage_localConfigTypeError: "Config type error: Local provider only supports type=local",
|
|
20
|
+
storage_operationFailed: "Operation failed: {error}",
|
|
21
|
+
storage_unsupportedType: "Unsupported storage type: {type}",
|
|
22
|
+
storage_config_bucketRequired: "Bucket is required",
|
|
23
|
+
storage_config_regionRequired: "Region is required",
|
|
24
|
+
storage_config_accessKeyIdRequired: "Access Key ID is required",
|
|
25
|
+
storage_config_secretAccessKeyRequired: "Secret Access Key is required",
|
|
26
|
+
storage_config_rootRequired: "Root path is required",
|
|
27
|
+
storage_fileNotFound: "File not found: {key}",
|
|
28
|
+
storage_permissionDenied: "Permission denied: {key}",
|
|
29
|
+
storage_pathIsDir: "Path is a directory: {key}",
|
|
30
|
+
storage_ioError: "IO error: {error}",
|
|
31
|
+
storage_deleteManyFailed: "Failed to delete {count} files",
|
|
32
|
+
storage_s3ClientNotInitialized: "S3 client not initialized",
|
|
33
|
+
storage_s3ConfigNotInitialized: "S3 config not initialized",
|
|
34
|
+
storage_uploadCanceled: "Upload canceled",
|
|
35
|
+
storage_downloadCanceled: "Download canceled",
|
|
36
|
+
storage_uploadFailedStatus: "Upload failed (status {status}): {statusText}",
|
|
37
|
+
storage_downloadFailedStatus: "Download failed (status {status}): {statusText}",
|
|
38
|
+
storage_unknownError: "Unknown error",
|
|
39
|
+
storage_saveFileFailed: "Failed to save file",
|
|
40
|
+
storage_downloadDefaultFilename: "download",
|
|
41
|
+
storage_pathTraversal: "Path traversal attack detected",
|
|
42
|
+
storage_localNotInitialized: "Local storage not initialized",
|
|
43
|
+
storage_configError: "Storage config validation failed: {error}",
|
|
44
|
+
storage_metaFileAccess: "Direct access to internal files is not allowed: {key}",
|
|
45
|
+
storage_fileAlreadyExists: "File already exists: {key}",
|
|
46
|
+
storage_checksumFailed: "Failed to calculate checksum",
|
|
47
|
+
storage_checksumInvalid: "Invalid checksum or algorithm",
|
|
48
|
+
storage_streamInvalidChunk: "File stream must contain byte chunks",
|
|
49
|
+
storage_sharedBackendRequired: "Production mode requires a shared backend; local files and memory backends are for development or explicit end-to-end tests."
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// messages/ja-JP.json
|
|
53
|
+
var ja_JP_default = {
|
|
54
|
+
$schema: "https://inlang.com/schema/inlang-message-format",
|
|
55
|
+
storage_notInitialized: "\u30B9\u30C8\u30EC\u30FC\u30B8\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u5148\u306Bstorage.init()\u3092\u547C\u3073\u51FA\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
56
|
+
storage_bucketNotExist: "\u30D0\u30B1\u30C3\u30C8\u304C\u5B58\u5728\u3057\u307E\u305B\u3093",
|
|
57
|
+
storage_networkError: "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC",
|
|
58
|
+
storage_responseBodyEmpty: "\u30EC\u30B9\u30DD\u30F3\u30B9\u672C\u6587\u304C\u7A7A\u3067\u3059",
|
|
59
|
+
storage_presignUrlFailed: "\u7F72\u540D\u4ED8\u304DURL\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
60
|
+
storage_presignUploadUrlFailed: "\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u7528\u7F72\u540D\u4ED8\u304DURL\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
61
|
+
storage_localPresignUnsupported: "\u30ED\u30FC\u30AB\u30EB\u30B9\u30C8\u30EC\u30FC\u30B8\u306F\u7F72\u540D\u4ED8\u304D URL \u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093\u3002\u8A8D\u8A3C\u4ED8\u304D\u306E\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u306E\u30D5\u30A1\u30A4\u30EB\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3067\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u307E\u305F\u306F\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
62
|
+
storage_listOptionsInvalid: "\u4E00\u89A7\u30AA\u30D7\u30B7\u30E7\u30F3\u304C\u7121\u52B9\u3067\u3059\u3002maxKeys \u306F1\u301C1000\u306E\u6574\u6570\u3001prefix \u3068 delimiter \u306F\u6709\u52B9\u306A Unicode \u6587\u5B57\u5217\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
63
|
+
storage_listCursorInvalid: "\u4E00\u89A7\u30AB\u30FC\u30BD\u30EB\u304C\u7121\u52B9\u304B\u3001\u30EB\u30FC\u30C8\u3001\u30D7\u30EC\u30D5\u30A3\u30C3\u30AF\u30B9\u3001\u533A\u5207\u308A\u6587\u5B57\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093",
|
|
64
|
+
storage_s3ConfigTypeError: "\u8A2D\u5B9A\u306E\u578B\u304C\u7121\u52B9\u3067\u3059\u3002S3\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306Ftype=s3\u306E\u307F\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059",
|
|
65
|
+
storage_diskSpaceInsufficient: "\u30C7\u30A3\u30B9\u30AF\u306E\u7A7A\u304D\u5BB9\u91CF\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059",
|
|
66
|
+
storage_localConfigTypeError: "\u8A2D\u5B9A\u306E\u578B\u304C\u7121\u52B9\u3067\u3059\u3002\u30ED\u30FC\u30AB\u30EB\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306Ftype=local\u306E\u307F\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059",
|
|
67
|
+
storage_operationFailed: "\u64CD\u4F5C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
68
|
+
storage_unsupportedType: "\u672A\u5BFE\u5FDC\u306E\u30B9\u30C8\u30EC\u30FC\u30B8\u306E\u7A2E\u985E\u3067\u3059: {type}",
|
|
69
|
+
storage_config_bucketRequired: "\u30D0\u30B1\u30C3\u30C8\u306F\u5FC5\u9808\u3067\u3059",
|
|
70
|
+
storage_config_regionRequired: "\u30EA\u30FC\u30B8\u30E7\u30F3\u306F\u5FC5\u9808\u3067\u3059",
|
|
71
|
+
storage_config_accessKeyIdRequired: "Access Key ID\u306F\u5FC5\u9808\u3067\u3059",
|
|
72
|
+
storage_config_secretAccessKeyRequired: "Secret Access Key\u306F\u5FC5\u9808\u3067\u3059",
|
|
73
|
+
storage_config_rootRequired: "\u30EB\u30FC\u30C8\u30D1\u30B9\u306F\u5FC5\u9808\u3067\u3059",
|
|
74
|
+
storage_fileNotFound: "\u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: {key}",
|
|
75
|
+
storage_permissionDenied: "\u30A2\u30AF\u30BB\u30B9\u6A29\u9650\u304C\u3042\u308A\u307E\u305B\u3093: {key}",
|
|
76
|
+
storage_pathIsDir: "\u30D1\u30B9\u306F\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3067\u3059: {key}",
|
|
77
|
+
storage_ioError: "\u5165\u51FA\u529B\u30A8\u30E9\u30FC: {error}",
|
|
78
|
+
storage_deleteManyFailed: "{count}\u500B\u306E\u30D5\u30A1\u30A4\u30EB\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
79
|
+
storage_s3ClientNotInitialized: "S3\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093",
|
|
80
|
+
storage_s3ConfigNotInitialized: "S3\u8A2D\u5B9A\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093",
|
|
81
|
+
storage_uploadCanceled: "\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u3092\u30AD\u30E3\u30F3\u30BB\u30EB\u3057\u307E\u3057\u305F",
|
|
82
|
+
storage_downloadCanceled: "\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3092\u30AD\u30E3\u30F3\u30BB\u30EB\u3057\u307E\u3057\u305F",
|
|
83
|
+
storage_uploadFailedStatus: "\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08\u30B9\u30C6\u30FC\u30BF\u30B9{status}\uFF09: {statusText}",
|
|
84
|
+
storage_downloadFailedStatus: "\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08\u30B9\u30C6\u30FC\u30BF\u30B9{status}\uFF09: {statusText}",
|
|
85
|
+
storage_unknownError: "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC",
|
|
86
|
+
storage_saveFileFailed: "\u30D5\u30A1\u30A4\u30EB\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
87
|
+
storage_downloadDefaultFilename: "\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9",
|
|
88
|
+
storage_pathTraversal: "\u30D1\u30B9\u30C8\u30E9\u30D0\u30FC\u30B5\u30EB\u653B\u6483\u3092\u691C\u51FA\u3057\u307E\u3057\u305F",
|
|
89
|
+
storage_localNotInitialized: "\u30ED\u30FC\u30AB\u30EB\u30B9\u30C8\u30EC\u30FC\u30B8\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093",
|
|
90
|
+
storage_configError: "\u30B9\u30C8\u30EC\u30FC\u30B8\u8A2D\u5B9A\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
91
|
+
storage_metaFileAccess: "\u5185\u90E8\u30D5\u30A1\u30A4\u30EB\u3078\u306E\u76F4\u63A5\u30A2\u30AF\u30BB\u30B9\u306F\u8A31\u53EF\u3055\u308C\u3066\u3044\u307E\u305B\u3093: {key}",
|
|
92
|
+
storage_fileAlreadyExists: "\u30D5\u30A1\u30A4\u30EB\u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059: {key}",
|
|
93
|
+
storage_checksumFailed: "\u30C1\u30A7\u30C3\u30AF\u30B5\u30E0\u306E\u8A08\u7B97\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
94
|
+
storage_checksumInvalid: "\u30C1\u30A7\u30C3\u30AF\u30B5\u30E0\u307E\u305F\u306F\u30A2\u30EB\u30B4\u30EA\u30BA\u30E0\u304C\u7121\u52B9\u3067\u3059",
|
|
95
|
+
storage_streamInvalidChunk: "\u30D5\u30A1\u30A4\u30EB\u30B9\u30C8\u30EA\u30FC\u30E0\u306B\u306F\u30D0\u30A4\u30C8\u30C1\u30E3\u30F3\u30AF\u304C\u5FC5\u8981\u3067\u3059",
|
|
96
|
+
storage_sharedBackendRequired: "\u672C\u756A\u74B0\u5883\u306B\u306F\u5171\u6709\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u304C\u5FC5\u8981\u3067\u3059\u3002\u30ED\u30FC\u30AB\u30EB\u30D5\u30A1\u30A4\u30EB\u3068\u30E1\u30E2\u30EA\u30FC\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u306F\u958B\u767A\u74B0\u5883\u307E\u305F\u306F\u660E\u793A\u7684\u306A E2E \u30C6\u30B9\u30C8\u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002"
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// messages/zh-CN.json
|
|
100
|
+
var zh_CN_default = {
|
|
101
|
+
$schema: "https://inlang.com/schema/inlang-message-format",
|
|
102
|
+
storage_notInitialized: "\u5B58\u50A8\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 storage.init()",
|
|
103
|
+
storage_bucketNotExist: "\u5B58\u50A8\u6876\u4E0D\u5B58\u5728",
|
|
104
|
+
storage_networkError: "\u7F51\u7EDC\u9519\u8BEF",
|
|
105
|
+
storage_responseBodyEmpty: "\u54CD\u5E94\u4F53\u4E3A\u7A7A",
|
|
106
|
+
storage_presignUrlFailed: "\u751F\u6210\u7B7E\u540D URL \u5931\u8D25",
|
|
107
|
+
storage_presignUploadUrlFailed: "\u751F\u6210\u4E0A\u4F20\u7B7E\u540D URL \u5931\u8D25",
|
|
108
|
+
storage_localPresignUnsupported: "\u672C\u5730\u5B58\u50A8\u4E0D\u652F\u6301\u9884\u7B7E\u540D URL\uFF0C\u8BF7\u901A\u8FC7\u5E94\u7528\u7684\u9274\u6743\u6587\u4EF6\u63A5\u53E3\u4E0A\u4F20\u6216\u4E0B\u8F7D",
|
|
109
|
+
storage_listOptionsInvalid: "\u5217\u8868\u53C2\u6570\u65E0\u6548\uFF1AmaxKeys \u5FC5\u987B\u4E3A 1 \u5230 1000 \u7684\u6574\u6570\uFF0Cprefix \u548C delimiter \u5FC5\u987B\u4E3A\u6709\u6548\u7684 Unicode \u5B57\u7B26\u4E32",
|
|
110
|
+
storage_listCursorInvalid: "\u5217\u8868\u6E38\u6807\u65E0\u6548\u6216\u4E0E\u5F53\u524D\u6839\u76EE\u5F55\u3001\u524D\u7F00\u3001\u5206\u9694\u7B26\u4E0D\u5339\u914D",
|
|
111
|
+
storage_s3ConfigTypeError: "\u914D\u7F6E\u7C7B\u578B\u9519\u8BEF\uFF1AS3 provider \u4EC5\u652F\u6301 type=s3",
|
|
112
|
+
storage_diskSpaceInsufficient: "\u78C1\u76D8\u7A7A\u95F4\u4E0D\u8DB3",
|
|
113
|
+
storage_localConfigTypeError: "\u914D\u7F6E\u7C7B\u578B\u9519\u8BEF\uFF1ALocal provider \u4EC5\u652F\u6301 type=local",
|
|
114
|
+
storage_operationFailed: "\u64CD\u4F5C\u5931\u8D25: {error}",
|
|
115
|
+
storage_unsupportedType: "\u4E0D\u652F\u6301\u7684\u5B58\u50A8\u7C7B\u578B\uFF1A{type}",
|
|
116
|
+
storage_config_bucketRequired: "\u5B58\u50A8\u6876\u4E0D\u80FD\u4E3A\u7A7A",
|
|
117
|
+
storage_config_regionRequired: "\u533A\u57DF\u4E0D\u80FD\u4E3A\u7A7A",
|
|
118
|
+
storage_config_accessKeyIdRequired: "Access Key ID \u4E0D\u80FD\u4E3A\u7A7A",
|
|
119
|
+
storage_config_secretAccessKeyRequired: "Secret Access Key \u4E0D\u80FD\u4E3A\u7A7A",
|
|
120
|
+
storage_config_rootRequired: "Root \u4E0D\u80FD\u4E3A\u7A7A",
|
|
121
|
+
storage_fileNotFound: "\u6587\u4EF6\u4E0D\u5B58\u5728: {key}",
|
|
122
|
+
storage_permissionDenied: "\u6743\u9650\u4E0D\u8DB3: {key}",
|
|
123
|
+
storage_pathIsDir: "\u8DEF\u5F84\u662F\u76EE\u5F55: {key}",
|
|
124
|
+
storage_ioError: "IO \u9519\u8BEF: {error}",
|
|
125
|
+
storage_deleteManyFailed: "\u5220\u9664 {count} \u4E2A\u6587\u4EF6\u5931\u8D25",
|
|
126
|
+
storage_s3ClientNotInitialized: "S3 \u5BA2\u6237\u7AEF\u672A\u521D\u59CB\u5316",
|
|
127
|
+
storage_s3ConfigNotInitialized: "S3 \u914D\u7F6E\u672A\u521D\u59CB\u5316",
|
|
128
|
+
storage_uploadCanceled: "\u4E0A\u4F20\u5DF2\u53D6\u6D88",
|
|
129
|
+
storage_downloadCanceled: "\u4E0B\u8F7D\u5DF2\u53D6\u6D88",
|
|
130
|
+
storage_uploadFailedStatus: "\u4E0A\u4F20\u5931\u8D25\uFF08\u72B6\u6001\u7801 {status}\uFF09\uFF1A{statusText}",
|
|
131
|
+
storage_downloadFailedStatus: "\u4E0B\u8F7D\u5931\u8D25\uFF08\u72B6\u6001\u7801 {status}\uFF09\uFF1A{statusText}",
|
|
132
|
+
storage_unknownError: "\u672A\u77E5\u9519\u8BEF",
|
|
133
|
+
storage_saveFileFailed: "\u4FDD\u5B58\u6587\u4EF6\u5931\u8D25",
|
|
134
|
+
storage_downloadDefaultFilename: "\u4E0B\u8F7D",
|
|
135
|
+
storage_pathTraversal: "\u68C0\u6D4B\u5230\u8DEF\u5F84\u7A7F\u8D8A\u653B\u51FB",
|
|
136
|
+
storage_localNotInitialized: "\u672C\u5730\u5B58\u50A8\u672A\u521D\u59CB\u5316",
|
|
137
|
+
storage_configError: "\u5B58\u50A8\u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A{error}",
|
|
138
|
+
storage_metaFileAccess: "\u4E0D\u5141\u8BB8\u76F4\u63A5\u8BBF\u95EE\u5185\u90E8\u6587\u4EF6\uFF1A{key}",
|
|
139
|
+
storage_fileAlreadyExists: "\u6587\u4EF6\u5DF2\u5B58\u5728\uFF1A{key}",
|
|
140
|
+
storage_checksumFailed: "\u8BA1\u7B97\u6821\u9A8C\u548C\u5931\u8D25",
|
|
141
|
+
storage_checksumInvalid: "\u6821\u9A8C\u548C\u6216\u7B97\u6CD5\u4E0D\u5408\u6CD5",
|
|
142
|
+
storage_streamInvalidChunk: "\u6587\u4EF6\u6D41\u5FC5\u987B\u5305\u542B\u5B57\u8282\u5757",
|
|
143
|
+
storage_sharedBackendRequired: "\u751F\u4EA7\u6A21\u5F0F\u9700\u8981\u8DE8\u8282\u70B9\u5171\u4EAB\u540E\u7AEF\uFF1B\u672C\u673A\u6587\u4EF6\u6216\u5185\u5B58\u540E\u7AEF\u4EC5\u7528\u4E8E\u5F00\u53D1\u548C\u663E\u5F0F\u7AEF\u5230\u7AEF\u6D4B\u8BD5\u3002"
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// src/storage-i18n.ts
|
|
147
|
+
var storageM = core.i18n.createMessageGetter({
|
|
148
|
+
"zh-CN": zh_CN_default,
|
|
149
|
+
"en-US": en_US_default,
|
|
150
|
+
"ja-JP": ja_JP_default
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// src/storage-mime.ts
|
|
154
|
+
var MIME_TYPES = {
|
|
155
|
+
// 图片
|
|
156
|
+
"jpg": "image/jpeg",
|
|
157
|
+
"jpeg": "image/jpeg",
|
|
158
|
+
"png": "image/png",
|
|
159
|
+
"gif": "image/gif",
|
|
160
|
+
"webp": "image/webp",
|
|
161
|
+
"svg": "image/svg+xml",
|
|
162
|
+
"ico": "image/x-icon",
|
|
163
|
+
// 文档
|
|
164
|
+
"pdf": "application/pdf",
|
|
165
|
+
"doc": "application/msword",
|
|
166
|
+
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
167
|
+
"xls": "application/vnd.ms-excel",
|
|
168
|
+
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
169
|
+
"ppt": "application/vnd.ms-powerpoint",
|
|
170
|
+
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
171
|
+
// 文本
|
|
172
|
+
"txt": "text/plain",
|
|
173
|
+
"html": "text/html",
|
|
174
|
+
"css": "text/css",
|
|
175
|
+
"js": "text/javascript",
|
|
176
|
+
"json": "application/json",
|
|
177
|
+
"xml": "application/xml",
|
|
178
|
+
"csv": "text/csv",
|
|
179
|
+
"md": "text/markdown",
|
|
180
|
+
// 音视频
|
|
181
|
+
"mp3": "audio/mpeg",
|
|
182
|
+
"wav": "audio/wav",
|
|
183
|
+
"mp4": "video/mp4",
|
|
184
|
+
"webm": "video/webm",
|
|
185
|
+
"ogg": "audio/ogg",
|
|
186
|
+
// 压缩包
|
|
187
|
+
"zip": "application/zip",
|
|
188
|
+
"rar": "application/x-rar-compressed",
|
|
189
|
+
"7z": "application/x-7z-compressed",
|
|
190
|
+
"tar": "application/x-tar",
|
|
191
|
+
"gz": "application/gzip",
|
|
192
|
+
// 字体
|
|
193
|
+
"woff": "font/woff",
|
|
194
|
+
"woff2": "font/woff2",
|
|
195
|
+
"ttf": "font/ttf",
|
|
196
|
+
"otf": "font/otf"
|
|
197
|
+
};
|
|
198
|
+
var MIME_TYPE_DEFAULT = "application/octet-stream";
|
|
199
|
+
|
|
200
|
+
// src/client/storage-client.ts
|
|
201
|
+
async function uploadWithPresignedUrl(url, data, options = {}) {
|
|
202
|
+
try {
|
|
203
|
+
const signal = options.abortController?.signal;
|
|
204
|
+
if (signal?.aborted)
|
|
205
|
+
return { success: false, error: storageM("storage_uploadCanceled") };
|
|
206
|
+
const body = data;
|
|
207
|
+
if (options.onProgress) {
|
|
208
|
+
const xhr = new XMLHttpRequest();
|
|
209
|
+
return new Promise((resolve) => {
|
|
210
|
+
let settled = false;
|
|
211
|
+
function finish(result) {
|
|
212
|
+
if (settled)
|
|
213
|
+
return;
|
|
214
|
+
settled = true;
|
|
215
|
+
signal?.removeEventListener("abort", onCancel);
|
|
216
|
+
xhr.upload.removeEventListener("progress", onProgress);
|
|
217
|
+
xhr.removeEventListener("load", onLoad);
|
|
218
|
+
xhr.removeEventListener("error", onError);
|
|
219
|
+
xhr.removeEventListener("abort", onAbort);
|
|
220
|
+
xhr.removeEventListener("timeout", onError);
|
|
221
|
+
resolve(result);
|
|
222
|
+
}
|
|
223
|
+
function onAbort() {
|
|
224
|
+
finish({ success: false, error: storageM("storage_uploadCanceled") });
|
|
225
|
+
}
|
|
226
|
+
function onCancel() {
|
|
227
|
+
onAbort();
|
|
228
|
+
xhr.abort();
|
|
229
|
+
}
|
|
230
|
+
function onError() {
|
|
231
|
+
finish({ success: false, error: storageM("storage_networkError") });
|
|
232
|
+
}
|
|
233
|
+
function onLoad() {
|
|
234
|
+
finish(xhr.status >= 200 && xhr.status < 300 ? { success: true } : { success: false, error: storageM("storage_uploadFailedStatus", { params: { status: xhr.status, statusText: xhr.statusText } }) });
|
|
235
|
+
}
|
|
236
|
+
function onProgress(event) {
|
|
237
|
+
if (settled || !event.lengthComputable)
|
|
238
|
+
return;
|
|
239
|
+
try {
|
|
240
|
+
options.onProgress?.({ loaded: event.loaded, total: event.total, percent: Math.round(event.loaded / event.total * 100) });
|
|
241
|
+
} catch (error) {
|
|
242
|
+
finish({ success: false, error: error instanceof Error ? error.message : storageM("storage_unknownError") });
|
|
243
|
+
xhr.abort();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
xhr.upload.addEventListener("progress", onProgress);
|
|
248
|
+
xhr.addEventListener("load", onLoad);
|
|
249
|
+
xhr.addEventListener("error", onError);
|
|
250
|
+
xhr.addEventListener("abort", onAbort);
|
|
251
|
+
xhr.addEventListener("timeout", onError);
|
|
252
|
+
signal?.addEventListener("abort", onCancel, { once: true });
|
|
253
|
+
xhr.open("PUT", url);
|
|
254
|
+
if (settled)
|
|
255
|
+
return;
|
|
256
|
+
if (options.contentType)
|
|
257
|
+
xhr.setRequestHeader("Content-Type", options.contentType);
|
|
258
|
+
if (signal?.aborted) {
|
|
259
|
+
onCancel();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
xhr.send(body);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
finish({ success: false, error: error instanceof Error ? error.message : storageM("storage_unknownError") });
|
|
265
|
+
xhr.abort();
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
const response = await fetch(url, {
|
|
270
|
+
method: "PUT",
|
|
271
|
+
body,
|
|
272
|
+
headers: options.contentType ? { "Content-Type": options.contentType } : void 0,
|
|
273
|
+
signal
|
|
274
|
+
});
|
|
275
|
+
if (!response.ok) {
|
|
276
|
+
return {
|
|
277
|
+
success: false,
|
|
278
|
+
error: storageM("storage_uploadFailedStatus", { params: { status: response.status, statusText: response.statusText } })
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
return { success: true };
|
|
282
|
+
} catch (error) {
|
|
283
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
284
|
+
return { success: false, error: storageM("storage_uploadCanceled") };
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
success: false,
|
|
288
|
+
error: error instanceof Error ? error.message : storageM("storage_unknownError")
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async function downloadWithPresignedUrl(url, options = {}) {
|
|
293
|
+
try {
|
|
294
|
+
const response = await fetch(url, {
|
|
295
|
+
method: "GET",
|
|
296
|
+
signal: options.abortController?.signal
|
|
297
|
+
});
|
|
298
|
+
if (!response.ok) {
|
|
299
|
+
return {
|
|
300
|
+
success: false,
|
|
301
|
+
error: storageM("storage_downloadFailedStatus", { params: { status: response.status, statusText: response.statusText } })
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
const blob = await response.blob();
|
|
305
|
+
return { success: true, data: blob };
|
|
306
|
+
} catch (error) {
|
|
307
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
308
|
+
return { success: false, error: storageM("storage_downloadCanceled") };
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
success: false,
|
|
312
|
+
error: error instanceof Error ? error.message : storageM("storage_unknownError")
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async function downloadAndSave(url, options = {}) {
|
|
317
|
+
const result = await downloadWithPresignedUrl(url, options);
|
|
318
|
+
if (!result.success || !result.data) {
|
|
319
|
+
return { success: false, error: result.error };
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
const blobUrl = URL.createObjectURL(result.data);
|
|
323
|
+
const link = document.createElement("a");
|
|
324
|
+
link.href = blobUrl;
|
|
325
|
+
link.download = options.filename || storageM("storage_downloadDefaultFilename");
|
|
326
|
+
document.body.appendChild(link);
|
|
327
|
+
link.click();
|
|
328
|
+
document.body.removeChild(link);
|
|
329
|
+
URL.revokeObjectURL(blobUrl);
|
|
330
|
+
return { success: true };
|
|
331
|
+
} catch (error) {
|
|
332
|
+
return {
|
|
333
|
+
success: false,
|
|
334
|
+
error: error instanceof Error ? error.message : storageM("storage_saveFileFailed")
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function getFileExtension(file) {
|
|
339
|
+
const name = file.name;
|
|
340
|
+
const lastDot = name.lastIndexOf(".");
|
|
341
|
+
return lastDot > -1 ? name.slice(lastDot + 1).toLowerCase() : "";
|
|
342
|
+
}
|
|
343
|
+
function getMimeType(extension) {
|
|
344
|
+
return MIME_TYPES[extension.toLowerCase()] || MIME_TYPE_DEFAULT;
|
|
345
|
+
}
|
|
346
|
+
function formatFileSize(bytes) {
|
|
347
|
+
if (bytes === 0)
|
|
348
|
+
return "0 B";
|
|
349
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
350
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
351
|
+
const size = bytes / 1024 ** i;
|
|
352
|
+
return `${size.toFixed(i > 0 ? 2 : 0)} ${units[i]}`;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export { MIME_TYPES, MIME_TYPE_DEFAULT, downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, storageM, uploadWithPresignedUrl };
|
|
356
|
+
//# sourceMappingURL=chunk-I4BSES63.js.map
|
|
357
|
+
//# sourceMappingURL=chunk-I4BSES63.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../messages/en-US.json","../messages/ja-JP.json","../messages/zh-CN.json","../src/storage-i18n.ts","../src/storage-mime.ts","../src/client/storage-client.ts"],"names":[],"mappings":"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","file":"chunk-I4BSES63.js","sourcesContent":["{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"storage_notInitialized\": \"Storage not initialized, please call storage.init() first\",\n \"storage_bucketNotExist\": \"Bucket does not exist\",\n \"storage_networkError\": \"Network error\",\n \"storage_responseBodyEmpty\": \"Response body is empty\",\n \"storage_presignUrlFailed\": \"Failed to generate presigned URL\",\n \"storage_presignUploadUrlFailed\": \"Failed to generate presigned upload URL\",\n \"storage_localPresignUnsupported\": \"Local storage does not support presigned URLs; upload or download through an authenticated application file endpoint\",\n \"storage_listOptionsInvalid\": \"Invalid list options: maxKeys must be an integer between 1 and 1000, and prefix and delimiter must be well-formed Unicode strings\",\n \"storage_listCursorInvalid\": \"Invalid list cursor or mismatched root, prefix or delimiter\",\n \"storage_s3ConfigTypeError\": \"Config type error: S3 provider only supports type=s3\",\n \"storage_diskSpaceInsufficient\": \"Insufficient disk space\",\n \"storage_localConfigTypeError\": \"Config type error: Local provider only supports type=local\",\n \"storage_operationFailed\": \"Operation failed: {error}\",\n \"storage_unsupportedType\": \"Unsupported storage type: {type}\",\n \"storage_config_bucketRequired\": \"Bucket is required\",\n \"storage_config_regionRequired\": \"Region is required\",\n \"storage_config_accessKeyIdRequired\": \"Access Key ID is required\",\n \"storage_config_secretAccessKeyRequired\": \"Secret Access Key is required\",\n \"storage_config_rootRequired\": \"Root path is required\",\n \"storage_fileNotFound\": \"File not found: {key}\",\n \"storage_permissionDenied\": \"Permission denied: {key}\",\n \"storage_pathIsDir\": \"Path is a directory: {key}\",\n \"storage_ioError\": \"IO error: {error}\",\n \"storage_deleteManyFailed\": \"Failed to delete {count} files\",\n \"storage_s3ClientNotInitialized\": \"S3 client not initialized\",\n \"storage_s3ConfigNotInitialized\": \"S3 config not initialized\",\n \"storage_uploadCanceled\": \"Upload canceled\",\n \"storage_downloadCanceled\": \"Download canceled\",\n \"storage_uploadFailedStatus\": \"Upload failed (status {status}): {statusText}\",\n \"storage_downloadFailedStatus\": \"Download failed (status {status}): {statusText}\",\n \"storage_unknownError\": \"Unknown error\",\n \"storage_saveFileFailed\": \"Failed to save file\",\n \"storage_downloadDefaultFilename\": \"download\",\n \"storage_pathTraversal\": \"Path traversal attack detected\",\n \"storage_localNotInitialized\": \"Local storage not initialized\",\n \"storage_configError\": \"Storage config validation failed: {error}\",\n \"storage_metaFileAccess\": \"Direct access to internal files is not allowed: {key}\",\n \"storage_fileAlreadyExists\": \"File already exists: {key}\",\n \"storage_checksumFailed\": \"Failed to calculate checksum\",\n \"storage_checksumInvalid\": \"Invalid checksum or algorithm\",\n \"storage_streamInvalidChunk\": \"File stream must contain byte chunks\",\n \"storage_sharedBackendRequired\": \"Production mode requires a shared backend; local files and memory backends are for development or explicit end-to-end tests.\"\n}\n","{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"storage_notInitialized\": \"ストレージが初期化されていません。先にstorage.init()を呼び出してください\",\n \"storage_bucketNotExist\": \"バケットが存在しません\",\n \"storage_networkError\": \"ネットワークエラー\",\n \"storage_responseBodyEmpty\": \"レスポンス本文が空です\",\n \"storage_presignUrlFailed\": \"署名付きURLの生成に失敗しました\",\n \"storage_presignUploadUrlFailed\": \"アップロード用署名付きURLの生成に失敗しました\",\n \"storage_localPresignUnsupported\": \"ローカルストレージは署名付き URL に対応していません。認証付きのアプリケーションのファイルエンドポイントでアップロードまたはダウンロードしてください\",\n \"storage_listOptionsInvalid\": \"一覧オプションが無効です。maxKeys は1〜1000の整数、prefix と delimiter は有効な Unicode 文字列で指定してください\",\n \"storage_listCursorInvalid\": \"一覧カーソルが無効か、ルート、プレフィックス、区切り文字が一致しません\",\n \"storage_s3ConfigTypeError\": \"設定の型が無効です。S3プロバイダーはtype=s3のみに対応しています\",\n \"storage_diskSpaceInsufficient\": \"ディスクの空き容量が不足しています\",\n \"storage_localConfigTypeError\": \"設定の型が無効です。ローカルプロバイダーはtype=localのみに対応しています\",\n \"storage_operationFailed\": \"操作に失敗しました: {error}\",\n \"storage_unsupportedType\": \"未対応のストレージの種類です: {type}\",\n \"storage_config_bucketRequired\": \"バケットは必須です\",\n \"storage_config_regionRequired\": \"リージョンは必須です\",\n \"storage_config_accessKeyIdRequired\": \"Access Key IDは必須です\",\n \"storage_config_secretAccessKeyRequired\": \"Secret Access Keyは必須です\",\n \"storage_config_rootRequired\": \"ルートパスは必須です\",\n \"storage_fileNotFound\": \"ファイルが見つかりません: {key}\",\n \"storage_permissionDenied\": \"アクセス権限がありません: {key}\",\n \"storage_pathIsDir\": \"パスはディレクトリです: {key}\",\n \"storage_ioError\": \"入出力エラー: {error}\",\n \"storage_deleteManyFailed\": \"{count}個のファイルの削除に失敗しました\",\n \"storage_s3ClientNotInitialized\": \"S3クライアントが初期化されていません\",\n \"storage_s3ConfigNotInitialized\": \"S3設定が初期化されていません\",\n \"storage_uploadCanceled\": \"アップロードをキャンセルしました\",\n \"storage_downloadCanceled\": \"ダウンロードをキャンセルしました\",\n \"storage_uploadFailedStatus\": \"アップロードに失敗しました(ステータス{status}): {statusText}\",\n \"storage_downloadFailedStatus\": \"ダウンロードに失敗しました(ステータス{status}): {statusText}\",\n \"storage_unknownError\": \"不明なエラー\",\n \"storage_saveFileFailed\": \"ファイルの保存に失敗しました\",\n \"storage_downloadDefaultFilename\": \"ダウンロード\",\n \"storage_pathTraversal\": \"パストラバーサル攻撃を検出しました\",\n \"storage_localNotInitialized\": \"ローカルストレージが初期化されていません\",\n \"storage_configError\": \"ストレージ設定の検証に失敗しました: {error}\",\n \"storage_metaFileAccess\": \"内部ファイルへの直接アクセスは許可されていません: {key}\",\n \"storage_fileAlreadyExists\": \"ファイルは既に存在します: {key}\",\n \"storage_checksumFailed\": \"チェックサムの計算に失敗しました\",\n \"storage_checksumInvalid\": \"チェックサムまたはアルゴリズムが無効です\",\n \"storage_streamInvalidChunk\": \"ファイルストリームにはバイトチャンクが必要です\",\n \"storage_sharedBackendRequired\": \"本番環境には共有バックエンドが必要です。ローカルファイルとメモリーバックエンドは開発環境または明示的な E2E テストでのみ使用できます。\"\n}\n","{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"storage_notInitialized\": \"存储未初始化,请先调用 storage.init()\",\n \"storage_bucketNotExist\": \"存储桶不存在\",\n \"storage_networkError\": \"网络错误\",\n \"storage_responseBodyEmpty\": \"响应体为空\",\n \"storage_presignUrlFailed\": \"生成签名 URL 失败\",\n \"storage_presignUploadUrlFailed\": \"生成上传签名 URL 失败\",\n \"storage_localPresignUnsupported\": \"本地存储不支持预签名 URL,请通过应用的鉴权文件接口上传或下载\",\n \"storage_listOptionsInvalid\": \"列表参数无效:maxKeys 必须为 1 到 1000 的整数,prefix 和 delimiter 必须为有效的 Unicode 字符串\",\n \"storage_listCursorInvalid\": \"列表游标无效或与当前根目录、前缀、分隔符不匹配\",\n \"storage_s3ConfigTypeError\": \"配置类型错误:S3 provider 仅支持 type=s3\",\n \"storage_diskSpaceInsufficient\": \"磁盘空间不足\",\n \"storage_localConfigTypeError\": \"配置类型错误:Local provider 仅支持 type=local\",\n \"storage_operationFailed\": \"操作失败: {error}\",\n \"storage_unsupportedType\": \"不支持的存储类型:{type}\",\n \"storage_config_bucketRequired\": \"存储桶不能为空\",\n \"storage_config_regionRequired\": \"区域不能为空\",\n \"storage_config_accessKeyIdRequired\": \"Access Key ID 不能为空\",\n \"storage_config_secretAccessKeyRequired\": \"Secret Access Key 不能为空\",\n \"storage_config_rootRequired\": \"Root 不能为空\",\n \"storage_fileNotFound\": \"文件不存在: {key}\",\n \"storage_permissionDenied\": \"权限不足: {key}\",\n \"storage_pathIsDir\": \"路径是目录: {key}\",\n \"storage_ioError\": \"IO 错误: {error}\",\n \"storage_deleteManyFailed\": \"删除 {count} 个文件失败\",\n \"storage_s3ClientNotInitialized\": \"S3 客户端未初始化\",\n \"storage_s3ConfigNotInitialized\": \"S3 配置未初始化\",\n \"storage_uploadCanceled\": \"上传已取消\",\n \"storage_downloadCanceled\": \"下载已取消\",\n \"storage_uploadFailedStatus\": \"上传失败(状态码 {status}):{statusText}\",\n \"storage_downloadFailedStatus\": \"下载失败(状态码 {status}):{statusText}\",\n \"storage_unknownError\": \"未知错误\",\n \"storage_saveFileFailed\": \"保存文件失败\",\n \"storage_downloadDefaultFilename\": \"下载\",\n \"storage_pathTraversal\": \"检测到路径穿越攻击\",\n \"storage_localNotInitialized\": \"本地存储未初始化\",\n \"storage_configError\": \"存储配置校验失败:{error}\",\n \"storage_metaFileAccess\": \"不允许直接访问内部文件:{key}\",\n \"storage_fileAlreadyExists\": \"文件已存在:{key}\",\n \"storage_checksumFailed\": \"计算校验和失败\",\n \"storage_checksumInvalid\": \"校验和或算法不合法\",\n \"storage_streamInvalidChunk\": \"文件流必须包含字节块\",\n \"storage_sharedBackendRequired\": \"生产模式需要跨节点共享后端;本机文件或内存后端仅用于开发和显式端到端测试。\"\n}\n","/**\n * @h-ai/storage — i18n 消息获取器\n *\n * 加载存储模块的中英文翻译文件,提供统一的文案获取函数。\n * 内部使用,不对外导出。\n * @module storage-i18n\n */\n\nimport { core } from '@h-ai/core'\nimport messagesEnUS from '../messages/en-US.json'\nimport messagesJaJP from '../messages/ja-JP.json'\nimport messagesZhCN from '../messages/zh-CN.json'\n\n/**\n * 消息键类型(从 zh-CN 翻译文件自动推断)\n * @example 模块内部上下文\n * ```ts\n * const key: StorageMessageKey = 'storage_notInitialized'\n * void storageM(key)\n * ```\n */\ntype StorageMessageKey = keyof typeof messagesZhCN\n\n/**\n * 获取存储模块本地化文案\n *\n * 根据当前 locale 自动选择 zh-CN、en-US 或 ja-JP 的文案,\n * 支持带参数插值(通过 `{ params: { key: value } }`)。\n * @example 模块内部上下文\n * ```ts\n * void storageM('storage_notInitialized', { locale: 'en-US' })\n * ```\n */\nexport const storageM = core.i18n.createMessageGetter<StorageMessageKey>({\n 'zh-CN': messagesZhCN,\n 'en-US': messagesEnUS,\n 'ja-JP': messagesJaJP,\n})\n","/**\n * @h-ai/storage — MIME 类型映射\n *\n * 集中维护文件扩展名到 MIME 类型的映射表。 供 local provider 和 client 端共用,避免重复定义。\n * @module storage-mime\n */\n\n/**\n * 文件扩展名 → MIME 类型映射表\n *\n * 键为小写扩展名(不含点号),值为标准 MIME 类型字符串。\n * 未匹配项统一回退到 `application/octet-stream`。\n * @example 模块内部上下文\n * ```ts\n * const mime = MIME_TYPES['pdf'] ?? MIME_TYPE_DEFAULT\n * void mime\n * ```\n */\nexport const MIME_TYPES: Record<string, string> = {\n // 图片\n 'jpg': 'image/jpeg',\n 'jpeg': 'image/jpeg',\n 'png': 'image/png',\n 'gif': 'image/gif',\n 'webp': 'image/webp',\n 'svg': 'image/svg+xml',\n 'ico': 'image/x-icon',\n\n // 文档\n 'pdf': 'application/pdf',\n 'doc': 'application/msword',\n 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'xls': 'application/vnd.ms-excel',\n 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'ppt': 'application/vnd.ms-powerpoint',\n 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n\n // 文本\n 'txt': 'text/plain',\n 'html': 'text/html',\n 'css': 'text/css',\n 'js': 'text/javascript',\n 'json': 'application/json',\n 'xml': 'application/xml',\n 'csv': 'text/csv',\n 'md': 'text/markdown',\n\n // 音视频\n 'mp3': 'audio/mpeg',\n 'wav': 'audio/wav',\n 'mp4': 'video/mp4',\n 'webm': 'video/webm',\n 'ogg': 'audio/ogg',\n\n // 压缩包\n 'zip': 'application/zip',\n 'rar': 'application/x-rar-compressed',\n '7z': 'application/x-7z-compressed',\n 'tar': 'application/x-tar',\n 'gz': 'application/gzip',\n\n // 字体\n 'woff': 'font/woff',\n 'woff2': 'font/woff2',\n 'ttf': 'font/ttf',\n 'otf': 'font/otf',\n}\n\n/**\n * MIME 类型默认值(未知扩展名回退)\n * @example 模块内部上下文\n * ```ts\n * void MIME_TYPE_DEFAULT // application/octet-stream\n * ```\n */\nexport const MIME_TYPE_DEFAULT = 'application/octet-stream'\n","/**\n * @h-ai/storage — 前端存储客户端\n *\n * 提供前端通过签名 URL 直接上传下载文件的功能。\n * @module storage-client\n */\n\nimport { storageM } from '../storage-i18n.js'\nimport { MIME_TYPE_DEFAULT, MIME_TYPES } from '../storage-mime.js'\n\n// ─── 客户端类型 ───\n\n/**\n * 上传进度回调数据\n *\n * 由 `uploadWithPresignedUrl` 在上传过程中通过 `onProgress` 回调返回。\n * @example\n * ```ts\n * import type { UploadProgress } from '@h-ai/storage'\n *\n * const value: UploadProgress = { loaded: 50, total: 100, percent: 50 }\n * void value\n * ```\n */\nexport interface UploadProgress {\n /** 已上传字节数 */\n loaded: number\n /** 总字节数(仅在 lengthComputable 时可靠) */\n total: number\n /** 进度百分比 (0–100),四舍五入到整数 */\n percent: number\n}\n\n/**\n * 前端上传选项\n *\n * 控制 `uploadWithPresignedUrl` 的行为,包括内容类型、进度监听和取消。\n * @example\n * ```ts\n * import type { ClientUploadOptions } from '@h-ai/storage'\n *\n * const value: ClientUploadOptions = { contentType: 'text/plain', onProgress: progress => { document.title = `${progress.percent}%` } }\n * void value\n * ```\n */\nexport interface ClientUploadOptions {\n /** 内容类型(设置后会加到请求 Content-Type 头) */\n contentType?: string\n /** 进度回调(设置后会使用 XMLHttpRequest;仅 lengthComputable=true 时回调) */\n onProgress?: (progress: UploadProgress) => void\n /** 取消上传;预取消不发送请求,进行中取消返回失败,完成后移除本次监听。取消不能撤销远端已写入对象。 */\n abortController?: AbortController\n}\n\n/**\n * 前端下载选项\n *\n * 控制 `downloadWithPresignedUrl` / `downloadAndSave` 的行为。\n * @example\n * ```ts\n * import type { ClientDownloadOptions } from '@h-ai/storage'\n *\n * const value: ClientDownloadOptions = { filename: 'report.pdf', abortController: new AbortController() }\n * void value\n * ```\n */\nexport interface ClientDownloadOptions {\n /** 保存的文件名(仅 downloadAndSave 使用,默认为 'download') */\n filename?: string\n /** AbortController 用于取消下载(abort 后返回 {success: false}) */\n abortController?: AbortController\n}\n\n// ─── 上传功能 ───\n\n/**\n * 使用签名 URL 上传文件\n *\n * @param url - 已成功获取的 S3 HTTP(S) 上传签名 URL;本地存储须使用应用的鉴权文件接口。\n * @param data - 要上传的数据(File、Blob、ArrayBuffer 或字符串)\n * @param options - 上传选项\n * @returns 上传结果;预取消不发送,进行中取消/网络失败/初始化异常结算为失败,终态移除本次监听。\n * 取消不代表远端对象已回滚;上传完成后的撤销由应用另行授权删除。\n * @example\n * ```ts\n * import { uploadWithPresignedUrl } from '@h-ai/storage/client'\n *\n * // url 由应用鉴权接口返回,且 S3 签名包含相同 Content-Type。\n * async function upload(url: string, file: File) {\n * const result = await uploadWithPresignedUrl(url, file, { contentType: file.type })\n * if (!result.success) throw new Error(result.error)\n * }\n * ```\n */\nexport async function uploadWithPresignedUrl(\n url: string,\n data: File | Blob | ArrayBuffer | string,\n options: ClientUploadOptions = {},\n): Promise<{ success: boolean, error?: string }> {\n try {\n const signal = options.abortController?.signal\n if (signal?.aborted)\n return { success: false, error: storageM('storage_uploadCanceled') }\n\n // 准备请求体\n const body: BodyInit = data\n\n // 使用 XMLHttpRequest 以支持上传进度\n if (options.onProgress) {\n const xhr = new XMLHttpRequest()\n return new Promise((resolve) => {\n let settled = false\n\n /**\n * 所有终态共用一次结算,先移除XHR/上传/signal监听,迟到事件不能改变结果。\n * 后续调用跳过,完成后取消不会再触碰旧XHR。\n * @param result - 本次最终结果,不推断远端是否已写对象。\n * @param result.success - 是否已成功完成本地请求。\n * @param result.error - 可选失败说明。\n * @example 模块内部上下文\n * ```ts\n * finish({ success: false, error: storageM('storage_networkError') })\n * ```\n */\n function finish(result: { success: boolean, error?: string }): void {\n if (settled)\n return\n settled = true\n signal?.removeEventListener('abort', onCancel)\n xhr.upload.removeEventListener('progress', onProgress)\n xhr.removeEventListener('load', onLoad)\n xhr.removeEventListener('error', onError)\n xhr.removeEventListener('abort', onAbort)\n xhr.removeEventListener('timeout', onError)\n resolve(result)\n }\n\n function onAbort(): void {\n finish({ success: false, error: storageM('storage_uploadCanceled') })\n }\n\n /**\n * signal取消先主动结算再终止XHR,覆盖未send时abort不发事件的原生行为。\n * 调用方在准备流程中检查settled后停止发送。\n * @example 模块内部上下文\n * ```ts\n * onCancel()\n * // 后续准备阶段读取settled,不再执行xhr.send。\n * ```\n */\n function onCancel(): void {\n // 未 send 的 XHR.abort 不一定发 abort 事件,取消方必须主动结算。\n onAbort()\n xhr.abort()\n }\n\n function onError(): void {\n finish({ success: false, error: storageM('storage_networkError') })\n }\n\n function onLoad(): void {\n finish(xhr.status >= 200 && xhr.status < 300\n ? { success: true }\n : { success: false, error: storageM('storage_uploadFailedStatus', { params: { status: xhr.status, statusText: xhr.statusText } }) })\n }\n\n function onProgress(event: ProgressEvent): void {\n if (settled || !event.lengthComputable)\n return\n try {\n options.onProgress?.({ loaded: event.loaded, total: event.total, percent: Math.round((event.loaded / event.total) * 100) })\n }\n catch (error) {\n finish({ success: false, error: error instanceof Error ? error.message : storageM('storage_unknownError') })\n xhr.abort()\n }\n }\n\n try {\n xhr.upload.addEventListener('progress', onProgress)\n xhr.addEventListener('load', onLoad)\n xhr.addEventListener('error', onError)\n xhr.addEventListener('abort', onAbort)\n xhr.addEventListener('timeout', onError)\n signal?.addEventListener('abort', onCancel, { once: true })\n xhr.open('PUT', url)\n if (settled)\n return\n if (options.contentType)\n xhr.setRequestHeader('Content-Type', options.contentType)\n // 覆盖从预检查到监听安装/请求准备之间发生的取消,不再进入 send。\n if (signal?.aborted) {\n onCancel()\n return\n }\n xhr.send(body)\n }\n catch (error) {\n finish({ success: false, error: error instanceof Error ? error.message : storageM('storage_unknownError') })\n xhr.abort()\n }\n })\n }\n\n // 不需要进度时使用 fetch\n const response = await fetch(url, {\n method: 'PUT',\n body,\n headers: options.contentType\n ? { 'Content-Type': options.contentType }\n : undefined,\n signal,\n })\n\n if (!response.ok) {\n return {\n success: false,\n error: storageM('storage_uploadFailedStatus', { params: { status: response.status, statusText: response.statusText } }),\n }\n }\n\n return { success: true }\n }\n catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n return { success: false, error: storageM('storage_uploadCanceled') }\n }\n return {\n success: false,\n error: error instanceof Error ? error.message : storageM('storage_unknownError'),\n }\n }\n}\n\n// ─── 下载功能 ───\n\n/**\n * 使用签名 URL 下载文件\n *\n * @param url - 后端生成的下载签名 URL\n * @param options - 下载选项\n * @returns 下载结果(成功时 data 为 Blob)\n * @example\n * ```ts\n * import { downloadWithPresignedUrl } from '@h-ai/storage/client'\n *\n * async function download(url: string) {\n * const result = await downloadWithPresignedUrl(url)\n * if (!result.success || !result.data) throw new Error(result.error ?? 'Empty download')\n * return result.data\n * }\n * ```\n */\nexport async function downloadWithPresignedUrl(\n url: string,\n options: ClientDownloadOptions = {},\n): Promise<{ success: boolean, data?: Blob, error?: string }> {\n try {\n const response = await fetch(url, {\n method: 'GET',\n signal: options.abortController?.signal,\n })\n\n if (!response.ok) {\n return {\n success: false,\n error: storageM('storage_downloadFailedStatus', { params: { status: response.status, statusText: response.statusText } }),\n }\n }\n\n const blob = await response.blob()\n return { success: true, data: blob }\n }\n catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n return { success: false, error: storageM('storage_downloadCanceled') }\n }\n return {\n success: false,\n error: error instanceof Error ? error.message : storageM('storage_unknownError'),\n }\n }\n}\n\n/**\n * 下载并保存到本地(浏览器环境)\n *\n * @param url - 下载 URL\n * @param options - 下载选项\n * @returns 声明返回类型对应的操作结果。\n * @example\n * ```ts\n * import { downloadAndSave } from '@h-ai/storage/client'\n *\n * async function save(url: string) {\n * const result = await downloadAndSave(url, { filename: 'report.pdf' })\n * if (!result.success) throw new Error(result.error)\n * }\n * ```\n */\nexport async function downloadAndSave(\n url: string,\n options: ClientDownloadOptions = {},\n): Promise<{ success: boolean, error?: string }> {\n const result = await downloadWithPresignedUrl(url, options)\n\n if (!result.success || !result.data) {\n return { success: false, error: result.error }\n }\n\n try {\n // 创建下载链接\n const blobUrl = URL.createObjectURL(result.data)\n const link = document.createElement('a')\n link.href = blobUrl\n link.download = options.filename || storageM('storage_downloadDefaultFilename')\n\n // 触发下载\n document.body.appendChild(link)\n link.click()\n document.body.removeChild(link)\n\n // 清理 URL\n URL.revokeObjectURL(blobUrl)\n\n return { success: true }\n }\n catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : storageM('storage_saveFileFailed'),\n }\n }\n}\n\n// ─── 辅助功能 ───\n\n/**\n * 从 File 对象获取文件扩展名\n *\n * 取文件名中最后一个 '.' 之后的部分,转为小写。\n * 没有点号或以点号结尾时返回空字符串;仅前导点号的文件名按其后文本返回扩展名。\n *\n * @param file - 浏览器 File 对象\n * @returns 扩展名(不含点),如 'png'、'tar.gz' 返回 'gz'\n * @example\n * ```ts\n * import { getFileExtension } from '@h-ai/storage/client'\n *\n * const extension = getFileExtension(new File(['hello'], 'report.txt'))\n * void extension\n * ```\n */\nexport function getFileExtension(file: File): string {\n const name = file.name\n const lastDot = name.lastIndexOf('.')\n return lastDot > -1 ? name.slice(lastDot + 1).toLowerCase() : ''\n}\n\n/**\n * 根据文件扩展名获取 MIME 类型\n *\n * 内置常见扩展名映射(图片/文档/文本/音视频/压缩包),\n * 未匹配时返回 'application/octet-stream'。\n *\n * @param extension - 文件扩展名(不含点,大小写不敏感)\n * @returns MIME 类型字符串\n * @example\n * ```ts\n * import { getMimeType } from '@h-ai/storage/client'\n *\n * const mime = getMimeType('pdf')\n * void mime\n * ```\n */\nexport function getMimeType(extension: string): string {\n return MIME_TYPES[extension.toLowerCase()] || MIME_TYPE_DEFAULT\n}\n\n/**\n * 格式化文件大小为人类可读字符串\n *\n * 使用 1024 进制,输出单位为 B/KB/MB/GB/TB。\n * 非零值使用对应单位并保留 2 位小数。\n *\n * @param bytes - 字节数(非负整数)\n * @returns 格式化后的字符串,如 '0 B'、'1.50 KB'、'2.30 MB'\n * @example\n * ```ts\n * import { formatFileSize } from '@h-ai/storage/client'\n *\n * void formatFileSize(1536)\n * ```\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0)\n return '0 B'\n\n const units = ['B', 'KB', 'MB', 'GB', 'TB']\n const i = Math.floor(Math.log(bytes) / Math.log(1024))\n const size = bytes / 1024 ** i\n\n return `${size.toFixed(i > 0 ? 2 : 0)} ${units[i]}`\n}\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
// src/storage-schemas.ts
|
|
4
|
+
var STORAGE_KEY_MAX_BYTES = 1024;
|
|
5
|
+
var STORAGE_DELETE_MAX_KEYS = 1e3;
|
|
6
|
+
function getStorageKeyIssue(key, allowEmpty = false) {
|
|
7
|
+
if (allowEmpty && key === "")
|
|
8
|
+
return void 0;
|
|
9
|
+
if (key.trim() === "")
|
|
10
|
+
return "empty";
|
|
11
|
+
if (new TextEncoder().encode(key).byteLength > STORAGE_KEY_MAX_BYTES)
|
|
12
|
+
return "tooLong";
|
|
13
|
+
if (key.startsWith("/") || key.includes("\\") || [...key].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
|
|
14
|
+
return "characters";
|
|
15
|
+
if (key.split("/").some((segment) => segment === "." || segment === ".."))
|
|
16
|
+
return "segments";
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
function keySchema(allowEmpty) {
|
|
20
|
+
return z.string().superRefine((key, context) => {
|
|
21
|
+
const issue = getStorageKeyIssue(key, allowEmpty);
|
|
22
|
+
if (issue)
|
|
23
|
+
context.addIssue({ code: "custom", params: { storageKeyIssue: issue } });
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
var StorageKeySchema = keySchema(false);
|
|
27
|
+
var StoragePrefixSchema = keySchema(true);
|
|
28
|
+
var StorageDeleteKeysSchema = z.array(StorageKeySchema).min(1).max(STORAGE_DELETE_MAX_KEYS);
|
|
29
|
+
var StoragePresignExpiresInSchema = z.number().int().min(1).max(604800);
|
|
30
|
+
|
|
31
|
+
export { STORAGE_DELETE_MAX_KEYS, STORAGE_KEY_MAX_BYTES, StorageDeleteKeysSchema, StorageKeySchema, StoragePrefixSchema, StoragePresignExpiresInSchema, getStorageKeyIssue };
|
|
32
|
+
//# sourceMappingURL=chunk-P65WYXJG.js.map
|
|
33
|
+
//# sourceMappingURL=chunk-P65WYXJG.js.map
|