---
title: "@unikvs/writeonly"
description: "Learn how to use @unikvs/writeonly, which turns an existing storage into a write-only storage."
---

## Overview [#overview]

`@unikvs/writeonly` is a storage plugin that wraps an `IStorage` implementation to make it write-only. Writes are delegated to the inner storage as-is, and reads always fail. It suits data that regulations require you to retain for a long time. It also works for backups you want to protect from modification or deletion.

- Pass the inner storage to `new WriteOnly(storage)` at initialization.
- `read` and `getReadable` always throw `KeyNotFoundError`, and `exists` always returns `false`.
- `delete` and `clear` do nothing by default. The `allowDelete` option enables them.
- Supported environments depend on the wrapped storage. You can wrap any `IStorage` implementation, such as `@unikvs/memory` or `@unikvs/fs.node`.

Install it with the following command.

```package-install
npm i @unikvs/writeonly
```

## Usage [#usage]

The class name is `WriteOnly`, and `name` is `"WriteOnly"`. Pass it to `appendStorage()` for `UniKvs`.

```ts
import { Memory } from "@unikvs/memory";
import { UniKvs, type Value } from "unikvs";
import { WriteOnly } from "@unikvs/writeonly";

const kvs = UniKvs.config<{
  foo: Value<string>;
}>()
  .appendStorage(new WriteOnly(new Memory()))
  .create();

await kvs.open();
await kvs.set("foo", "important");
await kvs.close();
```

The constructor arguments are as follows.

| Argument | Type | Required | Description |
| --- | --- | --- | --- |
| `storage` | `IStorage` | Yes | The inner storage that receives writes. |
| `options` | `WriteOnlyOptions` | No | When omitted, behaves as an empty object. |
| `options.allowDelete` | `boolean` | No | Whether to allow `delete` and `clear`. Defaults to `false`. |

`isOpen` returns the state of the inner storage, and `open` and `close` delegate to it. If the inner storage has no `open` or `close`, they do nothing.

When you operate it directly, everything except the read paths works like the inner storage.

```ts
import { Memory } from "@unikvs/memory";
import { WriteOnly } from "@unikvs/writeonly";

const storage = new WriteOnly(new Memory());

await storage.write({ key: "k1", data: "v1" });
```

## Read restrictions [#reads]

`read` and `getReadable` throw `KeyNotFoundError` even when you specify a key that was written. `exists` always returns `false`. You cannot read data from this storage.

```ts
import { KeyNotFoundError, WriteOnly } from "@unikvs/writeonly";
import { Memory } from "@unikvs/memory";

const storage = new WriteOnly(new Memory());
await storage.write({ key: "k1", data: "v1" });

console.log(storage.exists({ key: "k1" })); // false

try {
  storage.read({ key: "k1" });
} catch (error) {
  if (error instanceof KeyNotFoundError) {
    console.log(error.meta.key); // "k1"
  } else {
    throw error;
  }
}
```

Each operation through `UniKvs` behaves as follows.

| Operation | Behavior |
| --- | --- |
| `set` | Writes to the inner storage, like any other storage. |
| `get` / `stream` | Skips this storage because `exists` returns `false`, and looks at the next one. If no other storage has the key, it fails with `KeyNotFoundError`. |
| `has` | Returns `false`. |
| `delete` / `clear` | Does nothing by default and applies only to other storages. |

:::warning
`has` returns `false` regardless of whether the value is stored. If you want to confirm that a write succeeded with `has`, inspect the inner storage directly.
:::

## Delete control [#delete]

`allowDelete` defaults to `false`. `delete` and `clear` succeed without changing the inner storage.

```ts
import { Memory } from "@unikvs/memory";
import { WriteOnly } from "@unikvs/writeonly";

const inner = new Memory();
const storage = new WriteOnly(inner);

await storage.write({ key: "k1", data: "v1" });
await storage.delete({ key: "k1" }); // Does nothing
console.log(inner.exists({ key: "k1" })); // true
```

Passing `allowDelete: true` delegates deletion to the inner storage.

```ts
const storage = new WriteOnly(inner, { allowDelete: true });

await storage.delete({ key: "k1" });
await storage.clear();
```

## Streams [#streams]

The writable stream is exposed only when the inner storage has `getWritable`. Otherwise, `getWritable` is undefined.

```ts
import { NodeFs } from "@unikvs/fs.node";
import { WriteOnly } from "@unikvs/writeonly";

const storage = new WriteOnly(new NodeFs(".archive"));

if (typeof storage.getWritable === "function") {
  const writable = await storage.getWritable({ key: "s1" });
  const writer = writable.getWriter();
  await writer.write(new Uint8Array([1, 2, 3]));
  await writer.close();
}
```

You cannot obtain a readable stream. `getReadable` also throws `KeyNotFoundError`.

## Notes [#notes]

- The contents cannot be inspected through the read paths. To recover data, operate the inner storage directly.
- If you register only a write-only storage, `get`, `stream`, and `has` cannot find stored data. If you need reads, combine it with a readable and writable storage.
- Writes run in parallel against all registered storages, so the position of a write-only storage does not affect write results. Reads search in registration order, so register read storages first.
- Disabling `delete` and `clear` does not prevent deletion from other storages. To prevent modification, apply the same restriction to the other storages or run without delete operations.
- If you wrap a storage that does not support stream writes, passing a `ReadableStream` to `UniKvs.set` fails only for this storage. Use plain values or wrap a storage that supports streams.

## Errors [#errors]

| Error | When thrown |
| --- | --- |
| `KeyNotFoundError` | Thrown whenever `read` or `getReadable` is called. A shared error from `@unikvs/core`. |

## Examples [#examples]

This example combines a readable file storage with an immutable archive.

```ts
import { NodeFs } from "@unikvs/fs.node";
import { UniKvs, type Value } from "unikvs";
import { WriteOnly } from "@unikvs/writeonly";

const kvs = UniKvs.config<{
  report: Value<Uint8Array<ArrayBuffer>>;
}>()
  .appendStorage(new NodeFs(".data"))
  .appendStorage(new WriteOnly(new NodeFs(".archive")))
  .create();

await kvs.open();

await kvs.set("report", new Uint8Array([1, 2, 3]));

// Reads from NodeFs(".data"). The archive is not used for reads.
const report = await kvs.get("report");

// Deletes only the data in NodeFs(".data"). Deletion is not delegated to the archive.
await kvs.delete("report");

await kvs.close();
```

For the archive, use persistent storage such as `@unikvs/s3.node`. It also works for off-site backups.
