---
title: "@unikvs/fs.node"
description: "Node.js のローカルファイルシステムにバイト列を保存するストレージプラグインの使い方を説明します。"
---

## 概要 [#overview]

`@unikvs/fs.node` は、Node.js のローカルファイルシステムに保存するストレージプラグインです。指定したルートディレクトリー配下にキーをファイル名として保存します。Node.js 専用

扱うデータはバイト列専用です。`write` は `Uint8Array<ArrayBuffer>` を受け付け、`read` は `Uint8Array<ArrayBuffer>` を返します。実装は `node:fs`、`node:path`、`node:crypto`、`node:stream` を動的インポートして使います。

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

```package-install
npm install @unikvs/fs.node
```

依存関係として `@unikvs/core` と `@unikvs/utils` を使用します。

## 使い方 [#usage]

中心は `NodeFs` クラスです。

```ts
import { NodeFs } from "@unikvs/fs.node";
```

```ts
public constructor(root: string = ".unikvs", options: NodeFsOptions = {})
```

`root` は保存先のルートディレクトリーです。相対パスは `open()` 実行時にカレントディレクトリー基準で解決します。既定値は `".unikvs"` です。

`options.allowRepair` は書き戻しによる書き込みを許可するかどうかです。既定値は `false` です。`false` を指定すると、書き戻し（`vars["unikvs:repair"]` が `true`）の `write`・`getWritable` は `RepairNotAllowedError` で失敗します。

主なメンバーは次のとおりです。

- `name` は `"NodeFs"` です。
- `allowRepair` は書き戻しによる書き込みを許可するかどうかを示します。
- `isOpen` はオープン済みかどうかを示します。
- `open()` はルートディレクトリーを再帰的に作り、読み書き可能にします。
- `write`・`read`・`exists`・`delete`・`clear` で読み書きします。

```ts
import { NodeFs } from "@unikvs/fs.node";

const storage = new NodeFs("./.tmp/unikvs-fs");
await storage.open();

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

const data = await storage.read({ key: "hello.bin" });
console.log(new TextDecoder().decode(data));
```

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

キーからファイルパスへの対応は次のとおりです。ルート直下にキーがそのまま並び、サブディレクトリーは作りません。

- .unikvs/
  - hello.bin
  - greeting.bin

- 最終パスは `path.join(this.root, key)` です。
- 書き込み時の一時ファイルは `` `${dest}.${crypto.randomUUID()}.tmp` `` です。同じディレクトリーに作り、`rename` がアトミックになることを保証します。
- `open()` では `path.resolve(this.root)` で絶対パス化し、`mkdir(root, { recursive: true })` で再帰的に作ります。
- `clear()` ではルートを `rm(this.root, { recursive: true, force: true })` で削除後に作り直します。

ファイル名の検証には `@unikvs/utils` の `assertValidFilename` を使います。`write`・`read`・`exists`・`delete`・`getWritable`・`getReadable` のいずれも最初に検証し、無効なら `InvalidFilenameError` を投げます。空文字、`.`、`..`、255 バイト超、NFC 不一致、予約文字・Windows 予約名を含むキーは使えません。

## ストリーム [#streams]

読み書きどちらのストリームにも対応しています。

書き込み用は次のメソッドで取得します。

```ts
public async getWritable(
  args: Pick<IStorage.GetWritableArgs, "key"> & { signal?: AbortSignal },
): Promise<WritableStream<Uint8Array<ArrayBuffer>>>
```

一時ファイルへ書き出し、`close` 成功時のみ最終パスへ `rename` する swap-on-close 方式です。中断時はストリームを破棄して一時ファイルを削除します。バックプレッシャーに対応し、失敗・中断時に既存データを壊しません。

読み取り用は次のメソッドで取得します。

```ts
public getReadable(
  args: Pick<IStorage.GetReadableArgs, "key" | "signal">,
): ReadableStream<Uint8Array<ArrayBuffer>>
```

`fs.createReadStream(file, { signal })` を `stream.Readable.toWeb(readStream)` で Web ストリームに変換して返します。`signal` を中断すると読み取りストリームが中断理由で破棄され、進行中の読み取りも失敗します。

## 注意点 [#notes]

- `open()` 前に `write`・`read` を実行しないでください。内部の接続が `null` のため処理できません。`isOpen` で確認できます。
- `open()` はオープン済みでも再初期化します。`root` は絶対パスで保持します。
- `write` は一時ファイルへの書き込み後に `rename` します。失敗・中断時は一時ファイルを削除して既存データを保全します。
- 同一キーへの並行書き込みは一時ファイルが衝突しません。最後に `rename` が完了した書き込みが残ります。
- `exists` は `access(file)` の成否で判定し、エラー時は `false` を返します。
- `delete` は存在しないファイルだとエラーになります。
- 権限や所有者は変更しません。OS と Node.js の既定値に従います。
- `close()` はありません。`IStorage` の `close` は任意のため、後始末が必要なら各自で管理してください。

:::warning
権限は OS と Node.js の既定値に従います。同一キーへの並行書き込みは最後の `rename` が残るため、必要ならアプリケーション側で排他制御してください。
:::

## 使用例 [#examples]

`.tmp/` 配下に保存する最小完動例です。

```ts
import { NodeFs } from "@unikvs/fs.node";

const storage = new NodeFs("./.tmp/unikvs-fs-node-example");
await storage.open();

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

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

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