Middleware
@unikvs/core IMiddleware: hooks and contexts implemented by middleware.
Overview
IMiddleware is the interface middleware implements. Only name and isOpen are required; every hook is optional. Implement just the hooks you need.
import type { IMiddleware } from "@unikvs/core";
type MyMiddleware = IMiddleware<string, string, ArrayBuffer, ArrayBuffer>;
Type arguments
| Type argument | Default | Corresponding hooks |
|---|---|---|
TEncodeData |
any |
encode input and decode output |
TDecodeData |
any |
decode input and encode output |
TEncodeChunk |
any |
encodeStream in/out |
TDecodeChunk |
any |
decodeStream in/out |
encode converts raw data to the stored form; decode reverses it. Data and stream conversions have separate type arguments, so values and chunks can have different types.
Identity
| Member | Required | Description |
|---|---|---|
name |
required | Plugin name |
isOpen |
required | Whether initialized |
open(c) and close(c) are optional. As with storage lifecycles, implement them only when needed.
Hooks
| Hook | Arguments | Description |
|---|---|---|
mapKey |
MapKeyContext |
Transforms the key, returns string |
open |
OpenContext |
Initialization |
close |
CloseContext |
Teardown |
use |
UseContext, next |
Hook before storage operations (wraps downstream use hooks) |
afterUse |
UseContext, UseOutcome |
Notification after storage I/O settles |
encode |
EncodeContext, next |
Write-data conversion |
decode |
DecodeContext, next |
Read-data reverse conversion |
encodeStream |
EncodeStreamContext, next |
Write-stream conversion |
decodeStream |
DecodeStreamContext, next |
Read-stream reverse conversion |
mapKey
Transforms the key itself. Synchronously returns a string. Unlike other hooks, it has no next.
use
Hook that runs before storage operations (set / get / stream / has / delete / copy / move / clear). use runs before storage I/O, and next() runs only downstream use hooks. The storage operation runs after all use hooks complete, so use cannot observe whether it succeeds. Receive the outcome of mutating operations with afterUse. Narrow by action for type-safe access to each operation’s key, repair, or 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 is a notification-only hook called once after storage I/O settles for mutating operations (set / delete / copy / move / clear). It is not called for reads (get / stream / has). Repair writes performed by get / stream are treated as part of the read and do not call afterUse.
Unlike use, it can observe whether the storage operation succeeded. It runs after every target has finished, while the key and I/O locks are still held. outcome and outcome.targets are frozen before they are passed to the hook.
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 is success, error, or aborted. Each entry in outcome.targets has a target name, index, and status. A middleware applied to only one branch receives only that branch’s targets. The status is committed (write or delete confirmed), notFound (key absent), skipped (reserved; current UniKvs does not return it for an executed target), or failed (failure). Only failed targets carry error.
A committed target does not imply the whole operation succeeded. When only some targets fail, or when an operation is aborted after storage has settled, targets that actually committed are still reported as committed. aborted means the operation as a whole was aborted; targets already committed remain committed. Decide whether the operation is usable from the per-target results, not from outcome.status alone. This lets metrics and cleanup decisions use the real per-target result.
encode and decode
Data conversion and its inverse. source is the input; assign the result to 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> equals IEncodeContext<string, string>. With a single type argument, input and output resolve to the same type.
encodeStream and decodeStream
Stream conversion and its inverse. Unlike data conversion, source and destination are IReadableStream.
next
use / encode / decode / encodeStream / decodeStream advance downstream by calling next(). INext takes no arguments and returns Promise<void>.
public async use(c: IMiddleware.UseContext, next: IMiddleware.INext): Promise<void> {
await next(); // advance downstream
}
Write pre/post processing around next(). The return is PromiseLike<void> — always async, never MaybePromise.
Context aliases
The IMiddleware namespace provides aliases of pipeline contexts.
| Alias | Entity |
|---|---|
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> |
Example
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();
}
}