# happy-opfs

[![License](https://img.shields.io/npm/l/happy-opfs.svg)](LICENSE)
[![Build Status](https://github.com/JiangJie/happy-opfs/actions/workflows/test.yml/badge.svg)](https://github.com/JiangJie/happy-opfs/actions/workflows/test.yml)
[![codecov](https://codecov.io/gh/JiangJie/happy-opfs/graph/badge.svg)](https://codecov.io/gh/JiangJie/happy-opfs)
[![NPM version](https://img.shields.io/npm/v/happy-opfs.svg)](https://npmjs.org/package/happy-opfs)
[![NPM downloads](https://badgen.net/npm/dm/happy-opfs)](https://npmjs.org/package/happy-opfs)
[![JSR Version](https://jsr.io/badges/@happy-js/happy-opfs)](https://jsr.io/@happy-js/happy-opfs)
[![JSR Score](https://jsr.io/badges/@happy-js/happy-opfs/score)](https://jsr.io/@happy-js/happy-opfs/score)

基于 [OPFS](https://developer.mozilla.org/zh-CN/docs/Web/API/File_System_API/Origin_private_file_system) 的浏览器文件系统模块，提供 [Deno](https://deno.land/api#File_System) 风格的 API。

---

[English](README.md) | [API 文档](https://jiangjie.github.io/happy-opfs/)

---

## 为什么选择 happy-opfs

标准的 OPFS API 与我们熟悉的基于路径的文件系统 API（如 Node.js 和 Deno）差异较大。本库通过在浏览器中提供 Deno 风格的 API 来弥合这一差距。

所有异步 API 返回 [Result](https://github.com/JiangJie/happy-rusty) 类型（类似 Rust），提供更好的错误处理体验。

## 安装

```sh
pnpm add happy-opfs
# 或
npm install happy-opfs
# 或
yarn add happy-opfs
# 或通过 JSR
jsr add @happy-js/happy-opfs
```

> [!NOTE]
> 本项目依赖 JSR 的 `@std/path`，需要在 `.npmrc` 中添加：
> ```
> @jsr:registry=https://npm.jsr.io
> ```

## 功能

| 分类 | API |
|------|-----|
| **核心** | `createFile`, `mkdir`, `readDir`, `readFile`, `writeFile`, `remove`, `stat` |
| **扩展** | `appendFile`, `copy`, `move`, `exists`, `emptyDir`, `readTextFile`, `readBlobFile`, `readJsonFile`, `writeJsonFile` |
| **流式** | `readFile` 配合 `{ encoding: 'stream' }`, `openWritableFileStream` |
| **临时文件** | `mkTemp`, `generateTempPath`, `pruneTemp`, `deleteTemp` |
| **压缩** | `zip`, `unzip`, `zipFromUrl`, `unzipFromUrl` |
| **网络** | `downloadFile`, `uploadFile` |
| **同步** | 所有核心操作都有同步版本（如 `mkdirSync`, `readFileSync`），通过 Web Workers 实现。使用 `SyncChannel.connect`, `SyncChannel.listen`, `SyncChannel.attach`, `SyncChannel.isReady` 进行设置 |

## 示例

本地运行示例：

```sh
pnpm run eg
# 打开 https://localhost:5173
```

### 快速开始

```ts
import { mkdir, writeFile, readTextFile, remove } from 'happy-opfs';

// 写入和读取文件
await mkdir('/data');
await writeFile('/data/hello.txt', 'Hello, OPFS!');

(await readTextFile('/data/hello.txt')).inspect((content) => {
    console.log(content); // 'Hello, OPFS!'
});

await remove('/data');
```

更多示例请参阅 [examples/](./examples/) 目录：

- [基本用法](./examples/basic.ts) - 文件 CRUD 操作
- [下载和上传](./examples/download-upload.ts) - 带进度的网络操作
- [压缩操作](./examples/zip.ts) - 压缩和解压文件
- [流式操作](./examples/stream.ts) - 使用流读写文件
- [同步 API](./examples/sync-api.ts) - 通过 Worker 实现的同步操作
- [共享 Messenger](./examples/shared-messenger.ts) - 在不同上下文间共享同步实例

## 浏览器兼容性

| 浏览器 | 版本 |
|--------|------|
| Chrome | 86+  |
| Edge   | 86+  |
| Firefox| 111+ |
| Safari | 15.2+|

详细兼容性信息请参阅 [MDN - OPFS](https://developer.mozilla.org/zh-CN/docs/Web/API/File_System_API/Origin_private_file_system#browser_compatibility)。

可以安装 [OPFS Explorer](https://chromewebstore.google.com/detail/acndjpgkpaclldomagafnognkcgjignd) 来可视化查看文件系统。

## 从 1.x 迁移到 2.x

### 破坏性变更 1：`readFile` 默认返回类型

在 1.x 中，`readFile` 默认返回 `ArrayBuffer`。在 2.x 中，默认返回 `Uint8Array`。

```ts
// 1.x - 默认返回 ArrayBuffer
const result = await readFile('/path/to/file');

// 2.x - 默认返回 Uint8Array
const result = await readFile('/path/to/file');

// 迁移方案：如需 ArrayBuffer，使用 .buffer 属性
const uint8Array = await readFile('/path/to/file');
const arrayBuffer = uint8Array.unwrap().buffer;
```

### 破坏性变更 2：移除 `readFileStream` 和 `writeFileStream`

这些已废弃的 API 已被移除，请使用新的替代方案：

```ts
// 1.x
const stream = await readFileStream('/path/to/file');
const writable = await writeFileStream('/path/to/file');

// 2.x
const stream = await readFile('/path/to/file', { encoding: 'stream' });
const writable = await openWritableFileStream('/path/to/file');
```

## 测试覆盖率

覆盖率在真实浏览器环境中使用 V8 provider 收集。

- `src/sync/channel/listen.ts` 被排除，因为它运行在 Web Worker 上下文中，V8 无法对其插桩
- `src/async/core/*.ts` 有部分分支通过 `createSyncAccessHandle` 在 Worker 上下文中运行，已通过 mock 测试覆盖

## 许可证

[MIT](LICENSE)
