@unikvs/fs.node
Node.js のローカルファイルシステムにバイト列を保存するストレージプラグインの使い方を説明します。
概要
@unikvs/fs.node は、Node.js のローカルファイルシステムに保存するストレージプラグインです。指定したルートディレクトリー配下にキーをファイル名として保存します。Node.js 専用
扱うデータはバイト列専用です。write は Uint8Array<ArrayBuffer> を受け付け、read は Uint8Array<ArrayBuffer> を返します。実装は node:fs、node:path、node:crypto、node:stream を動的インポートして使います。
インストールは次のように行います。
npm install @unikvs/fs.nodepnpm add @unikvs/fs.nodeyarn add @unikvs/fs.nodebun add @unikvs/fs.nodenub add @unikvs/fs.nodeaube add @unikvs/fs.node依存関係として @unikvs/core と @unikvs/utils を使用します。
使い方
中心は NodeFs クラスです。
import { NodeFs } from "@unikvs/fs.node";
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で読み書きします。
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));
ファイル配置
キーからファイルパスへの対応は次のとおりです。ルート直下にキーがそのまま並び、サブディレクトリーは作りません。
- .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 予約名を含むキーは使えません。
ストリーム
読み書きどちらのストリームにも対応しています。
書き込み用は次のメソッドで取得します。
public async getWritable(
args: Pick<IStorage.GetWritableArgs, "key"> & { signal?: AbortSignal },
): Promise<WritableStream<Uint8Array<ArrayBuffer>>>
一時ファイルへ書き出し、close 成功時のみ最終パスへ rename する swap-on-close 方式です。中断時はストリームを破棄して一時ファイルを削除します。バックプレッシャーに対応し、失敗・中断時に既存データを壊しません。
読み取り用は次のメソッドで取得します。
public getReadable(
args: Pick<IStorage.GetReadableArgs, "key" | "signal">,
): ReadableStream<Uint8Array<ArrayBuffer>>
fs.createReadStream(file, { signal }) を stream.Readable.toWeb(readStream) で Web ストリームに変換して返します。signal を中断すると読み取りストリームが中断理由で破棄され、進行中の読み取りも失敗します。
注意点
open()前にwrite・readを実行しないでください。内部の接続がnullのため処理できません。isOpenで確認できます。open()はオープン済みでも再初期化します。rootは絶対パスで保持します。writeは一時ファイルへの書き込み後にrenameします。失敗・中断時は一時ファイルを削除して既存データを保全します。- 同一キーへの並行書き込みは一時ファイルが衝突しません。最後に
renameが完了した書き込みが残ります。 existsはaccess(file)の成否で判定し、エラー時はfalseを返します。deleteは存在しないファイルだとエラーになります。- 権限や所有者は変更しません。OS と Node.js の既定値に従います。
close()はありません。IStorageのcloseは任意のため、後始末が必要なら各自で管理してください。
使用例
.tmp/ 配下に保存する最小完動例です。
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();