UNPKG

json-crdt-server

Version:

JSON CRDT server and syncing local-first browser client

206 lines (205 loc) 6.18 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MemoryStore = exports.MemoryBlock = void 0; const rpc_error_1 = require("rpc-error"); const AvlMap_1 = require("sonic-forest/lib/avl/AvlMap"); const tick = new Promise((resolve) => setImmediate(resolve)); class MemoryBlock { constructor(start, data, history) { this.start = start; this.data = data; this.history = history; } } exports.MemoryBlock = MemoryBlock; class MemoryStore { constructor() { this.blocks = new Map(); } async get(id) { await tick; const block = this.blocks.get(id); if (!block) return; return { block: block.data }; } async getSnapshot(id, seq) { await tick; const block = this.blocks.get(id); if (!block) throw rpc_error_1.RpcError.notFound(); const snapshot = block.start; const history = block.history; const length = history.length; const batches = []; for (let i = 0; i < length; i++) { const batch = history[i]; const seq2 = batch.seq; if (seq2 <= snapshot.seq) continue; if (seq2 > seq) break; batches.push(batch); } return { snapshot, batches }; } async exists(id) { await tick; return this.blocks.has(id); } async seq(id) { await tick; return this.blocks.get(id)?.data.snapshot.seq; } async create(start, end, batch) { const { id } = end; await tick; if (this.blocks.has(id)) throw rpc_error_1.RpcError.conflict(); const now = end.ts; const data = { id, snapshot: end, tip: [], ts: now, uts: now }; const block = new MemoryBlock(start, data, []); this.blocks.set(id, block); if (batch) { const { cts, patches } = batch; if (!Array.isArray(patches)) throw new Error('NO_PATCHES'); const batch2 = { seq: 0, ts: end.ts, cts, patches, }; block.history.push(batch2); return { block: block.data, batch: batch2 }; } return { block: block.data }; } async push(snapshot0, batch0) { const { id, seq } = snapshot0; const { patches } = batch0; await tick; if (!Array.isArray(patches) || !patches.length) throw new Error('NO_PATCHES'); const block = this.blocks.get(id); if (!block) throw rpc_error_1.RpcError.notFound(); const blockData = block.data; const snapshot = blockData.snapshot; if (snapshot.seq + 1 !== seq) throw new Error('PATCH_SEQ_INV'); const history = block.history; const now = Date.now(); blockData.uts = now; snapshot.seq = seq; snapshot.ts = now; snapshot.blob = snapshot0.blob; const batch1 = { seq, ts: now, cts: batch0.cts, patches, }; history.push(batch1); return { snapshot, batch: batch1 }; } async compact(id, to, advance) { const block = this.blocks.get(id); if (!block) throw rpc_error_1.RpcError.notFound(); const start = block.start; const batches = block.history; const length = batches.length; let i = 0; async function* iterator() { for (; i < length; i++) { const batch = batches[i]; const seq = batch.seq; if (seq <= start.seq) continue; if (seq > to) break; yield batch; } } start.blob = await advance(start.blob, iterator()); start.ts = Date.now(); start.seq = to; batches.splice(0, i); } async scan(id, min, max) { await tick; const block = this.blocks.get(id); if (!block) return []; const history = block.history; const length = history.length; const list = []; for (let i = 0; i < length; i++) { const batch = history[i]; const seq = batch.seq; if (seq > max) break; if (seq >= min && seq <= max) list.push(batch); } return list; } async remove(id) { await tick; return this.removeSync(id); } removeSync(id) { return this.blocks.delete(id); } stats() { return { blocks: this.blocks.size, batches: [...this.blocks.values()].reduce((acc, v) => acc + v.history.length, 0), }; } async removeOlderThan(ts) { await tick; for (const [id, block] of this.blocks) if (block.data.ts < ts) this.removeSync(id); } async removeAccessedBefore(ts, limit = 10) { await tick; let cnt = 0; for (const [id, block] of this.blocks) if (block.data.uts < ts) { this.removeSync(id); cnt++; if (cnt >= limit) return; } } async removeOldest(x) { const heap = new AvlMap_1.AvlMap((a, b) => b - a); let first = heap.first(); for await (const [id, block] of this.blocks.entries()) { const time = block.data.uts; if (heap.size() < x) { heap.set(time, id); continue; } if (!first) first = heap.first(); if (first && time < first.k) { heap.del(first.k); first = undefined; heap.set(time, id); } } if (!heap.size()) return; for (const { v } of heap.entries()) { try { await this.remove(v); } catch { } } } } exports.MemoryStore = MemoryStore;