ミドルウェア
@unikvs/core の IMiddleware。ミドルウェアが実装するフックとコンテキストを説明します。
概要
IMiddleware はミドルウェアが実装するインターフェースです。name と isOpen のみ必須で、フックはすべて任意です。必要なフックだけを実装します。
import type { IMiddleware } from "@unikvs/core";
type MyMiddleware = IMiddleware<string, string, ArrayBuffer, ArrayBuffer>;
型引数
| 型引数 | 既定値 | 対応するフック |
|---|---|---|
TEncodeData |
any |
encode の入力と decode の出力 |
TDecodeData |
any |
decode の入力と encode の出力 |
TEncodeChunk |
any |
encodeStream の入出力 |
TDecodeChunk |
any |
decodeStream の入出力 |
encode で生データを保存形式に変換し、decode で逆変換します。データ変換とストリーム変換で型引数が分かれているため、値とチャンクの型が異なるプラグインも表現できます。
基本プロパティー
| メンバー | 必須 | 説明 |
|---|---|---|
name |
必須 | プラグイン名 |
isOpen |
必須 | 初期化済みかどうか |
open(c) と close(c) は任意です。ストレージのライフサイクルと同じく、必要な場合のみ実装します。
フック
| フック | 引数 | 説明 |
|---|---|---|
mapKey |
MapKeyContext |
キーを変換して string を返す |
open |
OpenContext |
初期化処理 |
close |
CloseContext |
終了処理 |
use |
UseContext, next |
ストレージ操作の前段のフック (後段の use を包む) |
afterUse |
UseContext, UseOutcome |
ストレージ I/O の確定後の通知 |
encode |
EncodeContext, next |
書き込みデータの変換 |
decode |
DecodeContext, next |
読み取りデータの逆変換 |
encodeStream |
EncodeStreamContext, next |
書き込みストリームの変換 |
decodeStream |
DecodeStreamContext, next |
読み取りストリームの逆変換 |
mapKey
キーそのものを変換します。同期的に string を返します。他のフックと異なり next はありません。
use
ストレージ操作 (set・get・stream・has・delete・copy・move・clear) の前段で実行されるフックです。use はストレージ I/O の前に実行され、next() が実行するのは後段の use フックまでです。ストレージ操作はすべての use フックが完了した後に実行されるため、use からストレージ操作の成否は観測できません。書き込み系の操作の結果は afterUse で受け取ります。action で絞り込むと、各操作の key や repair、destinationKey に型安全に触れます。
import type { IMiddleware } from "@unikvs/core";
export class Timing implements IMiddleware {
public readonly name = "Timing";
public isOpen = false;
public async use(c: IMiddleware.UseContext, next: IMiddleware.INext): Promise<void> {
const startedAt = Date.now();
await next();
console.log(`use chain for ${c.action} took ${Date.now() - startedAt}ms`);
}
}
afterUse
afterUse は、書き込み系の操作 (set・delete・copy・move・clear) でストレージ I/O が確定した後に 1 回だけ呼ばれる通知専用のフックです。読み取り系の操作 (get・stream・has) では呼ばれません。get・stream の repair による書き戻しは読み取りの一部として扱われ、afterUse は呼ばれません。
use と異なり、ストレージ操作の成否を観測できます。キーロックと I/O ロックを保持したまま、すべての宛先の処理が終わってから実行されます。outcome.targets と outcome は読み取り専用に凍結されて渡されます。
import type { IMiddleware } from "@unikvs/core";
export class Audit implements IMiddleware {
public readonly name = "Audit";
public isOpen = false;
public afterUse(c: IMiddleware.UseContext, outcome: IMiddleware.UseOutcome): void {
console.log({
action: c.action,
status: outcome.status,
targets: outcome.targets,
});
}
}
outcome.status は success・error・aborted のいずれかです。outcome.targets には宛先ごとの name・index・status が入ります。分岐にだけ適用したミドルウェアには、その分岐の宛先だけが渡されます。status は committed (書き込み・削除などの確定)、notFound (キー不在)、skipped (予約済み。現在の UniKvs は実行した宛先について返しません)、failed (失敗) のいずれかで、failed のときだけ error を持ちます。
committed は操作全体が成功したときだけ現れるわけではありません。一部の宛先だけが失敗した場合や、ストレージの確定後に操作が中断された場合でも、実際に確定した宛先は committed として渡されます。aborted は操作全体が中断されたことを表し、その時点で確定済みの宛先は committed のまま残ります。成功として扱えるかは outcome.status ではなく、targets の実結果で判断してください。そのため、メトリクスの集計や後片付けのように、宛先ごとの実結果を使う処理を書けます。
encode と decode
データの変換と逆変換です。source が入力で、destination に結果を代入します。
import type { IMiddleware } from "@unikvs/core";
export class UpperCase implements IMiddleware<string> {
public readonly name = "UpperCase";
public isOpen = false;
public async encode(
c: IMiddleware.EncodeContext<string>,
next: IMiddleware.INext,
): Promise<void> {
c.destination = c.source.toUpperCase();
await next();
}
public async decode(
c: IMiddleware.DecodeContext<string>,
next: IMiddleware.INext,
): Promise<void> {
c.destination = c.source.toLowerCase();
await next();
}
}
EncodeContext<string> は IEncodeContext<string, string> と等価です。型引数を 1 つだけ渡すと、入出力が同じ型として解決されます。
encodeStream と decodeStream
ストリームの変換と逆変換です。データ変換と異なり、source と destination は IReadableStream です。
next
use・encode・decode・encodeStream・decodeStream は next() を呼ぶと後段へ進みます。INext は引数なしで Promise<void> を返します。
public async use(c: IMiddleware.UseContext, next: IMiddleware.INext): Promise<void> {
await next(); // 後段へ進む
}
next() の前後で前処理と後処理を書けます。戻り値は PromiseLike<void> で、MaybePromise ではなく必ず非同期として扱われます。
コンテキストの別名
IMiddleware 名前空間は パイプラインコンテキスト の別名を提供します。
| 別名 | 実体 |
|---|---|
IMiddleware.MapKeyContext |
IMapKeyContext |
IMiddleware.OpenContext |
IOpenContext |
IMiddleware.CloseContext |
ICloseContext |
IMiddleware.UseContext |
ISetContext | IGetContext | IStreamContext | IHasContext | IDeleteContext | ICopyContext | IMoveContext | IClearContext |
IMiddleware.EncodeContext<T> |
IEncodeContext<T, T> |
IMiddleware.DecodeContext<T> |
IDecodeContext<T, T> |
IMiddleware.EncodeStreamContext<T> |
IEncodeStreamContext<T, T> |
IMiddleware.DecodeStreamContext<T> |
IDecodeStreamContext<T, T> |
実装例
import type { IMiddleware } from "@unikvs/core";
export class PrefixKey implements IMiddleware {
public readonly name = "PrefixKey";
public isOpen = false;
public mapKey(c: IMiddleware.MapKeyContext): string {
return `app:${c.key}`;
}
}import type { IMiddleware } from "@unikvs/core";
export class PrefixKey implements IMiddleware {
public readonly name = "PrefixKey";
public isOpen = false;
public async use(c: IMiddleware.UseContext, next: IMiddleware.INext): Promise<void> {
console.log(`start: ${c.action}`);
await next();
console.log(`end: ${c.action}`);
}
}import type { IMiddleware } from "@unikvs/core";
export class UpperCase implements IMiddleware<string> {
public readonly name = "UpperCase";
public isOpen = false;
public async encode(
c: IMiddleware.EncodeContext<string>,
next: IMiddleware.INext,
): Promise<void> {
c.destination = c.source.toUpperCase();
await next();
}
public async decode(
c: IMiddleware.DecodeContext<string>,
next: IMiddleware.INext,
): Promise<void> {
c.destination = c.source.toLowerCase();
await next();
}
}