json-crdt-server
Version:
JSON CRDT server and syncing local-first browser client
206 lines (205 loc) • 6.18 kB
JavaScript
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;
;