@oobleck/fluid-backend
Version:
Fluid Framework backend for nteract RTC
147 lines (130 loc) • 4.28 kB
text/typescript
import { EMPTY, fromEvent, Observable, of } from "rxjs";
import { filter, map, mergeMap } from "rxjs/operators";
import {
DataObject,
IMergeTreeDelta,
IMergeTreeGroupMsg,
IMergeTreeInsertMsg,
IMergeTreeRemoveMsg,
MergeTreeDeltaType,
SharedMap,
SharedString
} from "@fluid-experimental/fluid-framework";
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
import {
CellSourceEdit,
ICellMetadataEvent,
MetadataEntryDef,
PatchCellSourceInput,
TextCellInput
} from "../schema";
import { AugmentedCellEdit } from "./types";
enum PropertyKey {
Metadata = "metadata",
Source = "source",
}
/**
* Fluid DataObject
*/
export class ProtoCellDDS<S extends TextCellInput = TextCellInput> extends DataObject<{}, S> {
private sharedSource!: SharedString;
private metadataMap!: SharedMap;
//#region ISolidCell
get source(): string {
return this.sharedSource.getText();
}
edits$!: Observable<AugmentedCellEdit>;
metadata$!: Observable<ICellMetadataEvent>;
get metadata(): MetadataEntryDef[] {
const result: MetadataEntryDef[] = [];
this.metadataMap?.forEach((value, key) => result.push({ key, value }));
return result;
}
applySourceEdit({ type, diff, start, len }: PatchCellSourceInput) {
switch (type) {
case "insert":
this.sharedSource.insertText(start, diff ?? "");
break;
case "replace":
this.sharedSource.replaceText(start, start + (len ?? 0), diff ?? "");
break;
case "delete":
this.sharedSource.removeText(start, start + (len ?? 0));
break;
}
}
updateMetadata(key: string, value: unknown) {
this.metadataMap.set(key, value);
}
//#endregion
//#region DataObject
protected async initializingFirstTime(input?: S): Promise<void> {
const source = SharedString.create(this.runtime);
const metadata = SharedMap.create(this.runtime);
if (input) {
source.insertText(0, input.source);
input.metadata?.forEach(({ key, value }) => {
metadata.set(key, value);
});
}
this.root.set(PropertyKey.Source, source.handle).set(PropertyKey.Metadata, metadata.handle);
}
protected async hasInitialized(): Promise<void> {
this.sharedSource = await this.root.get(PropertyKey.Source).get();
this.metadataMap = await this.root.get(PropertyKey.Metadata).get();
this.setupObservables();
}
private setupObservables() {
this.edits$ = fromEvent<[ISequencedDocumentMessage, boolean]>(this.sharedSource, "op").pipe(
filter(([, local]) => !local),
mergeMap(([{ contents: delta }]): Observable<IMergeTreeDelta> => {
if (delta.type === MergeTreeDeltaType.GROUP) {
const { ops } = delta as IMergeTreeGroupMsg;
return of(...ops);
} else {
return of(delta);
}
}),
mergeMap((delta): Observable<CellSourceEdit> => {
switch (delta.type) {
case MergeTreeDeltaType.INSERT: {
const { seg, pos1 } = delta as IMergeTreeInsertMsg;
return of({
__typename: "TextInserted",
id: this.id,
diff: seg ?? "",
start: pos1!
});
}
case MergeTreeDeltaType.REMOVE: {
const { pos1, pos2 } = delta as IMergeTreeRemoveMsg;
return of({
__typename: "TextDeleted",
id: this.id,
start: pos1!,
len: pos2! - pos1!
});
}
case MergeTreeDeltaType.GROUP:
console.log(delta);
break;
}
return EMPTY;
}),
map((edit) => ({ source: this.sharedSource, edit }))
);
this.metadata$ = fromEvent<[ISequencedDocumentMessage, boolean]>(this.metadataMap, "op").pipe(
filter(([op, local]) => !local && op.contents.type === "set"),
map(([{ contents }]) => {
return {
id: this.id,
entry: {
key: contents.key,
value: contents.value.value
}
} as ICellMetadataEvent;
})
);
}
//#endregion
}