@lhy-meta-server/meta-oss 0.0.0-stage → 0.0.1
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/LICENSE +21 -0
- package/README.md +378 -3
- package/dist/ali-oss/client.d.ts +185 -0
- package/dist/ali-oss/client.d.ts.map +1 -0
- package/dist/ali-oss/driver.d.ts +156 -0
- package/dist/ali-oss/driver.d.ts.map +1 -0
- package/dist/ali-oss/index.d.ts +11 -0
- package/dist/ali-oss/index.d.ts.map +1 -0
- package/dist/ali-oss/index.js +3 -0
- package/dist/ali-oss/index.js.map +11 -0
- package/dist/ali-oss/mapping.d.ts +25 -0
- package/dist/ali-oss/mapping.d.ts.map +1 -0
- package/dist/core/driver.d.ts +194 -0
- package/dist/core/driver.d.ts.map +1 -0
- package/dist/core/errors.d.ts +106 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/execution.d.ts +130 -0
- package/dist/core/execution.d.ts.map +1 -0
- package/dist/core/keys.d.ts +34 -0
- package/dist/core/keys.d.ts.map +1 -0
- package/dist/core/mime.d.ts +35 -0
- package/dist/core/mime.d.ts.map +1 -0
- package/dist/core/observability.d.ts +224 -0
- package/dist/core/observability.d.ts.map +1 -0
- package/dist/core/payload.d.ts +81 -0
- package/dist/core/payload.d.ts.map +1 -0
- package/dist/core/policy.d.ts +31 -0
- package/dist/core/policy.d.ts.map +1 -0
- package/dist/core/storage.d.ts +344 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/types.d.ts +301 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/validation.d.ts +73 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/elysia/index.d.ts +81 -0
- package/dist/elysia/index.d.ts.map +1 -0
- package/dist/elysia/index.js +3 -0
- package/dist/elysia/index.js.map +10 -0
- package/dist/filesystem/driver.d.ts +242 -0
- package/dist/filesystem/driver.d.ts.map +1 -0
- package/dist/filesystem/durability.d.ts +28 -0
- package/dist/filesystem/durability.d.ts.map +1 -0
- package/dist/filesystem/index.d.ts +11 -0
- package/dist/filesystem/index.d.ts.map +1 -0
- package/dist/filesystem/index.js +4 -0
- package/dist/filesystem/index.js.map +14 -0
- package/dist/filesystem/metadata.d.ts +56 -0
- package/dist/filesystem/metadata.d.ts.map +1 -0
- package/dist/filesystem/paths.d.ts +48 -0
- package/dist/filesystem/paths.d.ts.map +1 -0
- package/dist/filesystem/signer.d.ts +58 -0
- package/dist/filesystem/signer.d.ts.map +1 -0
- package/dist/index-5w45sfp6.js +4 -0
- package/dist/index-5w45sfp6.js.map +10 -0
- package/dist/index-7dpmhwry.js +4 -0
- package/dist/index-7dpmhwry.js.map +11 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +16 -0
- package/package.json +81 -6
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["..\\src\\core\\driver.ts", "..\\src\\core\\keys.ts", "..\\src\\core\\mime.ts", "..\\src\\core\\observability.ts", "..\\src\\core\\execution.ts", "..\\src\\core\\policy.ts", "..\\src\\core\\storage.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type { StoragePayload } from './payload'\nimport type {\n DeleteManyResult,\n ListResult,\n MultipartUpload,\n ObjectHttpOptions,\n ObjectInfo,\n ObjectRange,\n ObjectStream,\n SignedUrlMethod,\n UploadedPart,\n VerifiedUrl,\n VerifyUrlRequest,\n} from './types'\n\n/**\n * 服务层传给适配器的单次执行上下文。\n */\nexport interface DriverContext {\n /**\n * 合并了调用方取消与超时的信号;适配器应尽可能将其传给底层 I/O。\n */\n signal: AbortSignal\n}\n\n/**\n * 适配器写入对象时收到的已归一化参数。\n */\nexport interface DriverPutOptions extends ObjectHttpOptions, DriverContext {\n /**\n * 已推断或校验后的 MIME 类型。\n */\n contentType: string\n /**\n * 已校验的自定义元数据。\n */\n metadata: Record<string, string>\n /**\n * 同名对象存在时是否覆盖;为 `false` 时适配器必须原子地拒绝并抛出 `ALREADY_EXISTS`。\n */\n overwrite: boolean\n}\n\n/**\n * 适配器读取对象时收到的参数。\n */\nexport interface DriverGetOptions extends DriverContext {\n /**\n * 已校验的读取范围。\n */\n range?: ObjectRange\n}\n\n/**\n * 适配器列举对象时收到的已归一化参数。\n */\nexport interface DriverListOptions extends DriverContext {\n /**\n * 对象键前缀,未设置时为空字符串。\n */\n prefix: string\n /**\n * 目录分隔符。\n */\n delimiter?: string\n /**\n * 上一页游标。\n */\n cursor?: string\n /**\n * 单页最大条目数。\n */\n limit: number\n}\n\n/**\n * 适配器生成签名 URL 时收到的已归一化参数。\n */\nexport interface DriverSignOptions extends DriverContext {\n /**\n * HTTP 方法。\n */\n method: SignedUrlMethod\n /**\n * 有效期秒数。\n */\n expiresIn: number\n /**\n * `PUT` 签名要求的 MIME 类型。\n */\n contentType?: string\n /**\n * `GET` 下载时覆盖的 `Content-Disposition`。\n */\n responseContentDisposition?: string\n}\n\n/**\n * 适配器复制对象时收到的参数。\n */\nexport interface DriverCopyOptions extends DriverContext {\n /**\n * 目标对象存在时是否覆盖。\n */\n overwrite: boolean\n}\n\n/**\n * 可选的分片上传能力。\n */\nexport interface MultipartDriver {\n /**\n * 创建分片上传会话。\n */\n createMultipart(key: string, options: DriverPutOptions): Promise<MultipartUpload>\n /**\n * 上传单个分片,分片内容总是可重复读取且大小已知。\n */\n uploadPart(\n upload: MultipartUpload,\n partNumber: number,\n payload: StoragePayload,\n context: DriverContext,\n ): Promise<UploadedPart>\n /**\n * 按分片序号升序合并全部分片。\n */\n completeMultipart(upload: MultipartUpload, parts: UploadedPart[], context: DriverContext): Promise<ObjectInfo>\n /**\n * 放弃会话并清理已上传的分片,会话不存在时应静默成功。\n */\n abortMultipart(upload: MultipartUpload, context: DriverContext): Promise<void>\n}\n\n/**\n * 存储适配器契约。\n *\n * 适配器只负责与具体后端交互;键校验、前缀、超时、重试、上传限制、\n * 并发控制与可观测性都由 `ObjectStorage` 统一处理。适配器收到的对象键\n * 已经包含服务级前缀并通过安全校验。\n *\n * 所有方法都必须把“对象不存在”归一化为 `NOT_FOUND` 的 `StorageError`。\n * 可选能力缺失时,服务层会抛出 `UNSUPPORTED`。\n */\nexport interface StorageDriver {\n /**\n * 适配器名称,用于可观测事件,例如 `filesystem`、`ali-oss`。\n */\n readonly name: string\n /**\n * 写入对象并返回其描述信息。\n */\n put(key: string, payload: StoragePayload, options: DriverPutOptions): Promise<ObjectInfo>\n /**\n * 以流方式读取对象。\n */\n get(key: string, options: DriverGetOptions): Promise<ObjectStream>\n /**\n * 读取对象描述信息。\n */\n head(key: string, context: DriverContext): Promise<ObjectInfo>\n /**\n * 删除对象;对象不存在时应静默成功。\n */\n delete(key: string, context: DriverContext): Promise<void>\n /**\n * 批量删除对象;未实现时服务层会以受控并发逐个删除。\n */\n deleteMany?(keys: string[], context: DriverContext): Promise<DeleteManyResult>\n /**\n * 分页列举对象。\n */\n list?(options: DriverListOptions): Promise<ListResult>\n /**\n * 在后端内部复制对象;未实现时服务层会读取后重新写入。\n */\n copy?(source: string, destination: string, options: DriverCopyOptions): Promise<ObjectInfo>\n /**\n * 生成签名访问 URL。\n */\n signUrl?(key: string, options: DriverSignOptions): Promise<string>\n /**\n * 校验由本适配器签发的签名 URL;仅自签名的后端(如本地文件系统)需要实现。\n */\n verifyUrl?(request: VerifyUrlRequest): VerifiedUrl\n /**\n * 分片上传能力。\n */\n multipart?: MultipartDriver\n /**\n * 释放连接、句柄等资源。\n */\n close?(): Promise<void>\n}\n\n/**\n * 以最少代码接入任意第三方对象存储。\n *\n * 只需实现 `put`、`get`、`head`、`delete` 四个方法即可获得服务层的全部能力\n * (校验、重试、超时、并发、可观测性、批量删除与复制降级);其余能力按需补充。\n * 底层 SDK 抛出的 404/403/409/5xx 等错误会被服务层自动归一化。\n *\n * @typeParam Driver 适配器的具体类型。\n * @param driver 适配器实现。\n * @returns 原样返回的适配器,用于获得类型检查与推断。\n *\n * @example\n * ```ts\n * const driver = defineDriver({\n * name: 'my-oss',\n * async put(key, payload, options) { ... },\n * async get(key, options) { ... },\n * async head(key) { ... },\n * async delete(key) { ... },\n * })\n * const storage = new ObjectStorage({ driver })\n * ```\n */\nexport function defineDriver<const Driver extends StorageDriver>(driver: Driver): Driver {\n return driver\n}\n",
|
|
6
|
+
"import { randomUUID } from 'node:crypto'\nimport { extensionOf } from './mime'\n\n/**\n * 根据原始文件名生成对象键的函数。\n */\nexport type KeyGenerator = (filename: string) => string\n\n/**\n * 默认对象键生成器的选项。\n */\nexport interface CreateObjectKeyOptions {\n /**\n * 生成键的目录前缀,例如 `avatars`。\n */\n directory?: string\n /**\n * 用于日期目录的时间,默认为当前时间。\n */\n date?: Date\n}\n\n/**\n * 生成形如 `yyyy/mm/dd/<uuid>.<ext>` 的不可预测对象键。\n *\n * 原始文件名只用于提取扩展名,扩展名仅保留字母与数字,避免用户输入污染路径。\n *\n * @param filename 原始文件名,例如 `头像.PNG`。\n * @param options 目录前缀与日期。\n * @returns 新的对象键,例如 `avatars/2026/10/13/<uuid>.png`。\n *\n * @example\n * ```ts\n * createObjectKey('report.pdf', { directory: 'docs' })\n * // docs/2026/10/13/3b1f...e2.pdf\n * ```\n */\nexport function createObjectKey(filename: string, options: CreateObjectKeyOptions = {}): string {\n const date = options.date ?? new Date()\n const year = String(date.getUTCFullYear())\n const month = String(date.getUTCMonth() + 1).padStart(2, '0')\n const day = String(date.getUTCDate()).padStart(2, '0')\n const extension = extensionOf(filename)\n .replace(/[^a-z0-9]/g, '')\n .slice(0, 16)\n const name = extension ? `${randomUUID()}.${extension}` : randomUUID()\n const directory = options.directory?.replace(/^\\/+|\\/+$/g, '')\n return [directory, year, month, day, name].filter(Boolean).join('/')\n}\n",
|
|
7
|
+
"/**\n * 未知扩展名时采用的默认 MIME 类型。\n */\nexport const DEFAULT_CONTENT_TYPE = 'application/octet-stream'\n\nconst builtinTypes: Readonly<Record<string, string>> = {\n aac: 'audio/aac',\n avif: 'image/avif',\n bmp: 'image/bmp',\n css: 'text/css; charset=utf-8',\n csv: 'text/csv; charset=utf-8',\n doc: 'application/msword',\n docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n gif: 'image/gif',\n gz: 'application/gzip',\n htm: 'text/html; charset=utf-8',\n html: 'text/html; charset=utf-8',\n ico: 'image/x-icon',\n jpeg: 'image/jpeg',\n jpg: 'image/jpeg',\n js: 'text/javascript; charset=utf-8',\n json: 'application/json',\n m4a: 'audio/mp4',\n md: 'text/markdown; charset=utf-8',\n mjs: 'text/javascript; charset=utf-8',\n mov: 'video/quicktime',\n mp3: 'audio/mpeg',\n mp4: 'video/mp4',\n ogg: 'audio/ogg',\n pdf: 'application/pdf',\n png: 'image/png',\n ppt: 'application/vnd.ms-powerpoint',\n pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n svg: 'image/svg+xml',\n tar: 'application/x-tar',\n txt: 'text/plain; charset=utf-8',\n wav: 'audio/wav',\n webm: 'video/webm',\n webp: 'image/webp',\n woff: 'font/woff',\n woff2: 'font/woff2',\n xls: 'application/vnd.ms-excel',\n xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n xml: 'application/xml',\n zip: 'application/zip',\n}\n\n/**\n * 读取对象键的小写扩展名,不含点号。\n *\n * @param key 对象键。\n * @returns 扩展名;无扩展名时返回空字符串。\n */\nexport function extensionOf(key: string): string {\n const name = key.slice(key.lastIndexOf('/') + 1)\n const dot = name.lastIndexOf('.')\n return dot <= 0 ? '' : name.slice(dot + 1).toLowerCase()\n}\n\n/**\n * 按对象键扩展名推断 MIME 类型。\n *\n * @param key 对象键。\n * @param overrides 自定义扩展名映射,优先于内置映射,键为不含点号的小写扩展名。\n * @returns 推断出的 MIME 类型;无法识别时返回 `application/octet-stream`。\n */\nexport function lookupContentType(key: string, overrides: Readonly<Record<string, string>> = {}): string {\n const extension = extensionOf(key)\n return overrides[extension] ?? builtinTypes[extension] ?? DEFAULT_CONTENT_TYPE\n}\n\n/**\n * 去除 MIME 类型的参数部分并转换为小写,用于比较。\n *\n * @param contentType 完整 MIME 类型,例如 `text/plain; charset=utf-8`。\n * @returns 基础 MIME 类型,例如 `text/plain`。\n */\nexport function essenceOf(contentType: string): string {\n return contentType.split(';', 1)[0]?.trim().toLowerCase() ?? ''\n}\n\n/**\n * 判断 MIME 类型是否命中允许列表,支持 `image/*` 形式的通配。\n *\n * @param contentType 待判断的 MIME 类型。\n * @param patterns 允许列表。\n * @returns 是否命中。\n */\nexport function matchContentType(contentType: string, patterns: readonly string[]): boolean {\n const essence = essenceOf(contentType)\n return patterns.some((pattern) => {\n const normalized = pattern.toLowerCase()\n return normalized.endsWith('/*') ? essence.startsWith(normalized.slice(0, -1)) : essence === normalized\n })\n}\n",
|
|
8
|
+
"import type { StorageError, StorageErrorCode } from './errors'\n\n/**\n * 可观测的对象存储操作名称。\n */\nexport type StorageOperation =\n | 'put'\n | 'get'\n | 'head'\n | 'exists'\n | 'delete'\n | 'deleteMany'\n | 'list'\n | 'copy'\n | 'move'\n | 'signUrl'\n | 'createMultipart'\n | 'uploadPart'\n | 'completeMultipart'\n | 'abortMultipart'\n | 'close'\n\n/**\n * 操作结果分类。\n */\nexport type StorageOutcome = 'success' | 'error'\n\n/**\n * 单次操作完成后产生的结构化事件。\n *\n * 事件不会包含凭据、签名或签名 URL;`key` 为业务可见键,不含服务级前缀。\n */\nexport interface StorageEvent {\n /**\n * 操作名称。\n */\n readonly operation: StorageOperation\n /**\n * 操作结果。\n */\n readonly outcome: StorageOutcome\n /**\n * 适配器名称。\n */\n readonly driver: string\n /**\n * 关联的对象键;批量或列举操作时省略。\n */\n readonly key?: string\n /**\n * 包含重试在内的总耗时毫秒数。\n */\n readonly duration: number\n /**\n * 实际尝试次数,首次执行计为 1。\n */\n readonly attempts: number\n /**\n * 写入或读取的已知字节数。\n */\n readonly bytes?: number\n /**\n * 失败时的稳定错误码。\n */\n readonly errorCode?: StorageErrorCode\n /**\n * 失败时的脱敏错误摘要,可直接写入日志。\n */\n readonly error?: StorageErrorSummary\n /**\n * 事件产生时的 Unix 毫秒时间戳。\n */\n readonly timestamp: number\n}\n\n/**\n * 写入可观测事件的脱敏错误摘要。\n *\n * 只保留分类与排障所需的少量字段,不包含原始错误对象:SDK 错误通常携带请求参数、\n * 请求头或签名信息,直接记录存在泄露凭据的风险。需要完整错误时请在业务调用处捕获。\n */\nexport interface StorageErrorSummary {\n /**\n * 稳定错误码。\n */\n readonly code: StorageErrorCode\n /**\n * 存储错误消息;底层原始消息不会出现在此处。\n */\n readonly message: string\n /**\n * 是否可重试。\n */\n readonly retryable: boolean\n /**\n * 底层错误的 HTTP 状态码。\n */\n readonly status?: number\n /**\n * 底层错误的字符串错误码,例如 `NoSuchKey`、`ECONNRESET`。\n */\n readonly causeCode?: string\n /**\n * 底层错误携带的请求 ID,便于向云厂商排障。\n */\n readonly requestId?: string\n}\n\n/**\n * 每次重试前产生的事件。\n */\nexport interface StorageRetryEvent {\n /**\n * 操作名称。\n */\n readonly operation: StorageOperation\n /**\n * 适配器名称。\n */\n readonly driver: string\n /**\n * 关联的对象键。\n */\n readonly key?: string\n /**\n * 即将开始的尝试序号。\n */\n readonly attempt: number\n /**\n * 本次重试前的等待毫秒数。\n */\n readonly delay: number\n /**\n * 触发重试的脱敏错误摘要。\n */\n readonly error: StorageErrorSummary\n}\n\n/**\n * 结构化事件观察器。\n *\n * 回调应快速返回并把耗时上报放入自身队列;回调抛出的异常会被服务隔离,\n * 不会影响业务请求。\n */\nexport interface StorageObserver {\n /**\n * 每次操作完成(成功或失败)后调用。\n */\n onOperation?(event: StorageEvent): void\n /**\n * 每次重试等待前调用。\n */\n onRetry?(event: StorageRetryEvent): void\n}\n\n/**\n * 进程内累计指标快照。\n */\nexport interface StorageMetricsSnapshot {\n /**\n * 已完成的操作数量。\n */\n readonly operations: number\n /**\n * 失败的操作数量。\n */\n readonly errors: number\n /**\n * 重试次数。\n */\n readonly retries: number\n /**\n * 当前执行中的操作数量。\n */\n readonly inFlight: number\n /**\n * 成功写入的已知字节数。\n */\n readonly bytesWritten: number\n /**\n * 所有操作累计耗时毫秒数。\n */\n readonly totalDuration: number\n /**\n * 按操作名称累计的调用数量。\n */\n readonly byOperation: Readonly<Partial<Record<StorageOperation, number>>>\n /**\n * 按错误码累计的失败数量。\n */\n readonly byErrorCode: Readonly<Partial<Record<StorageErrorCode, number>>>\n}\n\n/**\n * 低开销的进程内对象存储指标收集器。\n *\n * 可在多个 `ObjectStorage` 实例间共享,用于汇总同一进程的指标。\n */\nexport class StorageMetrics {\n private operations = 0\n private errors = 0\n private retries = 0\n private inFlight = 0\n private bytesWritten = 0\n private totalDuration = 0\n private byOperation: Partial<Record<StorageOperation, number>> = {}\n private byErrorCode: Partial<Record<StorageErrorCode, number>> = {}\n\n /**\n * 创建空的指标收集器。\n */\n public constructor() {}\n\n /**\n * 标记一个操作开始执行。\n */\n public start(): void {\n this.inFlight += 1\n }\n\n /**\n * 记录一个已完成操作。\n *\n * @param event 操作完成事件。\n */\n public record(event: StorageEvent): void {\n this.inFlight = Math.max(0, this.inFlight - 1)\n this.operations += 1\n this.totalDuration += event.duration\n this.byOperation[event.operation] = (this.byOperation[event.operation] ?? 0) + 1\n if (event.outcome === 'error') {\n this.errors += 1\n const code = event.errorCode ?? 'BACKEND'\n this.byErrorCode[code] = (this.byErrorCode[code] ?? 0) + 1\n } else if (event.operation === 'put' || event.operation === 'uploadPart') {\n this.bytesWritten += event.bytes ?? 0\n }\n }\n\n /**\n * 记录一次重试。\n */\n public retry(): void {\n this.retries += 1\n }\n\n /**\n * 返回当前累计指标的冻结快照。\n *\n * @returns 与后续更新隔离的只读快照。\n */\n public snapshot(): StorageMetricsSnapshot {\n return Object.freeze({\n operations: this.operations,\n errors: this.errors,\n retries: this.retries,\n inFlight: this.inFlight,\n bytesWritten: this.bytesWritten,\n totalDuration: this.totalDuration,\n byOperation: Object.freeze({ ...this.byOperation }),\n byErrorCode: Object.freeze({ ...this.byErrorCode }),\n })\n }\n\n /**\n * 将累计指标清零;执行中的操作数保持不变。\n */\n public reset(): void {\n this.operations = 0\n this.errors = 0\n this.retries = 0\n this.bytesWritten = 0\n this.totalDuration = 0\n this.byOperation = {}\n this.byErrorCode = {}\n }\n}\n\n/**\n * 生成存储错误的脱敏摘要。\n *\n * 由服务层生成的错误消息只包含固定文本、操作名与配置值;`BACKEND` 错误的消息可能来自\n * 底层 SDK,因此统一替换为固定文本。\n *\n * @param error 存储错误。\n * @returns 可安全记录的错误摘要。\n */\nexport function summarizeError(error: StorageError): StorageErrorSummary {\n const cause = error.cause as { status?: unknown; statusCode?: unknown; code?: unknown; requestId?: unknown } | null\n const status =\n typeof cause?.status === 'number'\n ? cause.status\n : typeof cause?.statusCode === 'number'\n ? cause.statusCode\n : undefined\n const causeCode = typeof cause?.code === 'string' ? cause.code : undefined\n const requestId = typeof cause?.requestId === 'string' ? cause.requestId : undefined\n return Object.freeze({\n code: error.code,\n message: error.code === 'BACKEND' ? 'Storage backend error' : error.message,\n retryable: error.retryable,\n ...(status === undefined ? {} : { status }),\n ...(causeCode === undefined ? {} : { causeCode }),\n ...(requestId === undefined ? {} : { requestId }),\n })\n}\n",
|
|
9
|
+
"import { isStorageError, StorageError } from './errors'\n\n/**\n * 重试退避配置。\n */\nexport interface RetryOptions {\n /**\n * 首次失败后的最大重试次数,默认为 2。\n */\n retries?: number\n /**\n * 第一次重试前的等待毫秒数,默认为 100。\n */\n baseDelay?: number\n /**\n * 单次等待的上限毫秒数,默认为 2000。\n */\n maxDelay?: number\n /**\n * 是否在指数退避上叠加随机抖动,默认为 `true`。\n */\n jitter?: boolean\n /**\n * 自定义可重试判定;默认仅重试 `retryable` 为 `true` 的错误。\n */\n shouldRetry?(error: unknown, attempt: number): boolean\n}\n\n/**\n * 计算指数退避等待时间。\n *\n * @param attempt 已失败的尝试次数,从 1 开始。\n * @param baseDelay 基础等待毫秒数。\n * @param maxDelay 等待上限毫秒数。\n * @param jitter 是否叠加 0 到 50% 的随机抖动。\n * @param random 随机数来源,便于测试注入。\n * @returns 等待毫秒数。\n */\nexport function backoff(\n attempt: number,\n baseDelay: number,\n maxDelay: number,\n jitter: boolean,\n random: () => number = Math.random,\n): number {\n const exponential = Math.min(maxDelay, baseDelay * 2 ** (attempt - 1))\n return jitter ? Math.min(maxDelay, Math.round(exponential * (1 + random() * 0.5))) : exponential\n}\n\n/**\n * 默认可重试判定:只有被标记为 `retryable` 的存储错误才会重试。\n *\n * @param error 失败原因。\n * @returns 是否允许重试。\n */\nexport function isRetryable(error: unknown): boolean {\n return isStorageError(error) && error.retryable\n}\n\n/**\n * 单次尝试的受控执行范围,合并调用方取消与超时。\n */\nexport interface AttemptScope {\n /**\n * 传给适配器的合并信号。\n */\n readonly signal: AbortSignal\n /**\n * 释放定时器与监听器。\n */\n dispose(): void\n}\n\n/**\n * 创建一次尝试的取消范围。\n *\n * @param timeout 超时毫秒数;`0` 表示不限制。\n * @param parent 调用方取消信号。\n * @returns 合并后的执行范围。\n */\nexport function createScope(timeout: number, parent?: AbortSignal): AttemptScope {\n const controller = new AbortController()\n const onAbort = () => controller.abort(parent?.reason)\n if (parent?.aborted) onAbort()\n else parent?.addEventListener('abort', onAbort, { once: true })\n const timer =\n timeout > 0\n ? setTimeout(\n () =>\n controller.abort(\n new StorageError('TIMEOUT', `Operation timed out after ${timeout} ms`, { retryable: true }),\n ),\n timeout,\n )\n : undefined\n return {\n signal: controller.signal,\n dispose() {\n if (timer) clearTimeout(timer)\n parent?.removeEventListener('abort', onAbort)\n },\n }\n}\n\n/**\n * 在信号中止时立即拒绝,即使底层操作未响应信号。\n *\n * @param task 实际执行的异步任务。\n * @param signal 合并信号。\n * @returns 任务结果。\n */\nexport function race<T>(task: Promise<T>, signal: AbortSignal): Promise<T> {\n if (signal.aborted) return Promise.reject(abortReason(signal))\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(abortReason(signal))\n signal.addEventListener('abort', onAbort, { once: true })\n task.then(\n (value) => {\n signal.removeEventListener('abort', onAbort)\n resolve(value)\n },\n (error: unknown) => {\n signal.removeEventListener('abort', onAbort)\n reject(error)\n },\n )\n })\n}\n\n/**\n * 将信号的中止原因转换为存储错误;超时原因保持 `TIMEOUT`,其余视为 `ABORTED`。\n *\n * @param signal 已中止的信号。\n * @returns 对应的存储错误。\n */\nexport function abortReason(signal: AbortSignal): StorageError {\n const reason: unknown = signal.reason\n if (isStorageError(reason)) return reason\n return new StorageError('ABORTED', 'Operation was aborted', { cause: reason })\n}\n\n/**\n * 可被取消的等待。\n *\n * @param milliseconds 等待毫秒数。\n * @param signal 调用方取消信号。\n * @throws {@link StorageError} 等待期间被取消时抛出 `ABORTED`。\n */\nexport function sleep(milliseconds: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) return Promise.reject(abortReason(signal))\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer)\n reject(abortReason(signal as AbortSignal))\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort)\n resolve()\n }, milliseconds)\n signal?.addEventListener('abort', onAbort, { once: true })\n })\n}\n\n/**\n * 公平的异步信号量,用于限制对后端的并发请求数。\n */\nexport class Semaphore {\n private active = 0\n private readonly queue: Array<() => void> = []\n\n /**\n * 创建信号量。\n *\n * @param limit 最大并发数。\n */\n public constructor(private readonly limit: number) {}\n\n /**\n * 在获得许可后执行任务,并确保任务结束后释放许可。\n *\n * @param task 要执行的任务。\n * @returns 任务结果。\n */\n public async run<T>(task: () => Promise<T>): Promise<T> {\n if (this.active >= this.limit) await new Promise<void>((resolve) => this.queue.push(resolve))\n else this.active += 1\n try {\n return await task()\n } finally {\n const next = this.queue.shift()\n if (next) next()\n else this.active -= 1\n }\n }\n}\n\n/**\n * 以受控并发映射数组,结果顺序与输入一致;任一任务失败时整体拒绝。\n *\n * @param items 输入数组。\n * @param concurrency 最大并发数。\n * @param mapper 映射函数。\n * @returns 与输入顺序一致的结果数组。\n */\nexport async function mapLimit<T, R>(\n items: readonly T[],\n concurrency: number,\n mapper: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length)\n let cursor = 0\n const worker = async () => {\n while (cursor < items.length) {\n const index = cursor\n cursor += 1\n results[index] = await mapper(items[index] as T, index)\n }\n }\n await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, worker))\n return results\n}\n\n/**\n * 等待任务结束(成功或失败均可),最长等待 `grace` 毫秒;任务的结果与错误都会被吞掉。\n *\n * 用于在超时或取消后让底层操作有机会响应信号并释放资源,同时避免不响应取消的任务\n * 无限期阻塞调用方。\n *\n * @param task 正在执行的任务。\n * @param grace 最长等待毫秒数;`0` 表示不等待。\n * @returns 任务是否在宽限期内结束。\n */\nexport async function settle(task: Promise<unknown>, grace: number): Promise<boolean> {\n const settled = task.then(\n () => true,\n () => true,\n )\n if (grace <= 0) return false\n let timer: ReturnType<typeof setTimeout> | undefined\n const expired = new Promise<boolean>((resolve) => {\n timer = setTimeout(() => resolve(false), grace)\n })\n try {\n return await Promise.race([settled, expired])\n } finally {\n clearTimeout(timer)\n }\n}\n",
|
|
10
|
+
"import { StorageError } from './errors'\nimport { essenceOf, extensionOf, matchContentType } from './mime'\n\n/**\n * 上传限制策略,同时作用于普通上传、分片上传与 `PUT` 签名 URL。\n */\nexport interface UploadPolicy {\n /**\n * 单个对象允许的最大字节数。`PUT` 签名 URL 无法在服务端强制该限制。\n */\n maxSize?: number\n /**\n * 允许的 MIME 类型,支持 `image/*` 通配;省略时不限制。\n */\n allowedContentTypes?: readonly string[]\n /**\n * 允许的扩展名(不含点号,大小写不敏感);省略时不限制。\n */\n allowedExtensions?: readonly string[]\n /**\n * 禁止的扩展名(不含点号,大小写不敏感),优先于允许列表。\n */\n deniedExtensions?: readonly string[]\n}\n\n/**\n * 按策略校验对象键扩展名与 MIME 类型。\n *\n * @param policy 上传策略。\n * @param key 业务对象键。\n * @param contentType 最终采用的 MIME 类型。\n * @throws {@link StorageError} 不满足策略时抛出 `UPLOAD_REJECTED`。\n */\nexport function checkUploadPolicy(policy: UploadPolicy, key: string, contentType: string): void {\n const extension = extensionOf(key)\n const lower = (values: readonly string[]) => values.map((value) => value.toLowerCase())\n if (policy.deniedExtensions && lower(policy.deniedExtensions).includes(extension)) {\n throw rejected(key, `Extension is not allowed: ${extension}`)\n }\n if (policy.allowedExtensions && !lower(policy.allowedExtensions).includes(extension)) {\n throw rejected(key, `Extension is not allowed: ${extension || '(none)'}`)\n }\n if (policy.allowedContentTypes && !matchContentType(contentType, policy.allowedContentTypes)) {\n throw rejected(key, `Content type is not allowed: ${essenceOf(contentType)}`)\n }\n}\n\nfunction rejected(key: string, message: string): StorageError {\n return new StorageError('UPLOAD_REJECTED', message, { key })\n}\n",
|
|
11
|
+
"import type { DriverContext, DriverPutOptions, MultipartDriver, StorageDriver } from './driver'\nimport { StorageError, toStorageError } from './errors'\nimport {\n abortReason,\n backoff,\n createScope,\n isRetryable,\n mapLimit,\n type RetryOptions,\n race,\n Semaphore,\n settle,\n sleep,\n} from './execution'\nimport { lookupContentType } from './mime'\nimport {\n type StorageEvent,\n StorageMetrics,\n type StorageMetricsSnapshot,\n type StorageObserver,\n type StorageOperation,\n type StorageRetryEvent,\n summarizeError,\n} from './observability'\nimport { createPayload, iterate, limitPayload, readAll, type StoragePayload } from './payload'\nimport { checkUploadPolicy, type UploadPolicy } from './policy'\nimport type {\n DeleteManyResult,\n GetOptions,\n ListOptions,\n ListResult,\n MultipartUpload,\n MultipartUploadOptions,\n ObjectBody,\n ObjectInfo,\n ObjectStream,\n OperationOptions,\n PutOptions,\n SignedUrlOptions,\n UploadedPart,\n VerifiedUrl,\n VerifyUrlRequest,\n} from './types'\nimport {\n assertContentType,\n assertHeaderValue,\n assertInteger,\n assertKey,\n assertPrefix,\n normalizeDirectory,\n normalizeMetadata,\n} from './validation'\n\nconst MIN_PART_SIZE = 100 * 1024\nconst MAX_PARTS = 10_000\n\n/**\n * 对象存储服务配置。\n */\nexport interface ObjectStorageOptions {\n /**\n * 实际执行存储操作的适配器。\n */\n driver: StorageDriver\n /**\n * 自动添加到所有对象键前的目录前缀,例如 `tenant-a`;对业务透明。\n */\n prefix?: string\n /**\n * 单次尝试的默认超时毫秒数,默认为 30000;`0` 表示不限制。\n */\n timeout?: number\n /**\n * 重试退避配置。只会重试幂等或可重复读取内容的操作。\n */\n retry?: RetryOptions\n /**\n * 对后端的最大并发请求数,默认为 32。\n *\n * 超时或取消后,许可会一直占用到适配器的底层操作真正结束(最长 `abortGrace`),\n * 因此该值能够限制实际在途的后端请求数。\n */\n concurrency?: number\n /**\n * 超时或取消后等待适配器底层操作结束的最长毫秒数,默认为 5000;`0` 表示不等待。\n *\n * 适配器应响应 `signal` 尽快结束;超过宽限期仍未结束的操作会被放弃追踪并释放许可,\n * 防止不响应取消的 SDK 永久占用并发额度。\n */\n abortGrace?: number\n /**\n * 上传限制策略。\n */\n upload?: UploadPolicy\n /**\n * 单次 `put` 的内容超过该字节数时自动改用分片上传,默认为 64 MiB;\n * 仅在适配器支持分片上传时生效,设为 `0` 可禁用。\n *\n * 自动分片以流方式切片,内存占用约为 `partSize × partConcurrency`。\n */\n multipartThreshold?: number\n /**\n * 同名对象存在时是否默认覆盖,默认为 `true`。\n */\n overwrite?: boolean\n /**\n * 自定义扩展名到 MIME 类型的映射,键为不含点号的小写扩展名。\n */\n contentTypes?: Readonly<Record<string, string>>\n /**\n * 分片上传默认分片字节数,默认为 8 MiB,不得小于 100 KiB。\n */\n partSize?: number\n /**\n * 分片上传默认并行数,默认为 4。\n */\n partConcurrency?: number\n /**\n * 结构化事件观察器。\n */\n observer?: StorageObserver\n /**\n * 指标收集器,可在多个实例间共享;默认创建私有实例。\n */\n metrics?: StorageMetrics\n}\n\ninterface RunOptions {\n key?: string\n bytes?: number\n idempotent: boolean\n}\n\n/**\n * 与框架和具体后端无关的对象存储服务。\n *\n * 服务统一负责对象键校验、前缀隔离、上传限制、MIME 推断、超时、取消、\n * 指数退避重试、并发控制与可观测性;适配器只需实现 {@link StorageDriver}。\n *\n * @example\n * ```ts\n * import { ObjectStorage } from '@lhy-meta-server/meta-oss'\n * import { FileSystemDriver } from '@lhy-meta-server/meta-oss/filesystem'\n *\n * const storage = new ObjectStorage({\n * driver: new FileSystemDriver({ root: './storage' }),\n * upload: { maxSize: 10 * 1024 * 1024, allowedContentTypes: ['image/*'] },\n * })\n * await storage.put('avatars/1.png', file)\n * ```\n */\nexport class ObjectStorage {\n /**\n * 当前使用的适配器名称。\n */\n public readonly driverName: string\n private readonly driver: StorageDriver\n private readonly prefix: string\n private readonly timeout: number\n private readonly retries: number\n private readonly baseDelay: number\n private readonly maxDelay: number\n private readonly jitter: boolean\n private readonly shouldRetry: (error: unknown, attempt: number) => boolean\n private readonly semaphore: Semaphore\n private readonly concurrency: number\n private readonly policy: UploadPolicy\n private readonly maxSize: number\n private readonly overwrite: boolean\n private readonly contentTypes: Readonly<Record<string, string>>\n private readonly partSize: number\n private readonly partConcurrency: number\n private readonly abortGrace: number\n private readonly multipartThreshold: number\n private readonly observer?: StorageObserver\n private readonly metrics: StorageMetrics\n private closing?: Promise<void>\n\n /**\n * 创建对象存储服务并校验全部配置。\n *\n * @param options 适配器、可靠性、上传策略与可观测性配置。\n * @throws {@link StorageError} 配置非法时抛出 `INVALID_OPTION`。\n */\n public constructor(options: ObjectStorageOptions) {\n if (!options?.driver) throw new StorageError('INVALID_OPTION', 'driver is required')\n this.driver = options.driver\n this.driverName = options.driver.name\n this.prefix = normalizeDirectory(options.prefix)\n this.timeout = assertInteger('timeout', options.timeout ?? 30_000, 0)\n const retry = options.retry ?? {}\n this.retries = assertInteger('retry.retries', retry.retries ?? 2, 0, 10)\n this.baseDelay = assertInteger('retry.baseDelay', retry.baseDelay ?? 100, 0)\n this.maxDelay = assertInteger('retry.maxDelay', retry.maxDelay ?? 2_000, this.baseDelay)\n this.jitter = retry.jitter ?? true\n this.shouldRetry = retry.shouldRetry ?? isRetryable\n this.concurrency = assertInteger('concurrency', options.concurrency ?? 32, 1)\n this.semaphore = new Semaphore(this.concurrency)\n this.policy = options.upload ?? {}\n this.maxSize = assertInteger('upload.maxSize', this.policy.maxSize ?? Number.MAX_SAFE_INTEGER, 0)\n this.overwrite = options.overwrite ?? true\n this.contentTypes = options.contentTypes ?? {}\n this.partSize = assertInteger('partSize', options.partSize ?? 8 * 1024 * 1024, MIN_PART_SIZE)\n this.partConcurrency = assertInteger('partConcurrency', options.partConcurrency ?? 4, 1)\n this.abortGrace = assertInteger('abortGrace', options.abortGrace ?? 5_000, 0)\n this.multipartThreshold = assertInteger('multipartThreshold', options.multipartThreshold ?? 64 * 1024 * 1024, 0)\n if (this.multipartThreshold > 0 && this.multipartThreshold < this.partSize) {\n throw new StorageError('INVALID_OPTION', 'multipartThreshold must be 0 or not less than partSize')\n }\n this.observer = options.observer\n this.metrics = options.metrics ?? new StorageMetrics()\n }\n\n /**\n * 写入对象。\n *\n * 可重复读取的内容(字符串、二进制、`Blob`)在可重试错误时会自动重试;\n * 流式内容只尝试一次。已知大小超过 `multipartThreshold` 且适配器支持分片上传时,\n * 自动改用流式分片上传,避免把大文件整体读入内存。\n *\n * @param key 业务对象键。\n * @param body 对象内容。\n * @param options HTTP 属性、元数据、覆盖策略与执行控制。\n * @returns 写入后的对象描述信息。\n * @throws {@link StorageError} `INVALID_KEY`、`UPLOAD_REJECTED`、`ALREADY_EXISTS` 等。\n */\n public async put(key: string, body: ObjectBody, options: PutOptions = {}): Promise<ObjectInfo> {\n assertKey(key)\n const putOptions = this.putOptions(key, options)\n const payload = limitPayload(createPayload(body), this.maxSize)\n if (this.shouldUseMultipart(payload, putOptions.overwrite)) {\n return this.uploadMultipartPayload(key, payload, options)\n }\n const info = await this.run(\n 'put',\n options,\n { key, bytes: payload.size, idempotent: payload.replayable },\n (context) => this.driver.put(this.fullKey(key), payload, { ...putOptions, ...context }),\n )\n return this.publicInfo(info)\n }\n\n /**\n * 以流方式读取对象。\n *\n * 超时与重试只作用于“打开对象”阶段;返回的流由调用方负责消费或取消。\n *\n * @param key 业务对象键。\n * @param options 读取范围与执行控制。\n * @returns 对象描述信息与 Web 可读流。\n * @throws {@link StorageError} 对象不存在时抛出 `NOT_FOUND`。\n */\n public async get(key: string, options: GetOptions = {}): Promise<ObjectStream> {\n assertKey(key)\n const range = options.range\n if (range) {\n assertInteger('range.start', range.start, 0)\n if (range.end !== undefined) assertInteger('range.end', range.end, range.start)\n }\n const result = await this.run('get', options, { key, idempotent: true }, (context) =>\n this.driver.get(this.fullKey(key), { ...context, range }),\n )\n return { ...result, info: this.publicInfo(result.info) }\n }\n\n /**\n * 读取对象全部内容为字节数组。\n *\n * @param key 业务对象键。\n * @param options 读取范围与执行控制。\n * @returns 对象内容。\n */\n public async getBytes(key: string, options: GetOptions = {}): Promise<Uint8Array> {\n return readAll((await this.get(key, options)).body)\n }\n\n /**\n * 以 UTF-8 解码读取对象全部内容。\n *\n * @param key 业务对象键。\n * @param options 读取范围与执行控制。\n * @returns 对象文本内容。\n */\n public async getText(key: string, options: GetOptions = {}): Promise<string> {\n return new TextDecoder().decode(await this.getBytes(key, options))\n }\n\n /**\n * 读取对象描述信息。\n *\n * @param key 业务对象键。\n * @param options 执行控制。\n * @returns 对象描述信息。\n * @throws {@link StorageError} 对象不存在时抛出 `NOT_FOUND`。\n */\n public async head(key: string, options: OperationOptions = {}): Promise<ObjectInfo> {\n assertKey(key)\n const info = await this.run('head', options, { key, idempotent: true }, (context) =>\n this.driver.head(this.fullKey(key), context),\n )\n return this.publicInfo(info)\n }\n\n /**\n * 判断对象是否存在。\n *\n * @param key 业务对象键。\n * @param options 执行控制。\n * @returns 对象是否存在。\n */\n public async exists(key: string, options: OperationOptions = {}): Promise<boolean> {\n assertKey(key)\n return this.run('exists', options, { key, idempotent: true }, async (context) => {\n try {\n await this.driver.head(this.fullKey(key), context)\n return true\n } catch (error) {\n if (toStorageError(error).code === 'NOT_FOUND') return false\n throw error\n }\n })\n }\n\n /**\n * 删除对象;对象不存在时静默成功。\n *\n * @param key 业务对象键。\n * @param options 执行控制。\n */\n public async delete(key: string, options: OperationOptions = {}): Promise<void> {\n assertKey(key)\n await this.run('delete', options, { key, idempotent: true }, (context) =>\n this.driver.delete(this.fullKey(key), context),\n )\n }\n\n /**\n * 批量删除对象。\n *\n * 适配器支持原生批量删除时一次提交(每批最多 1000 个),否则以受控并发逐个删除。\n * 单个对象失败不会中断其他对象,失败项记录在结果的 `failed` 中;降级路径下结果\n * 顺序与输入顺序一致。\n *\n * @param keys 业务对象键列表,重复键会被去重。\n * @param options 执行控制。\n * @returns 成功与失败的对象键。\n * @throws {@link StorageError} 任一键非法时抛出 `INVALID_KEY`,此时不会删除任何对象。\n */\n public async deleteMany(keys: readonly string[], options: OperationOptions = {}): Promise<DeleteManyResult> {\n const unique = [...new Set(keys)]\n unique.forEach(assertKey)\n if (unique.length === 0) return { deleted: [], failed: [] }\n return this.run('deleteMany', options, { idempotent: true }, async (context) => {\n const result: DeleteManyResult = { deleted: [], failed: [] }\n const driverDeleteMany = this.driver.deleteMany?.bind(this.driver)\n if (driverDeleteMany) {\n for (let index = 0; index < unique.length; index += 1000) {\n const batch = unique.slice(index, index + 1000)\n const partial = await driverDeleteMany(\n batch.map((key) => this.fullKey(key)),\n context,\n )\n result.deleted.push(...partial.deleted.map((key) => this.publicKey(key)))\n result.failed.push(...partial.failed.map((item) => ({ ...item, key: this.publicKey(item.key) })))\n }\n return result\n }\n const outcomes = await mapLimit(unique, Math.min(this.concurrency, 16), async (key) => {\n try {\n await this.driver.delete(this.fullKey(key), context)\n return undefined\n } catch (error) {\n return toStorageError(error, 'delete', key)\n }\n })\n outcomes.forEach((error, index) => {\n const key = unique[index] as string\n if (error) result.failed.push({ key, error })\n else result.deleted.push(key)\n })\n return result\n })\n }\n\n /**\n * 分页列举对象。\n *\n * @param options 前缀、分隔符、游标与页大小。\n * @returns 当前页对象、公共前缀与下一页游标。\n * @throws {@link StorageError} 适配器不支持列举时抛出 `UNSUPPORTED`。\n */\n public async list(options: ListOptions = {}): Promise<ListResult> {\n const prefix = assertPrefix(options.prefix)\n const limit = assertInteger('limit', options.limit ?? 1000, 1, 1000)\n if (options.delimiter !== undefined && options.delimiter.length === 0) {\n throw new StorageError('INVALID_OPTION', 'delimiter must not be empty')\n }\n const list = this.capability('list', this.driver.list)\n const result = await this.run('list', options, { idempotent: true }, (context) =>\n list({ ...context, prefix: this.prefix + prefix, delimiter: options.delimiter, cursor: options.cursor, limit }),\n )\n return {\n objects: result.objects.map((info) => this.publicInfo(info)),\n prefixes: result.prefixes.map((value) => this.publicKey(value)),\n cursor: result.cursor,\n }\n }\n\n /**\n * 遍历前缀下的全部对象,自动处理分页。\n *\n * @param options 前缀与页大小;游标与分隔符会被忽略。\n * @returns 逐个产生对象描述信息的异步迭代器。\n */\n public async *listAll(options: Omit<ListOptions, 'cursor' | 'delimiter'> = {}): AsyncGenerator<ObjectInfo> {\n let cursor: string | undefined\n do {\n const page = await this.list({ ...options, cursor, delimiter: undefined })\n yield* page.objects\n cursor = page.cursor\n } while (cursor)\n }\n\n /**\n * 复制对象。\n *\n * 适配器支持服务端复制时直接使用,否则读取源对象后流式写入目标,并保留\n * HTTP 属性与元数据。\n *\n * @param source 源对象键。\n * @param destination 目标对象键。\n * @param options 覆盖策略与执行控制。\n * @returns 目标对象描述信息。\n */\n public async copy(\n source: string,\n destination: string,\n options: OperationOptions & { overwrite?: boolean } = {},\n ): Promise<ObjectInfo> {\n assertKey(source)\n assertKey(destination)\n const overwrite = options.overwrite ?? this.overwrite\n const info = await this.run('copy', options, { key: destination, idempotent: overwrite }, (context) =>\n this.copyObject(source, destination, overwrite, context),\n )\n return this.publicInfo(info)\n }\n\n /**\n * 移动对象:先复制到目标,成功后删除源对象。\n *\n * @param source 源对象键。\n * @param destination 目标对象键。\n * @param options 覆盖策略与执行控制。\n * @returns 目标对象描述信息。\n */\n public async move(\n source: string,\n destination: string,\n options: OperationOptions & { overwrite?: boolean } = {},\n ): Promise<ObjectInfo> {\n assertKey(source)\n assertKey(destination)\n if (source === destination) throw new StorageError('INVALID_OPTION', 'source and destination must differ')\n const overwrite = options.overwrite ?? this.overwrite\n const info = await this.run('move', options, { key: destination, idempotent: false }, async (context) => {\n const copied = await this.copyObject(source, destination, overwrite, context)\n await this.driver.delete(this.fullKey(source), context)\n return copied\n })\n return this.publicInfo(info)\n }\n\n private async copyObject(\n source: string,\n destination: string,\n overwrite: boolean,\n context: DriverContext,\n ): Promise<ObjectInfo> {\n const from = this.fullKey(source)\n const to = this.fullKey(destination)\n if (this.driver.copy) return this.driver.copy(from, to, { ...context, overwrite })\n const { info, body } = await this.driver.get(from, context)\n const payload = createPayload(body)\n return this.driver.put(to, payload, {\n ...context,\n contentType: info.contentType ?? lookupContentType(destination, this.contentTypes),\n cacheControl: info.cacheControl,\n contentDisposition: info.contentDisposition,\n metadata: info.metadata,\n overwrite,\n })\n }\n\n /**\n * 生成限时签名 URL,供客户端直接下载或上传。\n *\n * `PUT` 签名会校验扩展名与 MIME 策略,但无法在服务端限制客户端上传的字节数。\n * 签名 URL 属于凭据,不会出现在可观测事件中。\n *\n * @param key 业务对象键。\n * @param options HTTP 方法、有效期与响应头覆盖。\n * @returns 签名 URL。\n * @throws {@link StorageError} 适配器不支持签名时抛出 `UNSUPPORTED`。\n */\n public async signUrl(key: string, options: SignedUrlOptions = {}): Promise<string> {\n assertKey(key)\n const method = options.method ?? 'GET'\n if (method !== 'GET' && method !== 'PUT') throw new StorageError('INVALID_OPTION', `Unsupported method: ${method}`)\n const expiresIn = assertInteger('expiresIn', options.expiresIn ?? 900, 1, 604_800)\n let contentType: string | undefined\n if (method === 'PUT') {\n contentType = assertContentType(options.contentType ?? lookupContentType(key, this.contentTypes))\n checkUploadPolicy(this.policy, key, contentType)\n }\n const responseContentDisposition = assertHeaderValue(\n 'responseContentDisposition',\n options.responseContentDisposition,\n )\n const signUrl = this.capability('signUrl', this.driver.signUrl)\n return this.run('signUrl', options, { key, idempotent: true }, (context) =>\n signUrl(this.fullKey(key), { ...context, method, expiresIn, contentType, responseContentDisposition }),\n )\n }\n\n /**\n * 校验由本服务签发的签名 URL,供业务自建的下载或上传路由鉴权使用。\n *\n * 第三方 OSS 的签名由其服务端校验,因此通常只有自签名后端支持该方法。\n * 返回的 `key` 已去除服务级前缀;签名对象不属于当前前缀时视为拒绝。\n *\n * @param request 请求 URL、方法与 `Content-Type`。\n * @returns 授权信息。\n * @throws {@link StorageError} 校验失败时抛出 `ACCESS_DENIED`;适配器不支持时抛出 `UNSUPPORTED`。\n */\n public verifyUrl(request: VerifyUrlRequest): VerifiedUrl {\n if (this.closing) throw new StorageError('CLOSED', 'Object storage has been closed', { operation: 'verifyUrl' })\n const verify = this.capability('verifyUrl', this.driver.verifyUrl)\n const verified = verify(request)\n if (!verified.key.startsWith(this.prefix)) {\n throw new StorageError('ACCESS_DENIED', 'Signed URL is outside of the storage prefix')\n }\n return { ...verified, key: assertKey(this.publicKey(verified.key)) }\n }\n\n /**\n * 创建分片上传会话,适用于断点续传或客户端分片场景。\n *\n * @param key 业务对象键。\n * @param options HTTP 属性、元数据与执行控制。\n * @returns 上传会话。\n * @throws {@link StorageError} 适配器不支持分片上传时抛出 `UNSUPPORTED`。\n */\n public async createMultipart(key: string, options: PutOptions = {}): Promise<MultipartUpload> {\n assertKey(key)\n const multipart = this.multipartDriver()\n const putOptions = this.putOptions(key, options)\n const upload = await this.run('createMultipart', options, { key, idempotent: false }, (context) =>\n multipart.createMultipart(this.fullKey(key), { ...putOptions, ...context }),\n )\n return { key, uploadId: upload.uploadId }\n }\n\n /**\n * 上传单个分片;分片内容会先读入内存,以便失败时安全重试。\n *\n * @param upload 上传会话。\n * @param partNumber 分片序号,1 到 10000。\n * @param body 分片内容。\n * @param options 执行控制。\n * @returns 已上传分片。\n */\n public async uploadPart(\n upload: MultipartUpload,\n partNumber: number,\n body: ObjectBody,\n options: OperationOptions = {},\n ): Promise<UploadedPart> {\n this.assertUpload(upload)\n assertInteger('partNumber', partNumber, 1, MAX_PARTS)\n const multipart = this.multipartDriver()\n const bytes = await limitPayload(createPayload(body), this.maxSize).bytes()\n const payload = createPayload(bytes)\n return this.run('uploadPart', options, { key: upload.key, bytes: bytes.byteLength, idempotent: true }, (context) =>\n multipart.uploadPart(this.driverUpload(upload), partNumber, payload, context),\n )\n }\n\n /**\n * 合并分片完成上传;分片会按序号排序,重复序号以最后一次为准。\n *\n * @param upload 上传会话。\n * @param parts 已上传分片。\n * @param options 执行控制。\n * @returns 合并后的对象描述信息。\n */\n public async completeMultipart(\n upload: MultipartUpload,\n parts: readonly UploadedPart[],\n options: OperationOptions = {},\n ): Promise<ObjectInfo> {\n this.assertUpload(upload)\n if (parts.length === 0) throw new StorageError('INVALID_OPTION', 'parts must not be empty')\n const ordered = new Map<number, UploadedPart>()\n for (const part of parts) {\n assertInteger('partNumber', part.partNumber, 1, MAX_PARTS)\n if (typeof part.etag !== 'string' || part.etag.length === 0) {\n throw new StorageError('INVALID_OPTION', 'part etag is required')\n }\n ordered.set(part.partNumber, part)\n }\n const sorted = [...ordered.values()].sort((left, right) => left.partNumber - right.partNumber)\n const multipart = this.multipartDriver()\n const info = await this.run('completeMultipart', options, { key: upload.key, idempotent: false }, (context) =>\n multipart.completeMultipart(this.driverUpload(upload), sorted, context),\n )\n return this.publicInfo(info)\n }\n\n /**\n * 放弃分片上传并清理已上传的分片。\n *\n * @param upload 上传会话。\n * @param options 执行控制。\n */\n public async abortMultipart(upload: MultipartUpload, options: OperationOptions = {}): Promise<void> {\n this.assertUpload(upload)\n const multipart = this.multipartDriver()\n await this.run('abortMultipart', options, { key: upload.key, idempotent: true }, (context) =>\n multipart.abortMultipart(this.driverUpload(upload), context),\n )\n }\n\n /**\n * 自动分片上传大对象。\n *\n * 内容以流方式切片,内存占用约为 `partSize × concurrency`;任一分片失败时会\n * 中止会话并清理已上传分片。空内容会退化为普通写入。\n *\n * @param key 业务对象键。\n * @param body 对象内容。\n * @param options 分片大小、并行数、HTTP 属性与执行控制。\n * @returns 合并后的对象描述信息。\n */\n public async uploadMultipart(\n key: string,\n body: ObjectBody,\n options: MultipartUploadOptions = {},\n ): Promise<ObjectInfo> {\n assertKey(key)\n this.multipartDriver()\n return this.uploadMultipartPayload(key, limitPayload(createPayload(body), this.maxSize), options)\n }\n\n private shouldUseMultipart(payload: StoragePayload, overwrite: boolean): boolean {\n // 分片合并无法原子地拒绝覆盖,禁止覆盖的写入始终走单次 put。\n return (\n this.multipartThreshold > 0 &&\n overwrite &&\n this.driver.multipart !== undefined &&\n payload.size !== undefined &&\n payload.size > this.multipartThreshold\n )\n }\n\n private async uploadMultipartPayload(\n key: string,\n payload: StoragePayload,\n options: MultipartUploadOptions,\n ): Promise<ObjectInfo> {\n const partSize = assertInteger('partSize', options.partSize ?? this.partSize, MIN_PART_SIZE)\n const concurrency = assertInteger('concurrency', options.concurrency ?? this.partConcurrency, 1)\n const upload = await this.createMultipart(key, options)\n const pending = new Set<Promise<void>>()\n const parts: UploadedPart[] = []\n let failure: unknown\n const submit = (partNumber: number, bytes: Uint8Array) => {\n const task = this.uploadPart(upload, partNumber, bytes, options).then(\n (part) => {\n parts.push(part)\n },\n (error: unknown) => {\n failure ??= error\n },\n )\n const tracked = task.finally(() => pending.delete(tracked))\n pending.add(tracked)\n }\n try {\n let partNumber = 0\n for await (const chunk of chunked(payload, partSize)) {\n if (failure) break\n partNumber += 1\n if (partNumber > MAX_PARTS)\n throw new StorageError('UPLOAD_REJECTED', `Object exceeds ${MAX_PARTS} parts`, { key })\n submit(partNumber, chunk)\n while (pending.size >= concurrency) await Promise.race(pending)\n }\n await Promise.all(pending)\n if (failure) throw failure\n if (partNumber === 0) {\n await this.abortMultipart(upload, { ...options, retries: 0 })\n return await this.put(key, new Uint8Array(0), options)\n }\n return await this.completeMultipart(upload, parts, options)\n } catch (error) {\n await Promise.allSettled(pending)\n await this.abortMultipart(upload, { timeout: options.timeout, retries: 0 }).catch(() => undefined)\n throw error\n }\n }\n\n /**\n * 返回累计指标的冻结快照。\n *\n * @returns 指标快照。\n */\n public getMetrics(): StorageMetricsSnapshot {\n return this.metrics.snapshot()\n }\n\n /**\n * 将累计指标清零。\n */\n public resetMetrics(): void {\n this.metrics.reset()\n }\n\n /**\n * 关闭服务并释放适配器资源;重复调用返回同一结果。关闭后的所有操作都会抛出 `CLOSED`。\n */\n public close(): Promise<void> {\n this.closing ??= (async () => {\n const started = performance.now()\n this.metrics.start()\n try {\n await this.driver.close?.()\n this.emit({ operation: 'close', outcome: 'success', started, attempts: 1 })\n } catch (error) {\n const storageError = toStorageError(error, 'close')\n this.emit({ operation: 'close', outcome: 'error', started, attempts: 1, error: storageError })\n throw storageError\n }\n })()\n return this.closing\n }\n\n private putOptions(key: string, options: PutOptions): Omit<DriverPutOptions, 'signal'> {\n const contentType = assertContentType(options.contentType ?? lookupContentType(key, this.contentTypes))\n checkUploadPolicy(this.policy, key, contentType)\n return {\n contentType,\n cacheControl: assertHeaderValue('cacheControl', options.cacheControl),\n contentDisposition: assertHeaderValue('contentDisposition', options.contentDisposition),\n metadata: normalizeMetadata(options.metadata),\n overwrite: options.overwrite ?? this.overwrite,\n }\n }\n\n private capability<T>(name: string, method: T | undefined): T {\n if (!method) {\n throw new StorageError('UNSUPPORTED', `Driver \"${this.driverName}\" does not support ${name}`, {\n operation: name,\n })\n }\n return (method as unknown as (...values: unknown[]) => unknown).bind(this.driver) as T\n }\n\n private multipartDriver(): MultipartDriver {\n const multipart = this.driver.multipart\n if (!multipart) {\n throw new StorageError('UNSUPPORTED', `Driver \"${this.driverName}\" does not support multipart upload`)\n }\n return multipart\n }\n\n private assertUpload(upload: MultipartUpload): void {\n assertKey(upload?.key)\n if (typeof upload.uploadId !== 'string' || upload.uploadId.length === 0) {\n throw new StorageError('INVALID_OPTION', 'uploadId is required', { key: upload.key })\n }\n }\n\n private driverUpload(upload: MultipartUpload): MultipartUpload {\n return { key: this.fullKey(upload.key), uploadId: upload.uploadId }\n }\n\n private fullKey(key: string): string {\n return this.prefix + key\n }\n\n private publicKey(key: string): string {\n return key.startsWith(this.prefix) ? key.slice(this.prefix.length) : key\n }\n\n private publicInfo(info: ObjectInfo): ObjectInfo {\n return { ...info, key: this.publicKey(info.key) }\n }\n\n private async run<T>(\n operation: StorageOperation,\n options: OperationOptions,\n run: RunOptions,\n task: (context: DriverContext) => Promise<T>,\n ): Promise<T> {\n if (this.closing) throw new StorageError('CLOSED', 'Object storage has been closed', { operation, key: run.key })\n const timeout = assertInteger('timeout', options.timeout ?? this.timeout, 0)\n const retries = run.idempotent ? assertInteger('retries', options.retries ?? this.retries, 0, 10) : 0\n const started = performance.now()\n this.metrics.start()\n let attempt = 0\n try {\n const value = await this.semaphore.run(async () => {\n for (;;) {\n attempt += 1\n const scope = createScope(timeout, options.signal)\n // 超时或取消时 race 立即拒绝;随后在持有许可的情况下等待底层任务结束\n // (最长 abortGrace),既限制实际在途请求数,也避免重试与残留写入并发操作同一对象。\n const pending = Promise.resolve().then(() => task({ signal: scope.signal }))\n try {\n return await race(pending, scope.signal)\n } catch (error) {\n if (scope.signal.aborted) await settle(pending, this.abortGrace)\n // 信号已触发时以超时/取消为准:适配器常因取消而抛出连接中断等次生错误。\n const cause = scope.signal.aborted ? abortReason(scope.signal) : error\n const storageError = toStorageError(cause, operation, run.key)\n if (options.signal?.aborted || attempt > retries || !this.shouldRetry(storageError, attempt)) {\n throw storageError\n }\n const delay = backoff(attempt, this.baseDelay, this.maxDelay, this.jitter)\n this.metrics.retry()\n this.notify('onRetry', {\n operation,\n driver: this.driverName,\n key: run.key,\n attempt: attempt + 1,\n delay,\n error: summarizeError(storageError),\n })\n await sleep(delay, options.signal)\n } finally {\n scope.dispose()\n }\n }\n })\n this.emit({ operation, outcome: 'success', started, attempts: attempt, key: run.key, bytes: run.bytes })\n return value\n } catch (error) {\n const storageError = toStorageError(error, operation, run.key)\n this.emit({ operation, outcome: 'error', started, attempts: attempt, key: run.key, error: storageError })\n throw storageError\n }\n }\n\n private emit(input: {\n operation: StorageOperation\n outcome: StorageEvent['outcome']\n started: number\n attempts: number\n key?: string\n bytes?: number\n error?: StorageError\n }): void {\n const event: StorageEvent = Object.freeze({\n operation: input.operation,\n outcome: input.outcome,\n driver: this.driverName,\n ...(input.key === undefined ? {} : { key: input.key }),\n duration: Math.max(0, performance.now() - input.started),\n attempts: input.attempts,\n ...(input.bytes === undefined ? {} : { bytes: input.bytes }),\n ...(input.error === undefined ? {} : { errorCode: input.error.code, error: summarizeError(input.error) }),\n timestamp: Date.now(),\n })\n this.metrics.record(event)\n this.notify('onOperation', event)\n }\n\n private notify(name: 'onOperation', event: StorageEvent): void\n private notify(name: 'onRetry', event: StorageRetryEvent): void\n private notify(name: keyof StorageObserver, event: StorageEvent | StorageRetryEvent): void {\n try {\n const callback = this.observer?.[name] as ((value: StorageEvent | StorageRetryEvent) => void) | undefined\n callback?.call(this.observer, Object.freeze(event))\n } catch {\n // 观察器故障必须与业务请求隔离。\n }\n }\n}\n\n/**\n * 将上传内容按固定大小切片;最后一片可能更小。\n *\n * @param payload 上传内容。\n * @param size 分片字节数。\n * @returns 逐片产生字节数组的异步迭代器。\n */\nasync function* chunked(payload: StoragePayload, size: number): AsyncGenerator<Uint8Array> {\n let buffer = new Uint8Array(size)\n let filled = 0\n for await (const chunk of iterate(payload.stream())) {\n let offset = 0\n while (offset < chunk.byteLength) {\n const length = Math.min(size - filled, chunk.byteLength - offset)\n buffer.set(chunk.subarray(offset, offset + length), filled)\n filled += length\n offset += length\n if (filled === size) {\n yield buffer\n buffer = new Uint8Array(size)\n filled = 0\n }\n }\n }\n if (filled > 0) yield buffer.subarray(0, filled)\n}\n"
|
|
12
|
+
],
|
|
13
|
+
"mappings": "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",
|
|
14
|
+
"debugId": "C8A2B002EC4BF53A64756E2164756E21",
|
|
15
|
+
"names": []
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,81 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@lhy-meta-server/meta-oss",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@lhy-meta-server/meta-oss",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "面向 Bun 与 Node.js 的生产级对象存储抽象,内置本地文件系统驱动,提供 ali-oss 适配器与 Elysia 集成",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"bun",
|
|
7
|
+
"nodejs",
|
|
8
|
+
"oss",
|
|
9
|
+
"object-storage",
|
|
10
|
+
"filesystem",
|
|
11
|
+
"ali-oss",
|
|
12
|
+
"elysia",
|
|
13
|
+
"typescript"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./filesystem": {
|
|
31
|
+
"types": "./dist/filesystem/index.d.ts",
|
|
32
|
+
"import": "./dist/filesystem/index.js",
|
|
33
|
+
"default": "./dist/filesystem/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./ali-oss": {
|
|
36
|
+
"types": "./dist/ali-oss/index.d.ts",
|
|
37
|
+
"import": "./dist/ali-oss/index.js",
|
|
38
|
+
"default": "./dist/ali-oss/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./elysia": {
|
|
41
|
+
"types": "./dist/elysia/index.d.ts",
|
|
42
|
+
"import": "./dist/elysia/index.js",
|
|
43
|
+
"default": "./dist/elysia/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./package.json": "./package.json"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "bun run clean && bun build ./src/index.ts ./src/filesystem/index.ts ./src/ali-oss/index.ts ./src/elysia/index.ts --root ./src --outdir ./dist --target node --minify --format esm --splitting --sourcemap=external --external elysia && tsc -p ./tsconfig.build.json",
|
|
49
|
+
"typecheck": "tsc --noEmit -p ./tsconfig.json",
|
|
50
|
+
"lint": "biome check src test",
|
|
51
|
+
"test": "bun test ./test/*.test.ts",
|
|
52
|
+
"test:coverage": "bun test --coverage ./test/*.test.ts",
|
|
53
|
+
"test:package": "node ./test/package-runtime.mjs && bun ./test/package-runtime.mjs",
|
|
54
|
+
"check": "bun run typecheck && bun run lint && bun run test:coverage && bun run build && bun run test:package",
|
|
55
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
56
|
+
"prepack": "bun run check"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"bun": ">=1.3.0",
|
|
60
|
+
"node": ">=20.0.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"elysia": ">=1.4.0 <2.0.0"
|
|
64
|
+
},
|
|
65
|
+
"peerDependenciesMeta": {
|
|
66
|
+
"elysia": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"publishConfig": {
|
|
71
|
+
"access": "public",
|
|
72
|
+
"provenance": false,
|
|
73
|
+
"registry": "https://registry.npmjs.org/"
|
|
74
|
+
},
|
|
75
|
+
"devDependencies": {
|
|
76
|
+
"@types/bun": "1.4.0",
|
|
77
|
+
"@types/node": "26.4.0",
|
|
78
|
+
"elysia": "1.4.30",
|
|
79
|
+
"typescript": "7.0.2"
|
|
80
|
+
}
|
|
81
|
+
}
|