@lhy-meta-server/meta-oss 0.0.1 → 0.0.2

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.
@@ -2,13 +2,13 @@
2
2
  "version": 3,
3
3
  "sources": ["..\\src\\filesystem\\driver.ts", "..\\src\\filesystem\\durability.ts", "..\\src\\filesystem\\metadata.ts", "..\\src\\filesystem\\paths.ts", "..\\src\\filesystem\\signer.ts"],
4
4
  "sourcesContent": [
5
- "import { createHash, randomUUID } from 'node:crypto'\nimport type { Dirent } from 'node:fs'\nimport { createReadStream } from 'node:fs'\nimport {\n type FileHandle,\n link,\n lstat,\n mkdir,\n open,\n readdir,\n readFile,\n realpath,\n rename,\n rm,\n rmdir,\n stat,\n unlink,\n} from 'node:fs/promises'\nimport { dirname, join, resolve } from 'node:path'\nimport { Readable } from 'node:stream'\nimport type {\n DriverContext,\n DriverCopyOptions,\n DriverGetOptions,\n DriverListOptions,\n DriverPutOptions,\n DriverSignOptions,\n MultipartDriver,\n StorageDriver,\n} from '../core/driver'\nimport { StorageError, toStorageError } from '../core/errors'\nimport { createPayload, type StoragePayload } from '../core/payload'\nimport type {\n ListResult,\n MultipartUpload,\n ObjectInfo,\n ObjectStream,\n UploadedPart,\n VerifiedUrl,\n VerifyUrlRequest,\n} from '../core/types'\nimport { errorCode, removeStale, syncDirectory } from './durability'\nimport { metadataDirectoryName, metadataFileName, parseMetadata, type StoredMetadata } from './metadata'\nimport { assertFileKey, INTERNAL_DIRECTORY, internalPath, isInside, resolveInside, toKey } from './paths'\nimport { UrlSigner, type UrlSignerOptions } from './signer'\n\n/**\n * 本地文件系统适配器配置。\n */\nexport interface FileSystemDriverOptions {\n /**\n * 存储根目录;不存在时自动创建。\n */\n root: string\n /**\n * 签名 URL 配置;省略时 `signUrl` 抛出 `UNSUPPORTED`。\n */\n signing?: UrlSignerOptions\n /**\n * 新建目录的权限,默认为 `0o750`。\n */\n directoryMode?: number\n /**\n * 新建文件的权限,默认为 `0o640`。\n */\n fileMode?: number\n /**\n * 未完成分片会话的保留毫秒数,超时会在新建会话时清理,默认为 24 小时。\n */\n multipartTtl?: number\n /**\n * 崩溃残留临时文件的保留毫秒数,超时会在初始化时清理,默认为 1 小时。\n *\n * 该值应大于单次写入的最长耗时,避免误删仍在写入的临时文件。\n */\n temporaryTtl?: number\n /**\n * 是否对文件与目录执行 `fsync` 以保证断电持久性,默认为 `true`。\n * 关闭可提升吞吐,但断电时最近的写入可能丢失。\n */\n durable?: boolean\n}\n\ninterface MultipartManifest {\n key: string\n createdAt: number\n options: Omit<StoredMetadata, 'key' | 'size' | 'etag'> & { overwrite: boolean }\n}\n\ninterface WrittenFile {\n path: string\n etag: string\n size: number\n}\n\nconst uploadIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/\nconst notFoundCodes = new Set(['ENOENT', 'ENOTDIR'])\n\n/**\n * 生产级本地文件系统适配器。\n *\n * - 内容先写入内部临时目录并 `fsync`,再原子 `rename` / `link` 到目标位置,并同步父目录。\n * - 元数据以内容文件的 inode 命名并在内容提交前落盘,内容与元数据始终成对可见;\n * 并发写入同一键时,读取方看到的内容、ETag 与元数据总是来自同一次写入。\n * - 读取先打开文件句柄,再基于该句柄读取状态与元数据,句柄打开后对象被删除或覆盖也不影响本次读取。\n * - 拒绝路径穿越、符号链接逃逸、Windows 保留名与内部目录访问。\n * - 初始化时回收崩溃残留的临时文件,新建分片会话时回收过期会话。\n *\n * @example\n * ```ts\n * const driver = new FileSystemDriver({\n * root: '/data/oss',\n * signing: { secret: config.ossSecret, baseUrl: 'https://api.example.com/files' },\n * })\n * ```\n */\nexport class FileSystemDriver implements StorageDriver {\n /**\n * 适配器名称。\n */\n public readonly name = 'filesystem'\n /**\n * 分片上传能力。\n */\n public readonly multipart: MultipartDriver\n private readonly root: string\n private readonly signer?: UrlSigner\n private readonly directoryMode: number\n private readonly fileMode: number\n private readonly multipartTtl: number\n private readonly temporaryTtl: number\n private readonly durable: boolean\n private ready?: Promise<string>\n\n /**\n * 创建本地文件系统适配器。\n *\n * @param options 根目录、签名、权限与持久性配置。\n * @throws {@link StorageError} 配置非法时抛出 `INVALID_OPTION`。\n */\n public constructor(options: FileSystemDriverOptions) {\n if (typeof options?.root !== 'string' || options.root.length === 0) {\n throw new StorageError('INVALID_OPTION', 'root is required')\n }\n this.root = resolve(options.root)\n this.signer = options.signing ? new UrlSigner(options.signing) : undefined\n this.directoryMode = options.directoryMode ?? 0o750\n this.fileMode = options.fileMode ?? 0o640\n this.multipartTtl = options.multipartTtl ?? 24 * 60 * 60 * 1000\n this.temporaryTtl = options.temporaryTtl ?? 60 * 60 * 1000\n this.durable = options.durable ?? true\n this.multipart = {\n createMultipart: (key, putOptions) => this.createMultipart(key, putOptions),\n uploadPart: (upload, partNumber, payload, context) => this.uploadPart(upload, partNumber, payload, context),\n completeMultipart: (upload, parts, context) => this.completeMultipart(upload, parts, context),\n abortMultipart: (upload) => this.abortMultipart(upload),\n }\n }\n\n /**\n * 原子写入对象:内容、ETag 与元数据作为一个整体提交。\n *\n * 提交顺序为:内容写入临时文件并同步 → 以临时文件 inode 写入元数据并同步 →\n * 原子 `rename`(覆盖)或 `link`(禁止覆盖)到目标位置 → 同步父目录 → 回收被替换内容的元数据。\n * 任一步骤失败或进程崩溃时,目标位置要么保持旧对象,要么已是完整的新对象。\n *\n * @param key 完整对象键。\n * @param payload 上传内容。\n * @param options 已归一化的写入选项。\n * @returns 对象描述信息。\n */\n public async put(key: string, payload: StoragePayload, options: DriverPutOptions): Promise<ObjectInfo> {\n const target = await this.objectPath(key, true)\n const written = await this.writeTemporary(payload, options.signal)\n let metadataPath: string | undefined\n try {\n const ino = (await stat(written.path, { bigint: true })).ino\n metadataPath = await this.writeMetadata(key, ino, {\n key,\n size: written.size,\n contentType: options.contentType,\n metadata: options.metadata,\n etag: written.etag,\n ...(options.cacheControl ? { cacheControl: options.cacheControl } : {}),\n ...(options.contentDisposition ? { contentDisposition: options.contentDisposition } : {}),\n })\n if (options.signal.aborted) throw options.signal.reason\n const replaced = await this.commit(written.path, target, key, options.overwrite)\n metadataPath = undefined\n if (replaced !== undefined && replaced !== ino) await this.removeMetadata(key, replaced)\n } finally {\n await rm(written.path, { force: true })\n if (metadataPath) await rm(metadataPath, { force: true })\n }\n return this.head(key)\n }\n\n /**\n * 以流方式读取对象。\n *\n * 先打开文件句柄,再从同一句柄读取状态与元数据,因此句柄打开后对象被删除或覆盖,\n * 本次读取仍返回打开时刻的完整内容;返回的流结束或被取消时自动关闭句柄。\n *\n * @param key 完整对象键。\n * @param options 读取范围与取消信号。\n * @returns 对象描述信息与可读流。\n */\n public async get(key: string, options: DriverGetOptions): Promise<ObjectStream> {\n const handle = await this.openObject(key)\n let info: ObjectInfo\n let end: number | undefined\n try {\n info = await this.describe(key, handle)\n const range = options.range\n if (range && range.start >= info.size && !(info.size === 0 && range.start === 0)) {\n throw new StorageError('INVALID_OPTION', 'Requested range is not satisfiable', { key })\n }\n end = range && info.size > 0 ? Math.min(range.end ?? info.size - 1, info.size - 1) : undefined\n } catch (error) {\n await handle.close()\n throw error\n }\n const start = end === undefined ? undefined : (options.range?.start as number)\n const nodeStream = handle.createReadStream({ start, end, autoClose: true, signal: options.signal })\n const body = Readable.toWeb(nodeStream) as unknown as ReadableStream<Uint8Array>\n return { info, body, ...(end === undefined ? {} : { range: { start: start as number, end } }) }\n }\n\n /**\n * 读取对象描述信息。\n *\n * @param key 完整对象键。\n * @returns 对象描述信息。\n */\n public async head(key: string): Promise<ObjectInfo> {\n const handle = await this.openObject(key)\n try {\n return await this.describe(key, handle)\n } finally {\n await handle.close()\n }\n }\n\n /**\n * 删除对象与元数据,并清理随之变空的父目录。\n *\n * @param key 完整对象键。\n */\n public async delete(key: string): Promise<void> {\n const root = await this.ensureRoot()\n const path = await this.objectPath(key, false)\n let ino: bigint\n try {\n const details = await lstat(path, { bigint: true })\n if (!details.isFile()) return\n ino = details.ino\n await unlink(path)\n } catch (error) {\n if (notFoundCodes.has(errorCode(error) ?? '')) return\n throw toStorageError(error, 'delete', key)\n }\n await this.syncParent(path)\n await this.removeMetadata(key, ino)\n await this.pruneDirectories(root, dirname(path))\n }\n\n /**\n * 按键名字典序分页列举对象,支持分隔符折叠目录。\n *\n * 实现按目录逐层有序遍历,在凑满一页后立即停止,不会一次性加载整个前缀;\n * 设置分隔符时不会进入被折叠的子目录。每页只为当前页对象读取描述信息,\n * 并发数受限,适合大多数单机场景。海量对象建议使用云端 OSS。\n *\n * @param options 前缀、分隔符、游标与页大小。\n * @returns 当前页结果。\n */\n public async list(options: DriverListOptions): Promise<ListResult> {\n const root = await this.ensureRoot()\n const slash = options.prefix.lastIndexOf('/')\n const baseKey = slash >= 0 ? options.prefix.slice(0, slash) : ''\n const start = baseKey ? resolveInside(root, baseKey) : root\n const page: Array<{ name: string; prefix: boolean }> = []\n let truncated = false\n for await (const entry of this.entries(root, start, options)) {\n // 非 `/` 分隔符可能把不同目录折叠为同一公共前缀;输出全局有序,重复项必然相邻。\n if (entry.prefix && page.at(-1)?.name === entry.name) continue\n if (page.length === options.limit) {\n truncated = true\n break\n }\n page.push(entry)\n }\n const names = page.filter((entry) => !entry.prefix).map((entry) => entry.name)\n const objects: ObjectInfo[] = []\n for (let index = 0; index < names.length; index += 32) {\n const batch = await Promise.all(names.slice(index, index + 32).map((name) => this.headIfExists(name)))\n for (const info of batch) if (info) objects.push(info)\n }\n const last = page.at(-1)\n return {\n objects,\n prefixes: page.filter((entry) => entry.prefix).map((entry) => entry.name),\n ...(truncated && last ? { cursor: last.name } : {}),\n }\n }\n\n /**\n * 在本地复制对象及其元数据;读取基于源文件句柄,复制过程中源对象被修改也不影响结果一致性。\n *\n * @param source 完整源对象键。\n * @param destination 完整目标对象键。\n * @param options 覆盖策略与取消信号。\n * @returns 目标对象描述信息。\n */\n public async copy(source: string, destination: string, options: DriverCopyOptions): Promise<ObjectInfo> {\n const { info, body } = await this.get(source, { signal: options.signal })\n return this.put(destination, createPayload(body), {\n signal: options.signal,\n overwrite: options.overwrite,\n contentType: info.contentType as string,\n metadata: info.metadata,\n cacheControl: info.cacheControl,\n contentDisposition: info.contentDisposition,\n })\n }\n\n /**\n * 生成本地 HMAC 签名 URL。\n *\n * @param key 完整对象键。\n * @param options 方法、有效期与头部约束。\n * @returns 签名 URL。\n */\n public async signUrl(key: string, options: DriverSignOptions): Promise<string> {\n await this.objectPath(key, false)\n return this.requireSigner().sign(\n key,\n options.method,\n options.expiresIn,\n options.contentType,\n options.responseContentDisposition,\n )\n }\n\n /**\n * 校验本地签名 URL。\n *\n * @param request 请求信息。\n * @returns 授权信息。\n */\n public verifyUrl(request: VerifyUrlRequest): VerifiedUrl {\n const verified = this.requireSigner().verify(request)\n assertFileKey(verified.key)\n return verified\n }\n\n /**\n * 文件系统不持有长连接,关闭为空操作;读取流持有的句柄随流结束自动释放。\n */\n public async close(): Promise<void> {}\n\n /**\n * 回收崩溃残留的临时文件与孤立元数据。\n *\n * 初始化时会自动清理临时文件;孤立元数据(内容已不存在或已被替换)的回收需要\n * 遍历元数据目录,建议在低峰期由定时任务调用。\n *\n * @returns 删除的临时文件与元数据文件数量。\n */\n public async cleanup(): Promise<{ temporary: number; metadata: number }> {\n const root = await this.ensureRoot()\n const temporary = await removeStale(internalPath(root, 'tmp'), this.temporaryTtl)\n let metadata = 0\n const base = internalPath(root, 'meta')\n for (const group of await readdir(base, { withFileTypes: true })) {\n if (!group.isDirectory()) continue\n const directory = join(base, group.name)\n for (const file of await readdir(directory, { withFileTypes: true })) {\n if (!file.isFile()) continue\n if (await this.isOrphan(join(directory, file.name), file.name)) {\n await rm(join(directory, file.name), { force: true })\n metadata += 1\n }\n }\n try {\n await rmdir(directory)\n } catch {\n // 仍有有效元数据,或并发写入刚向该目录提交元数据,保留目录即可。\n }\n }\n return { temporary, metadata }\n }\n\n private async createMultipart(key: string, options: DriverPutOptions): Promise<MultipartUpload> {\n const root = await this.ensureRoot()\n await this.objectPath(key, true)\n await removeStale(internalPath(root, 'multipart'), this.multipartTtl)\n const uploadId = randomUUID()\n const directory = internalPath(root, 'multipart', uploadId)\n await mkdir(directory, { recursive: true, mode: this.directoryMode })\n const manifest: MultipartManifest = {\n key,\n createdAt: Date.now(),\n options: {\n contentType: options.contentType,\n metadata: options.metadata,\n overwrite: options.overwrite,\n ...(options.cacheControl ? { cacheControl: options.cacheControl } : {}),\n ...(options.contentDisposition ? { contentDisposition: options.contentDisposition } : {}),\n },\n }\n await this.writeFileAtomic(join(directory, 'manifest.json'), JSON.stringify(manifest))\n return { key, uploadId }\n }\n\n private async uploadPart(\n upload: MultipartUpload,\n partNumber: number,\n payload: StoragePayload,\n context: DriverContext,\n ): Promise<UploadedPart> {\n const directory = await this.uploadDirectory(upload)\n const written = await this.writeTemporary(payload, context.signal)\n try {\n if (context.signal.aborted) throw context.signal.reason\n await rename(written.path, join(directory, `${partNumber}.part`))\n } finally {\n await rm(written.path, { force: true })\n }\n return { partNumber, etag: written.etag }\n }\n\n private async completeMultipart(\n upload: MultipartUpload,\n parts: UploadedPart[],\n context: DriverContext,\n ): Promise<ObjectInfo> {\n const directory = await this.uploadDirectory(upload)\n const manifest = await this.readManifest(directory, upload)\n const files: string[] = []\n for (const part of parts) {\n const file = join(directory, `${part.partNumber}.part`)\n let actual: string\n try {\n actual = await this.digestFile(file)\n } catch (error) {\n throw new StorageError('INVALID_OPTION', `Part ${part.partNumber} has not been uploaded`, {\n key: upload.key,\n cause: error,\n })\n }\n if (actual !== part.etag) {\n throw new StorageError('INVALID_OPTION', `Part ${part.partNumber} etag mismatch`, { key: upload.key })\n }\n files.push(file)\n }\n const { overwrite, ...http } = manifest.options\n const info = await this.put(upload.key, createPayload(concatFiles(files)), {\n ...http,\n overwrite,\n signal: context.signal,\n })\n await rm(directory, { recursive: true, force: true })\n return info\n }\n\n private async abortMultipart(upload: MultipartUpload): Promise<void> {\n if (!uploadIdPattern.test(upload.uploadId)) return\n const root = await this.ensureRoot()\n await rm(internalPath(root, 'multipart', upload.uploadId), { recursive: true, force: true })\n }\n\n private ensureRoot(): Promise<string> {\n this.ready ??= (async () => {\n await mkdir(this.root, { recursive: true, mode: this.directoryMode })\n const root = await realpath(this.root)\n for (const name of ['tmp', 'meta', 'multipart']) {\n await mkdir(internalPath(root, name), { recursive: true, mode: this.directoryMode })\n }\n await removeStale(internalPath(root, 'tmp'), this.temporaryTtl)\n return root\n })().catch((error: unknown) => {\n this.ready = undefined\n throw toStorageError(error, 'init')\n })\n return this.ready\n }\n\n private async objectPath(key: string, createParents: boolean): Promise<string> {\n assertFileKey(key)\n const root = await this.ensureRoot()\n const target = resolveInside(root, key)\n const parent = dirname(target)\n if (createParents) await mkdir(parent, { recursive: true, mode: this.directoryMode })\n let realParent: string\n try {\n realParent = await realpath(parent)\n } catch (error) {\n if (notFoundCodes.has(errorCode(error) ?? '')) return target\n throw toStorageError(error, 'resolve', key)\n }\n if (realParent !== root && !isInside(root, realParent)) {\n throw new StorageError('INVALID_KEY', 'Invalid object key: escapes the storage root', { key })\n }\n return join(realParent, target.slice(parent.length + 1))\n }\n\n /**\n * 打开对象内容文件,拒绝符号链接、目录与特殊文件。\n *\n * 先以 `lstat` 排除符号链接,再打开并以 `fstat` 确认打开的正是该普通文件,\n * 消除检查与打开之间被替换为符号链接的竞态窗口。\n */\n private async openObject(key: string): Promise<FileHandle> {\n const path = await this.objectPath(key, false)\n let handle: FileHandle | undefined\n try {\n const linkDetails = await lstat(path, { bigint: true })\n if (!linkDetails.isFile()) throw notFound(key)\n handle = await open(path, 'r')\n const details = await handle.stat({ bigint: true })\n if (!details.isFile() || details.ino !== linkDetails.ino || details.dev !== linkDetails.dev) {\n throw notFound(key)\n }\n return handle\n } catch (error) {\n await handle?.close()\n if (notFoundCodes.has(errorCode(error) ?? '')) throw notFound(key, error)\n throw toStorageError(error, 'open', key)\n }\n }\n\n private async describe(key: string, handle: FileHandle): Promise<ObjectInfo> {\n const details = await handle.stat({ bigint: true })\n const size = Number(details.size)\n const stored = await this.readMetadata(key, details.ino, size)\n return {\n key,\n size,\n lastModified: details.mtime,\n contentType: stored?.contentType ?? 'application/octet-stream',\n metadata: stored?.metadata ?? {},\n ...(stored?.etag ? { etag: stored.etag } : {}),\n ...(stored?.cacheControl ? { cacheControl: stored.cacheControl } : {}),\n ...(stored?.contentDisposition ? { contentDisposition: stored.contentDisposition } : {}),\n }\n }\n\n private async headIfExists(key: string): Promise<ObjectInfo | undefined> {\n try {\n return await this.head(key)\n } catch (error) {\n // 列举与读取之间对象可能被删除,跳过即可。\n if ((error as StorageError).code === 'NOT_FOUND') return undefined\n throw error\n }\n }\n\n private async writeTemporary(payload: StoragePayload, signal: AbortSignal): Promise<WrittenFile> {\n const root = await this.ensureRoot()\n const path = internalPath(root, 'tmp', `${randomUUID()}.tmp`)\n const handle = await open(path, 'wx', this.fileMode)\n const hash = createHash('md5')\n let size = 0\n const reader = payload.stream().getReader()\n // 源流可能长期不产出数据:取消时通过读取器终止挂起的 read(),确保句柄及时释放。\n const onAbort = () => {\n reader.cancel(signal.reason).then(ignore, ignore)\n }\n signal.addEventListener('abort', onAbort, { once: true })\n try {\n if (signal.aborted) throw signal.reason\n for (;;) {\n const { done, value } = await reader.read()\n if (signal.aborted) throw signal.reason\n if (done) break\n hash.update(value)\n size += value.byteLength\n await handle.write(value)\n }\n if (this.durable) await handle.sync()\n } catch (error) {\n await handle.close()\n await rm(path, { force: true })\n throw error\n } finally {\n signal.removeEventListener('abort', onAbort)\n reader.releaseLock()\n }\n await handle.close()\n return { path, etag: hash.digest('hex'), size }\n }\n\n /**\n * 把临时文件原子提交到目标位置。\n *\n * @returns 被替换对象的 inode;目标此前不存在时返回 `undefined`。\n */\n private async commit(\n temporary: string,\n target: string,\n key: string,\n overwrite: boolean,\n ): Promise<bigint | undefined> {\n let replaced: bigint | undefined\n try {\n const existing = await lstat(target, { bigint: true })\n if (!existing.isFile()) {\n throw new StorageError('ALREADY_EXISTS', 'A directory or special file exists at this key', { key })\n }\n replaced = existing.ino\n } catch (error) {\n if (!notFoundCodes.has(errorCode(error) ?? '')) throw toStorageError(error, 'put', key)\n }\n try {\n if (overwrite) await renameWithRetry(temporary, target)\n else await link(temporary, target)\n } catch (error) {\n if (errorCode(error) === 'EEXIST') throw new StorageError('ALREADY_EXISTS', 'Object already exists', { key })\n if (lockedCodes.has(errorCode(error) ?? '')) {\n throw new StorageError('BACKEND', 'Object is locked by another reader or writer', {\n key,\n cause: error,\n retryable: true,\n })\n }\n throw toStorageError(error, 'put', key)\n }\n await this.syncParent(target)\n return overwrite ? replaced : undefined\n }\n\n private async syncParent(path: string): Promise<void> {\n if (this.durable) await syncDirectory(dirname(path))\n }\n\n private async metadataDirectory(key: string): Promise<string> {\n const root = await this.ensureRoot()\n return internalPath(root, 'meta', metadataDirectoryName(key))\n }\n\n private async writeMetadata(key: string, ino: bigint, metadata: StoredMetadata): Promise<string> {\n const directory = await this.metadataDirectory(key)\n await mkdir(directory, { recursive: true, mode: this.directoryMode })\n const path = join(directory, metadataFileName(ino))\n await this.writeFileAtomic(path, JSON.stringify(metadata))\n return path\n }\n\n private async readMetadata(key: string, ino: bigint, size: number): Promise<StoredMetadata | undefined> {\n const path = join(await this.metadataDirectory(key), metadataFileName(ino))\n try {\n return parseMetadata(await readFile(path, 'utf8'), key, size)\n } catch (error) {\n if (notFoundCodes.has(errorCode(error) ?? '')) return undefined\n throw toStorageError(error, 'head', key)\n }\n }\n\n /**\n * 删除指定 inode 的元数据文件。\n *\n * 不在此处删除空的分组目录:并发写入可能正在向同一目录提交元数据,空目录由\n * {@link FileSystemDriver.cleanup} 统一回收。\n */\n private async removeMetadata(key: string, ino: bigint): Promise<void> {\n const directory = await this.metadataDirectory(key)\n await rm(join(directory, metadataFileName(ino)), { force: true })\n }\n\n /**\n * 判断元数据文件是否已失去对应的内容文件(对象被删除或已被新写入替换)。\n */\n private async isOrphan(path: string, file: string): Promise<boolean> {\n let key: string\n try {\n const value = JSON.parse(await readFile(path, 'utf8')) as Partial<StoredMetadata> | null\n if (typeof value?.key !== 'string') return true\n key = value.key\n } catch {\n // 内容损坏或已被并发删除,均可安全回收。\n return true\n }\n try {\n const target = await this.objectPath(key, false)\n const details = await lstat(target, { bigint: true })\n return !details.isFile() || metadataFileName(details.ino) !== file\n } catch {\n return true\n }\n }\n\n private async writeFileAtomic(path: string, content: string): Promise<void> {\n const root = await this.ensureRoot()\n const temporary = internalPath(root, 'tmp', `${randomUUID()}.tmp`)\n const handle = await open(temporary, 'wx', this.fileMode)\n try {\n await handle.writeFile(content)\n if (this.durable) await handle.sync()\n } finally {\n await handle.close()\n }\n try {\n await rename(temporary, path)\n } finally {\n await rm(temporary, { force: true })\n }\n await this.syncParent(path)\n }\n\n private async pruneDirectories(root: string, directory: string): Promise<void> {\n let current = directory\n while (current !== root && isInside(root, current)) {\n try {\n await rmdir(current)\n } catch {\n return\n }\n current = dirname(current)\n }\n }\n\n /**\n * 按全局字典序惰性产生列举条目。\n *\n * 目录以 `名称/` 参与排序,使逐层深度优先遍历的输出顺序与“扁平键排序”完全一致;\n * 不与前缀或游标相交的目录会被整体跳过,设置分隔符时被折叠的目录不会被展开。\n */\n private async *entries(\n root: string,\n directory: string,\n options: DriverListOptions,\n ): AsyncGenerator<{ name: string; prefix: boolean }> {\n let dirents: Dirent[]\n try {\n dirents = await readdir(directory, { withFileTypes: true })\n } catch (error) {\n if (notFoundCodes.has(errorCode(error) ?? '')) return\n throw toStorageError(error, 'list')\n }\n const base = directory === root ? '' : `${toKey(root, directory)}/`\n const items = dirents\n .filter((entry) => !(directory === root && entry.name === INTERNAL_DIRECTORY))\n .filter((entry) => entry.isFile() || entry.isDirectory())\n .map((entry) => ({ entry, key: base + entry.name + (entry.isDirectory() ? '/' : '') }))\n .sort((left, right) => (left.key < right.key ? -1 : left.key > right.key ? 1 : 0))\n const { prefix, delimiter, cursor } = options\n for (const { entry, key } of items) {\n if (entry.isDirectory()) {\n // 目录内所有键都以 key 开头:与前缀不相交、或全部不大于游标时整体跳过。\n if (!key.startsWith(prefix) && !prefix.startsWith(key)) continue\n if (cursor !== undefined && cursor >= key && !cursor.startsWith(key)) continue\n // 目录键本身以 `/` 结尾,若在前缀之后出现分隔符,整个目录折叠为一个公共前缀。\n const folded = foldedPrefix(key, prefix, delimiter)\n if (folded === undefined) yield* this.entries(root, join(directory, entry.name), options)\n else if (cursor === undefined || folded > cursor) yield { name: folded, prefix: true }\n continue\n }\n if (!key.startsWith(prefix) || (cursor !== undefined && key <= cursor)) continue\n const folded = foldedPrefix(key, prefix, delimiter)\n if (folded === undefined) yield { name: key, prefix: false }\n else if (cursor === undefined || folded > cursor) yield { name: folded, prefix: true }\n }\n }\n\n private async uploadDirectory(upload: MultipartUpload): Promise<string> {\n if (!uploadIdPattern.test(upload.uploadId)) throw noSuchUpload(upload)\n const root = await this.ensureRoot()\n const directory = internalPath(root, 'multipart', upload.uploadId)\n await this.readManifest(directory, upload)\n return directory\n }\n\n private async readManifest(directory: string, upload: MultipartUpload): Promise<MultipartManifest> {\n let manifest: MultipartManifest\n try {\n manifest = JSON.parse(await readFile(join(directory, 'manifest.json'), 'utf8')) as MultipartManifest\n } catch (error) {\n throw noSuchUpload(upload, error)\n }\n if (manifest.key !== upload.key) throw noSuchUpload(upload)\n return manifest\n }\n\n private async digestFile(path: string): Promise<string> {\n const hash = createHash('md5')\n for await (const chunk of createReadStream(path)) hash.update(chunk as Buffer)\n return hash.digest('hex')\n }\n\n private requireSigner(): UrlSigner {\n if (!this.signer) {\n throw new StorageError('UNSUPPORTED', 'Signed URLs require the \"signing\" option on FileSystemDriver')\n }\n return this.signer\n }\n}\n\n/**\n * 计算键在前缀之后按分隔符折叠出的公共前缀。\n *\n * @param key 对象键或以 `/` 结尾的目录键。\n * @param prefix 列举前缀。\n * @param delimiter 分隔符;未设置时不折叠。\n * @returns 公共前缀;未设置分隔符、键不以前缀开头(前缀落在目录内部)或前缀之后\n * 不含分隔符时返回 `undefined`。\n */\nexport function foldedPrefix(key: string, prefix: string, delimiter: string | undefined): string | undefined {\n if (!delimiter || !key.startsWith(prefix)) return undefined\n const index = key.indexOf(delimiter, prefix.length)\n return index < 0 ? undefined : key.slice(0, index + delimiter.length)\n}\n\n/**\n * Windows 上目标文件被其他句柄打开时 `rename` 会返回的错误码。\n */\nconst lockedCodes = new Set(['EPERM', 'EACCES', 'EBUSY'])\n\n/**\n * 原子替换目标文件;目标被其他句柄短暂占用(Windows)时以退避重试,最长约 1 秒。\n *\n * POSIX 平台上 `rename` 从不因读取方占用而失败,因此只在首次失败时进入重试。\n *\n * @param from 源路径。\n * @param to 目标路径。\n * @param attempts 最大尝试次数。\n */\nexport async function renameWithRetry(from: string, to: string, attempts = 8): Promise<void> {\n for (let attempt = 1; ; attempt += 1) {\n try {\n await rename(from, to)\n return\n } catch (error) {\n if (attempt >= attempts || !lockedCodes.has(errorCode(error) ?? '')) throw error\n await new Promise((resolve) => setTimeout(resolve, Math.min(250, 10 * 2 ** attempt)))\n }\n }\n}\n\nasync function* concatFiles(files: string[]): AsyncGenerator<Uint8Array> {\n for (const file of files) {\n for await (const chunk of createReadStream(file)) yield chunk as Buffer\n }\n}\n\n/**\n * 吞掉 Promise 结果的空回调,用于无需关心结果的清理操作。\n */\nfunction ignore(): void {}\n\nfunction notFound(key: string, cause?: unknown): StorageError {\n return new StorageError('NOT_FOUND', 'Object not found', { key, cause })\n}\n\nfunction noSuchUpload(upload: MultipartUpload, cause?: unknown): StorageError {\n return new StorageError('NOT_FOUND', 'Multipart upload not found', { key: upload.key, cause })\n}\n",
5
+ "import { createHash, randomUUID } from 'node:crypto'\nimport type { Dirent } from 'node:fs'\nimport { createReadStream } from 'node:fs'\nimport {\n type FileHandle,\n link,\n lstat,\n mkdir,\n open,\n readdir,\n readFile,\n realpath,\n rename,\n rm,\n rmdir,\n stat,\n unlink,\n} from 'node:fs/promises'\nimport { dirname, join, resolve } from 'node:path'\nimport { Readable } from 'node:stream'\nimport type {\n DriverContext,\n DriverCopyOptions,\n DriverGetOptions,\n DriverListOptions,\n DriverPutOptions,\n DriverSignOptions,\n MultipartDriver,\n StorageDriver,\n} from '../core/driver'\nimport { StorageError, toStorageError } from '../core/errors'\nimport { createPayload, type StoragePayload } from '../core/payload'\nimport type {\n ListObjectsResult,\n MultipartUpload,\n MultipartUploadPart,\n ObjectInfo,\n ObjectStream,\n VerifiedUrl,\n VerifyUrlRequest,\n} from '../core/types'\nimport { errorCode, removeStale, syncDirectory } from './durability'\nimport { metadataDirectoryName, metadataFileName, parseMetadata, type StoredMetadata } from './metadata'\nimport { assertFileKey, INTERNAL_DIRECTORY, internalPath, isInside, resolveInside, toKey } from './paths'\nimport { UrlSigner, type UrlSignerOptions } from './signer'\n\n/**\n * 本地文件系统适配器配置。\n */\nexport interface FileSystemDriverOptions {\n /**\n * 存储根目录;不存在时自动创建。\n */\n root: string\n /**\n * 签名 URL 配置;省略时 `signUrl` 抛出 `UNSUPPORTED`。\n */\n signing?: UrlSignerOptions\n /**\n * 新建目录的权限,默认为 `0o750`。\n */\n directoryMode?: number\n /**\n * 新建文件的权限,默认为 `0o640`。\n */\n fileMode?: number\n /**\n * 未完成分片会话的保留毫秒数,超时会在新建会话时清理,默认为 24 小时。\n */\n multipartTtl?: number\n /**\n * 崩溃残留临时文件的保留毫秒数,超时会在初始化时清理,默认为 1 小时。\n *\n * 该值应大于单次写入的最长耗时,避免误删仍在写入的临时文件。\n */\n temporaryTtl?: number\n /**\n * 是否对文件与目录执行 `fsync` 以保证断电持久性,默认为 `true`。\n * 关闭可提升吞吐,但断电时最近的写入可能丢失。\n */\n durable?: boolean\n}\n\ninterface MultipartManifest {\n key: string\n createdAt: number\n options: Omit<StoredMetadata, 'key' | 'size' | 'etag'> & { overwrite: boolean }\n}\n\ninterface WrittenFile {\n path: string\n etag: string\n size: number\n}\n\nconst uploadIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/\nconst notFoundCodes = new Set(['ENOENT', 'ENOTDIR'])\n\n/**\n * 生产级本地文件系统适配器。\n *\n * - 内容先写入内部临时目录并 `fsync`,再原子 `rename` / `link` 到目标位置,并同步父目录。\n * - 元数据以内容文件的 inode 命名并在内容提交前落盘,内容与元数据始终成对可见;\n * 并发写入同一键时,读取方看到的内容、ETag 与元数据总是来自同一次写入。\n * - 读取先打开文件句柄,再基于该句柄读取状态与元数据,句柄打开后对象被删除或覆盖也不影响本次读取。\n * - 拒绝路径穿越、符号链接逃逸、Windows 保留名与内部目录访问。\n * - 初始化时回收崩溃残留的临时文件,新建分片会话时回收过期会话。\n *\n * @example\n * ```ts\n * const driver = new FileSystemDriver({\n * root: '/data/oss',\n * signing: { secret: config.ossSecret, baseUrl: 'https://api.example.com/files' },\n * })\n * ```\n */\nexport class FileSystemDriver implements StorageDriver {\n /**\n * 适配器名称。\n */\n public readonly name = 'filesystem'\n /**\n * 分片上传能力。\n */\n public readonly multipart: MultipartDriver\n private readonly root: string\n private readonly signer?: UrlSigner\n private readonly directoryMode: number\n private readonly fileMode: number\n private readonly multipartTtl: number\n private readonly temporaryTtl: number\n private readonly durable: boolean\n private ready?: Promise<string>\n\n /**\n * 创建本地文件系统适配器。\n *\n * @param options 根目录、签名、权限与持久性配置。\n * @throws {@link StorageError} 配置非法时抛出 `INVALID_OPTION`。\n */\n public constructor(options: FileSystemDriverOptions) {\n if (typeof options?.root !== 'string' || options.root.length === 0) {\n throw new StorageError('INVALID_OPTION', 'root is required')\n }\n this.root = resolve(options.root)\n this.signer = options.signing ? new UrlSigner(options.signing) : undefined\n this.directoryMode = options.directoryMode ?? 0o750\n this.fileMode = options.fileMode ?? 0o640\n this.multipartTtl = options.multipartTtl ?? 24 * 60 * 60 * 1000\n this.temporaryTtl = options.temporaryTtl ?? 60 * 60 * 1000\n this.durable = options.durable ?? true\n this.multipart = {\n createMultipart: (key, putOptions) => this.createMultipart(key, putOptions),\n uploadPart: (upload, partNumber, payload, context) => this.uploadPart(upload, partNumber, payload, context),\n completeMultipart: (upload, parts, context) => this.completeMultipart(upload, parts, context),\n abortMultipart: (upload) => this.abortMultipart(upload),\n }\n }\n\n /**\n * 原子写入对象:内容、ETag 与元数据作为一个整体提交。\n *\n * 提交顺序为:内容写入临时文件并同步 → 以临时文件 inode 写入元数据并同步 →\n * 原子 `rename`(覆盖)或 `link`(禁止覆盖)到目标位置 → 同步父目录 → 回收被替换内容的元数据。\n * 任一步骤失败或进程崩溃时,目标位置要么保持旧对象,要么已是完整的新对象。\n *\n * @param key 完整对象键。\n * @param payload 上传内容。\n * @param options 已归一化的写入选项。\n * @returns 对象描述信息。\n */\n public async put(key: string, payload: StoragePayload, options: DriverPutOptions): Promise<ObjectInfo> {\n const target = await this.objectPath(key, true)\n const written = await this.writeTemporary(payload, options.signal)\n let metadataPath: string | undefined\n try {\n const ino = (await stat(written.path, { bigint: true })).ino\n metadataPath = await this.writeMetadata(key, ino, {\n key,\n size: written.size,\n contentType: options.contentType,\n metadata: options.metadata,\n etag: written.etag,\n ...(options.cacheControl ? { cacheControl: options.cacheControl } : {}),\n ...(options.contentDisposition ? { contentDisposition: options.contentDisposition } : {}),\n })\n if (options.signal.aborted) throw options.signal.reason\n const replaced = await this.commit(written.path, target, key, options.overwrite)\n metadataPath = undefined\n if (replaced !== undefined && replaced !== ino) await this.removeMetadata(key, replaced)\n } finally {\n await rm(written.path, { force: true })\n if (metadataPath) await rm(metadataPath, { force: true })\n }\n return this.head(key)\n }\n\n /**\n * 以流方式读取对象。\n *\n * 先打开文件句柄,再从同一句柄读取状态与元数据,因此句柄打开后对象被删除或覆盖,\n * 本次读取仍返回打开时刻的完整内容;返回的流结束或被取消时自动关闭句柄。\n *\n * @param key 完整对象键。\n * @param options 读取范围与取消信号。\n * @returns 对象描述信息与可读流。\n */\n public async get(key: string, options: DriverGetOptions): Promise<ObjectStream> {\n const handle = await this.openObject(key)\n let info: ObjectInfo\n let end: number | undefined\n try {\n info = await this.describe(key, handle)\n const range = options.range\n if (range && range.start >= info.size && !(info.size === 0 && range.start === 0)) {\n throw new StorageError('INVALID_OPTION', 'Requested range is not satisfiable', { key })\n }\n end = range && info.size > 0 ? Math.min(range.end ?? info.size - 1, info.size - 1) : undefined\n } catch (error) {\n await handle.close()\n throw error\n }\n const start = end === undefined ? undefined : (options.range?.start as number)\n const nodeStream = handle.createReadStream({ start, end, autoClose: true, signal: options.signal })\n const body = Readable.toWeb(nodeStream) as unknown as ReadableStream<Uint8Array>\n return { info, body, ...(end === undefined ? {} : { range: { start: start as number, end } }) }\n }\n\n /**\n * 读取对象描述信息。\n *\n * @param key 完整对象键。\n * @returns 对象描述信息。\n */\n public async head(key: string): Promise<ObjectInfo> {\n const handle = await this.openObject(key)\n try {\n return await this.describe(key, handle)\n } finally {\n await handle.close()\n }\n }\n\n /**\n * 删除对象与元数据,并清理随之变空的父目录。\n *\n * @param key 完整对象键。\n */\n public async delete(key: string): Promise<void> {\n const root = await this.ensureRoot()\n const path = await this.objectPath(key, false)\n let ino: bigint\n try {\n const details = await lstat(path, { bigint: true })\n if (!details.isFile()) return\n ino = details.ino\n await unlink(path)\n } catch (error) {\n if (notFoundCodes.has(errorCode(error) ?? '')) return\n throw toStorageError(error, 'delete', key)\n }\n await this.syncParent(path)\n await this.removeMetadata(key, ino)\n await this.pruneDirectories(root, dirname(path))\n }\n\n /**\n * 按键名字典序分页列举对象,支持分隔符折叠目录。\n *\n * 实现按目录逐层有序遍历,在凑满一页后立即停止,不会一次性加载整个前缀;\n * 设置分隔符时不会进入被折叠的子目录。每页只为当前页对象读取描述信息,\n * 并发数受限,适合大多数单机场景。海量对象建议使用云端 OSS。\n *\n * @param options 前缀、分隔符、游标与页大小。\n * @returns 当前页结果。\n */\n public async list(options: DriverListOptions): Promise<ListObjectsResult> {\n const root = await this.ensureRoot()\n const slash = options.prefix.lastIndexOf('/')\n const baseKey = slash >= 0 ? options.prefix.slice(0, slash) : ''\n const start = baseKey ? resolveInside(root, baseKey) : root\n const page: Array<{ name: string; prefix: boolean }> = []\n let truncated = false\n for await (const entry of this.entries(root, start, options)) {\n // 非 `/` 分隔符可能把不同目录折叠为同一公共前缀;输出全局有序,重复项必然相邻。\n if (entry.prefix && page.at(-1)?.name === entry.name) continue\n if (page.length === options.limit) {\n truncated = true\n break\n }\n page.push(entry)\n }\n const names = page.filter((entry) => !entry.prefix).map((entry) => entry.name)\n const objects: ObjectInfo[] = []\n for (let index = 0; index < names.length; index += 32) {\n const batch = await Promise.all(names.slice(index, index + 32).map((name) => this.headIfExists(name)))\n for (const info of batch) if (info) objects.push(info)\n }\n const last = page.at(-1)\n return {\n objects,\n prefixes: page.filter((entry) => entry.prefix).map((entry) => entry.name),\n ...(truncated && last ? { cursor: last.name } : {}),\n }\n }\n\n /**\n * 在本地复制对象及其元数据;读取基于源文件句柄,复制过程中源对象被修改也不影响结果一致性。\n *\n * @param source 完整源对象键。\n * @param destination 完整目标对象键。\n * @param options 覆盖策略与取消信号。\n * @returns 目标对象描述信息。\n */\n public async copy(source: string, destination: string, options: DriverCopyOptions): Promise<ObjectInfo> {\n const { info, body } = await this.get(source, { signal: options.signal })\n return this.put(destination, createPayload(body), {\n signal: options.signal,\n overwrite: options.overwrite,\n contentType: info.contentType as string,\n metadata: info.metadata,\n cacheControl: info.cacheControl,\n contentDisposition: info.contentDisposition,\n })\n }\n\n /**\n * 生成本地 HMAC 签名 URL。\n *\n * @param key 完整对象键。\n * @param options 方法、有效期与头部约束。\n * @returns 签名 URL。\n */\n public async signUrl(key: string, options: DriverSignOptions): Promise<string> {\n await this.objectPath(key, false)\n return this.requireSigner().sign(\n key,\n options.method,\n options.expiresIn,\n options.contentType,\n options.responseContentDisposition,\n )\n }\n\n /**\n * 校验本地签名 URL。\n *\n * @param request 请求信息。\n * @returns 授权信息。\n */\n public verifyUrl(request: VerifyUrlRequest): VerifiedUrl {\n const verified = this.requireSigner().verify(request)\n assertFileKey(verified.key)\n return verified\n }\n\n /**\n * 文件系统不持有长连接,关闭为空操作;读取流持有的句柄随流结束自动释放。\n */\n public async close(): Promise<void> {}\n\n /**\n * 回收崩溃残留的临时文件与孤立元数据。\n *\n * 初始化时会自动清理临时文件;孤立元数据(内容已不存在或已被替换)的回收需要\n * 遍历元数据目录,建议在低峰期由定时任务调用。\n *\n * @returns 删除的临时文件与元数据文件数量。\n */\n public async cleanup(): Promise<{ temporary: number; metadata: number }> {\n const root = await this.ensureRoot()\n const temporary = await removeStale(internalPath(root, 'tmp'), this.temporaryTtl)\n let metadata = 0\n const base = internalPath(root, 'meta')\n for (const group of await readdir(base, { withFileTypes: true })) {\n if (!group.isDirectory()) continue\n const directory = join(base, group.name)\n for (const file of await readdir(directory, { withFileTypes: true })) {\n if (!file.isFile()) continue\n if (await this.isOrphan(join(directory, file.name), file.name)) {\n await rm(join(directory, file.name), { force: true })\n metadata += 1\n }\n }\n try {\n await rmdir(directory)\n } catch {\n // 仍有有效元数据,或并发写入刚向该目录提交元数据,保留目录即可。\n }\n }\n return { temporary, metadata }\n }\n\n private async createMultipart(key: string, options: DriverPutOptions): Promise<MultipartUpload> {\n const root = await this.ensureRoot()\n await this.objectPath(key, true)\n await removeStale(internalPath(root, 'multipart'), this.multipartTtl)\n const uploadId = randomUUID()\n const directory = internalPath(root, 'multipart', uploadId)\n await mkdir(directory, { recursive: true, mode: this.directoryMode })\n const manifest: MultipartManifest = {\n key,\n createdAt: Date.now(),\n options: {\n contentType: options.contentType,\n metadata: options.metadata,\n overwrite: options.overwrite,\n ...(options.cacheControl ? { cacheControl: options.cacheControl } : {}),\n ...(options.contentDisposition ? { contentDisposition: options.contentDisposition } : {}),\n },\n }\n await this.writeFileAtomic(join(directory, 'manifest.json'), JSON.stringify(manifest))\n return { key, uploadId }\n }\n\n private async uploadPart(\n upload: MultipartUpload,\n partNumber: number,\n payload: StoragePayload,\n context: DriverContext,\n ): Promise<MultipartUploadPart> {\n const directory = await this.uploadDirectory(upload)\n const written = await this.writeTemporary(payload, context.signal)\n try {\n if (context.signal.aborted) throw context.signal.reason\n await rename(written.path, join(directory, `${partNumber}.part`))\n } finally {\n await rm(written.path, { force: true })\n }\n return { partNumber, etag: written.etag }\n }\n\n private async completeMultipart(\n upload: MultipartUpload,\n parts: MultipartUploadPart[],\n context: DriverContext,\n ): Promise<ObjectInfo> {\n const directory = await this.uploadDirectory(upload)\n const manifest = await this.readManifest(directory, upload)\n const files: string[] = []\n for (const part of parts) {\n const file = join(directory, `${part.partNumber}.part`)\n let actual: string\n try {\n actual = await this.digestFile(file)\n } catch (error) {\n throw new StorageError('INVALID_OPTION', `Part ${part.partNumber} has not been uploaded`, {\n key: upload.key,\n cause: error,\n })\n }\n if (actual !== part.etag) {\n throw new StorageError('INVALID_OPTION', `Part ${part.partNumber} etag mismatch`, { key: upload.key })\n }\n files.push(file)\n }\n const { overwrite, ...http } = manifest.options\n const info = await this.put(upload.key, createPayload(concatFiles(files)), {\n ...http,\n overwrite,\n signal: context.signal,\n })\n await rm(directory, { recursive: true, force: true })\n return info\n }\n\n private async abortMultipart(upload: MultipartUpload): Promise<void> {\n if (!uploadIdPattern.test(upload.uploadId)) return\n const root = await this.ensureRoot()\n await rm(internalPath(root, 'multipart', upload.uploadId), { recursive: true, force: true })\n }\n\n private ensureRoot(): Promise<string> {\n this.ready ??= (async () => {\n await mkdir(this.root, { recursive: true, mode: this.directoryMode })\n const root = await realpath(this.root)\n for (const name of ['tmp', 'meta', 'multipart']) {\n await mkdir(internalPath(root, name), { recursive: true, mode: this.directoryMode })\n }\n await removeStale(internalPath(root, 'tmp'), this.temporaryTtl)\n return root\n })().catch((error: unknown) => {\n this.ready = undefined\n throw toStorageError(error, 'init')\n })\n return this.ready\n }\n\n private async objectPath(key: string, createParents: boolean): Promise<string> {\n assertFileKey(key)\n const root = await this.ensureRoot()\n const target = resolveInside(root, key)\n const parent = dirname(target)\n if (createParents) await mkdir(parent, { recursive: true, mode: this.directoryMode })\n let realParent: string\n try {\n realParent = await realpath(parent)\n } catch (error) {\n if (notFoundCodes.has(errorCode(error) ?? '')) return target\n throw toStorageError(error, 'resolve', key)\n }\n if (realParent !== root && !isInside(root, realParent)) {\n throw new StorageError('INVALID_KEY', 'Invalid object key: escapes the storage root', { key })\n }\n return join(realParent, target.slice(parent.length + 1))\n }\n\n /**\n * 打开对象内容文件,拒绝符号链接、目录与特殊文件。\n *\n * 先以 `lstat` 排除符号链接,再打开并以 `fstat` 确认打开的正是该普通文件,\n * 消除检查与打开之间被替换为符号链接的竞态窗口。\n */\n private async openObject(key: string): Promise<FileHandle> {\n const path = await this.objectPath(key, false)\n let handle: FileHandle | undefined\n try {\n const linkDetails = await lstat(path, { bigint: true })\n if (!linkDetails.isFile()) throw notFound(key)\n handle = await open(path, 'r')\n const details = await handle.stat({ bigint: true })\n if (!details.isFile() || details.ino !== linkDetails.ino || details.dev !== linkDetails.dev) {\n throw notFound(key)\n }\n return handle\n } catch (error) {\n await handle?.close()\n if (notFoundCodes.has(errorCode(error) ?? '')) throw notFound(key, error)\n throw toStorageError(error, 'open', key)\n }\n }\n\n private async describe(key: string, handle: FileHandle): Promise<ObjectInfo> {\n const details = await handle.stat({ bigint: true })\n const size = Number(details.size)\n const stored = await this.readMetadata(key, details.ino, size)\n return {\n key,\n size,\n lastModified: details.mtime,\n contentType: stored?.contentType ?? 'application/octet-stream',\n metadata: stored?.metadata ?? {},\n ...(stored?.etag ? { etag: stored.etag } : {}),\n ...(stored?.cacheControl ? { cacheControl: stored.cacheControl } : {}),\n ...(stored?.contentDisposition ? { contentDisposition: stored.contentDisposition } : {}),\n }\n }\n\n private async headIfExists(key: string): Promise<ObjectInfo | undefined> {\n try {\n return await this.head(key)\n } catch (error) {\n // 列举与读取之间对象可能被删除,跳过即可。\n if ((error as StorageError).code === 'NOT_FOUND') return undefined\n throw error\n }\n }\n\n private async writeTemporary(payload: StoragePayload, signal: AbortSignal): Promise<WrittenFile> {\n const root = await this.ensureRoot()\n const path = internalPath(root, 'tmp', `${randomUUID()}.tmp`)\n const handle = await open(path, 'wx', this.fileMode)\n const hash = createHash('md5')\n let size = 0\n const reader = payload.stream().getReader()\n // 源流可能长期不产出数据:取消时通过读取器终止挂起的 read(),确保句柄及时释放。\n const onAbort = () => {\n reader.cancel(signal.reason).then(ignore, ignore)\n }\n signal.addEventListener('abort', onAbort, { once: true })\n try {\n if (signal.aborted) throw signal.reason\n for (;;) {\n const { done, value } = await reader.read()\n if (signal.aborted) throw signal.reason\n if (done) break\n hash.update(value)\n size += value.byteLength\n await handle.write(value)\n }\n if (this.durable) await handle.sync()\n } catch (error) {\n await handle.close()\n await rm(path, { force: true })\n throw error\n } finally {\n signal.removeEventListener('abort', onAbort)\n reader.releaseLock()\n }\n await handle.close()\n return { path, etag: hash.digest('hex'), size }\n }\n\n /**\n * 把临时文件原子提交到目标位置。\n *\n * @returns 被替换对象的 inode;目标此前不存在时返回 `undefined`。\n */\n private async commit(\n temporary: string,\n target: string,\n key: string,\n overwrite: boolean,\n ): Promise<bigint | undefined> {\n let replaced: bigint | undefined\n try {\n const existing = await lstat(target, { bigint: true })\n if (!existing.isFile()) {\n throw new StorageError('ALREADY_EXISTS', 'A directory or special file exists at this key', { key })\n }\n replaced = existing.ino\n } catch (error) {\n if (!notFoundCodes.has(errorCode(error) ?? '')) throw toStorageError(error, 'put', key)\n }\n try {\n if (overwrite) await renameWithRetry(temporary, target)\n else await link(temporary, target)\n } catch (error) {\n if (errorCode(error) === 'EEXIST') throw new StorageError('ALREADY_EXISTS', 'Object already exists', { key })\n if (lockedCodes.has(errorCode(error) ?? '')) {\n throw new StorageError('BACKEND', 'Object is locked by another reader or writer', {\n key,\n cause: error,\n retryable: true,\n })\n }\n throw toStorageError(error, 'put', key)\n }\n await this.syncParent(target)\n return overwrite ? replaced : undefined\n }\n\n private async syncParent(path: string): Promise<void> {\n if (this.durable) await syncDirectory(dirname(path))\n }\n\n private async metadataDirectory(key: string): Promise<string> {\n const root = await this.ensureRoot()\n return internalPath(root, 'meta', metadataDirectoryName(key))\n }\n\n private async writeMetadata(key: string, ino: bigint, metadata: StoredMetadata): Promise<string> {\n const directory = await this.metadataDirectory(key)\n await mkdir(directory, { recursive: true, mode: this.directoryMode })\n const path = join(directory, metadataFileName(ino))\n await this.writeFileAtomic(path, JSON.stringify(metadata))\n return path\n }\n\n private async readMetadata(key: string, ino: bigint, size: number): Promise<StoredMetadata | undefined> {\n const path = join(await this.metadataDirectory(key), metadataFileName(ino))\n try {\n return parseMetadata(await readFile(path, 'utf8'), key, size)\n } catch (error) {\n if (notFoundCodes.has(errorCode(error) ?? '')) return undefined\n throw toStorageError(error, 'head', key)\n }\n }\n\n /**\n * 删除指定 inode 的元数据文件。\n *\n * 不在此处删除空的分组目录:并发写入可能正在向同一目录提交元数据,空目录由\n * {@link FileSystemDriver.cleanup} 统一回收。\n */\n private async removeMetadata(key: string, ino: bigint): Promise<void> {\n const directory = await this.metadataDirectory(key)\n await rm(join(directory, metadataFileName(ino)), { force: true })\n }\n\n /**\n * 判断元数据文件是否已失去对应的内容文件(对象被删除或已被新写入替换)。\n */\n private async isOrphan(path: string, file: string): Promise<boolean> {\n let key: string\n try {\n const value = JSON.parse(await readFile(path, 'utf8')) as Partial<StoredMetadata> | null\n if (typeof value?.key !== 'string') return true\n key = value.key\n } catch {\n // 内容损坏或已被并发删除,均可安全回收。\n return true\n }\n try {\n const target = await this.objectPath(key, false)\n const details = await lstat(target, { bigint: true })\n return !details.isFile() || metadataFileName(details.ino) !== file\n } catch {\n return true\n }\n }\n\n private async writeFileAtomic(path: string, content: string): Promise<void> {\n const root = await this.ensureRoot()\n const temporary = internalPath(root, 'tmp', `${randomUUID()}.tmp`)\n const handle = await open(temporary, 'wx', this.fileMode)\n try {\n await handle.writeFile(content)\n if (this.durable) await handle.sync()\n } finally {\n await handle.close()\n }\n try {\n await rename(temporary, path)\n } finally {\n await rm(temporary, { force: true })\n }\n await this.syncParent(path)\n }\n\n private async pruneDirectories(root: string, directory: string): Promise<void> {\n let current = directory\n while (current !== root && isInside(root, current)) {\n try {\n await rmdir(current)\n } catch {\n return\n }\n current = dirname(current)\n }\n }\n\n /**\n * 按全局字典序惰性产生列举条目。\n *\n * 目录以 `名称/` 参与排序,使逐层深度优先遍历的输出顺序与“扁平键排序”完全一致;\n * 不与前缀或游标相交的目录会被整体跳过,设置分隔符时被折叠的目录不会被展开。\n */\n private async *entries(\n root: string,\n directory: string,\n options: DriverListOptions,\n ): AsyncGenerator<{ name: string; prefix: boolean }> {\n let dirents: Dirent[]\n try {\n dirents = await readdir(directory, { withFileTypes: true })\n } catch (error) {\n if (notFoundCodes.has(errorCode(error) ?? '')) return\n throw toStorageError(error, 'list')\n }\n const base = directory === root ? '' : `${toKey(root, directory)}/`\n const items = dirents\n .filter((entry) => !(directory === root && entry.name === INTERNAL_DIRECTORY))\n .filter((entry) => entry.isFile() || entry.isDirectory())\n .map((entry) => ({ entry, key: base + entry.name + (entry.isDirectory() ? '/' : '') }))\n .sort((left, right) => (left.key < right.key ? -1 : left.key > right.key ? 1 : 0))\n const { prefix, delimiter, cursor } = options\n for (const { entry, key } of items) {\n if (entry.isDirectory()) {\n // 目录内所有键都以 key 开头:与前缀不相交、或全部不大于游标时整体跳过。\n if (!key.startsWith(prefix) && !prefix.startsWith(key)) continue\n if (cursor !== undefined && cursor >= key && !cursor.startsWith(key)) continue\n // 目录键本身以 `/` 结尾,若在前缀之后出现分隔符,整个目录折叠为一个公共前缀。\n const folded = foldedPrefix(key, prefix, delimiter)\n if (folded === undefined) yield* this.entries(root, join(directory, entry.name), options)\n else if (cursor === undefined || folded > cursor) yield { name: folded, prefix: true }\n continue\n }\n if (!key.startsWith(prefix) || (cursor !== undefined && key <= cursor)) continue\n const folded = foldedPrefix(key, prefix, delimiter)\n if (folded === undefined) yield { name: key, prefix: false }\n else if (cursor === undefined || folded > cursor) yield { name: folded, prefix: true }\n }\n }\n\n private async uploadDirectory(upload: MultipartUpload): Promise<string> {\n if (!uploadIdPattern.test(upload.uploadId)) throw noSuchUpload(upload)\n const root = await this.ensureRoot()\n const directory = internalPath(root, 'multipart', upload.uploadId)\n await this.readManifest(directory, upload)\n return directory\n }\n\n private async readManifest(directory: string, upload: MultipartUpload): Promise<MultipartManifest> {\n let manifest: MultipartManifest\n try {\n manifest = JSON.parse(await readFile(join(directory, 'manifest.json'), 'utf8')) as MultipartManifest\n } catch (error) {\n throw noSuchUpload(upload, error)\n }\n if (manifest.key !== upload.key) throw noSuchUpload(upload)\n return manifest\n }\n\n private async digestFile(path: string): Promise<string> {\n const hash = createHash('md5')\n for await (const chunk of createReadStream(path)) hash.update(chunk as Buffer)\n return hash.digest('hex')\n }\n\n private requireSigner(): UrlSigner {\n if (!this.signer) {\n throw new StorageError('UNSUPPORTED', 'Signed URLs require the \"signing\" option on FileSystemDriver')\n }\n return this.signer\n }\n}\n\n/**\n * 计算键在前缀之后按分隔符折叠出的公共前缀。\n *\n * @param key 对象键或以 `/` 结尾的目录键。\n * @param prefix 列举前缀。\n * @param delimiter 分隔符;未设置时不折叠。\n * @returns 公共前缀;未设置分隔符、键不以前缀开头(前缀落在目录内部)或前缀之后\n * 不含分隔符时返回 `undefined`。\n */\nexport function foldedPrefix(key: string, prefix: string, delimiter: string | undefined): string | undefined {\n if (!delimiter || !key.startsWith(prefix)) return undefined\n const index = key.indexOf(delimiter, prefix.length)\n return index < 0 ? undefined : key.slice(0, index + delimiter.length)\n}\n\n/**\n * Windows 上目标文件被其他句柄打开时 `rename` 会返回的错误码。\n */\nconst lockedCodes = new Set(['EPERM', 'EACCES', 'EBUSY'])\n\n/**\n * 原子替换目标文件;目标被其他句柄短暂占用(Windows)时以退避重试,最长约 1 秒。\n *\n * POSIX 平台上 `rename` 从不因读取方占用而失败,因此只在首次失败时进入重试。\n *\n * @param from 源路径。\n * @param to 目标路径。\n * @param attempts 最大尝试次数。\n */\nexport async function renameWithRetry(from: string, to: string, attempts = 8): Promise<void> {\n for (let attempt = 1; ; attempt += 1) {\n try {\n await rename(from, to)\n return\n } catch (error) {\n if (attempt >= attempts || !lockedCodes.has(errorCode(error) ?? '')) throw error\n await new Promise((resolve) => setTimeout(resolve, Math.min(250, 10 * 2 ** attempt)))\n }\n }\n}\n\nasync function* concatFiles(files: string[]): AsyncGenerator<Uint8Array> {\n for (const file of files) {\n for await (const chunk of createReadStream(file)) yield chunk as Buffer\n }\n}\n\n/**\n * 吞掉 Promise 结果的空回调,用于无需关心结果的清理操作。\n */\nfunction ignore(): void {}\n\nfunction notFound(key: string, cause?: unknown): StorageError {\n return new StorageError('NOT_FOUND', 'Object not found', { key, cause })\n}\n\nfunction noSuchUpload(upload: MultipartUpload, cause?: unknown): StorageError {\n return new StorageError('NOT_FOUND', 'Multipart upload not found', { key: upload.key, cause })\n}\n",
6
6
  "import { open, readdir, rm, stat } from 'node:fs/promises'\nimport { join } from 'node:path'\n\n/**\n * 读取 Node.js 系统错误码。\n *\n * @param error 任意错误值。\n * @returns 错误码;不存在时返回 `undefined`。\n */\nexport function errorCode(error: unknown): string | undefined {\n return (error as { code?: string } | null)?.code\n}\n\n/**\n * 对目录执行 `fsync`,确保其中的 `rename`、`link`、`unlink` 在断电后依然可见。\n *\n * Windows 与部分文件系统不允许打开或同步目录(返回 `EPERM`、`EISDIR`、`EINVAL`、\n * `EACCES` 等),这些平台上目录项更新由文件系统自身保证,此时静默跳过。\n *\n * @param directory 目录绝对路径。\n */\nexport async function syncDirectory(directory: string): Promise<void> {\n let handle: Awaited<ReturnType<typeof open>> | undefined\n try {\n handle = await open(directory, 'r')\n await handle.sync()\n } catch (error) {\n if (!unsupportedSync.has(errorCode(error) ?? '')) throw error\n } finally {\n await handle?.close()\n }\n}\n\nconst unsupportedSync = new Set(['EPERM', 'EISDIR', 'EINVAL', 'EACCES', 'EBADF', 'ENOTSUP'])\n\n/**\n * 删除目录中修改时间早于阈值的条目,用于回收崩溃残留的临时文件与过期会话。\n *\n * 条目可能被并发操作删除,单个条目失败不会中断清理。\n *\n * @param directory 待清理目录。\n * @param maxAge 保留的最长毫秒数。\n * @param now 当前时间。\n * @returns 实际删除的条目数。\n */\nexport async function removeStale(directory: string, maxAge: number, now = Date.now()): Promise<number> {\n let removed = 0\n for (const name of await readdir(directory)) {\n const path = join(directory, name)\n try {\n if (now - (await stat(path)).mtimeMs <= maxAge) continue\n await rm(path, { recursive: true, force: true })\n removed += 1\n } catch {\n // 条目已被并发删除或暂时不可访问,留待下次清理。\n }\n }\n return removed\n}\n",
7
7
  "import { createHash } from 'node:crypto'\nimport type { ObjectHttpOptions } from '../core/types'\n\n/**\n * 持久化到旁路 JSON 文件中的对象属性。\n *\n * 元数据文件以内容文件的 inode 命名,与内容文件一一绑定:写入时先落盘元数据,\n * 再原子提交内容文件,因此读取方通过同一个文件句柄的 inode 总能找到与内容匹配的\n * 元数据,不会出现“新内容配旧元数据”的情况。\n */\nexport interface StoredMetadata extends ObjectHttpOptions {\n /**\n * 完整对象键,用于孤立元数据回收时定位对象。\n */\n key: string\n /**\n * 写入时的内容字节数,用于读取时二次校验。\n */\n size: number\n /**\n * 内容 MIME 类型。\n */\n contentType: string\n /**\n * 自定义元数据。\n */\n metadata: Record<string, string>\n /**\n * 写入时计算的内容 MD5 十六进制摘要,作为 ETag。\n */\n etag: string\n}\n\n/**\n * 计算对象键对应的元数据目录名。\n *\n * 使用 SHA-256 哈希而非原始键,避免长键或特殊字符超过文件系统限制。\n *\n * @param key 完整对象键。\n * @returns 元数据目录名。\n */\nexport function metadataDirectoryName(key: string): string {\n return createHash('sha256').update(key).digest('hex')\n}\n\n/**\n * 计算内容文件 inode 对应的元数据文件名。\n *\n * @param ino 内容文件的 inode(`bigint` 精度,避免超过安全整数范围)。\n * @returns 元数据文件名。\n */\nexport function metadataFileName(ino: bigint): string {\n return `${ino}.json`\n}\n\n/**\n * 解析元数据文件内容;内容损坏或与当前内容文件不匹配时返回 `undefined`,由调用方降级处理。\n *\n * @param text 元数据文件文本。\n * @param key 期望的完整对象键。\n * @param size 当前内容文件的字节数。\n * @returns 解析结果。\n */\nexport function parseMetadata(text: string, key: string, size: number): StoredMetadata | undefined {\n let value: Partial<StoredMetadata> | null\n try {\n value = JSON.parse(text) as Partial<StoredMetadata> | null\n } catch {\n return undefined\n }\n if (\n typeof value?.contentType !== 'string' ||\n typeof value.etag !== 'string' ||\n value.key !== key ||\n value.size !== size\n ) {\n return undefined\n }\n return {\n key,\n size,\n contentType: value.contentType,\n etag: value.etag,\n metadata: value.metadata && typeof value.metadata === 'object' ? value.metadata : {},\n ...(typeof value.cacheControl === 'string' ? { cacheControl: value.cacheControl } : {}),\n ...(typeof value.contentDisposition === 'string' ? { contentDisposition: value.contentDisposition } : {}),\n }\n}\n",
8
8
  "import { isAbsolute, join, relative, resolve, sep } from 'node:path'\nimport { StorageError } from '../core/errors'\n\n/**\n * 存放元数据、临时文件与分片会话的内部目录名,对象键不得以此开头。\n */\nexport const INTERNAL_DIRECTORY = '.meta-oss'\n\nconst windowsReserved = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\\..*)?$/i\nconst windowsInvalid = /[<>:\"|?*]/\n\n/**\n * 校验对象键能否安全、跨平台地映射到文件系统路径。\n *\n * 在服务层通用校验之外,额外拒绝内部目录、Windows 保留名、Windows 非法字符,\n * 以及以点号或空格结尾的路径段,保证同一份数据可在 Linux、macOS 与 Windows 间迁移。\n *\n * @param key 已通过通用校验的对象键。\n * @throws {@link StorageError} 不可映射时抛出 `INVALID_KEY`。\n */\nexport function assertFileKey(key: string): void {\n const segments = key.split('/')\n if (segments[0] === INTERNAL_DIRECTORY) throw invalid(key, 'uses the reserved internal directory')\n for (const segment of segments) {\n if (windowsInvalid.test(segment)) throw invalid(key, 'contains characters not allowed on Windows')\n if (windowsReserved.test(segment)) throw invalid(key, 'uses a reserved device name')\n if (segment.endsWith('.') || segment.endsWith(' ')) throw invalid(key, 'segment ends with dot or space')\n }\n}\n\n/**\n * 将对象键解析为根目录下的绝对路径,并再次确认路径未逃逸根目录。\n *\n * @param root 存储根目录的绝对路径。\n * @param key 对象键。\n * @returns 对象文件的绝对路径。\n * @throws {@link StorageError} 路径逃逸根目录时抛出 `INVALID_KEY`。\n */\nexport function resolveInside(root: string, key: string): string {\n const target = resolve(root, ...key.split('/'))\n if (!isInside(root, target)) throw invalid(key, 'escapes the storage root')\n return target\n}\n\n/**\n * 判断目标路径是否严格位于根目录内部。\n *\n * @param root 根目录绝对路径。\n * @param target 目标绝对路径。\n * @returns 是否位于根目录内部且不等于根目录本身。\n */\nexport function isInside(root: string, target: string): boolean {\n const path = relative(root, target)\n return path !== '' && !path.startsWith(`..${sep}`) && path !== '..' && !isAbsolute(path)\n}\n\n/**\n * 将相对于根目录的系统路径转换为以 `/` 分隔的对象键。\n *\n * @param root 根目录绝对路径。\n * @param path 文件绝对路径。\n * @returns 对象键。\n */\nexport function toKey(root: string, path: string): string {\n return relative(root, path).split(sep).join('/')\n}\n\n/**\n * 拼接内部目录下的路径。\n *\n * @param root 存储根目录。\n * @param parts 内部目录下的路径段。\n * @returns 绝对路径。\n */\nexport function internalPath(root: string, ...parts: string[]): string {\n return join(root, INTERNAL_DIRECTORY, ...parts)\n}\n\nfunction invalid(key: string, reason: string): StorageError {\n return new StorageError('INVALID_KEY', `Invalid object key: ${reason}`, { key })\n}\n",
9
9
  "import { createHmac, timingSafeEqual } from 'node:crypto'\nimport { StorageError } from '../core/errors'\nimport type { SignedUrlMethod, VerifiedUrl, VerifyUrlRequest } from '../core/types'\n\n/**\n * 本地签名 URL 的签名配置。\n */\nexport interface UrlSignerOptions {\n /**\n * HMAC-SHA256 密钥,至少 32 个字符;只能通过构造参数传入。\n */\n secret: string\n /**\n * 签名 URL 的基础地址,例如 `https://cdn.example.com/files`;对象键会追加在其后。\n */\n baseUrl: string\n /**\n * 当前时间来源,便于测试注入,默认为 `Date.now`。\n */\n now?: () => number\n}\n\n/**\n * 生成并校验本地文件存储的 HMAC 签名 URL。\n *\n * 插件不提供 HTTP 路由;业务可在自己的下载/上传路由中调用 {@link UrlSigner.verify}\n * 完成鉴权,再使用 `ObjectStorage` 读写对象。签名覆盖方法、对象键、过期时间、\n * `Content-Type` 与 `Content-Disposition`,并使用常量时间比较防止计时攻击。\n */\nexport class UrlSigner {\n private readonly secret: string\n private readonly base: URL\n private readonly now: () => number\n\n /**\n * 创建签名器。\n *\n * @param options 密钥、基础地址与时间来源。\n * @throws {@link StorageError} 密钥过短或基础地址非法时抛出 `INVALID_OPTION`。\n */\n public constructor(options: UrlSignerOptions) {\n if (typeof options?.secret !== 'string' || options.secret.length < 32) {\n throw new StorageError('INVALID_OPTION', 'signing secret must be at least 32 characters')\n }\n let base: URL\n try {\n base = new URL(options.baseUrl)\n } catch (error) {\n throw new StorageError('INVALID_OPTION', 'baseUrl must be an absolute URL', { cause: error })\n }\n if (!base.pathname.endsWith('/')) base.pathname += '/'\n this.secret = options.secret\n this.base = base\n this.now = options.now ?? Date.now\n }\n\n /**\n * 生成签名 URL。\n *\n * @param key 完整对象键。\n * @param method 授权的 HTTP 方法。\n * @param expiresIn 有效期秒数。\n * @param contentType `PUT` 要求的 MIME 类型。\n * @param responseContentDisposition `GET` 响应的 `Content-Disposition`。\n * @returns 签名 URL。\n */\n public sign(\n key: string,\n method: SignedUrlMethod,\n expiresIn: number,\n contentType?: string,\n responseContentDisposition?: string,\n ): string {\n const expires = Math.floor(this.now() / 1000) + expiresIn\n const url = new URL(key.split('/').map(encodeURIComponent).join('/'), this.base)\n url.searchParams.set('method', method)\n url.searchParams.set('expires', String(expires))\n if (contentType) url.searchParams.set('contentType', contentType)\n if (responseContentDisposition) url.searchParams.set('disposition', responseContentDisposition)\n url.searchParams.set('signature', this.digest(method, key, expires, contentType, responseContentDisposition))\n return url.href\n }\n\n /**\n * 校验签名 URL。\n *\n * @param request 请求 URL、方法与 `Content-Type`。\n * @returns 校验通过后的授权信息。\n * @throws {@link StorageError} 签名缺失、被篡改、已过期或与请求不匹配时抛出 `ACCESS_DENIED`。\n */\n public verify(request: VerifyUrlRequest): VerifiedUrl {\n const url = typeof request.url === 'string' ? new URL(request.url) : request.url\n if (url.origin !== this.base.origin || !url.pathname.startsWith(this.base.pathname)) {\n throw denied('URL does not match the signing base')\n }\n const params = url.searchParams\n const method = params.get('method')\n const signature = params.get('signature')\n const expiresText = params.get('expires') ?? ''\n if ((method !== 'GET' && method !== 'PUT') || !signature || !/^\\d+$/.test(expiresText)) {\n throw denied('Malformed signed URL')\n }\n let key: string\n try {\n key = url.pathname.slice(this.base.pathname.length).split('/').map(decodeURIComponent).join('/')\n } catch (error) {\n throw denied('Malformed object key', error)\n }\n const expires = Number(expiresText)\n const contentType = params.get('contentType') ?? undefined\n const disposition = params.get('disposition') ?? undefined\n const expected = Buffer.from(this.digest(method, key, expires, contentType, disposition))\n const actual = Buffer.from(signature)\n if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) throw denied('Invalid signature')\n if (expires * 1000 < this.now()) throw denied('Signed URL has expired')\n if ((request.method ?? 'GET').toUpperCase() !== method) throw denied('HTTP method does not match')\n if (method === 'PUT' && contentType && request.contentType !== contentType) {\n throw denied('Content-Type does not match')\n }\n return {\n key,\n method,\n expires,\n ...(contentType ? { contentType } : {}),\n ...(disposition ? { responseContentDisposition: disposition } : {}),\n }\n }\n\n private digest(\n method: string,\n key: string,\n expires: number,\n contentType: string | undefined,\n disposition: string | undefined,\n ): string {\n return createHmac('sha256', this.secret)\n .update([method, key, expires, contentType ?? '', disposition ?? ''].join('\\n'))\n .digest('base64url')\n }\n}\n\nfunction denied(message: string, cause?: unknown): StorageError {\n return new StorageError('ACCESS_DENIED', message, { cause })\n}\n"
10
10
  ],
11
- "mappings": "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",
11
+ "mappings": "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",
12
12
  "debugId": "FD5C6DD2996003AE64756E2164756E21",
13
13
  "names": []
14
14
  }
package/dist/index.d.ts CHANGED
@@ -10,13 +10,13 @@
10
10
  */
11
11
  export { type DriverContext, type DriverCopyOptions, type DriverGetOptions, type DriverListOptions, type DriverPutOptions, type DriverSignOptions, defineDriver, type MultipartDriver, type StorageDriver, } from './core/driver';
12
12
  export { isStorageError, StorageError, type StorageErrorCode, type StorageErrorOptions, toStorageError, } from './core/errors';
13
- export type { RetryOptions } from './core/execution';
13
+ export type { StorageRetryOptions } from './core/execution';
14
14
  export { type CreateObjectKeyOptions, createObjectKey, type KeyGenerator } from './core/keys';
15
15
  export { DEFAULT_CONTENT_TYPE, lookupContentType } from './core/mime';
16
- export { type StorageErrorSummary, type StorageEvent, StorageMetrics, type StorageMetricsSnapshot, type StorageObserver, type StorageOperation, type StorageOutcome, type StorageRetryEvent, } from './core/observability';
16
+ export { type StorageErrorSummary, StorageMetrics, type StorageMetricsSnapshot, type StorageObserver, type StorageOperation, type StorageOperationEvent, type StorageOutcome, type StorageRetryEvent, } from './core/observability';
17
17
  export { createPayload, readAll, type StoragePayload, toNodeReadable } from './core/payload';
18
18
  export type { UploadPolicy } from './core/policy';
19
19
  export { ObjectStorage, type ObjectStorageOptions } from './core/storage';
20
- export type { DeleteManyResult, GetOptions, ListOptions, ListResult, MultipartUpload, MultipartUploadOptions, ObjectBody, ObjectHttpOptions, ObjectInfo, ObjectMetadata, ObjectRange, ObjectStream, OperationOptions, PutOptions, SignedUrlMethod, SignedUrlOptions, UploadedPart, VerifiedUrl, VerifyUrlRequest, } from './core/types';
20
+ export type { DeleteObjectsResult, GetObjectOptions, ListObjectsOptions, ListObjectsResult, MultipartUpload, MultipartUploadOptions, MultipartUploadPart, ObjectBody, ObjectHttpOptions, ObjectInfo, ObjectMetadata, ObjectRange, ObjectStream, PutObjectOptions, SignedUrlMethod, SignedUrlOptions, StorageOperationOptions, VerifiedUrl, VerifyUrlRequest, } from './core/types';
21
21
  export { assertKey } from './core/validation';
22
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,YAAY,EACZ,KAAK,eAAe,EACpB,KAAK,aAAa,GACnB,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,cAAc,EACd,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,cAAc,GACf,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,KAAK,sBAAsB,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAC7F,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACrE,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,cAAc,EACd,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,iBAAiB,GACvB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC5F,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AACzE,YAAY,EACV,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,UAAU,EACV,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,gBAAgB,GACjB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,YAAY,EACZ,KAAK,eAAe,EACpB,KAAK,aAAa,GACnB,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,cAAc,EACd,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,cAAc,GACf,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,EAAE,KAAK,sBAAsB,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAC7F,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACrE,OAAO,EACL,KAAK,mBAAmB,EACxB,cAAc,EACd,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,iBAAiB,GACvB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC5F,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AACzE,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,EACf,sBAAsB,EACtB,mBAAmB,EACnB,UAAU,EACV,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,uBAAuB,EACvB,WAAW,EACX,gBAAgB,GACjB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA"}
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import{a as H,b as i,c as a,d as s,e as K,f as I,g as $}from"./index-5w45sfp6.js";import{h as W,i as S,j as Z,k as b,l as p,m as X,n as w,o as GO}from"./index-7dpmhwry.js";function r(O){return O}import{randomUUID as x}from"node:crypto";var t="application/octet-stream",o={aac:"audio/aac",avif:"image/avif",bmp:"image/bmp",css:"text/css; charset=utf-8",csv:"text/csv; charset=utf-8",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",gif:"image/gif",gz:"application/gzip",htm:"text/html; charset=utf-8",html:"text/html; charset=utf-8",ico:"image/x-icon",jpeg:"image/jpeg",jpg:"image/jpeg",js:"text/javascript; charset=utf-8",json:"application/json",m4a:"audio/mp4",md:"text/markdown; charset=utf-8",mjs:"text/javascript; charset=utf-8",mov:"video/quicktime",mp3:"audio/mpeg",mp4:"video/mp4",ogg:"audio/ogg",pdf:"application/pdf",png:"image/png",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",svg:"image/svg+xml",tar:"application/x-tar",txt:"text/plain; charset=utf-8",wav:"audio/wav",webm:"video/webm",webp:"image/webp",woff:"font/woff",woff2:"font/woff2",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",xml:"application/xml",zip:"application/zip"};function q(O){let G=O.slice(O.lastIndexOf("/")+1),L=G.lastIndexOf(".");return L<=0?"":G.slice(L+1).toLowerCase()}function M(O,G={}){let L=q(O);return G[L]??o[L]??"application/octet-stream"}function z(O){return O.split(";",1)[0]?.trim().toLowerCase()??""}function f(O,G){let L=z(O);return G.some((B)=>{let F=B.toLowerCase();return F.endsWith("/*")?L.startsWith(F.slice(0,-1)):L===F})}function e(O,G={}){let L=G.date??new Date,B=String(L.getUTCFullYear()),F=String(L.getUTCMonth()+1).padStart(2,"0"),U=String(L.getUTCDate()).padStart(2,"0"),Q=q(O).replace(/[^a-z0-9]/g,"").slice(0,16),Y=Q?`${x()}.${Q}`:x();return[G.directory?.replace(/^\/+|\/+$/g,""),B,F,U,Y].filter(Boolean).join("/")}class N{operations=0;errors=0;retries=0;inFlight=0;bytesWritten=0;totalDuration=0;byOperation={};byErrorCode={};constructor(){}start(){this.inFlight+=1}record(O){if(this.inFlight=Math.max(0,this.inFlight-1),this.operations+=1,this.totalDuration+=O.duration,this.byOperation[O.operation]=(this.byOperation[O.operation]??0)+1,O.outcome==="error"){this.errors+=1;let G=O.errorCode??"BACKEND";this.byErrorCode[G]=(this.byErrorCode[G]??0)+1}else if(O.operation==="put"||O.operation==="uploadPart")this.bytesWritten+=O.bytes??0}retry(){this.retries+=1}snapshot(){return Object.freeze({operations:this.operations,errors:this.errors,retries:this.retries,inFlight:this.inFlight,bytesWritten:this.bytesWritten,totalDuration:this.totalDuration,byOperation:Object.freeze({...this.byOperation}),byErrorCode:Object.freeze({...this.byErrorCode})})}reset(){this.operations=0,this.errors=0,this.retries=0,this.bytesWritten=0,this.totalDuration=0,this.byOperation={},this.byErrorCode={}}}function A(O){let G=O.cause,L=typeof G?.status==="number"?G.status:typeof G?.statusCode==="number"?G.statusCode:void 0,B=typeof G?.code==="string"?G.code:void 0,F=typeof G?.requestId==="string"?G.requestId:void 0;return Object.freeze({code:O.code,message:O.code==="BACKEND"?"Storage backend error":O.message,retryable:O.retryable,...L===void 0?{}:{status:L},...B===void 0?{}:{causeCode:B},...F===void 0?{}:{requestId:F}})}function g(O,G,L,B,F=Math.random){let U=Math.min(L,G*2**(O-1));return B?Math.min(L,Math.round(U*(1+F()*0.5))):U}function m(O){return S(O)&&O.retryable}function k(O,G){let L=new AbortController,B=()=>L.abort(G?.reason);if(G?.aborted)B();else G?.addEventListener("abort",B,{once:!0});let F=O>0?setTimeout(()=>L.abort(new W("TIMEOUT",`Operation timed out after ${O} ms`,{retryable:!0})),O):void 0;return{signal:L.signal,dispose(){if(F)clearTimeout(F);G?.removeEventListener("abort",B)}}}function c(O,G){if(G.aborted)return Promise.reject(D(G));return new Promise((L,B)=>{let F=()=>B(D(G));G.addEventListener("abort",F,{once:!0}),O.then((U)=>{G.removeEventListener("abort",F),L(U)},(U)=>{G.removeEventListener("abort",F),B(U)})})}function D(O){let G=O.reason;if(S(G))return G;return new W("ABORTED","Operation was aborted",{cause:G})}function d(O,G){if(G?.aborted)return Promise.reject(D(G));return new Promise((L,B)=>{let F=()=>{clearTimeout(U),B(D(G))},U=setTimeout(()=>{G?.removeEventListener("abort",F),L()},O);G?.addEventListener("abort",F,{once:!0})})}class h{limit;active=0;queue=[];constructor(O){this.limit=O}async run(O){if(this.active>=this.limit)await new Promise((G)=>this.queue.push(G));else this.active+=1;try{return await O()}finally{let G=this.queue.shift();if(G)G();else this.active-=1}}}async function y(O,G,L){let B=Array(O.length),F=0,U=async()=>{while(F<O.length){let Q=F;F+=1,B[Q]=await L(O[Q],Q)}};return await Promise.all(Array.from({length:Math.min(G,O.length)},U)),B}async function l(O,G){let L=O.then(()=>!0,()=>!0);if(G<=0)return!1;let B,F=new Promise((U)=>{B=setTimeout(()=>U(!1),G)});try{return await Promise.race([L,F])}finally{clearTimeout(B)}}function E(O,G,L){let B=q(G),F=(U)=>U.map((Q)=>Q.toLowerCase());if(O.deniedExtensions&&F(O.deniedExtensions).includes(B))throw T(G,`Extension is not allowed: ${B}`);if(O.allowedExtensions&&!F(O.allowedExtensions).includes(B))throw T(G,`Extension is not allowed: ${B||"(none)"}`);if(O.allowedContentTypes&&!f(L,O.allowedContentTypes))throw T(G,`Content type is not allowed: ${z(L)}`)}function T(O,G){return new W("UPLOAD_REJECTED",G,{key:O})}var u=102400,P=1e4;class n{driverName;driver;prefix;timeout;retries;baseDelay;maxDelay;jitter;shouldRetry;semaphore;concurrency;policy;maxSize;overwrite;contentTypes;partSize;partConcurrency;abortGrace;multipartThreshold;observer;metrics;closing;constructor(O){if(!O?.driver)throw new W("INVALID_OPTION","driver is required");this.driver=O.driver,this.driverName=O.driver.name,this.prefix=i(O.prefix),this.timeout=$("timeout",O.timeout??30000,0);let G=O.retry??{};if(this.retries=$("retry.retries",G.retries??2,0,10),this.baseDelay=$("retry.baseDelay",G.baseDelay??100,0),this.maxDelay=$("retry.maxDelay",G.maxDelay??2000,this.baseDelay),this.jitter=G.jitter??!0,this.shouldRetry=G.shouldRetry??m,this.concurrency=$("concurrency",O.concurrency??32,1),this.semaphore=new h(this.concurrency),this.policy=O.upload??{},this.maxSize=$("upload.maxSize",this.policy.maxSize??Number.MAX_SAFE_INTEGER,0),this.overwrite=O.overwrite??!0,this.contentTypes=O.contentTypes??{},this.partSize=$("partSize",O.partSize??8388608,u),this.partConcurrency=$("partConcurrency",O.partConcurrency??4,1),this.abortGrace=$("abortGrace",O.abortGrace??5000,0),this.multipartThreshold=$("multipartThreshold",O.multipartThreshold??67108864,0),this.multipartThreshold>0&&this.multipartThreshold<this.partSize)throw new W("INVALID_OPTION","multipartThreshold must be 0 or not less than partSize");this.observer=O.observer,this.metrics=O.metrics??new N}async put(O,G,L={}){H(O);let B=this.putOptions(O,L),F=w(X(G),this.maxSize);if(this.shouldUseMultipart(F,B.overwrite))return this.uploadMultipartPayload(O,F,L);let U=await this.run("put",L,{key:O,bytes:F.size,idempotent:F.replayable},(Q)=>this.driver.put(this.fullKey(O),F,{...B,...Q}));return this.publicInfo(U)}async get(O,G={}){H(O);let L=G.range;if(L){if($("range.start",L.start,0),L.end!==void 0)$("range.end",L.end,L.start)}let B=await this.run("get",G,{key:O,idempotent:!0},(F)=>this.driver.get(this.fullKey(O),{...F,range:L}));return{...B,info:this.publicInfo(B.info)}}async getBytes(O,G={}){return b((await this.get(O,G)).body)}async getText(O,G={}){return new TextDecoder().decode(await this.getBytes(O,G))}async head(O,G={}){H(O);let L=await this.run("head",G,{key:O,idempotent:!0},(B)=>this.driver.head(this.fullKey(O),B));return this.publicInfo(L)}async exists(O,G={}){return H(O),this.run("exists",G,{key:O,idempotent:!0},async(L)=>{try{return await this.driver.head(this.fullKey(O),L),!0}catch(B){if(Z(B).code==="NOT_FOUND")return!1;throw B}})}async delete(O,G={}){H(O),await this.run("delete",G,{key:O,idempotent:!0},(L)=>this.driver.delete(this.fullKey(O),L))}async deleteMany(O,G={}){let L=[...new Set(O)];if(L.forEach(H),L.length===0)return{deleted:[],failed:[]};return this.run("deleteMany",G,{idempotent:!0},async(B)=>{let F={deleted:[],failed:[]},U=this.driver.deleteMany?.bind(this.driver);if(U){for(let Y=0;Y<L.length;Y+=1000){let j=L.slice(Y,Y+1000),V=await U(j.map((J)=>this.fullKey(J)),B);F.deleted.push(...V.deleted.map((J)=>this.publicKey(J))),F.failed.push(...V.failed.map((J)=>({...J,key:this.publicKey(J.key)})))}return F}return(await y(L,Math.min(this.concurrency,16),async(Y)=>{try{await this.driver.delete(this.fullKey(Y),B);return}catch(j){return Z(j,"delete",Y)}})).forEach((Y,j)=>{let V=L[j];if(Y)F.failed.push({key:V,error:Y});else F.deleted.push(V)}),F})}async list(O={}){let G=a(O.prefix),L=$("limit",O.limit??1000,1,1000);if(O.delimiter!==void 0&&O.delimiter.length===0)throw new W("INVALID_OPTION","delimiter must not be empty");let B=this.capability("list",this.driver.list),F=await this.run("list",O,{idempotent:!0},(U)=>B({...U,prefix:this.prefix+G,delimiter:O.delimiter,cursor:O.cursor,limit:L}));return{objects:F.objects.map((U)=>this.publicInfo(U)),prefixes:F.prefixes.map((U)=>this.publicKey(U)),cursor:F.cursor}}async*listAll(O={}){let G;do{let L=await this.list({...O,cursor:G,delimiter:void 0});yield*L.objects,G=L.cursor}while(G)}async copy(O,G,L={}){H(O),H(G);let B=L.overwrite??this.overwrite,F=await this.run("copy",L,{key:G,idempotent:B},(U)=>this.copyObject(O,G,B,U));return this.publicInfo(F)}async move(O,G,L={}){if(H(O),H(G),O===G)throw new W("INVALID_OPTION","source and destination must differ");let B=L.overwrite??this.overwrite,F=await this.run("move",L,{key:G,idempotent:!1},async(U)=>{let Q=await this.copyObject(O,G,B,U);return await this.driver.delete(this.fullKey(O),U),Q});return this.publicInfo(F)}async copyObject(O,G,L,B){let F=this.fullKey(O),U=this.fullKey(G);if(this.driver.copy)return this.driver.copy(F,U,{...B,overwrite:L});let{info:Q,body:Y}=await this.driver.get(F,B),j=X(Y);return this.driver.put(U,j,{...B,contentType:Q.contentType??M(G,this.contentTypes),cacheControl:Q.cacheControl,contentDisposition:Q.contentDisposition,metadata:Q.metadata,overwrite:L})}async signUrl(O,G={}){H(O);let L=G.method??"GET";if(L!=="GET"&&L!=="PUT")throw new W("INVALID_OPTION",`Unsupported method: ${L}`);let B=$("expiresIn",G.expiresIn??900,1,604800),F;if(L==="PUT")F=I(G.contentType??M(O,this.contentTypes)),E(this.policy,O,F);let U=K("responseContentDisposition",G.responseContentDisposition),Q=this.capability("signUrl",this.driver.signUrl);return this.run("signUrl",G,{key:O,idempotent:!0},(Y)=>Q(this.fullKey(O),{...Y,method:L,expiresIn:B,contentType:F,responseContentDisposition:U}))}verifyUrl(O){if(this.closing)throw new W("CLOSED","Object storage has been closed",{operation:"verifyUrl"});let L=this.capability("verifyUrl",this.driver.verifyUrl)(O);if(!L.key.startsWith(this.prefix))throw new W("ACCESS_DENIED","Signed URL is outside of the storage prefix");return{...L,key:H(this.publicKey(L.key))}}async createMultipart(O,G={}){H(O);let L=this.multipartDriver(),B=this.putOptions(O,G),F=await this.run("createMultipart",G,{key:O,idempotent:!1},(U)=>L.createMultipart(this.fullKey(O),{...B,...U}));return{key:O,uploadId:F.uploadId}}async uploadPart(O,G,L,B={}){this.assertUpload(O),$("partNumber",G,1,P);let F=this.multipartDriver(),U=await w(X(L),this.maxSize).bytes(),Q=X(U);return this.run("uploadPart",B,{key:O.key,bytes:U.byteLength,idempotent:!0},(Y)=>F.uploadPart(this.driverUpload(O),G,Q,Y))}async completeMultipart(O,G,L={}){if(this.assertUpload(O),G.length===0)throw new W("INVALID_OPTION","parts must not be empty");let B=new Map;for(let Y of G){if($("partNumber",Y.partNumber,1,P),typeof Y.etag!=="string"||Y.etag.length===0)throw new W("INVALID_OPTION","part etag is required");B.set(Y.partNumber,Y)}let F=[...B.values()].sort((Y,j)=>Y.partNumber-j.partNumber),U=this.multipartDriver(),Q=await this.run("completeMultipart",L,{key:O.key,idempotent:!1},(Y)=>U.completeMultipart(this.driverUpload(O),F,Y));return this.publicInfo(Q)}async abortMultipart(O,G={}){this.assertUpload(O);let L=this.multipartDriver();await this.run("abortMultipart",G,{key:O.key,idempotent:!0},(B)=>L.abortMultipart(this.driverUpload(O),B))}async uploadMultipart(O,G,L={}){return H(O),this.multipartDriver(),this.uploadMultipartPayload(O,w(X(G),this.maxSize),L)}shouldUseMultipart(O,G){return this.multipartThreshold>0&&G&&this.driver.multipart!==void 0&&O.size!==void 0&&O.size>this.multipartThreshold}async uploadMultipartPayload(O,G,L){let B=$("partSize",L.partSize??this.partSize,u),F=$("concurrency",L.concurrency??this.partConcurrency,1),U=await this.createMultipart(O,L),Q=new Set,Y=[],j,V=(J,_)=>{let C=this.uploadPart(U,J,_,L).then((R)=>{Y.push(R)},(R)=>{j??=R}).finally(()=>Q.delete(C));Q.add(C)};try{let J=0;for await(let _ of OO(G,B)){if(j)break;if(J+=1,J>P)throw new W("UPLOAD_REJECTED",`Object exceeds ${P} parts`,{key:O});V(J,_);while(Q.size>=F)await Promise.race(Q)}if(await Promise.all(Q),j)throw j;if(J===0)return await this.abortMultipart(U,{...L,retries:0}),await this.put(O,new Uint8Array(0),L);return await this.completeMultipart(U,Y,L)}catch(J){throw await Promise.allSettled(Q),await this.abortMultipart(U,{timeout:L.timeout,retries:0}).catch(()=>{return}),J}}getMetrics(){return this.metrics.snapshot()}resetMetrics(){this.metrics.reset()}close(){return this.closing??=(async()=>{let O=performance.now();this.metrics.start();try{await this.driver.close?.(),this.emit({operation:"close",outcome:"success",started:O,attempts:1})}catch(G){let L=Z(G,"close");throw this.emit({operation:"close",outcome:"error",started:O,attempts:1,error:L}),L}})(),this.closing}putOptions(O,G){let L=I(G.contentType??M(O,this.contentTypes));return E(this.policy,O,L),{contentType:L,cacheControl:K("cacheControl",G.cacheControl),contentDisposition:K("contentDisposition",G.contentDisposition),metadata:s(G.metadata),overwrite:G.overwrite??this.overwrite}}capability(O,G){if(!G)throw new W("UNSUPPORTED",`Driver "${this.driverName}" does not support ${O}`,{operation:O});return G.bind(this.driver)}multipartDriver(){let O=this.driver.multipart;if(!O)throw new W("UNSUPPORTED",`Driver "${this.driverName}" does not support multipart upload`);return O}assertUpload(O){if(H(O?.key),typeof O.uploadId!=="string"||O.uploadId.length===0)throw new W("INVALID_OPTION","uploadId is required",{key:O.key})}driverUpload(O){return{key:this.fullKey(O.key),uploadId:O.uploadId}}fullKey(O){return this.prefix+O}publicKey(O){return O.startsWith(this.prefix)?O.slice(this.prefix.length):O}publicInfo(O){return{...O,key:this.publicKey(O.key)}}async run(O,G,L,B){if(this.closing)throw new W("CLOSED","Object storage has been closed",{operation:O,key:L.key});let F=$("timeout",G.timeout??this.timeout,0),U=L.idempotent?$("retries",G.retries??this.retries,0,10):0,Q=performance.now();this.metrics.start();let Y=0;try{let j=await this.semaphore.run(async()=>{for(;;){Y+=1;let V=k(F,G.signal),J=Promise.resolve().then(()=>B({signal:V.signal}));try{return await c(J,V.signal)}catch(_){if(V.signal.aborted)await l(J,this.abortGrace);let v=V.signal.aborted?D(V.signal):_,C=Z(v,O,L.key);if(G.signal?.aborted||Y>U||!this.shouldRetry(C,Y))throw C;let R=g(Y,this.baseDelay,this.maxDelay,this.jitter);this.metrics.retry(),this.notify("onRetry",{operation:O,driver:this.driverName,key:L.key,attempt:Y+1,delay:R,error:A(C)}),await d(R,G.signal)}finally{V.dispose()}}});return this.emit({operation:O,outcome:"success",started:Q,attempts:Y,key:L.key,bytes:L.bytes}),j}catch(j){let V=Z(j,O,L.key);throw this.emit({operation:O,outcome:"error",started:Q,attempts:Y,key:L.key,error:V}),V}}emit(O){let G=Object.freeze({operation:O.operation,outcome:O.outcome,driver:this.driverName,...O.key===void 0?{}:{key:O.key},duration:Math.max(0,performance.now()-O.started),attempts:O.attempts,...O.bytes===void 0?{}:{bytes:O.bytes},...O.error===void 0?{}:{errorCode:O.error.code,error:A(O.error)},timestamp:Date.now()});this.metrics.record(G),this.notify("onOperation",G)}notify(O,G){try{this.observer?.[O]?.call(this.observer,Object.freeze(G))}catch{}}}async function*OO(O,G){let L=new Uint8Array(G),B=0;for await(let F of p(O.stream())){let U=0;while(U<F.byteLength){let Q=Math.min(G-B,F.byteLength-U);if(L.set(F.subarray(U,U+Q),B),B+=Q,U+=Q,B===G)yield L,L=new Uint8Array(G),B=0}}if(B>0)yield L.subarray(0,B)}export{Z as toStorageError,GO as toNodeReadable,b as readAll,M as lookupContentType,S as isStorageError,r as defineDriver,X as createPayload,e as createObjectKey,H as assertKey,N as StorageMetrics,W as StorageError,n as ObjectStorage,t as DEFAULT_CONTENT_TYPE};
1
+ import{a as H,b as i,c as a,d as s,e as K,f as E,g as W}from"./index-5w45sfp6.js";import{h as Q,i as S,j as Z,k as I,l as p,m as X,n as w,o as jO}from"./index-7dpmhwry.js";function r(O){return O}import{randomUUID as x}from"node:crypto";var t="application/octet-stream",o={aac:"audio/aac",avif:"image/avif",bmp:"image/bmp",css:"text/css; charset=utf-8",csv:"text/csv; charset=utf-8",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",gif:"image/gif",gz:"application/gzip",htm:"text/html; charset=utf-8",html:"text/html; charset=utf-8",ico:"image/x-icon",jpeg:"image/jpeg",jpg:"image/jpeg",js:"text/javascript; charset=utf-8",json:"application/json",m4a:"audio/mp4",md:"text/markdown; charset=utf-8",mjs:"text/javascript; charset=utf-8",mov:"video/quicktime",mp3:"audio/mpeg",mp4:"video/mp4",ogg:"audio/ogg",pdf:"application/pdf",png:"image/png",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",svg:"image/svg+xml",tar:"application/x-tar",txt:"text/plain; charset=utf-8",wav:"audio/wav",webm:"video/webm",webp:"image/webp",woff:"font/woff",woff2:"font/woff2",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",xml:"application/xml",zip:"application/zip"};function q(O){let j=O.slice(O.lastIndexOf("/")+1),G=j.lastIndexOf(".");return G<=0?"":j.slice(G+1).toLowerCase()}function M(O,j={}){let G=q(O);return j[G]??o[G]??"application/octet-stream"}function z(O){return O.split(";",1)[0]?.trim().toLowerCase()??""}function f(O,j){let G=z(O);return j.some((L)=>{let B=L.toLowerCase();return B.endsWith("/*")?G.startsWith(B.slice(0,-1)):G===B})}function e(O,j={}){let G=j.date??new Date,L=String(G.getUTCFullYear()),B=String(G.getUTCMonth()+1).padStart(2,"0"),F=String(G.getUTCDate()).padStart(2,"0"),Y=q(O).replace(/[^a-z0-9]/g,"").slice(0,16),U=Y?`${x()}.${Y}`:x();return[j.directory?.replace(/^\/+|\/+$/g,""),L,B,F,U].filter(Boolean).join("/")}class N{operations=0;errors=0;retries=0;inFlight=0;bytesWritten=0;totalDuration=0;byOperation={};byErrorCode={};constructor(){}start(){this.inFlight+=1}record(O){if(this.inFlight=Math.max(0,this.inFlight-1),this.operations+=1,this.totalDuration+=O.duration,this.byOperation[O.operation]=(this.byOperation[O.operation]??0)+1,O.outcome==="error"){this.errors+=1;let j=O.errorCode??"BACKEND";this.byErrorCode[j]=(this.byErrorCode[j]??0)+1}else if(O.operation==="put"||O.operation==="uploadPart")this.bytesWritten+=O.bytes??0}retry(){this.retries+=1}snapshot(){return Object.freeze({operations:this.operations,errors:this.errors,retries:this.retries,inFlight:this.inFlight,bytesWritten:this.bytesWritten,totalDuration:this.totalDuration,byOperation:Object.freeze({...this.byOperation}),byErrorCode:Object.freeze({...this.byErrorCode})})}reset(){this.operations=0,this.errors=0,this.retries=0,this.bytesWritten=0,this.totalDuration=0,this.byOperation={},this.byErrorCode={}}}function A(O){let j=O.cause,G=typeof j?.status==="number"?j.status:typeof j?.statusCode==="number"?j.statusCode:void 0,L=typeof j?.code==="string"?j.code:void 0,B=typeof j?.requestId==="string"?j.requestId:void 0;return Object.freeze({code:O.code,message:O.code==="BACKEND"?"Storage backend error":O.message,retryable:O.retryable,...G===void 0?{}:{status:G},...L===void 0?{}:{causeCode:L},...B===void 0?{}:{requestId:B}})}function g(O,j,G,L,B=Math.random){let F=Math.min(G,j*2**(O-1));return L?Math.min(G,Math.round(F*(1+B()*0.5))):F}function m(O){return S(O)&&O.retryable}function k(O,j){let G=new AbortController,L=()=>G.abort(j?.reason);if(j?.aborted)L();else j?.addEventListener("abort",L,{once:!0});let B=O>0?setTimeout(()=>G.abort(new Q("TIMEOUT",`Operation timed out after ${O} ms`,{retryable:!0})),O):void 0;return{signal:G.signal,dispose(){if(B)clearTimeout(B);j?.removeEventListener("abort",L)}}}function c(O,j){if(j.aborted)return Promise.reject(D(j));return new Promise((G,L)=>{let B=()=>L(D(j));j.addEventListener("abort",B,{once:!0}),O.then((F)=>{j.removeEventListener("abort",B),G(F)},(F)=>{j.removeEventListener("abort",B),L(F)})})}function D(O){let j=O.reason;if(S(j))return j;return new Q("ABORTED","Operation was aborted",{cause:j})}function d(O,j){if(j?.aborted)return Promise.reject(D(j));return new Promise((G,L)=>{let B=()=>{clearTimeout(F),L(D(j))},F=setTimeout(()=>{j?.removeEventListener("abort",B),G()},O);j?.addEventListener("abort",B,{once:!0})})}class h{limit;active=0;queue=[];constructor(O){this.limit=O}async run(O){if(this.active>=this.limit)await new Promise((j)=>this.queue.push(j));else this.active+=1;try{return await O()}finally{let j=this.queue.shift();if(j)j();else this.active-=1}}}async function l(O,j,G){let L=Array(O.length),B=0,F=async()=>{while(B<O.length){let Y=B;B+=1,L[Y]=await G(O[Y],Y)}};return await Promise.all(Array.from({length:Math.min(j,O.length)},F)),L}async function y(O,j){let G=O.then(()=>!0,()=>!0);if(j<=0)return!1;let L,B=new Promise((F)=>{L=setTimeout(()=>F(!1),j)});try{return await Promise.race([G,B])}finally{clearTimeout(L)}}function b(O,j,G){let L=q(j),B=(F)=>F.map((Y)=>Y.toLowerCase());if(O.deniedExtensions&&B(O.deniedExtensions).includes(L))throw T(j,`Extension is not allowed: ${L}`);if(O.allowedExtensions&&!B(O.allowedExtensions).includes(L))throw T(j,`Extension is not allowed: ${L||"(none)"}`);if(O.allowedContentTypes&&!f(G,O.allowedContentTypes))throw T(j,`Content type is not allowed: ${z(G)}`)}function T(O,j){return new Q("UPLOAD_REJECTED",j,{key:O})}var u=102400,P=1e4;class n{driverName;driver;prefix;timeout;retries;baseDelay;maxDelay;jitter;shouldRetry;semaphore;concurrency;policy;maxSize;overwrite;contentTypes;partSize;partConcurrency;abortGrace;multipartThreshold;observer;metrics;closing;constructor(O){if(!O?.driver)throw new Q("INVALID_OPTION","driver is required");this.driver=O.driver,this.driverName=O.driver.name,this.prefix=i(O.prefix),this.timeout=W("timeout",O.timeout??30000,0);let j=O.retry??{};if(this.retries=W("retry.retries",j.retries??2,0,10),this.baseDelay=W("retry.baseDelay",j.baseDelay??100,0),this.maxDelay=W("retry.maxDelay",j.maxDelay??2000,this.baseDelay),this.jitter=j.jitter??!0,this.shouldRetry=j.shouldRetry??m,this.concurrency=W("concurrency",O.concurrency??32,1),this.semaphore=new h(this.concurrency),this.policy=O.upload??{},this.maxSize=W("upload.maxSize",this.policy.maxSize??Number.MAX_SAFE_INTEGER,0),this.overwrite=O.overwrite??!0,this.contentTypes=O.contentTypes??{},this.partSize=W("partSize",O.partSize??8388608,u),this.partConcurrency=W("partConcurrency",O.partConcurrency??4,1),this.abortGrace=W("abortGrace",O.abortGrace??5000,0),this.multipartThreshold=W("multipartThreshold",O.multipartThreshold??67108864,0),this.multipartThreshold>0&&this.multipartThreshold<this.partSize)throw new Q("INVALID_OPTION","multipartThreshold must be 0 or not less than partSize");this.observer=O.observer,this.metrics=O.metrics??new N}async put(O,j,G={}){H(O);let L=this.putOptions(O,G),B=w(X(j),this.maxSize);if(this.shouldUseMultipart(B,L.overwrite))return this.uploadMultipartPayload(O,B,G);let F=await this.run("put",G,{key:O,bytes:B.size,idempotent:B.replayable},(Y)=>this.driver.put(this.fullKey(O),B,{...L,...Y}));return this.publicInfo(F)}async get(O,j={}){H(O);let G=j.range;if(G){if(W("range.start",G.start,0),G.end!==void 0)W("range.end",G.end,G.start)}let L=await this.run("get",j,{key:O,idempotent:!0},(B)=>this.driver.get(this.fullKey(O),{...B,range:G}));return{...L,info:this.publicInfo(L.info)}}async getBytes(O,j={}){return I((await this.get(O,j)).body)}async getText(O,j={}){return new TextDecoder().decode(await this.getBytes(O,j))}async head(O,j={}){H(O);let G=await this.run("head",j,{key:O,idempotent:!0},(L)=>this.driver.head(this.fullKey(O),L));return this.publicInfo(G)}async exists(O,j={}){return H(O),this.run("exists",j,{key:O,idempotent:!0},async(G)=>{try{return await this.driver.head(this.fullKey(O),G),!0}catch(L){if(Z(L).code==="NOT_FOUND")return!1;throw L}})}async delete(O,j={}){H(O),await this.run("delete",j,{key:O,idempotent:!0},(G)=>this.driver.delete(this.fullKey(O),G))}async deleteMany(O,j={}){let G=[...new Set(O)];if(G.forEach(H),G.length===0)return{deleted:[],failed:[]};return this.run("deleteMany",j,{idempotent:!0},async(L)=>{let B={deleted:[],failed:[]},F=this.driver.deleteMany?.bind(this.driver);if(F){for(let U=0;U<G.length;U+=1000){let $=G.slice(U,U+1000),V=await F($.map((J)=>this.fullKey(J)),L);B.deleted.push(...V.deleted.map((J)=>this.publicKey(J))),B.failed.push(...V.failed.map((J)=>({...J,key:this.publicKey(J.key)})))}return B}return(await l(G,Math.min(this.concurrency,16),async(U)=>{try{await this.driver.delete(this.fullKey(U),L);return}catch($){return Z($,"delete",U)}})).forEach((U,$)=>{let V=G[$];if(U)B.failed.push({key:V,error:U});else B.deleted.push(V)}),B})}async list(O={}){let j=a(O.prefix),G=W("limit",O.limit??1000,1,1000);if(O.delimiter!==void 0&&O.delimiter.length===0)throw new Q("INVALID_OPTION","delimiter must not be empty");let L=this.capability("list",this.driver.list),B=await this.run("list",O,{idempotent:!0},(F)=>L({...F,prefix:this.prefix+j,delimiter:O.delimiter,cursor:O.cursor,limit:G}));return{objects:B.objects.map((F)=>this.publicInfo(F)),prefixes:B.prefixes.map((F)=>this.publicKey(F)),cursor:B.cursor}}async*listAll(O={}){let j;do{let G=await this.list({...O,cursor:j,delimiter:void 0});yield*G.objects,j=G.cursor}while(j)}async copy(O,j,G={}){H(O),H(j);let L=G.overwrite??this.overwrite,B=await this.run("copy",G,{key:j,idempotent:L},(F)=>this.copyObject(O,j,L,F));return this.publicInfo(B)}async move(O,j,G={}){if(H(O),H(j),O===j)throw new Q("INVALID_OPTION","source and destination must differ");let L=G.overwrite??this.overwrite,B=await this.run("move",G,{key:j,idempotent:!1},async(F)=>{let Y=await this.copyObject(O,j,L,F);return await this.driver.delete(this.fullKey(O),F),Y});return this.publicInfo(B)}async copyObject(O,j,G,L){let B=this.fullKey(O),F=this.fullKey(j);if(this.driver.copy)return this.driver.copy(B,F,{...L,overwrite:G});let{info:Y,body:U}=await this.driver.get(B,L),$=X(U);return this.driver.put(F,$,{...L,contentType:Y.contentType??M(j,this.contentTypes),cacheControl:Y.cacheControl,contentDisposition:Y.contentDisposition,metadata:Y.metadata,overwrite:G})}async signUrl(O,j={}){H(O);let G=j.method??"GET";if(G!=="GET"&&G!=="PUT")throw new Q("INVALID_OPTION",`Unsupported method: ${G}`);let L=W("expiresIn",j.expiresIn??900,1,604800),B;if(G==="PUT")B=E(j.contentType??M(O,this.contentTypes)),b(this.policy,O,B);let F=K("responseContentDisposition",j.responseContentDisposition),Y=this.capability("signUrl",this.driver.signUrl);return this.run("signUrl",j,{key:O,idempotent:!0},(U)=>Y(this.fullKey(O),{...U,method:G,expiresIn:L,contentType:B,responseContentDisposition:F}))}verifyUrl(O){if(this.closing)throw new Q("CLOSED","Object storage has been closed",{operation:"verifyUrl"});let G=this.capability("verifyUrl",this.driver.verifyUrl)(O);if(!G.key.startsWith(this.prefix))throw new Q("ACCESS_DENIED","Signed URL is outside of the storage prefix");return{...G,key:H(this.publicKey(G.key))}}async createMultipart(O,j={}){H(O);let G=this.multipartDriver(),L=this.putOptions(O,j),B=await this.run("createMultipart",j,{key:O,idempotent:!1},(F)=>G.createMultipart(this.fullKey(O),{...L,...F}));return{key:O,uploadId:B.uploadId}}async uploadPart(O,j,G,L={}){this.assertUpload(O),W("partNumber",j,1,P);let B=this.multipartDriver(),F=await w(X(G),this.maxSize).bytes(),Y=X(F);return this.run("uploadPart",L,{key:O.key,bytes:F.byteLength,idempotent:!0},(U)=>B.uploadPart(this.driverUpload(O),j,Y,U))}async completeMultipart(O,j,G={}){if(this.assertUpload(O),j.length===0)throw new Q("INVALID_OPTION","parts must not be empty");let L=new Map;for(let U of j){if(W("partNumber",U.partNumber,1,P),typeof U.etag!=="string"||U.etag.length===0)throw new Q("INVALID_OPTION","part etag is required");L.set(U.partNumber,U)}let B=[...L.values()].sort((U,$)=>U.partNumber-$.partNumber),F=this.multipartDriver(),Y=await this.run("completeMultipart",G,{key:O.key,idempotent:!1},(U)=>F.completeMultipart(this.driverUpload(O),B,U));return this.publicInfo(Y)}async abortMultipart(O,j={}){this.assertUpload(O);let G=this.multipartDriver();await this.run("abortMultipart",j,{key:O.key,idempotent:!0},(L)=>G.abortMultipart(this.driverUpload(O),L))}async uploadMultipart(O,j,G={}){return H(O),this.multipartDriver(),this.uploadMultipartPayload(O,w(X(j),this.maxSize),G)}shouldUseMultipart(O,j){return this.multipartThreshold>0&&j&&this.driver.multipart!==void 0&&O.size!==void 0&&O.size>this.multipartThreshold}async uploadMultipartPayload(O,j,G){let L=W("partSize",G.partSize??this.partSize,u),B=W("concurrency",G.concurrency??this.partConcurrency,1),F=await this.createMultipart(O,G),Y=new Set,U=[],$,V=(J,_)=>{let C=this.uploadPart(F,J,_,G).then((R)=>{U.push(R)},(R)=>{$??=R}).finally(()=>Y.delete(C));Y.add(C)};try{let J=0;for await(let _ of OO(j,L)){if($)break;if(J+=1,J>P)throw new Q("UPLOAD_REJECTED",`Object exceeds ${P} parts`,{key:O});V(J,_);while(Y.size>=B)await Promise.race(Y)}if(await Promise.all(Y),$)throw $;if(J===0)return await this.abortMultipart(F,{...G,retries:0}),await this.put(O,new Uint8Array(0),G);return await this.completeMultipart(F,U,G)}catch(J){throw await Promise.allSettled(Y),await this.abortMultipart(F,{timeout:G.timeout,retries:0}).catch(()=>{return}),J}}getMetrics(){return this.metrics.snapshot()}resetMetrics(){this.metrics.reset()}close(){return this.closing??=(async()=>{let O=performance.now();this.metrics.start();try{await this.driver.close?.(),this.emit({operation:"close",outcome:"success",started:O,attempts:1})}catch(j){let G=Z(j,"close");throw this.emit({operation:"close",outcome:"error",started:O,attempts:1,error:G}),G}})(),this.closing}putOptions(O,j){let G=E(j.contentType??M(O,this.contentTypes));return b(this.policy,O,G),{contentType:G,cacheControl:K("cacheControl",j.cacheControl),contentDisposition:K("contentDisposition",j.contentDisposition),metadata:s(j.metadata),overwrite:j.overwrite??this.overwrite}}capability(O,j){if(!j)throw new Q("UNSUPPORTED",`Driver "${this.driverName}" does not support ${O}`,{operation:O});return j.bind(this.driver)}multipartDriver(){let O=this.driver.multipart;if(!O)throw new Q("UNSUPPORTED",`Driver "${this.driverName}" does not support multipart upload`);return O}assertUpload(O){if(H(O?.key),typeof O.uploadId!=="string"||O.uploadId.length===0)throw new Q("INVALID_OPTION","uploadId is required",{key:O.key})}driverUpload(O){return{key:this.fullKey(O.key),uploadId:O.uploadId}}fullKey(O){return this.prefix+O}publicKey(O){return O.startsWith(this.prefix)?O.slice(this.prefix.length):O}publicInfo(O){return{...O,key:this.publicKey(O.key)}}async run(O,j,G,L){if(this.closing)throw new Q("CLOSED","Object storage has been closed",{operation:O,key:G.key});let B=W("timeout",j.timeout??this.timeout,0),F=G.idempotent?W("retries",j.retries??this.retries,0,10):0,Y=performance.now();this.metrics.start();let U=0;try{let $=await this.semaphore.run(async()=>{for(;;){U+=1;let V=k(B,j.signal),J=Promise.resolve().then(()=>L({signal:V.signal}));try{return await c(J,V.signal)}catch(_){if(V.signal.aborted)await y(J,this.abortGrace);let v=V.signal.aborted?D(V.signal):_,C=Z(v,O,G.key);if(j.signal?.aborted||U>F||!this.shouldRetry(C,U))throw C;let R=g(U,this.baseDelay,this.maxDelay,this.jitter);this.metrics.retry(),this.notify("onRetry",{operation:O,driver:this.driverName,key:G.key,attempt:U+1,delay:R,error:A(C)}),await d(R,j.signal)}finally{V.dispose()}}});return this.emit({operation:O,outcome:"success",started:Y,attempts:U,key:G.key,bytes:G.bytes}),$}catch($){let V=Z($,O,G.key);throw this.emit({operation:O,outcome:"error",started:Y,attempts:U,key:G.key,error:V}),V}}emit(O){let j=Object.freeze({operation:O.operation,outcome:O.outcome,driver:this.driverName,...O.key===void 0?{}:{key:O.key},duration:Math.max(0,performance.now()-O.started),attempts:O.attempts,...O.bytes===void 0?{}:{bytes:O.bytes},...O.error===void 0?{}:{errorCode:O.error.code,error:A(O.error)},timestamp:Date.now()});this.metrics.record(j),this.notify("onOperation",j)}notify(O,j){try{this.observer?.[O]?.call(this.observer,Object.freeze(j))}catch{}}}async function*OO(O,j){let G=new Uint8Array(j),L=0;for await(let B of p(O.stream())){let F=0;while(F<B.byteLength){let Y=Math.min(j-L,B.byteLength-F);if(G.set(B.subarray(F,F+Y),L),L+=Y,F+=Y,L===j)yield G,G=new Uint8Array(j),L=0}}if(L>0)yield G.subarray(0,L)}export{Z as toStorageError,jO as toNodeReadable,I as readAll,M as lookupContentType,S as isStorageError,r as defineDriver,X as createPayload,e as createObjectKey,H as assertKey,N as StorageMetrics,Q as StorageError,n as ObjectStorage,t as DEFAULT_CONTENT_TYPE};
2
2
 
3
- //# debugId=C8A2B002EC4BF53A64756E2164756E21
3
+ //# debugId=A2EC1C3487782D8F64756E2164756E21