---
title: "@unikvs/opfs"
description: "ブラウザーの OPFS にバイト列を保存するストレージプラグインの使い方を説明します。"
---

## 概要 [#overview]

`@unikvs/opfs` は、ブラウザーの OPFS（Origin Private File System）に保存するストレージプラグインです。データはバイト列（`Uint8Array<ArrayBuffer>`）専用です。ブラウザー専用

メインスレッド・Web Worker での動作を前提としています。

:::warning[実行環境について]
OPFS の利用可否はブラウザーの種類・バージョン・設定に依存します。Secure Context や Worker と組み合わせることが多い機能です。対応条件はブラウザーのドキュメントで確認してください。
:::

インストールは次のように行います。

```package-install
npm install @unikvs/opfs
```

## 使い方 [#usage]

クラス名は `Opfs` です。デフォルト・名前付きのどちらでも取得できます。

```ts
import { Opfs } from "@unikvs/opfs";

const storage = new Opfs(".unikvs");
```

コンストラクター引数 `root` の型は `string | FileSystemDirectoryHandle` で、既定値は `".unikvs"` です。文字列は OPFS 内のルート名、ハンドルは作業対象そのものとして扱います。ハンドルを渡すとオープン済み扱いです。第 2 引数 `options` は省略可能で、`options.allowRepair`（`boolean`、既定値 `false`）で書き戻しによる書き込みの許可を指定します。`false` の場合、書き戻し（`vars["unikvs:repair"]` が `true`）の `write`・`getWritable` はエラーを投げます。

使う前に `open()` を呼び出します。`UniKvs` と組み合わせる場合は、`appendStorage` で登録してから `kvs.open()` を呼びます。内部で `Opfs.open()` が呼ばれます。

```ts
import { UniKvs } from "unikvs";
import { Opfs } from "@unikvs/opfs";

const storage = new Opfs(".unikvs");
const kvs = UniKvs.config().appendStorage(storage).create();

await kvs.open();
// バイト列の読み書きを行います。
await kvs.close();
```

直接操作する場合は、`write`・`read`・`exists`・`delete`・`clear` を使います。いずれも事前の `open()` が必要です。

```ts
import { Opfs } from "@unikvs/opfs";

const storage = new Opfs(".unikvs");
await storage.open();

await storage.write({ key: "data.bin", data: new Uint8Array([1, 2, 3]) });
const data = await storage.read({ key: "data.bin" });
const found = await storage.exists({ key: "data.bin" });
await storage.delete({ key: "data.bin" });
```

## ファイル配置 [#layout]

キーはファイル名として、指定ルートの直下に 1 対 1 で保存します。サブディレクトリーは作りません。キーは `assertValidFilename` で検証し、無効ならエラーになります。

文字列ルートは次の規則で正規化します。

- `""`・`"."`・`"/"` は空文字扱いで、OPFS ルート直下が作業対象になります。
- 連続する `/` は 1 つにまとめ、先頭と末尾の `/` は取り除きます。
- `/` で分割した各階層は `assertValidDirname` で検証します。

`open()` では `navigator.storage.getDirectory()` で起点を取得し、各階層を `create: true` で作成・取得します。`clear()` では、ルート直下なら各エントリーを個別に削除し、サブディレクトリーならごと削除して作り直します。

## ストリーム [#streams]

書き込み・読み取りの両ストリームに対応しています。

- `getWritable({ key, signal })` は `WritableStream<Uint8Array<ArrayBuffer>>` を返します。内部ではファイルハンドルから `createWritable()` で取得したストリームをライター経由で包みます。`signal` を中断すると書き込み途中の変更を破棄します。
- `getReadable({ key, signal })` は `ReadableStream<Uint8Array<ArrayBuffer>>` を返します。内部ではファイルを取得し、`File.stream()` をリーダー経由で包みます。`signal` を中断すると読み取りを中止し、以降の読み取りは中断理由で失敗します。

```ts
import { Opfs } from "@unikvs/opfs";

const storage = new Opfs(".unikvs");
await storage.open();

const writable = await storage.getWritable({ key: "large.bin" });
const writer = writable.getWriter();
await writer.write(new Uint8Array([1, 2, 3]));
await writer.close();

const readable = await storage.getReadable({ key: "large.bin" });
await readable.cancel();
```

## 注意点 [#notes]

- `open()` 前に `read`・`write` を行うと実行時エラーになります。必ず先にオープンしてください。
- すべてのメソッドは `signal` を受け取ります。各操作は待機の区切りで `signal` を確認し、中断されていればその中断理由で失敗します。`open` が中断された場合は未オープンのままです。
- `write` は失敗時に `abort` で変更を破棄するため、部分書き込みで既存データを壊しません。
- `exists` は存在しないキーに `false` を返します。`NotFoundError` の `DOMException` を捕捉して判定し、それ以外は投げます。
- キーとルート名にはファイル名の制約があります。無効な名前は検証エラーになります。

:::note[永続性とオリジンについて]
- OPFS はオリジンごとに隔離されたプライベートなファイルシステムです。通常のファイル操作で直接見る場所ではありません。
- 永続性はブラウザーのストレージ管理（容量逼迫時の削除など）の影響を受けます。重要なデータは別途バックアップしてください。
- ブラウザーによる差異があり得ます。対象ブラウザーで動作確認してください。
:::

## 使用例 [#examples]

ブラウザーで `Opfs` を直接使用する最小例を示します。

```ts
import { Opfs } from "@unikvs/opfs";

const storage = new Opfs(".unikvs");
await storage.open();

const key = "hello.bin";
await storage.write({ key, data: new TextEncoder().encode("hello") });

const loaded = await storage.read({ key });
console.log(new TextDecoder().decode(loaded));
console.log(await storage.exists({ key }));

await storage.delete({ key });
```
