140 lines
3.6 KiB
TypeScript
140 lines
3.6 KiB
TypeScript
import Debug from "debug";
|
|
import EventEmitter from "events";
|
|
import {Delta, DeltaID} from "./delta.js";
|
|
import {DomainEntityID, TransactionID} from "./types.js";
|
|
import {Lossless} from "./lossless.js";
|
|
const debug = Debug('rz:transactions');
|
|
|
|
function getDeltaTransactionId(delta: Delta): TransactionID | undefined {
|
|
const {target: transactionId} = delta.pointers.find(({
|
|
localContext,
|
|
target,
|
|
targetContext
|
|
}) =>
|
|
localContext === "_transaction" &&
|
|
typeof target === "string" &&
|
|
targetContext === "deltas"
|
|
) || {};
|
|
|
|
if (transactionId && typeof transactionId === "string") {
|
|
return transactionId;
|
|
}
|
|
}
|
|
|
|
function getTransactionSize(delta: Delta): {
|
|
transactionId: TransactionID,
|
|
size: number
|
|
} | undefined {
|
|
const {target: transactionId} = delta.pointers.find(({
|
|
localContext,
|
|
target,
|
|
targetContext
|
|
}) =>
|
|
localContext === "_transaction" &&
|
|
typeof target === "string" &&
|
|
targetContext === "size"
|
|
) || {};
|
|
|
|
if (transactionId && typeof transactionId === "string") {
|
|
// This delta describes a transaction
|
|
const {target: size} = delta.pointers.find(({
|
|
localContext,
|
|
target
|
|
}) =>
|
|
localContext === "size" &&
|
|
typeof target === "number"
|
|
) || {};
|
|
|
|
return {transactionId, size: size as number};
|
|
}
|
|
}
|
|
|
|
export class Transaction {
|
|
size?: number;
|
|
receivedDeltaIds = new Set<DeltaID>();
|
|
entityIds = new Set<DomainEntityID>();
|
|
}
|
|
|
|
export class Transactions {
|
|
transactions = new Map<TransactionID, Transaction>();
|
|
eventStream = new EventEmitter();
|
|
|
|
constructor(readonly lossless: Lossless) {}
|
|
|
|
get(id: TransactionID): Transaction | undefined {
|
|
return this.transactions.get(id);
|
|
}
|
|
|
|
getOrInit(id: TransactionID): Transaction {
|
|
let t = this.transactions.get(id);
|
|
if (!t) {
|
|
t = new Transaction();
|
|
this.transactions.set(id, t);
|
|
}
|
|
return t;
|
|
}
|
|
|
|
ingestDelta(delta: Delta, targets: DomainEntityID[]): TransactionID | undefined {
|
|
{
|
|
const transactionId = getDeltaTransactionId(delta);
|
|
if (transactionId) {
|
|
const t = this.getOrInit(transactionId);
|
|
for (const id of targets) {
|
|
t.entityIds.add(id);
|
|
}
|
|
|
|
// This delta is part of a transaction
|
|
// Add this to the delta's data structure for quick reference
|
|
delta.transactionId = transactionId;
|
|
|
|
// Update our transaction tracking
|
|
this.receivedDelta(transactionId, delta.id);
|
|
|
|
// Notify that the transaction is complete
|
|
if (this.isComplete(transactionId)) {
|
|
this.eventStream.emit("completed", transactionId);
|
|
}
|
|
|
|
return transactionId;
|
|
}
|
|
}
|
|
|
|
{
|
|
const {transactionId, size} = getTransactionSize(delta) || {};
|
|
if (transactionId && size) {
|
|
// This delta describes a transaction
|
|
|
|
debug(`[${this.lossless.rhizomeNode.config.peerId}]`, `Transaction ${transactionId} has size ${size}`);
|
|
|
|
this.setSize(transactionId, size as number);
|
|
|
|
// Check if the transaction is complete
|
|
if (this.isComplete(transactionId)) {
|
|
this.eventStream.emit("completed", transactionId);
|
|
}
|
|
|
|
return transactionId;
|
|
}
|
|
}
|
|
}
|
|
|
|
receivedDelta(id: TransactionID, deltaId: DeltaID) {
|
|
const t = this.getOrInit(id);
|
|
t.receivedDeltaIds.add(deltaId);
|
|
}
|
|
|
|
isComplete(id: TransactionID) {
|
|
const t = this.getOrInit(id);
|
|
return t.size !== undefined && t.receivedDeltaIds.size === t.size;
|
|
}
|
|
|
|
setSize(id: TransactionID, size: number) {
|
|
const t = this.getOrInit(id);
|
|
t.size = size;
|
|
}
|
|
|
|
get ids() {
|
|
return Array.from(this.transactions.keys());
|
|
}
|
|
}
|