ストレージ
@unikvs/core の IStorage。保存先プラグインが実装するインターフェース、型引数、必須と任意のメンバー、実装例を説明します。
概要
IStorage は保存先プラグインが実装するインターフェースです。name と isOpen、CRUD 操作を持ち、ライフサイクル、copy・move、ストリーム取得は任意です。4 つの型引数でデータとチャンクの型を調整します。
import type { IStorage } from "@unikvs/core";
type BinaryStorage = IStorage<Uint8Array>;
type ObjectStorage = IStorage<{ readonly id: number }>;
type FullStorage = IStorage<Uint8Array, string, ArrayBuffer, number>;
型引数
| 型引数 | 既定値 | 対応する操作 |
|---|---|---|
TWriteData |
any |
write(c, data) の data |
TReadData |
TWriteData |
read(c) の戻り値 |
TWriteChunk |
any |
getWritable(c) のチャンク |
TReadChunk |
TWriteChunk |
getReadable(c) のチャンク |
TReadData の既定値は TWriteData、TReadChunk の既定値は TWriteChunk です。読み書きで型が変わらない場合はデータの型引数 (TWriteData) のみを指定し、ストリームを使わない場合はチャンクの型引数を省略できます。
// 読み取りは文字列、書き込みはバイト列
type TextReadStorage = IStorage<Uint8Array, string>;
// チャンクまで個別に指定
type ChunkedStorage = IStorage<Uint8Array, string, ArrayBuffer, number>;
チャンク型とストリーム型の関係はストリームを参照してください。
基本プロパティー
| メンバー | 必須 | 説明 |
|---|---|---|
name |
必須 | プラグイン名 |
isOpen |
必須 | 初期化済みかどうか |
open(c) と close(c) は任意です。必要な初期化や後始末がある場合のみ実装します。呼び出し側は isOpen で状態を確認します。
CRUD
| メンバー | 必須 | シグネチャ | 説明 |
|---|---|---|---|
write |
必須 | (c: WriteContext, data: TWriteData) => MaybePromise<void> |
データを保存する |
read |
必須 | (c: ReadContext) => MaybePromise<TReadData> |
データを取得する |
exists |
必須 | (c: ExistsContext) => MaybePromise<boolean> |
キーの有無を返す |
delete |
必須 | (c: DeleteContext) => MaybePromise<void> |
キーを削除する |
clear |
必須 | (c: ClearContext) => MaybePromise<void> |
すべてのデータを削除する |
すべてのメソッドは MaybePromise (T | PromiseLike<T>) を返すため、同期実装も非同期実装も可能です。
コピーと移動
| メンバー | 必須 | シグネチャ | 説明 |
|---|---|---|---|
copy |
任意 | (c: CopyContext) => MaybePromise<void> |
キーを別のキーへ複製する |
move |
任意 | (c: MoveContext) => MaybePromise<void> |
キーを別のキーへ移動する |
CopyContext と MoveContext は、コピー元・移動元のキーを key、コピー先・移動先のキーを destinationKey に持ちます。コピー先・移動先に既存の値がある場合は上書きします。
move は移動先への書き込みが成功した後に移動元を削除します。移動先への書き込みに失敗した場合は移動元を残してください。コピー元・移動元が存在しない場合は KeyNotFoundError を投げます。key と destinationKey が同じ場合は、存在確認だけを行い、値を削除しません。
copy または move を省略した場合、UniKvs 内部の UniKvsStorage が read・write・delete を組み合わせて同じ動作を再現します。ストレージ固有の効率的な API がある場合だけ実装します。
ストリーム取得
| メンバー | 必須 | シグネチャ | 説明 |
|---|---|---|---|
getWritable |
任意 | (c: GetWritableContext) => MaybePromise<IWritableStream<TWriteChunk>> |
書き込み用ストリームを取得する |
getReadable |
任意 | (c: GetReadableContext) => MaybePromise<IReadableStream<TReadChunk>> |
読み取り用ストリームを取得する |
ストリームに対応しないストレージは実装を省略します。対応宣言は型ではなく実行時の有無で判定されるため、未対応の操作が呼ばれた場合は各プラグインが適切なエラーを投げます。
getWritable のコンテキストは ISetContext と同じ形状で、getReadable のコンテキストは IStreamContext と同じ形状です (コンテキストの別名)。開始前に中断済みのシグナルで呼ばれた場合は StorageAbortedError で即座に拒否します。
他ストレージの失敗
複数のストレージへ並列に書き込む際、他の保存先で失敗があっても各ストレージに通知されます。
| メンバー | 必須 | 説明 |
|---|---|---|
onOtherWriteError |
任意 | 複数ストレージ書き込み時に他ストレージの失敗を受け取る |
引数は (c: OtherWriteErrorHandlerContext, errors: readonly IOtherWriteError[]) です。OtherWriteErrorHandlerContext は WriteContext | GetWritableContext で、書き込み系のどちらの経路で失敗したかを示します。
IOtherWriteError の形状は次のとおりです。
コンテキストの別名
IStorage 名前空間は パイプラインコンテキスト の別名を提供します。プラグイン側は IStorage.* を使うと意図が明確になります。
| 別名 | 実体 | 用途 |
|---|---|---|
IStorage.OpenContext |
IOpenContext |
open |
IStorage.CloseContext |
ICloseContext |
close |
IStorage.WriteContext |
ISetContext | ICopyContext | IMoveContext |
write |
IStorage.GetWritableContext |
ISetContext |
getWritable |
IStorage.ReadContext |
IGetContext | ICopyContext | IMoveContext |
read |
IStorage.GetReadableContext |
IStreamContext |
getReadable |
IStorage.ExistsContext |
IHasContext |
exists |
IStorage.DeleteContext |
IDeleteContext | ICopyContext | IMoveContext |
delete |
IStorage.CopyContext |
ICopyContext | IMoveContext |
copy |
IStorage.MoveContext |
IMoveContext |
move |
IStorage.ClearContext |
IClearContext |
clear |
IStorage.OtherWriteErrorHandlerContext |
WriteContext | GetWritableContext |
onOtherWriteError |
copy / move のフォールバックでは、read・write・delete に元の操作種別 ("copy" または "move") と、呼び出しに応じたキーを持つコンテキストが渡されます。そのため ReadContext・WriteContext・DeleteContext には copy/move コンテキストも含まれます。
実装例
import {
KeyNotFoundError,
type IStorage,
} from "@unikvs/core";
export class Memory implements IStorage<string> {
public readonly name = "Memory";
public isOpen = false;
private readonly data = new Map<string, string>();
public open(): void {
this.isOpen = true;
}
public close(): void {
this.isOpen = false;
}
public write(c: IStorage.WriteContext, value: string): void {
this.data.set(c.key, value);
}
public read(c: IStorage.ReadContext): string {
const value = this.data.get(c.key);
if (value === undefined) {
throw new KeyNotFoundError({ key: c.key });
}
return value;
}
public exists(c: IStorage.ExistsContext): boolean {
return this.data.has(c.key);
}
public delete(c: IStorage.DeleteContext): void {
this.data.delete(c.key);
}
public copy(c: IStorage.CopyContext): void {
const value = this.data.get(c.key);
if (value === undefined) {
throw new KeyNotFoundError({ key: c.key });
}
if (c.key !== c.destinationKey) {
this.data.set(c.destinationKey, value);
}
}
public move(c: IStorage.MoveContext): void {
this.copy(c as unknown as IStorage.CopyContext);
if (c.key !== c.destinationKey) {
this.data.delete(c.key);
}
}
public clear(): void {
this.data.clear();
}
}import {
KeyNotFoundError,
type IStorage,
} from "@unikvs/core";
export class AsyncMemory implements IStorage<string> {
public readonly name = "AsyncMemory";
public isOpen = false;
private readonly data = new Map<string, string>();
public async open(): Promise<void> {
this.isOpen = true;
}
public async close(): Promise<void> {
this.isOpen = false;
}
public async write(c: IStorage.WriteContext, value: string): Promise<void> {
this.data.set(c.key, value);
}
public async read(c: IStorage.ReadContext): Promise<string> {
const value = this.data.get(c.key);
if (value === undefined) {
throw new KeyNotFoundError({ key: c.key });
}
return value;
}
public async exists(c: IStorage.ExistsContext): Promise<boolean> {
return this.data.has(c.key);
}
public async delete(c: IStorage.DeleteContext): Promise<void> {
this.data.delete(c.key);
}
public async copy(c: IStorage.CopyContext): Promise<void> {
const value = this.data.get(c.key);
if (value === undefined) {
throw new KeyNotFoundError({ key: c.key });
}
if (c.key !== c.destinationKey) {
this.data.set(c.destinationKey, value);
}
}
public async move(c: IStorage.MoveContext): Promise<void> {
await this.copy(c as unknown as IStorage.CopyContext);
if (c.key !== c.destinationKey) {
this.data.delete(c.key);
}
}
public async clear(): Promise<void> {
this.data.clear();
}
}実装時の要点は次のとおりです。
型引数を決める
保存する値の型を IStorage<T> に指定します。読み書きで型が変わる場合やストリームを使う場合は、4 つすべてを指定します。
必須メンバーを実装する
name・isOpen と CRUD の 5 メソッドは必須です。read では見つからないキーに KeyNotFoundError を投げます。copy・move は任意で、省略時は read・write・delete によるフォールバックが使われます。
任意メンバーを足す
初期化が必要なら open・close、ストリーム対応なら getWritable・getReadable、他ストレージの失敗通知が要れば onOtherWriteError を実装します。
API リファレンス
IStorage
保存先プラグインが実装するインターフェースです。4 つの型引数でデータとチャンクの型を調整します。
namestring
プラグイン名です。
stringisOpenboolean
初期化済みかどうかを示します。
booleanopen?(c: IStorage.OpenContext) => MaybePromise<void>
初期化処理を行います。
(c: IStorage.OpenContext) => MaybePromise<void>close?(c: IStorage.CloseContext) => MaybePromise<void>
終了処理を行います。
(c: IStorage.CloseContext) => MaybePromise<void>write(c: IStorage.WriteContext, data: TWriteData) => MaybePromise<void>
データを保存します。
(c: IStorage.WriteContext, data: TWriteData) => MaybePromise<void>read(c: IStorage.ReadContext) => MaybePromise<TReadData>
データを取得します。なければ `KeyNotFoundError` を投げます。
(c: IStorage.ReadContext) => MaybePromise<TReadData>exists(c: IStorage.ExistsContext) => MaybePromise<boolean>
キーの有無を返します。
(c: IStorage.ExistsContext) => MaybePromise<boolean>delete(c: IStorage.DeleteContext) => MaybePromise<void>
キーを削除します。
(c: IStorage.DeleteContext) => MaybePromise<void>clear(c: IStorage.ClearContext) => MaybePromise<void>
すべてのデータを削除します。
(c: IStorage.ClearContext) => MaybePromise<void>copy?(c: IStorage.CopyContext) => MaybePromise<void>
キーの値を別のキーへ複製します。省略時は read/write で再現されます。
(c: IStorage.CopyContext) => MaybePromise<void>move?(c: IStorage.MoveContext) => MaybePromise<void>
キーの値を別のキーへ移動します。省略時は copy/delete で再現されます。
(c: IStorage.MoveContext) => MaybePromise<void>onOtherWriteError?(c: IStorage.OtherWriteErrorHandlerContext, errors: readonly IStorage.IOtherWriteError[]) => MaybePromise<void>
他ストレージの失敗を受け取ります。
(c: IStorage.OtherWriteErrorHandlerContext, errors: readonly IStorage.IOtherWriteError[]) => MaybePromise<void>