/*! * Copyright (c) Microsoft Corporation and contributors. All rights reserved. * Licensed under the MIT License. */ import { assert, Lazy } from "@fluidframework/core-utils/internal"; import type { Client, IMergeTreeDeltaCallbackArgs, IMergeTreeDeltaOpArgs, IMergeTreeMaintenanceCallbackArgs, ISegment, MergeTreeDeltaOperationType, MergeTreeDeltaOperationTypes, PropertySet, } from "@fluidframework/merge-tree/internal"; import { MergeTreeDeltaType, MergeTreeMaintenanceType, SortedSegmentSet, } from "@fluidframework/merge-tree/internal"; /** * Base class for SequenceDeltaEvent and SequenceMaintenanceEvent. * * The properties of this object and its sub-objects represent the state of the sequence at the * point in time at which the operation was applied. * They will not take into any future modifications performed to the underlying sequence and merge tree. * @legacy @beta */ export interface SequenceEvent< TOperation extends MergeTreeDeltaOperationTypes = MergeTreeDeltaOperationTypes, > { readonly deltaOperation: TOperation; readonly deltaArgs: IMergeTreeDeltaCallbackArgs; /** * The in-order ranges affected by this delta. * These are not necessarily contiguous. * * @remarks If processing code doesn't care about the order of the ranges, it may instead consider using the * {@link @fluidframework/merge-tree#IMergeTreeDeltaCallbackArgs.deltaSegments|deltaSegments} field on {@link SequenceEvent.deltaArgs|deltaArgs}. */ readonly ranges: readonly Readonly>[]; /** * The client id of the client that made the change which caused the delta event */ readonly clientId: string | undefined; /** * The first of the modified ranges. */ readonly first: Readonly>; /** * The last of the modified ranges. */ readonly last: Readonly>; } export abstract class SequenceEventClass< TOperation extends MergeTreeDeltaOperationTypes = MergeTreeDeltaOperationTypes, > implements SequenceEvent { public readonly isLocal: boolean; public readonly deltaOperation: TOperation; private readonly sortedRanges: Lazy>>; private readonly pFirst: Lazy>; private readonly pLast: Lazy>; constructor( public readonly opArgs: IMergeTreeDeltaOpArgs | undefined, /** * Arguments reflecting the type of change that caused this event. */ public readonly deltaArgs: IMergeTreeDeltaCallbackArgs, private readonly mergeTreeClient: Client, ) { if ( deltaArgs.operation !== MergeTreeDeltaType.OBLITERATE && deltaArgs.operation !== MergeTreeMaintenanceType.ACKNOWLEDGED ) { assert( deltaArgs.deltaSegments.length > 0, 0x2d8 /* "Empty change event should not be emitted." */, ); } this.deltaOperation = deltaArgs.operation; this.isLocal = opArgs?.sequencedMessage === undefined; this.sortedRanges = new Lazy>>(() => { const set = new SortedSegmentSet>(); for (const delta of this.deltaArgs.deltaSegments) { const newRange: ISequenceDeltaRange = { operation: this.deltaArgs.operation, position: this.mergeTreeClient.getPosition(delta.segment), propertyDeltas: delta.propertyDeltas ?? {}, segment: delta.segment, }; set.addOrUpdate(newRange); } return set; }); this.pFirst = new Lazy>( () => this.sortedRanges.value.items[0], ); this.pLast = new Lazy>( () => this.sortedRanges.value.items[this.sortedRanges.value.size - 1], ); } /** * The in-order ranges affected by this delta. * These are not necessarily contiguous. * * @remarks If processing code doesn't care about the order of the ranges, it may instead consider using the * {@link @fluidframework/merge-tree#IMergeTreeDeltaCallbackArgs.deltaSegments|deltaSegments} field on {@link SequenceEvent.deltaArgs|deltaArgs}. */ public get ranges(): readonly Readonly>[] { return this.sortedRanges.value.items; } /** * The client id of the client that made the change which caused the delta event */ public get clientId(): string | undefined { return this.mergeTreeClient.longClientId; } /** * The first of the modified ranges. */ public get first(): Readonly> { return this.pFirst.value; } /** * The last of the modified ranges. */ public get last(): Readonly> { return this.pLast.value; } } /** * The event object returned on sequenceDelta events. * * The properties of this object and its sub-objects represent the state of the sequence at the * point in time at which the operation was applied. * They will not take into consideration any future modifications performed to the underlying sequence and merge tree. * * For group ops, each op will get its own event, and the group op property will be set on the op args. * * Ops may get multiple events. For instance, an insert-replace will get a remove then an insert event. * @legacy @beta */ export interface SequenceDeltaEvent extends SequenceEvent { readonly opArgs: IMergeTreeDeltaOpArgs; /** * Whether the event was caused by a locally-made change. */ readonly isLocal: boolean; } export class SequenceDeltaEventClass extends SequenceEventClass implements SequenceDeltaEvent { constructor( public readonly opArgs: IMergeTreeDeltaOpArgs, deltaArgs: IMergeTreeDeltaCallbackArgs, mergeTreeClient: Client, ) { super(opArgs, deltaArgs, mergeTreeClient); } } /** * The event object returned on maintenance events. * * The properties of this object and its sub-objects represent the state of the sequence at the * point in time at which the operation was applied. * They will not take into consideration any future modifications performed to the underlying sequence and merge tree. * @legacy @beta */ export interface SequenceMaintenanceEvent extends SequenceEvent { readonly opArgs: IMergeTreeDeltaOpArgs | undefined; } export class SequenceMaintenanceEventClass extends SequenceEventClass implements SequenceMaintenanceEvent { constructor( /** * Defined iff `deltaArgs.operation` is {@link @fluidframework/merge-tree#MergeTreeMaintenanceType.ACKNOWLEDGED|MergeTreeMaintenanceType.ACKNOWLEDGED}. * * In that case, this argument provides information about the change which was acknowledged. */ public readonly opArgs: IMergeTreeDeltaOpArgs | undefined, deltaArgs: IMergeTreeMaintenanceCallbackArgs, mergeTreeClient: Client, ) { super(opArgs, deltaArgs, mergeTreeClient); } } /** * A range that has changed corresponding to a segment modification. * @legacy @beta */ export interface ISequenceDeltaRange< TOperation extends MergeTreeDeltaOperationTypes = MergeTreeDeltaOperationTypes, > { /** * The type of operation that changed this range. * * @remarks Consuming code should typically compare this to the enum values defined in * `MergeTreeDeltaOperationTypes`. */ operation: TOperation; /** * The index of the start of the range. */ position: number; /** * The segment that corresponds to the range. */ segment: ISegment; /** * Deltas object which contains all modified properties with their previous values. * Since `undefined` doesn't survive a round-trip through JSON serialization, the old value being absent * is instead encoded with `null`. * * @remarks This object is motivated by undo/redo scenarios, and provides a convenient "inverse op" to apply to * undo a property change. * * @example * * If a segment initially had properties `{ foo: "1", bar: 2 }` and it was annotated with * `{ foo: 3, baz: 5 }`, the corresponding event would have a `propertyDeltas` of `{ foo: "1", baz: null }`. */ propertyDeltas: PropertySet; }