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:
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.
namestring
Plugin name.
stringisOpenboolean
Whether initialized.
booleanopen?(c: IStorage.OpenContext) => MaybePromise<void>
Runs initialization.
(c: IStorage.OpenContext) => MaybePromise<void>close?(c: IStorage.CloseContext) => MaybePromise<void>
Runs teardown.
(c: IStorage.CloseContext) => MaybePromise<void>write(c: IStorage.WriteContext, data: TWriteData) => MaybePromise<void>
Stores data.
(c: IStorage.WriteContext, data: TWriteData) => MaybePromise<void>read(c: IStorage.ReadContext) => MaybePromise<TReadData>
Fetches data. Throws `KeyNotFoundError` when missing.
(c: IStorage.ReadContext) => MaybePromise<TReadData>exists(c: IStorage.ExistsContext) => MaybePromise<boolean>
Returns key presence.
(c: IStorage.ExistsContext) => MaybePromise<boolean>delete(c: IStorage.DeleteContext) => MaybePromise<void>
Deletes a key.
(c: IStorage.DeleteContext) => MaybePromise<void>clear(c: IStorage.ClearContext) => MaybePromise<void>
Deletes all data.
(c: IStorage.ClearContext) => MaybePromise<void>copy?(c: IStorage.CopyContext) => MaybePromise<void>
Copies a key to another key. Falls back to read/write when omitted.
(c: IStorage.CopyContext) => MaybePromise<void>move?(c: IStorage.MoveContext) => MaybePromise<void>
Moves a key to another key. Falls back to copy/delete when omitted.
(c: IStorage.MoveContext) => MaybePromise<void>onOtherWriteError?(c: IStorage.OtherWriteErrorHandlerContext, errors: readonly IStorage.IOtherWriteError[]) => MaybePromise<void>
Receives other storages' failures.
(c: IStorage.OtherWriteErrorHandlerContext, errors: readonly IStorage.IOtherWriteError[]) => MaybePromise<void>