OrbitDB Documentation

repository·main·Indexed 27 days ago

https://github.com/orbitdb/orbitdb

A serverless, distributed, peer-to-peer database built on IPFS for storage and Libp2p Pubsub for synchronization. OrbitDB uses Merkle-CRDTs to provide conflict-free replication across various database models, including events, documents, and key-value stores. The @orbitdb/core package (v4.0.0) integrates with Helia and Libp2p to enable decentralized data management with support for both immutable (IPFSAccessController) and mutable (OrbitDBAccessController) access controls.

Tokens
13.8K
Snippets
36
Records
62
Agent score
94%

What's inside OrbitDB

  1. Use SimpleEncryption for password-based encryption

    main

    OrbitDB can use the @orbitdb/simple-encryption module for password-based encryption.

    WARNING: SimpleEncryption is an unaudited encryption module. Use at your own risk.

    1. Install the module: npm i @orbitdb/simple-encryption
    2. Initialize it with a password and pass it to orbitdb.open() via the encryption object.
    import { SimpleEncryption } from '@orbitdb/simple-encryption'
    
    const replication = await SimpleEncryption({ password: 'hello' })
    const data = await SimpleEncryption({ password: 'world' })
    
    const encryption = { data, replication }
    
    const db = await orbitdb.open(dbNameOrAddress, { encryption })
  2. Connect a Browser to a Node Daemon via WebSockets

    main

    Browsers cannot dial raw TCP or QUIC connections. To connect a browser to a Node.js daemon, the server must expose a WebSocket.

    Server Setup: Listen for incoming WebSocket connections using @libp2p/websockets and include circuitRelayServer in the services.

    Browser Setup: Dial the server's WebSocket address. Use webRTC and circuitRelayTransport to facilitate connectivity.

    Note: If you encounter connection issues with local WebSockets (ws instead of wss), ensure you pass the all filter to the webSockets function (use with caution in production).

    // Server-side configuration
    const options = {
      addresses: {
        listen: ['/ip4/0.0.0.0/tcp/12345/ws']
      },
      transports: [
        webSockets({
          filter: filters.all
        })
      ],
      connectionEncrypters: [noise()],
      streamMuxers: [yamux()],
      services: {
        identify: identify(),
        relay: circuitRelayServer()
      }
    }
    
    // Browser-side configuration
    const options = {
      addresses: {
        listen: ['/webrtc']
      },
      transports: [
        webSockets({
          filter: all
        }),
        webRTC(),
        circuitRelayTransport({
          discoverRelays: 1
        })
      ],
      connectionEncrypters: [noise()],
      streamMuxers: [yamux()],
      connectionGater: {
        denyDialMultiaddr: () => {
          return false
        }
      },
      services: {
        identify: identify()
      }
    }
  3. Manage mutable write access with OrbitDBAccessController

    main

    The OrbitDBAccessController is a mutable access controller that uses OrbitDB's keyvalue database to store permissions. Unlike the IPFS version, you can grant and revoke access without changing the database address.

    Use db.access.grant(capability, identityId) to add permissions and db.access.revoke(capability, identityId) to remove them. While 'write' is the standard capability, you can define custom capabilities.

    import { createOrbitDB, Identities, OrbitDBAccessController } from '@orbitdb/core'
    
    // ... setup ipfs and orbitdb ...
    
    const identities = await Identities({ ipfs })
    const anotherIdentity = identities.createIdentity('userB')
    
    const db = orbitdb.open('my-db', { 
      AccessController: OrbitDBAccessController({ 
        write: [orbitdb.identity.id, anotherIdentity.id] 
      }) 
    })
    
    // Grant and revoke access
    db.access.grant('write', anotherIdentity.id)
    db.access.revoke('write', anotherIdentity.id)
    
    // Custom capability example
    db.access.grant('custom-access', anotherIdentity.id)
  4. Use ComposedStorage to combine storage mechanisms

    main

    You can use ComposedStorage to balance speed and memory usage by combining two storage objects. ComposedStorage attempts to retrieve data from the first storage provided; if the data is not found, it attempts to retrieve it from the second storage.

    Important: Pass the performance-oriented storage (e.g., MemoryStorage or LRUStorage) as the first argument, and the permanent/distributed storage (e.g., IPFSBlockStorage or LevelStorage) as the second argument.

    const memoryStorage = await MemoryStorage()
    const ipfsStorage = await IPFSBlockStorage()
    
    const composedStorage = await ComposedStorage(memoryStorage, ipfsStorage)
  5. Create and open OrbitDB databases

    main

    You can create a new database or open an existing one using the orbitdb.open method. When creating a new database, you can specify a type and optional meta data. If no type is specified, it defaults to events.

    Supported default types:

    • events (default)
    • documents
    • keyvalue
    • indexedkeyvalue

    To open an existing database, pass its unique address instead of a name.

    const orbitdb = await createOrbitDB()
    
    // Create a documents database
    await orbitdb.open('my-db', { type: 'documents' })
    
    // Create a keyvalue database
    await orbitdb.open('my-db', { type: 'keyvalue' })
    
    // Create a database with metadata
    const meta = { description: 'A database with metadata.' }
    await orbitdb.open('my-db', { meta })
    
    // Open an existing database by its address
    const db = await orbitdb.open('my-db')
    const dbReopened = await orbitdb.open(db.address)
  6. Implement a Custom Access Controller

    main

    You can implement a custom access controller by defining a function that returns an async function with the signature async ({ orbitdb, identities, address }).

    Your implementation must include:

    1. A type constant.
    2. A canAppend function that accepts an entry and returns a boolean indicating if the entry can be appended to the log.

    To use the custom controller, you must first register it with OrbitDB using useAccessController(CustomAccessController) before calling orbitdb.open().

    import { createOrbitDB, useAccessController } from '@orbitdb/core'
    
    const type = 'custom'
    
    const CustomAccessController = ({ write }) => async ({ orbitdb, identities, address }) => {
      address = '/custom/access-controller'
    
      const canAppend = async (entry) => {
        const writerIdentity = await identities.getIdentity(entry.identity)
        if (!writerIdentity) return false
    
        const { id } = writerIdentity
        if (write.includes(id) || write.includes('*')) {
          return identities.verifyIdentity(writerIdentity)
        }
        return false
      }
    
      return { canAppend }
    }
    
    CustomAccessController.type = type
    
    // Registration and Usage
    useAccessController(CustomAccessController)
    const orbitdb = await createOrbitDB({ ipfs })
    const db = await orbitdb.open('my-db', { AccessController: CustomAccessController({ write: ['*'] }) })
  7. Configure permanent block storage for Helia

    main

    By default, Helia uses memory block storage, which is destroyed when the application ends. To persist data, you must configure Helia with a permanent block storage solution like blockstore-level.

    1. Install the storage package:
    npm i blockstore-level
    1. Instantiate and pass it to Helia during creation:
    import { LevelBlockstore } from 'blockstore-level'
    
    const blockstore = new LevelBlockstore('./ipfs/blocks')
    const ipfs = createHelia({ blockstore })
  8. Configure encryption layers in OrbitDB

    main

    OrbitDB supports two layers of encryption:

    1. Payload encryption: Encrypts only the value (data) being stored. This allows peers to replicate the database without being able to read the actual data.
    2. Log entry encryption: Encrypts the entire log entry. This is configured via the replication property.

    You can choose to encrypt only the data, only the log entries, or both by passing an encryption object to the orbitdb.open() method.

  9. Manage and customize the KeyStore

    main

    The KeyStore is a local manager used to store private keys generated by Identities.createIdentity. You can customize its location in several ways:

    1. Via OrbitDB: Pass a directory option to createOrbitDB. Note that this also changes the base path for the database.
    2. Via KeyStore function: Pass a path option directly to the KeyStore function.
    3. Via Identities function: Pass a path option directly to the Identities function.

    You can also provide an existing keystore instance to the Identities constructor.

  10. Create an identity with Identities

    main

    An identity in OrbitDB is a cryptographically signed public key used to verify write access and sign database updates. You can create an identity using the createIdentity method from the Identities class.

    When using the PublicKeyIdentityProvider, you must provide an id (an arbitrary string like 'userA') which serves as a reference for the root key pair in the keystore. Once created, the identities instance and the id can be passed to createOrbitDB to control access to database actions.

    import { Identities } from '@orbitdb/core'
    
    const id = 'userA'
    const identities = await Identities() 
    const identity = identities.createIdentity({ id })
    
    // Use the identity with OrbitDB
    const orbitdb = await createOrbitDB({ identities, id: 'userA' })
  11. Implement a custom OrbitDB database type

    main

    You can extend OrbitDB with custom data models by implementing the OrbitDB database interface and registering the type using useDatabaseType.

    A custom database implementation should extend the base Database object and implement methods like put, del, get, and iterator.

    import { createOrbitDB, useDatabaseType } from '@orbitdb/core'
    import CustomDB from './custom-db.js'
    
    // Register the custom type
    useDatabaseType(CustomDB)
    
    const orbitdb = await createOrbitDB()
    // Open using the custom type name defined in your implementation
    await orbitdb.open('my-custom-db', { type: 'customdb' })