8.3 KiB
Databases
OrbitDB is a multi-model database which means various different types of data models can be used and custom data models can be created.
Types
By default OrbitDB provides four types of databases:
- Events
- Documents
- Key/Value
- Indexed Key/Value
The type of database can be specified when calling OrbitDB's open
function by using the type
parameter:
const type = 'documents'
orbitdb.open('my-db', { type })
If no type is specified, Events will the default database type. The type of a database, when created, is stored in the database's manifest. When opening a database, OrbitDB will read the type from the manifest and return the correct database type automatically.
Address
When a database is created, it is assigned an address by OrbitDB. The address consists of three parts:
/orbitdb/zdpuAmrcSRUhkQcnRQ6p4bphs7DJWGBkqczSGFYynX6moTcDL
The first part, /orbitdb
, specifies the protocol in use.
The second part, an IPFS multihash zdpuAmrcSRUhkQcnRQ6p4bphs7DJWGBkqczSGFYynX6moTcDL
, is the database manifest which contains the database info such as the name and type, and a pointer to the access controller.
In order to replicate the database with peers, the address is what you need to give to other peers in order for them to start replicating the database.
import { createLibp2p } from 'libp2p'
import { createHelia } from 'helia'
import { createOrbitDB } from '@orbitdb/core'
import { Libp2pOptions } from './config/libp2p.js'
const libp2p = await createLibp2p(Libp2pOptions)
const ipfs = await createHelia({ libp2p })
const orbitdb = await createOrbitDB({ ipfs })
const db = await orbitdb.open('my-db')
console.log(db.address)
// /orbitdb/zdpuAmrcSRUhkQcnRQ6p4bphs7DJWGBkqczSGFYynX6moTcDL
Manifest
The second part of the address, the IPFS multihash zdpuAmrcSRUhkQcnRQ6p4bphs7DJWGBkqczSGFYynX6moTcDL
, is also the hash of the database's manifest. The manifest contains information about the database such as name, type and other metadata. It also contains a reference to the access controller, which is made up of the type and the hash of the access controller object.
An example of a manifest is given below:
{
name: 'my-db',
type: 'events',
accessController: '/ipfs/zdpuB1TUuF5E81MFChDbRsZZ1A3Kz2piLJwKQ2ddnfZLEBx64'
}
The manifest is an IPLD data structure which can be retrived from IPFS using the manifest's hash:
import { createLibp2p } from 'libp2p'
import { createHelia } from 'helia'
import * as Block from 'multiformats/block'
import { createOrbitDB, OrbitDBAddress } from '@orbitdb/core'
import * as dagCbor from '@ipld/dag-cbor'
import { sha256 } from 'multiformats/hashes/sha2'
import { base58btc } from 'multiformats/bases/base58'
import { CID } from 'multiformats/cid'
import { Libp2pOptions } from './config/libp2p.js'
const libp2p = await createLibp2p(Libp2pOptions)
const ipfs = await createHelia({ libp2p })
// Create the db then close.
const orbitdb = await createOrbitDB({ ipfs })
const db = await orbitdb.open('my-db')
await db.close()
// Get the db address.
const addr = OrbitDBAddress(db.address)
const cid = CID.parse(addr.path, base58btc)
// Extract the hash from the full db path.
const bytes = await ipfs.block.get(cid)
// Defines how we serialize/hash the data.
const codec = dagCbor
const hasher = sha256
// Retrieve the block data, decoding it to human-readable JSON text.
const { value } = await Block.decode({ bytes, codec, hasher })
console.log('manifest', value)
Creating a new database
Creating a default event store:
const orbitdb = await createOrbitDB()
await orbitdb.open('my-db')
Creating a documents database:
const orbitdb = await createOrbitDB()
await orbitdb.open('my-db', { type: 'documents' })
Creating a keyvalue database:
const orbitdb = await createOrbitDB()
await orbitdb.open('my-db', { type: 'keyvalue' })
Creating a database and adding meta
const meta = { description: 'A database with metadata.' }
const orbitdb = await createOrbitDB()
await orbitdb.open('my-db', { meta })
Opening an existing database
const orbitdb = await createOrbitDB()
const db = await orbitdb.open('my-db')
await db.close()
const dbReopened = await orbitdb.open(db.address)
Interacting with a database
Adding/Putting items in a database
Database types such as documents and keyvalue expose the put
function which is used to add items as a key/value combination to the database.
const orbitdb = await createOrbitDB()
const db = await orbitdb.open('my-db', { type: 'keyvalue' })
const hash = await db.put('key', 'value')
Alternatively, append-only database types such as events expose the add
function which adds a value to the database:
const orbitdb = await createOrbitDB()
const db = await orbitdb.open('my-db')
const hash = await db.add('event')
Removing/Deleting items from a database
To delete an item from a database, use the del
function:
const orbitdb = await createOrbitDB()
const db = await orbitdb.open('my-db', { type: 'keyvalue' })
const hash = await db.put('key', 'value')
await db.del(hash)
Replicating a database across peers
The power of OrbitDB lies in its ability to replicate databases across distributed systems that may not always be connected.
A simple way to replicate a database between peers can be accomplished by connecting one peer to another and then opening the database by its address:
import { createLibp2p } from 'libp2p'
import { createHelia } from 'helia'
import { createOrbitDB } from '@orbitdb/core'
import { Libp2pOptions } from './config/libp2p.js'
const initIPFSInstance = () => {
const libp2p = await createLibp2p(Libp2pOptions)
return createHelia({ libp2p })
}
const ipfs1 = await initIPFSInstance()
const ipfs2 = await initIPFSInstance()
orbitdb1 = await createOrbitDB({ ipfs: ipfs1, id: 'user1', directory: './orbitdb1' })
orbitdb2 = await createOrbitDB({ ipfs: ipfs2, id: 'user2', directory: './orbitdb2' })
const db1 = await orbitdb1.open('my-db')
await db1.add('hello world')
// Opening a db by address will start the synchronization process but only the
// database heads will be synchronized.
const db2 = await orbitdb2.open(db1.address)
for await (const record of db2.iterator()) {
console.log(record)
}
To learn more, check out OrbitDB's sychronization protocol and the OrbitDB replication documentation.
Custom databases
OrbitDB can be extended to use custom data models and database types. To implement a custom database, ensure the Database object is extended and that the OrbitDB database interface is implement. The database will also require a unique type.
const type = 'customdb'
const CustomDB = async ({ OpLog, Database, ipfs, identity, address, name, access, directory, storage, meta, syncAutomatically }) => {
const database = await Database({ OpLog, ipfs, identity, address, name, access, directory, storage, meta, syncAutomatically })
const { addOperation, log } = database
/**
* Puts an item to the underlying database. You will probably want to call
* Database's addOperation here with an op code 'PUT'.
*/
const put = async (doc) => {
}
/**
* Deletes an item from the underlying database. You will probably want to
* call Database's addOperation here with an op code 'DEL'.
*/
const del = async (key) => {
}
/**
* Gets an item from the underlying database. Use a hash or key to retrieve
* the value.
*/
const get = async (key) => {
}
/**
* Iterates over the data set.
*/
const iterator = async function * ({ amount } = {}) {
}
return {
...database,
type,
put,
del,
get,
iterator
}
}
CustomDB.type = type
export default CustomDB
Documents, Events and KeyValue provide good examples of how a database is implemented in OrbitDB and how to add the logic for returning records from the database (the state of the database).
To use a custom database, add it to the list of supported database types:
import { createOrbitDB, useDatabaseType } from '@orbitdb/core'
import CustomDB from './custom-db.js'
useDatabaseType(CustomDB)
const orbitdb = await createOrbitDB()
await orbitdb.open('my-custom-db', { type: 'customdb' })