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

@unikvs/writeonly

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

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.

npm install @unikvs/writeonly
pnpm add @unikvs/writeonly
yarn add @unikvs/writeonly
bun add @unikvs/writeonly
nub add @unikvs/writeonly
aube add @unikvs/writeonly

Usage

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

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.

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

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.

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.

Delete control

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

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.

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

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

Streams

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

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

  • 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

KeyNotFoundError is exported from @unikvs/writeonly.

Error Error name When thrown
KeyNotFoundError WriteOnlyKeyNotFoundError Thrown whenever read or getReadable is called.

It holds key in its metadata. The Japanese message is「キー {key} が見つかりません」.

Examples

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

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.

Was this page helpful?