---
title: "Custom Plugins"
description: "Explains how to implement Storage and Transformer and what to watch for."
---

## Plugin interface overview [#overview]

Implement plugins following the `@unikvs/core` types. Storages are data destinations, while Transformers handle transparent conversion.

1. **Storages implement IStorage**

    Implement `write`, `read`, `exists`, `delete`, and `clear`.

2. **Transformers implement ITransformer**

    Implement the encode/decode correspondence.

3. **Add stream support as needed**

    If needed, additionally implement the separate input/output stream interfaces.

4. **Errors extend ErrorBase**

    Define messages that include a description, cause, and resolution.

See the `@unikvs/core` package reference for detailed signatures.

## Implementing Storage [#storage]

1. Implement the exact semantics of `write`, `read`, `exists`, `delete`, and `clear`.
2. Throw a key-not-found error for `read` and `delete` on missing keys.
3. If needed, implement `open`, `close`, and `isOpen`. Otherwise, treat the Storage as always open. Memory Storage is an always-open example.
4. If stream writes are needed, add stream acquisition for writing and reading. Decide how non-byte input is handled.
5. If file names or keys have constraints, reuse the validation functions from `@unikvs/utils`.

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

export default class MyStorage implements IStorage {
  public readonly name = "MyStorage";

  public get isOpen(): boolean {
    return true;
  }

  public write(args: Pick<IStorage.WriteArgs<any>, "key" | "data">): void {
    // Implement the write logic
  }

  public read(args: Pick<IStorage.ReadArgs, "key">): unknown {
    // Implement the read logic
  }

  public exists(args: Pick<IStorage.ExistsArgs, "key">): boolean {
    // Implement the existence check
    return false;
  }

  public delete(args: Pick<IStorage.DeleteArgs, "key">): void {
    // Implement the delete logic
  }

  public clear(): void {
    // Implement the clear logic
  }
}
```

The above is a structural example. Follow the core type definitions for arguments and return values.

**Example plugin scaffold**

- my-plugin/
  - src/
    - index.ts
    - my-storage.ts
    - errors.ts
  - package.json
  - tsconfig.json

:::tip
If keys or file names have constraints, reuse the validation functions from `@unikvs/utils` for messages consistent with file-based Storages.
:::

## Implementing Transformers [#transformer]

1. Define a clear encode/decode correspondence. Verify with unit tests that round-trips restore the original.
2. Clarify whether stream conversion is supported. Throw the corresponding error when it is not supported.
3. If extra information is needed, accept it via runtime variables. The checksum hash specification is an example.
4. If needed, implement `open` and `close`.

## Defining errors [#errors]

Extend `ErrorBase` and define messages that include a description, cause, and resolution. Messages support i18n. The existing `InvalidFilenameError` and checksum mismatch errors are good references.

## Testing [#testing]

- Verify that a stored value matches the retrieved value.
- Check boundaries for missing keys, empty data, and large data.
- If stream support exists, verify multi-chunk concatenation and behavior on interruption.
- Verify behavior in multi-Storage configurations.
