Skip to content
UniKVS
Esc
↑↓navigate↵open⌘Jpreview
On this page

Storage

@unikvs/core IStorage: the interface destination plugins implement, its type arguments, required and optional members, with examples.

Overview

IStorage is the interface destination plugins implement. It has name, isOpen, and CRUD operations; lifecycle, copy/move, and stream acquisition are optional. Four type arguments tune data and chunk types.

import type { IStorage } from "@unikvs/core";

type BinaryStorage = IStorage<Uint8Array>;
type ObjectStorage = IStorage<{ readonly id: number }>;
type FullStorage = IStorage<Uint8Array, string, ArrayBuffer, number>;

Type arguments

Type argument Default Corresponding operation
TWriteData any data of write(c, data)
TReadData TWriteData return of read(c)
TWriteChunk any chunk of getWritable(c)
TReadChunk TWriteChunk chunk of getReadable(c)

TReadData defaults to TWriteData, and TReadChunk defaults to TWriteChunk. When read/write types match, specify only the data type argument (TWriteData); when you use no streams, omit the chunk type arguments.

// writes are bytes, reads are strings
type TextReadStorage = IStorage<Uint8Array, string>;

// specify down to chunks
type ChunkedStorage = IStorage<Uint8Array, string, ArrayBuffer, number>;

For the chunk/stream-type relationship, see Streams.

Identity

Member Required Description
name required Plugin name
isOpen required Whether initialized

open(c) and close(c) are optional. Implement them only when you need setup or teardown. Callers check state via isOpen.

CRUD

Member Required Signature Description
write required (c: WriteContext, data: TWriteData) => MaybePromise<void> Stores data
read required (c: ReadContext) => MaybePromise<TReadData> Fetches data
exists required (c: ExistsContext) => MaybePromise<boolean> Returns key presence
delete required (c: DeleteContext) => MaybePromise<void> Deletes a key
clear required (c: ClearContext) => MaybePromise<void> Deletes all data

Every method returns MaybePromise (T | PromiseLike<T>), so sync and async implementations both work.

Copy and move

Member Required Signature Description
copy optional (c: CopyContext) => MaybePromise<void> Copies a key to another key
move optional (c: MoveContext) => MaybePromise<void> Moves a key to another key

CopyContext and MoveContext hold the source key in key and the destination key in destinationKey. An existing destination value is overwritten.

move removes the source only after the destination write succeeds. If the destination write fails, leave the source in place. If the source does not exist, throw KeyNotFoundError. When key and destinationKey are the same, only verify that the source exists and do not delete it.

When copy or move is omitted, the internal UniKvsStorage reproduces the behavior with read, write, and delete. Implement them only when the backend has a more efficient native operation.

Stream acquisition

Member Required Signature Description
getWritable optional (c: GetWritableContext) => MaybePromise<IWritableStream<TWriteChunk>> Acquires a writable stream
getReadable optional (c: GetReadableContext) => MaybePromise<IReadableStream<TReadChunk>> Acquires a readable stream

Storages without stream support omit the implementation. Support is detected by runtime presence, not by types, so unimplemented operations must stay absent rather than throwing “unsupported”.

Multi-storage writes

Member Required Description
onOtherWriteError optional Receives other storages’ failures on multi-storage writes

Arguments are (c: OtherWriteErrorHandlerContext, errors: readonly IOtherWriteError[]). OtherWriteErrorHandlerContext is WriteContext | GetWritableContext, indicating which write path failed.

IOtherWriteError has the following shape:

PropType
plugin"storage"

Fixed value.

Type"storage"
errorsreadonly { plugin: "storage", reason: unknown }[]

Failure reason per other storage.

Typereadonly { plugin: "storage", reason: unknown }[]

Context aliases

The IStorage namespace provides aliases of pipeline contexts. Using IStorage.* on the plugin side clarifies intent.

Alias Entity Used by
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

During the copy / move fallback, read, write, and delete receive contexts with the original action ("copy" or "move") and the key appropriate for the call. Therefore ReadContext, WriteContext, and DeleteContext also include the copy/move contexts.

Example

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

Implementation points:

Pick type arguments

Declare the stored value type as IStorage<T>. When read/write types differ or you use streams, specify all four.

Implement required members

name, isOpen, and the 5 CRUD methods are required. read throws KeyNotFoundError for missing keys. copy and move are optional; when omitted, read/write/delete provide the fallback.

Add optional members

Implement open / close only with setup needs, getWritable / getReadable for streams, and onOtherWriteError when you need other storages’ failure reports.

API reference

IStorage

Interface implemented by destination plugins. Four type arguments tune data and chunk types.

PropType
namestring

Plugin name.

Typestring
isOpenboolean

Whether initialized.

Typeboolean
open?(c: IStorage.OpenContext) => MaybePromise<void>

Runs initialization.

Type(c: IStorage.OpenContext) => MaybePromise<void>
close?(c: IStorage.CloseContext) => MaybePromise<void>

Runs teardown.

Type(c: IStorage.CloseContext) => MaybePromise<void>
write(c: IStorage.WriteContext, data: TWriteData) => MaybePromise<void>

Stores data.

Type(c: IStorage.WriteContext, data: TWriteData) => MaybePromise<void>
read(c: IStorage.ReadContext) => MaybePromise<TReadData>

Fetches data. Throws `KeyNotFoundError` when missing.

Type(c: IStorage.ReadContext) => MaybePromise<TReadData>
exists(c: IStorage.ExistsContext) => MaybePromise<boolean>

Returns key presence.

Type(c: IStorage.ExistsContext) => MaybePromise<boolean>
delete(c: IStorage.DeleteContext) => MaybePromise<void>

Deletes a key.

Type(c: IStorage.DeleteContext) => MaybePromise<void>
clear(c: IStorage.ClearContext) => MaybePromise<void>

Deletes all data.

Type(c: IStorage.ClearContext) => MaybePromise<void>
copy?(c: IStorage.CopyContext) => MaybePromise<void>

Copies a key to another key. Falls back to read/write when omitted.

Type(c: IStorage.CopyContext) => MaybePromise<void>
move?(c: IStorage.MoveContext) => MaybePromise<void>

Moves a key to another key. Falls back to copy/delete when omitted.

Type(c: IStorage.MoveContext) => MaybePromise<void>
onOtherWriteError?(c: IStorage.OtherWriteErrorHandlerContext, errors: readonly IStorage.IOtherWriteError[]) => MaybePromise<void>

Receives other storages' failures.

Type(c: IStorage.OtherWriteErrorHandlerContext, errors: readonly IStorage.IOtherWriteError[]) => MaybePromise<void>

Was this page helpful?