[]
        
(Showing Draft Content)

Database Adapter

The database adapter (IDatabaseAdapter) is a key point of the server-side functionality, responsible for the persistent storage of document snapshots and operations.

The database adapter defines the interaction with the underlying storage. Operations (op) are continuously stored to record history, while snapshots only retain the latest version, updated by accumulated operations.

This document explains how to use and implement a database adapter and integrate it into DocumentServices.

  • Store and read document snapshots and operations

  • Support historical data queries and submissions

Applicable Scenarios: Persistent storage in production environments, custom storage solutions

Built-in Adapters: MemoryDb (in-memory storage), Postgres Adapter, and SQLite3 Adapter

Interface: IDatabaseAdapter

/**
 * Defines the interface for a database adapter in operational transformation.
 * @template S - The type of snapshot data.
 * @template T - The type of operation data.
 * @public
 */
export interface IDatabaseAdapter<S = unknown, T = unknown> {
    /**
     * Retrieves operations between two versions (inclusive of 'from', exclusive of 'to').
     * @param {string} id - The document ID.
     * @param {number} from - The starting version.
     * @param {number} [to] - The ending version (optional).
     * @param {unknown} [options] - Additional options (optional).
     * @returns {Promise<IOp<T>[]>} A promise resolving to an array of operations.
     */
    getOps(id: string, from: number, to?: number, options?: unknown): Promise<IOp<T>[]>;
    /**
     * Retrieves document information by ID.
     * @param {string} id - The document ID.
     * @param {unknown} [options] - Additional options (optional).
     * @returns {Promise<IDocument | null>} A promise resolving to the document info or null if not found.
     */
    getDocument(id: string, options?: unknown): Promise<IDocument | null>;

    /**
     * Retrieves the snapshot of a document by ID.
     * @param {string} id - The document ID.
     * @param {unknown} [options] - Additional options (optional).
     * @returns {Promise<ISnapshot<S> | null>} A promise resolving to the snapshot or null if not found.
     */
    getSnapshot(id: string, options?: unknown): Promise<ISnapshot<S> | null>;
    /**
     * Retrieves a specific fragment of a document by ID.
     * @param {string} id - The document ID.
     * @param {string} fragmentId - The ID of the fragment.
     * @param {unknown} [options] - Additional options (optional).
     * @returns {Promise<{ version: number, data: S | null } | null>} Resolves to an object with fragment version and data, or null if the document does not exist. If the document exists but the fragment does not, data is null.
     */
    getFragment(id: string, fragmentId: string, options?: unknown): Promise<{ version: number, data: S | null } | null>;

    /**
     * Commits an operation to the database.
     * @param {string} id - The document ID.
     * @param {IOp<T>} op - The operation to commit.
     * @param {IDocument} document - The document metadata.
     * @param {unknown} [options] - Additional options (optional).
     * @returns {Promise<boolean>} A promise resolving to true if the operation was committed, false otherwise.
     */
    commitOp(id: string, op: IOp<T>, document: IDocument, options?: unknown): Promise<boolean>;
    /**
     * Commits a snapshot to the database.
     * @param {string} id - The document ID.
     * @param {ICommitSnapshot<S>} snapshot - The snapshot to commit.
     * @param {unknown} [options] - Additional options (optional).
     * @returns {Promise<boolean>} A promise resolving to true if the snapshot was committed, false otherwise.
     */
    commitSnapshot(id: string, snapshot: ICommitSnapshot<S>, options?: unknown): Promise<boolean>;

    /**
     * Retrieves the committed version of an operation, if it exists.
     * @param {string} id - The document ID.
     * @param {number} to - The ending version to check up to.
     * @param {IOp<T>} op - The operation to verify.
     * @returns {Promise<number | null>} A promise resolving to the committed version or null if not committed.
     */
    getCommittedOpVersion(id: string, to: number, op: IOp): Promise<number | null>;

    /**
     * Closes the database connection.
     * @returns {Promise<void>} A promise that resolves when the database is closed.
     */
    close(): Promise<void>;
}

Integration into DocumentServices

Pass the database adapter into the configuration of DocumentServices:

import { DocumentServices, IDatabaseAdapter, MemoryDb } from '@mescius/js-collaboration-ot';

const dbAdapter: IDatabaseAdapter = new MemoryDb();
const docService = new DocumentServices({ db: dbAdapter });

Built-in Adapters

MemoryDb

  • Description: An in-memory adapter, with data stored in RAM.

  • Use Case: Development and testing.

  • Limitations: Data is lost after the server restarts, not suitable for production environments.

  • Notes: When creating DocumentServices, MemoryDb is the default data base adapter.

import { MemoryDb } from '@mescius/js-collaboration-ot';

const dbAdapter = new MemoryDb();

Postgres Database Adapter

  • Description: A persistent adapter based on PostgreSQL.

  • Use Case: Production environments, supporting persistent storage.

  • Schema

import pg from 'pg';
import { PostgresDb } from '@mescius/js-collaboration-ot-postgres';

const config = {
    host: 'localhost',
    database: 'your_database',
    user: 'your_user_name',
    password: 'your_password',
    port: 5432, // default port
};
const dbInstance = new pg.Pool(config);
const dbAdapter = new PostgresDb(dbInstance);

Sqlite3 Database Adapter

  • Description: A persistent adapter based on SQLite3.

  • Use Case: Development and testing environments, supporting persistent storage.

  • Schema

import sqlite3 from 'sqlite3';
import { SqliteDb } from '@mescius/js-collaboration-ot-sqlite';

const dbInstance = new sqlite3.Database("./sample.db");
const dbAdapter = new SqliteDb(dbInstance);

Implementing a Custom Database Adapter

If the built-in adapters do not meet your needs, you can create a custom database adapter by implementing the IDatabaseAdapter interface. Please refer to Custom Database Adapter.