recoder-code
Version:
Complete AI-powered development platform with ML model training, plugin registry, real-time collaboration, monitoring, infrastructure automation, and enterprise deployment capabilities
410 lines (338 loc) • 10.4 kB
text/typescript
/**
* Operational Transform Implementation for Real-time Collaboration
* Based on proven OT algorithms for conflict-free collaborative editing
*/
export enum OperationType {
Retain = 'retain',
Insert = 'insert',
Delete = 'delete'
}
export interface BaseOperation {
type: OperationType;
author: string;
timestamp: number;
id: string;
}
export interface RetainOperation extends BaseOperation {
type: OperationType.Retain;
length: number;
attributes?: Record<string, any>;
}
export interface InsertOperation extends BaseOperation {
type: OperationType.Insert;
text: string;
attributes?: Record<string, any>;
}
export interface DeleteOperation extends BaseOperation {
type: OperationType.Delete;
length: number;
}
export type Operation = RetainOperation | InsertOperation | DeleteOperation;
export interface OperationMeta {
author: string;
timestamp: number;
sessionId: string;
documentId: string;
clientId: string;
revision: number;
}
/**
* Represents a single operation component
*/
export class Op {
public readonly type: OperationType;
public readonly length?: number;
public readonly text?: string;
public readonly attributes?: Record<string, any>;
constructor(op: Partial<Operation>) {
this.type = op.type!;
this.length = (op as any).length;
this.text = (op as any).text;
this.attributes = (op as any).attributes;
}
static retain(length: number, attributes?: Record<string, any>): Op {
return new Op({ type: OperationType.Retain, length, attributes });
}
static insert(text: string, attributes?: Record<string, any>): Op {
return new Op({ type: OperationType.Insert, text, attributes });
}
static delete(length: number): Op {
return new Op({ type: OperationType.Delete, length });
}
isRetain(): this is Op & { length: number } {
return this.type === OperationType.Retain;
}
isInsert(): this is Op & { text: string } {
return this.type === OperationType.Insert;
}
isDelete(): this is Op & { length: number } {
return this.type === OperationType.Delete;
}
getLength(): number {
if (this.isRetain() || this.isDelete()) {
return this.length;
}
if (this.isInsert()) {
return this.text.length;
}
return 0;
}
equals(other: Op): boolean {
if (this.type !== other.type) return false;
switch (this.type) {
case OperationType.Retain:
return this.length === other.length &&
JSON.stringify(this.attributes) === JSON.stringify(other.attributes);
case OperationType.Insert:
return this.text === other.text &&
JSON.stringify(this.attributes) === JSON.stringify(other.attributes);
case OperationType.Delete:
return this.length === other.length;
default:
return false;
}
}
canMergeWith(other: Op): boolean {
if (this.type !== other.type) return false;
switch (this.type) {
case OperationType.Retain:
case OperationType.Delete:
return JSON.stringify(this.attributes) === JSON.stringify(other.attributes);
case OperationType.Insert:
return JSON.stringify(this.attributes) === JSON.stringify(other.attributes);
default:
return false;
}
}
merge(other: Op): Op {
if (!this.canMergeWith(other)) {
throw new Error('Cannot merge incompatible operations');
}
switch (this.type) {
case OperationType.Retain:
return Op.retain(this.length! + other.length!, this.attributes);
case OperationType.Insert:
return Op.insert(this.text! + other.text!, this.attributes);
case OperationType.Delete:
return Op.delete(this.length! + other.length!);
default:
throw new Error('Unknown operation type');
}
}
toJSON(): any {
const result: any = { type: this.type };
if (this.length !== undefined) result.length = this.length;
if (this.text !== undefined) result.text = this.text;
if (this.attributes !== undefined) result.attributes = this.attributes;
return result;
}
static fromJSON(json: any): Op {
return new Op(json);
}
}
/**
* Represents a sequence of operations
*/
export class Delta {
public ops: Op[];
public meta?: OperationMeta;
constructor(ops: Op[] = [], meta?: OperationMeta) {
this.ops = ops;
this.meta = meta;
}
static fromOps(ops: Op[]): Delta {
return new Delta(ops);
}
retain(length: number, attributes?: Record<string, any>): Delta {
if (length <= 0) return this;
const op = Op.retain(length, attributes);
return this.push(op);
}
insert(text: string, attributes?: Record<string, any>): Delta {
if (text.length === 0) return this;
const op = Op.insert(text, attributes);
return this.push(op);
}
delete(length: number): Delta {
if (length <= 0) return this;
const op = Op.delete(length);
return this.push(op);
}
push(newOp: Op): Delta {
const ops = [...this.ops];
if (ops.length === 0) {
ops.push(newOp);
} else {
const lastOp = ops[ops.length - 1];
if (lastOp.canMergeWith(newOp)) {
ops[ops.length - 1] = lastOp.merge(newOp);
} else {
ops.push(newOp);
}
}
return new Delta(ops, this.meta);
}
compose(other: Delta): Delta {
const result = new Delta();
let thisIndex = 0;
let otherIndex = 0;
while (thisIndex < this.ops.length || otherIndex < other.ops.length) {
const thisOp = this.ops[thisIndex];
const otherOp = other.ops[otherIndex];
if (!thisOp) {
result.ops.push(otherOp);
otherIndex++;
} else if (!otherOp) {
result.ops.push(thisOp);
thisIndex++;
} else if (thisOp.isInsert()) {
result.ops.push(thisOp);
thisIndex++;
} else if (otherOp.isDelete()) {
result.ops.push(otherOp);
otherIndex++;
} else {
const thisLength = thisOp.getLength();
const otherLength = otherOp.getLength();
if (thisLength === otherLength) {
if (otherOp.isInsert()) {
result.ops.push(otherOp);
} else {
result.ops.push(thisOp);
}
thisIndex++;
otherIndex++;
} else if (thisLength < otherLength) {
if (otherOp.isInsert()) {
result.ops.push(otherOp);
} else {
result.ops.push(thisOp);
}
// Split the other operation
if (otherOp.isRetain()) {
other.ops[otherIndex] = Op.retain(otherLength - thisLength, otherOp.attributes);
} else if (otherOp.isDelete()) {
other.ops[otherIndex] = Op.delete(otherLength - thisLength);
}
thisIndex++;
} else {
if (otherOp.isInsert()) {
result.ops.push(otherOp);
} else {
if (thisOp.isRetain()) {
result.ops.push(Op.retain(otherLength, thisOp.attributes));
} else {
result.ops.push(Op.delete(otherLength));
}
}
// Split this operation
if (thisOp.isRetain()) {
this.ops[thisIndex] = Op.retain(thisLength - otherLength, thisOp.attributes);
} else if (thisOp.isDelete()) {
this.ops[thisIndex] = Op.delete(thisLength - otherLength);
}
otherIndex++;
}
}
}
return result.normalize();
}
apply(text: string): string {
let result = '';
let index = 0;
for (const op of this.ops) {
if (op.isRetain()) {
result += text.slice(index, index + op.length);
index += op.length;
} else if (op.isInsert()) {
result += op.text;
} else if (op.isDelete()) {
index += op.length;
}
}
return result;
}
invert(text: string): Delta {
const inverted = new Delta();
let index = 0;
for (const op of this.ops) {
if (op.isRetain()) {
inverted.retain(op.length, op.attributes);
index += op.length;
} else if (op.isInsert()) {
inverted.delete(op.text.length);
} else if (op.isDelete()) {
inverted.insert(text.slice(index, index + op.length));
index += op.length;
}
}
return inverted;
}
normalize(): Delta {
const normalized = new Delta();
for (const op of this.ops) {
if (op.getLength() > 0) {
normalized.push(op);
}
}
return normalized;
}
getLength(): number {
return this.ops.reduce((length, op) => {
if (op.isInsert()) {
return length + op.text.length;
} else if (op.isRetain()) {
return length + op.length;
}
return length;
}, 0);
}
slice(start: number, end?: number): Delta {
const sliced = new Delta();
let index = 0;
for (const op of this.ops) {
const opLength = op.getLength();
if (index + opLength <= start) {
index += opLength;
continue;
}
if (end !== undefined && index >= end) {
break;
}
const sliceStart = Math.max(0, start - index);
const sliceEnd = end !== undefined ? Math.min(opLength, end - index) : opLength;
const sliceLength = sliceEnd - sliceStart;
if (sliceLength > 0) {
if (op.isRetain()) {
sliced.retain(sliceLength, op.attributes);
} else if (op.isInsert()) {
sliced.insert(op.text.slice(sliceStart, sliceEnd), op.attributes);
} else if (op.isDelete()) {
sliced.delete(sliceLength);
}
}
index += opLength;
}
return sliced;
}
toJSON(): any {
return {
ops: this.ops.map(op => op.toJSON()),
meta: this.meta
};
}
static fromJSON(json: any): Delta {
const ops = json.ops.map((op: any) => Op.fromJSON(op));
return new Delta(ops, json.meta);
}
toString(): string {
return JSON.stringify(this.toJSON());
}
equals(other: Delta): boolean {
if (this.ops.length !== other.ops.length) return false;
return this.ops.every((op, index) => op.equals(other.ops[index]));
}
clone(): Delta {
return Delta.fromJSON(this.toJSON());
}
}