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

@unikvs/soft-delete

How to use @unikvs/soft-delete, which keeps deleted data as restorable tombstones.

Overview

@unikvs/soft-delete is a wrapper that keeps deleted data as restorable tombstones.

This wrapper turns delete into a tombstone write to the same key instead of a physical delete, so restore can bring the value back. Tombstones and the registry live in the delegated storage, so they are replicated along with it.

  • name is SoftDelete.
  • isOpen follows the delegated storage.
  • With retentionMs, tombstones past their retention period are swept.
  • Stream support and chunk types follow the delegated storage.
  • Data mutations are serialized per key, so operations on different keys can proceed in parallel. This covers write, delete, restore, purge, listTombstones, the sweep performed when an expired tombstone is encountered, and the registry cleanup on stream close. read, exists, and getReadable do not take the per-key lock unless they sweep an expired tombstone. Registry updates are serialized across keys within a single instance. If multiple instances share the same delegated storage, any operation that can update the registry can race, so keep these operations on one instance.

Install with:

npm install @unikvs/soft-delete
pnpm add @unikvs/soft-delete
yarn add @unikvs/soft-delete
bun add @unikvs/soft-delete
nub add @unikvs/soft-delete
aube add @unikvs/soft-delete

Usage

SoftDelete implements IStorage. Pass it to UniKvs with persist().

import { SoftDelete } from "@unikvs/soft-delete";

SoftDelete

Wrapper that keeps deleted data as restorable tombstones.

Constructor arguments. Pass the delegated storage as the first argument.

PropType
storageIStorage

Delegated storage holding the tombstone data.

TypeIStorage
retentionMs?number

How long (milliseconds) to keep tombstones. Kept indefinitely when omitted.

Typenumber

The following example deletes a value and restores it.

import { Memory } from "@unikvs/memory";
import { SoftDelete } from "@unikvs/soft-delete";
import * as unikvs from "unikvs";

const softDelete = new SoftDelete(new Memory());

const kvs = new unikvs.Builder<{
  foo: unikvs.Value<Uint8Array>;
}>()
  .persist(softDelete)
  .build();

await kvs.open();
await kvs.set("foo", new Uint8Array([0, 1, 2]));
await kvs.delete("foo"); // remains as a tombstone
console.log(await kvs.has("foo")); // false

await softDelete.restore("foo");
console.log(await kvs.has("foo")); // true

await kvs.close();

It can also be used as middleware. When passed to use(), this middleware treats tombstones leaking into the read path as missing keys.

The following example uses it as middleware.

import { Memory } from "@unikvs/memory";
import { SoftDelete } from "@unikvs/soft-delete";
import * as unikvs from "unikvs";

const inner = new Memory();

const kvs = new unikvs.Builder<{
  foo: unikvs.Value<Uint8Array>;
}>()
  .use(new SoftDelete(inner))
  .persist(inner)
  .build();

Data types

Operation Supported data type
write/read Arbitrary values (strings, objects, etc.)
getWritable/getReadable chunks Follows the delegated storage
  • Stores arbitrary values. Tombstones are stored under the same key as internal records.
  • Tombstones and the registry live in the delegated storage. The reserved key __unikvs_soft_delete_registry__ is used for the registry.
  • Stream support and chunk types follow the delegated storage. Acquisition functions stay undefined when unsupported.
  • As middleware it is transparent. When a tombstone leaks into the read path, this middleware throws KeyNotFoundError.
  • purge(key) returns true when it removes a tombstone, and false when this call removes no tombstone. false covers not only an absent tombstone, but also a tombstone that was restored or replaced by a newer tombstone before removal. purge() with no key targets a snapshot of the registry and returns the number actually removed, even when other operations run concurrently.

Errors

This package throws the following errors.

TombstoneNotFoundError

Meaning

restore throws it when the target has no tombstone, or when the tombstone is past its retention period.

purge(key) does not throw this error when there is no tombstone. It returns false when this call removes no tombstone. A tombstone past its retention period is still purged by purge(key), which returns true when it removes it.

How to fix

Check the tombstone list with listTombstones before operating. A false from purge(key) means this call removed no tombstone; it does not necessarily mean no tombstone exists. This is distinct from being unable to restore.

SoftDeleteInvalidRetentionError

Meaning

Thrown at construction when retentionMs is not a non-negative finite number.

How to fix

Specify a non-negative finite number (milliseconds) for retentionMs, or omit it.

KeyNotFoundError

Meaning

Thrown when reading a key that has only a tombstone or does not exist. A shared error from @unikvs/core.

How to fix

Check with exists before reading. Use restore when you need a deleted value.

Example

The following example catches KeyNotFoundError.

import { KeyNotFoundError } from "@unikvs/soft-delete";

try {
  const data = await kvs.get("missing-key");
} catch (ex) {
  if (ex instanceof KeyNotFoundError) {
    console.log("Key not found");
  } else {
    throw ex;
  }
}

Was this page helpful?