UNPKG

diffable-objects

Version:

A package for dynamic state tracking for Cloudflare's Durable Objects using SQLite

103 lines (101 loc) 3.66 kB
import { replay } from "./tracker.js"; const INITIAL_QUERY = ` CREATE TABLE IF NOT EXISTS changes ( id INTEGER PRIMARY KEY AUTOINCREMENT, state TEXT NOT NULL, type TEXT NOT NULL, key TEXT NOT NULL, path TEXT NOT NULL, valueType TEXT, value TEXT, oldValue TEXT ); CREATE TABLE IF NOT EXISTS snapshots ( id INTEGER PRIMARY KEY AUTOINCREMENT, state TEXT NOT NULL, value TEXT NOT NULL, changes_id INTEGER NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `; export class SqliteState { #name; #storage; #db; constructor(name, storage) { this.#name = name; this.#storage = storage; this.#db = storage.sql; this.#db.exec(INITIAL_QUERY); } resume(initialValue) { const results = this.#db.exec("SELECT value, changes_id FROM snapshots WHERE state = ? ORDER BY created_at DESC LIMIT 1", this.#name); const result = results.next(); const mapChanges = (changes) => changes.map((change) => ({ type: "change", atomicChange: { type: change.type, key: change.key, path: change.path, valueType: change.valueType, value: change.value ? JSON.parse(change.value) : undefined, oldValue: change.oldValue ? JSON.parse(change.oldValue) : undefined, }, })); if (!result.done) { const { value, changes_id } = result.value; const changes = this.#db .exec("SELECT * FROM changes WHERE id > ? AND state = ?", changes_id, this.#name) .toArray(); const actions = [ { type: "snapshot", value: JSON.parse(value), }, ...mapChanges(changes), ]; return replay(actions, initialValue); } const changes = this.#db .exec("SELECT * FROM changes WHERE state = ?", this.#name) .toArray(); return replay(mapChanges(changes), initialValue); } appendChanges(changes) { if (changes.length === 0) { return; } this.#storage.transactionSync(() => { for (const { type, key, path, valueType, value, oldValue } of changes) { this.#db.exec("INSERT INTO changes (state, type, key, path, valueType, value, oldValue) VALUES (?, ?, ?, ?, ?, ?, ?)", this.#name, type, key, path, valueType, value ? JSON.stringify(value) : null, oldValue ? JSON.stringify(oldValue) : null); } }); } latestChange() { const result = this.#db .exec("SELECT id FROM changes WHERE state = ? ORDER BY id DESC LIMIT 1", this.#name) .next(); if (result.done) { return null; } return { id: result.value.id }; } latestSnapshot() { const result = this.#db .exec("SELECT created_at FROM snapshots WHERE state = ? ORDER BY created_at DESC LIMIT 1", this.#name) .next(); if (result.done) { return null; } return new Date(result.value.created_at); } snapshot(snapshot) { const { id } = this.#db .exec("SELECT id FROM changes WHERE state = ? ORDER BY id DESC LIMIT 1", this.#name) .one(); // TODO: assert there are changes in the DB before we can snapshot. this.#db.exec("INSERT INTO snapshots (state, value, changes_id) VALUES (?, ?, ?)", this.#name, JSON.stringify(snapshot), id); } }