@orbitdb/ordered-keyvalue-db
Version:
Ordered keyvalue database type for orbit-db.
525 lines (418 loc) • 13.5 kB
text/typescript
import { type HeliaLibp2p } from "helia";
import { Identities, Identity, KeyStore, KeyStoreType } from "@orbitdb/core";
import OrderedKeyValue, {
OrderedKeyValueDatabaseType,
} from "@/ordered-keyvalue.js";
import { DBElements } from "@/types.js";
import { createTestHelia } from "./config.js";
import { isBrowser } from "wherearewe";
import { expect } from "aegir/chai";
import { sortPosition } from "./utils.js";
const keysPath = "./testkeys";
describe("OrderedKeyValue Database", () => {
let ipfs: HeliaLibp2p;
let identities;
let keystore: KeyStoreType;
let testIdentity1: Identity;
let db: OrderedKeyValueDatabaseType;
const databaseId = "ordered-keyvalue-AAA";
before(async () => {
ipfs = await createTestHelia({ directory: "./ipfsOKV" });
keystore = await KeyStore({ path: keysPath });
identities = await Identities({ keystore });
testIdentity1 = await identities.createIdentity({ id: "userA" });
});
after(async () => {
if (ipfs) {
await ipfs.stop();
}
if (keystore) {
await keystore.close();
}
if (!isBrowser) {
const { rimraf } = await import("rimraf");
await rimraf(keysPath);
await rimraf("./orbitdb");
await rimraf("./ipfsOKV");
}
});
describe("Creating an Ordered KeyValue database", () => {
beforeEach(async () => {
db = await OrderedKeyValue()({
ipfs,
identity: testIdentity1,
address: databaseId,
});
});
afterEach(async () => {
if (db) {
await db.drop();
await db.close();
}
});
it("creates an ordered-keyvalue store", async () => {
expect(db.address.toString()).to.equal(databaseId);
expect(db.type).to.equal("ordered-keyvalue");
});
it("returns 0 items when it's a fresh database", async () => {
const all = [];
for await (const item of db.iterator()) {
all.unshift(item);
}
expect(all.length).to.equal(0);
});
});
describe("Ordered-keyvalue database API", () => {
beforeEach(async () => {
db = await OrderedKeyValue()({
ipfs,
identity: testIdentity1,
address: databaseId,
});
});
afterEach(async () => {
if (db) {
await db.drop();
await db.close();
}
});
it("add a value", async () => {
const actual = await db.put("key1", "value1");
expect(actual).to.be.a.not.empty("string");
});
it("get a value", async () => {
const value = "value1";
const key = "key1";
await db.put(key, value);
const actual = await db.get(key);
expect(actual).to.deep.equal(value);
});
it("override a key", async () => {
const value = "value1";
const value2 = "value2";
const key = "key1";
await db.put(key, value);
await db.put(key, value2);
const actual = await db.get(key);
expect(actual).to.deep.equal(value2);
});
it("del a value", async () => {
const key = "key1";
const value = "value1";
await db.put(key, value);
await db.del(key);
const actual = await db.all();
expect(actual).to.be.an.empty("array");
});
it("del and then add a value", async () => {
const value = "value1";
const key = "key1";
await db.put(key, value);
await db.del(key);
const hash = await db.put(key, value);
const actual = await db.all();
expect(actual).to.deep.equal([{ value, key, hash }]);
});
it("add a value with implicit position", async () => {
const value = "value1";
const key = "key1";
const value2 = "value2";
const key2 = "key2";
const hash = await db.put(key, value);
const hash2 = await db.put(key2, value2);
const actual = await db.all();
expect(actual).to.deep.equal([
{ value: value, key, hash },
{ value: value2, key: key2, hash: hash2 },
]);
});
it("add a value: index 0", async () => {
const value = "value1";
const key = "key1";
const value2 = "value2";
const key2 = "key2";
const hash = await db.put(key, value);
const hash2 = await db.put(key2, value2, 0);
const actual = await db.all();
expect(actual).to.deep.equal([
{ value: value2, key: key2, hash: hash2 },
{ value: value, key, hash },
]);
});
it("add a value: negative index", async () => {
const value = "value1";
const key = "key1";
const value2 = "value2";
const key2 = "key2";
const hash = await db.put(key, value);
const hash2 = await db.put(key2, value2, -1);
const actual = await db.all();
expect(actual).to.deep.equal([
{ value: value, key, hash },
{ value: value2, key: key2, hash: hash2 },
]);
});
it("add a value: index > length", async () => {
const value1 = "value1";
const key1 = "key1";
const value2 = "value2";
const key2 = "key2";
const hash = await db.put(key1, value1);
const hash2 = await db.put(key2, value2, 4);
const actual = await db.all();
expect(actual).to.deep.equal([
{ value: value1, key: key1, hash },
{ value: value2, key: key2, hash: hash2 },
]);
});
it("move a value", async () => {
const value = "value1";
const key = "key1";
const value2 = "value2";
const key2 = "key2";
const hash = await db.put(key, value);
const hash2 = await db.put(key2, value2);
await db.move(key, 1);
const actual = await db.all();
expect(actual).to.deep.equal([
{ value: value2, key: key2, hash: hash2 },
{ value: value, key, hash },
]);
});
it("move a value: index 0", async () => {
const value = "value1";
const key = "key1";
const value2 = "value2";
const key2 = "key2";
const hash = await db.put(key, value);
const hash2 = await db.put(key2, value2);
await db.move(key2, 0);
const actual = await db.all();
expect(actual).to.deep.equal([
{ value: value2, key: key2, hash: hash2 },
{ value: value, key, hash },
]);
});
it("move a value: negative index", async () => {
const value = "value1";
const key = "key1";
const value2 = "value2";
const key2 = "key2";
const hash = await db.put(key, value);
const hash2 = await db.put(key2, value2);
await db.move(key, -1);
const actual = await db.all();
expect(actual).to.deep.equal([
{ value: value2, key: key2, hash: hash2 },
{ value: value, key, hash },
]);
});
it("move a value: index > length", async () => {
const value = "value1";
const key = "key1";
const value2 = "value2";
const key2 = "key2";
const hash = await db.put(key, value);
const hash2 = await db.put(key2, value2);
await db.move(key, 3);
const actual = await db.all();
expect(actual).to.deep.equal([
{ value: value2, key: key2, hash: hash2 },
{ value: value, key, hash },
]);
});
it("add a value twice, with new position", async () => {
const value = "value1";
const key = "key1";
const value2 = "value2";
const key2 = "key2";
await db.put(key, value);
const hash2 = await db.put(key2, value2);
const hash1 = await db.put(key, value, 2);
const actual = await db.all();
expect(actual).to.deep.equal([
{ value: value2, key: key2, hash: hash2 },
{ value: value, key, hash: hash1 },
]);
});
it("move and override a key concurrently", async () => {
const key1 = "key1";
const value1 = "value1";
const key2 = "key2";
const value2 = "value2";
const value2Mod = "value2a";
const key3 = "key3";
const value3 = "value3";
const hash1 = await db.put(key1, value1);
await db.put(key2, value2);
const hash3 = await db.put(key3, value3);
await db.move(key1, 1);
const hash2 = await db.put(key2, value2Mod);
const actual = await db.all();
expect(actual).to.deep.equal([
{ value: value2Mod, key: key2, hash: hash2 },
{ value: value1, key: key1, hash: hash1 },
{ value: value3, key: key3, hash: hash3 },
]);
});
it("move a value twice", async () => {
const value = "value1";
const key = "key1";
const value2 = "value2";
const key2 = "key2";
const value3 = "value3";
const key3 = "key3";
const hash = await db.put(key, value);
const hash2 = await db.put(key2, value2);
const hash3 = await db.put(key3, value3);
await db.move(key, 1);
const actual1 = await db.all();
expect(actual1).to.deep.equal([
{ value: value2, key: key2, hash: hash2 },
{ value: value, key, hash },
{ value: value3, key: key3, hash: hash3 },
]);
await db.move(key, 2);
const actual2 = await db.all();
expect(actual2).to.deep.equal([
{ value: value2, key: key2, hash: hash2 },
{ value: value3, key: key3, hash: hash3 },
{ value: value, key, hash },
]);
});
it("synchronous addition", async () => {
await Promise.all(
[...Array(2).keys()].map((i) => db.put(`key${i}`, `value${i}`)),
);
const hash = await db.put("in", "between", 1);
const actual = await db.all();
// Here we only check the middle value, because we don't know if key0 or key1 was
// added first due to the race condition we intentionally introduced above
expect(actual[1]).to.deep.equal({ value: "between", key: "in", hash });
});
it("returns all values", async () => {
const keyvalue: {
value: DBElements;
key: string;
position: number;
hash?: string;
}[] = [
{
key: "key1",
position: 0,
value: "init",
},
{
key: "key2",
position: 1,
value: true,
},
{
key: "key3",
position: 2,
value: "hello",
},
{
key: "key4",
position: 3,
value: "friend",
},
{
key: "key5",
position: 4,
value: "12345",
},
{
key: "key6",
position: 5,
value: "empty",
},
{
key: "key7",
position: 6,
value: "friend33",
},
];
for (const entry of Object.values(keyvalue)) {
entry.hash = await db.put(entry.key, entry.value, entry.position);
}
const all = [];
for await (const pair of db.iterator()) {
all.unshift(pair);
}
expect(sortPosition(all)).to.deep.equal(sortPosition(keyvalue));
});
});
describe("Iterator", () => {
before(async () => {
db = await OrderedKeyValue()({
ipfs,
identity: testIdentity1,
address: databaseId,
});
});
after(async () => {
if (db) {
await db.drop();
await db.close();
}
});
it("has an iterator function", async () => {
expect(db.iterator).to.not.be.undefined();
expect(typeof db.iterator).to.equal("function");
});
it("returns no values when the database is empty", async () => {
const all = [];
for await (const { hash, value, key } of db.iterator()) {
all.unshift({ hash, value, key });
}
expect(all.length).to.equal(0);
});
it("returns all values when the database is not empty", async () => {
await db.put("key1", "value1");
await db.put("key2", "value2");
await db.put("key3", "value3");
await db.put("key4", "value4");
await db.put("key5", "value5");
// Add one more value and then delete it to count
// for the fact that the amount returned should be
// the amount of actual values returned and not
// the oplog length, and deleted values don't
// count towards the returned amount.
await db.put("key6", "value6");
await db.del("key6");
// Also check that moving values does not count towards
// the returned amount.
await db.move("key1", -1);
const all = [];
for await (const { hash, value } of db.iterator()) {
all.unshift({ hash, value });
}
expect(all.length).to.equal(5);
});
it("returns only the amount of values given as a parameter", async () => {
const amount = 3;
const all = [];
for await (const { hash, value } of db.iterator({ amount })) {
all.unshift({ hash, value });
}
expect(all.length).to.equal(amount);
});
it("returns only two values if amount given as a parameter is 2", async () => {
const amount = 2;
const all = [];
for await (const { hash, value } of db.iterator({ amount })) {
all.unshift({ hash, value });
}
expect(all.length).to.equal(amount);
});
it("returns only one value if amount given as a parameter is 1", async () => {
const amount = 1;
const all = [];
for await (const { hash, value } of db.iterator({ amount })) {
all.unshift({ hash, value });
}
expect(all.length).to.equal(amount);
});
});
});