y-cap-sqlite
Version:
y-cap-sqlite is a persistence provider for [Yjs](https://github.com/yjs/yjs) that uses [@capacitor-community/sqlite](https://github.com/capacitor-community/sqlite) to store document changes in a SQLite database. This is useful for Capacitor projects that
99 lines (87 loc) • 3.2 kB
text/typescript
import * as Y from "yjs";
import * as promise from "lib0/promise";
import { ObservableV2 } from "lib0/observable";
import { capConnectionOptions, SQLiteConnection, SQLiteDBConnection } from "@capacitor-community/sqlite";
import { Buffer } from "buffer";
const DB_PREFIX = "cap-sqlite-persistence-";
export class CapSQLitePersistence extends ObservableV2<{
synced: (persistence: CapSQLitePersistence) => void;
}> {
doc: Y.Doc;
config: Omit<capConnectionOptions, "database">;
whenSynced: Promise<this>;
name: string;
db: SQLiteDBConnection | undefined;
con: SQLiteConnection
synced: boolean = false;
_dbref = 0;
_dbsize = 0;
_destroyed = false;
constructor(config: Omit<capConnectionOptions, "database">, doc: Y.Doc, name: string, sqlite: SQLiteConnection) {
super();
this.doc = doc;
this.config = config;
this.name = name;
this.con = sqlite
this.whenSynced = promise.create((resolve) =>
this.on("synced", () => resolve(this))
);
this.destroy = this.destroy.bind(this);
this._storeUpdate.bind(this);
doc.on("update", this._storeUpdate.bind(this));
doc.on("destroy", this.destroy.bind(this));
}
async initDb() {
try {
const dbName = DB_PREFIX + this.name
console.log((await this.con.isConnection(dbName, this.config.readonly ?? false)).result)
if ((await this.con.isConnection(dbName, this.config.readonly ?? false)).result) {
this.db = await this.con.retrieveConnection(dbName, this.config.readonly ?? false)
}
else {
this.db = await this.con.createConnection(dbName, this.config.encrypted ?? false, this.config.mode ?? "", this.config.version ?? 1, this.config.readonly ?? false)
}
await this.db.open()
await this.db.execute("CREATE TABLE IF NOT EXISTS updates (id INTEGER PRIMARY KEY, content BLOB)")
const s = Y.encodeStateAsUpdate(this.doc);
if (s.length > 0) {
this._storeUpdate(s, null);
}
const { values } = await this.db.query("SELECT content FROM updates")
if (values != undefined) {
for (const row of values) {
Y.applyUpdate(this.doc, new Uint8Array(row.content));
}
}
this.synced = true;
this.emit("synced", [this]);
console.log("initialisation finished")
}
catch (e) {
console.log("error loading update", e)
throw e;
}
}
static async create(config: Omit<capConnectionOptions, "database">, doc: Y.Doc, name: string, sqlite: SQLiteConnection) {
const c = new CapSQLitePersistence(config, doc, name, sqlite)
await c.initDb()
return c;
}
_storeUpdate(update: Uint8Array, origin: any) {
if (!this.db || origin == this) {
!this.db && console.error("trying to store update without db");
return;
}
return this.db
.run(`INSERT INTO updates (content) VALUES (?)`, [Buffer.from(update)])
.catch((e) => console.error("error storing update", e));
}
destroy() {
this.doc.off("update", this._storeUpdate);
this.doc.off("destroy", this.destroy);
this._destroyed = true;
return this.db?.close().catch((e) => {
console.log("can't close", e)
});
}
}