@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.
nameisSoftDelete.isOpenfollows 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, andgetReadabledo 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-deletepnpm add @unikvs/soft-deleteyarn add @unikvs/soft-deletebun add @unikvs/soft-deletenub add @unikvs/soft-deleteaube add @unikvs/soft-deleteUsage
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.
storageIStorage
Delegated storage holding the tombstone data.
IStorageretentionMs?number
How long (milliseconds) to keep tombstones. Kept indefinitely when omitted.
numberThe 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)returnstruewhen it removes a tombstone, andfalsewhen this call removes no tombstone.falsecovers 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;
}
}