コンテンツにスキップ
UniKVS
Esc
↑↓移動↵開く⌘Jプレビュー
このページの内容

ミドルウェア

@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();
  }
}

このページは役に立ちましたか?