@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. readandgetReadablealways throwKeyNotFoundError, andexistsalways returnsfalse.deleteandcleardo nothing by default. TheallowDeleteoption enables them.- Supported environments depend on the wrapped storage. You can wrap any
IStorageimplementation, such as@unikvs/memoryor@unikvs/fs.node.
Install it with the following command.
npm install @unikvs/writeonlypnpm add @unikvs/writeonlyyarn add @unikvs/writeonlybun add @unikvs/writeonlynub add @unikvs/writeonlyaube add @unikvs/writeonlyUsage
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, andhascannot 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
deleteandcleardoes 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
ReadableStreamtoUniKvs.setfails 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.