/*! * Copyright (c) Microsoft Corporation and contributors. All rights reserved. * Licensed under the MIT License. */ import type { IEventThisPlaceHolder } from "@fluidframework/core-interfaces"; import type { IChannelAttributes, IFluidDataStoreRuntime, IChannelStorageService } from "@fluidframework/datastore-definitions/internal"; import type { IJSONSegment, IMergeTreeGroupMsg, IRelativePosition, ISegment, ISegmentAction, LocalReferencePosition, MergeTreeRevertibleDriver, PropertySet, ReferencePosition, ReferenceType, SlidingPreference } from "@fluidframework/merge-tree/internal"; import { Client, type AdjustParams, type InteriorSequencePlace, type MapLike } from "@fluidframework/merge-tree/internal"; import type { ISummaryTreeWithStats, ITelemetryContext, IRuntimeMessageCollection } from "@fluidframework/runtime-definitions/internal"; import type { IFluidSerializer, ISharedObject, ISharedObjectEvents } from "@fluidframework/shared-object-base/internal"; import { SharedObject } from "@fluidframework/shared-object-base/internal"; import type { ISequenceIntervalCollection } from "./intervalCollection.js"; import type { SequenceDeltaEvent, SequenceMaintenanceEvent } from "./sequenceDeltaEvent.js"; /** * Events emitted in response to changes to the sequence data. * * @remarks * * The following is the list of events emitted. * * ### "sequenceDelta" * * The sequenceDelta event is emitted when segments are inserted, annotated, or removed. * * #### Listener signature * * ```typescript * (event: SequenceDeltaEvent, target: IEventThisPlaceHolder) => void * ``` * - `event` - Various information on the segments that were modified. * * - `target` - The sequence itself. * * ### "maintenance" * * The maintenance event is emitted when segments are modified during merge-tree maintenance. * * #### Listener signature * * ```typescript * (event: SequenceMaintenanceEvent, target: IEventThisPlaceHolder) => void * ``` * - `event` - Various information on the segments that were modified. * * - `target` - The sequence itself. * @legacy @beta */ export interface ISharedSegmentSequenceEvents extends ISharedObjectEvents { (event: "createIntervalCollection", listener: (label: string, local: boolean, target: IEventThisPlaceHolder) => void): void; (event: "sequenceDelta", listener: (event: SequenceDeltaEvent, target: IEventThisPlaceHolder) => void): void; (event: "maintenance", listener: (event: SequenceMaintenanceEvent, target: IEventThisPlaceHolder) => void): void; } /** * @legacy @beta */ export interface ISharedSegmentSequence extends ISharedObject, MergeTreeRevertibleDriver { /** * Creates a `LocalReferencePosition` on this SharedString. If the refType does not include * ReferenceType.Transient, the returned reference will be added to the localRefs on the provided segment. * @param segment - Segment to add the local reference on, or "start"/"end" for endpoint segments * @param offset - Offset on the segment at which to place the local reference * @param refType - ReferenceType for the created local reference * @param properties - PropertySet to place on the created local reference */ createLocalReferencePosition(segment: T | "start" | "end", offset: number | undefined, refType: ReferenceType, properties: PropertySet | undefined, slidingPreference?: SlidingPreference, canSlideToEndpoint?: boolean): LocalReferencePosition; /** * Removes a `LocalReferencePosition` from this SharedString. */ removeLocalReferencePosition(lref: LocalReferencePosition): LocalReferencePosition | undefined; /** * Returns the length of the current sequence for the client */ getLength(): number; /** * Returns the current position of a segment, and -1 if the segment * does not exist in this sequence * @param segment - The segment to get the position of */ getPosition(segment: ISegment): number; /** * Resolves a `ReferencePosition` into a character position using this client's perspective. * * Reference positions that point to a character that has been removed will * always return the position of the nearest non-removed character, regardless * of `ReferenceType`. To handle this case specifically, one may wish * to look at the segment returned by `ReferencePosition.getSegment`. */ localReferencePositionToPosition(lref: ReferencePosition): number; /** * Walk the underlying segments of the sequence. * The walked segments may extend beyond the range if the segments cross the * ranges start or end boundaries. * * Set split range to true to ensure only segments within the range are walked. * * @param handler - The function to handle each segment. Traversal ends if * this function returns true. * @param start - Optional. The start of range walk. * @param end - Optional. The end of range walk * @param accum - Optional. An object that will be passed to the handler for accumulation * @param splitRange - Optional. Splits boundary segments on the range boundaries. Defaults to false. */ walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: TClientData, splitRange?: boolean): void; /** * Inserts a segment directly before a `ReferencePosition`. * @param refPos - The reference position to insert the segment at * @param segment - The segment to insert */ insertAtReferencePosition(pos: ReferencePosition, segment: T): void; /** * Finds the segment information (i.e. segment + offset) corresponding to a character position in the SharedString. * If the position is past the end of the string, `segment` and `offset` on the returned object may be undefined. * @param pos - Character position (index) into the current local view of the SharedString. */ getContainingSegment(pos: number): { segment: T | undefined; offset: number | undefined; }; getPropertiesAtPosition(pos: number): PropertySet | undefined; /** * @returns An iterable object that enumerates the IntervalCollection labels. * * @example * * ```typescript * const iter = this.getIntervalCollectionKeys(); * for (key of iter) * const collection = this.getIntervalCollection(key); * ... * ``` */ getIntervalCollectionLabels(): IterableIterator; /** * Retrieves the interval collection keyed on `label`. If no such interval collection exists, * creates one. */ getIntervalCollection(label: string): ISequenceIntervalCollection; /** * Obliterate is similar to remove, but differs in that segments concurrently * inserted into an obliterated range will also be removed. * Inserts are considered concurrent to an obliterate iff the insert op's seq is after the obliterate op's refSeq * and the insert's refSeq is before the obliterates seq. * Inserts made by the client which most recently obliterated a range containing the insert position * are not considered concurrent to any obliteration (the last client to obliterate gets the right to insert). * * The endpoints can either be inclusive or exclusive. * Exclusive endpoints allow the obliterated range to "grow" to include adjacent concurrently inserted segments on that side. * * @param start - The start of the range to obliterate. * Inclusive if side is Before or a number is provided. * @param end - The end of the range to obliterate. Inclusive if side is After. * If a number is provided it is treated as exclusive, * but the endpoint does not expand in order to preserve existing behavior. * * @example Given the initial state `"|ABC>"`, * `obliterateRange({ pos: 0, side: Side.After }, { pos: 4, side: Side.Before })` obliterates `"ABC"`, leaving only `"|>"`. * `insertFromSpec(1, { text: "AAA"})` would insert `"AAA"` before |, resulting in `"|AAA>"`. * If another client does the same thing but inserts `"BBB"` and gets sequenced later, all clients will eventually see `|BBB>`. */ obliterateRange(start: number | InteriorSequencePlace, end: number | InteriorSequencePlace): void; /** * @returns The most recent sequence number which has been acked by the server and processed by this * SharedSegmentSequence. */ getCurrentSeq(): number; /** * Annotates the range with the provided properties * * @param start - The inclusive start position of the range to annotate * @param end - The exclusive end position of the range to annotate * @param props - The properties to annotate the range with * */ annotateRange(start: number, end: number, props: PropertySet): void; /** * Annotates a specified range within the sequence by applying the provided adjustments. * * @param start - The inclusive start position of the range to annotate. This is a zero-based index. * @param end - The exclusive end position of the range to annotate. This is a zero-based index. * @param adjust - A map-like object specifying the properties to adjust. Each key-value pair represents a property and its corresponding adjustment to be applied over the range. * An adjustment is defined by an object containing a `delta` to be added to the current property value, and optional `min` and `max` constraints to limit the adjusted value. * * @remarks * The range is defined by the start and end positions, where the start position is inclusive and the end position is exclusive. * The properties provided in the adjust parameter will be applied to the specified range. Each adjustment modifies the current value of the property by adding the specified `value`. * If the current value is not a number, the `delta` will be summed with 0 to compute the new value. The optional `min` and `max` constraints are applied after the adjustment to ensure the final value falls within the specified bounds. */ annotateAdjustRange(start: number, end: number, adjust: MapLike): void; /** * @param start - The inclusive start of the range to remove * @param end - The exclusive end of the range to remove */ removeRange(start: number, end: number): void; /** * Resolves a remote client's position against the local sequence * and returns the remote client's position relative to the local * sequence. The client ref seq must be above the minimum sequence number * or the return value will be undefined. * Generally this method is used in conjunction with signals which provide * point in time values for the below parameters, and is useful for things * like displaying user position. It should not be used with persisted values * as persisted values will quickly become invalid as the remoteClientRefSeq * moves below the minimum sequence number * @param remoteClientPosition - The remote client's position to resolve * @param remoteClientRefSeq - The reference sequence number of the remote client * @param remoteClientId - The client id of the remote client */ resolveRemoteClientPosition(remoteClientPosition: number, remoteClientRefSeq: number, remoteClientId: string): number | undefined; /** * Initializes the object as a local, non-shared object. This object can become shared after * it is attached to the document. * @privateRemarks * TODO: determine if this API (from SharedObject) is needed by users of the encapsulated API, declarative API or both, * and handle exposing it in a consistent way for all SharedObjects if needed. */ initializeLocal(): void; /** * @deprecated The ability to create group ops will be removed in an upcoming * release, as group ops are redundant with the native batching capabilities * of the runtime */ groupOperation(groupOp: IMergeTreeGroupMsg): void; getRangeExtentsOfPosition(pos: number): { posStart: number | undefined; posAfterEnd: number | undefined; }; /** * Inserts a segment * @param start - The position to insert the segment at * @param spec - The segment to inserts spec */ insertFromSpec(pos: number, spec: IJSONSegment): void; /** * Given a position specified relative to a marker id, lookup the marker * and convert the position to a character position. * @param relativePos - Id of marker (may be indirect) and whether position is before or after marker. */ posFromRelativePos(relativePos: IRelativePosition): number; } /** * @internal */ export declare abstract class SharedSegmentSequence extends SharedObject implements ISharedSegmentSequence { id: string; readonly segmentFromSpec: (spec: IJSONSegment) => ISegment; /** * This is a safeguard to avoid problematic reentrancy of local ops. This type of scenario occurs if the user of SharedString subscribes * to the `sequenceDelta` event and uses the callback for a local op to submit further local ops. * Historically (before 2.0.0-internal.6.1.0), doing so would result in eventual consistency issues or a corrupted document. * These issues were fixed in #16815 which makes such reentrancy no different from applying the ops in order but not from within the change events, * but there is still little test coverage for reentrant scenarios. * Additionally, applications submitting ops from inside change events need to take extreme care that their data models also support reentrancy. * Since this is likely not the case, by default SharedString throws when encountering reentrant ops. * * An application using SharedString which explicitly wants to opt in to allowing reentrancy anyway can set `sharedStringPreventReentrancy` * on the data store options to `false`. */ protected guardReentrancy: (callback: () => TRet) => TRet; private static createOpsFromDelta; /** * Note: this field only provides a lower-bound on the reference sequence numbers for in-flight ops. * The exact reason isn't understood, but some e2e tests suggest that the runtime may sometimes process * incoming leave/join ops before putting an op that this DDS submits over the wire. * * E.g. SharedString submits an op while deltaManager has lastSequenceNumber = 10, but before the runtime * puts this op over the wire, it processes a client join/leave op with sequence number 11, so the referenceSequenceNumber * on the SharedString op is 11. * * The reference sequence numbers placed in this queue are also not accurate for stashed ops due to how the applyStashedOp * flow works at the runtime level. This is a legitimate bug, and AB#6602 tracks one way to fix it (stop reaching all the way * to deltaManager's lastSequenceNumber to obtain refSeq, instead leveraging some analogous notion on the container or datastore * runtime). */ private readonly inFlightRefSeqs; private ongoingResubmitRefSeq; /** * Gets the reference sequence number (i.e. sequence number of the runtime's last processed op) for an op submitted * in the current context. * * This value can be optionally overridden using `useResubmitRefSeq`. * IntervalCollection's resubmit logic currently relies on preserving merge information from when the op was originally submitted, * even if the op is resubmitted more than once. Thus during resubmit, `inFlightRefSeqs` gets populated with the * original refSeq rather than the refSeq at the time of reconnection. * * @remarks In some not fully understood cases, the runtime may process incoming ops before putting an op that this * DDS submits over the wire. See `inFlightRefSeqs` for more details. */ private get currentRefSeq(); protected client: Client; private messagesSinceMSNChange; private readonly intervalCollections; /** * The loaded snapshot format, consulted by the lazy options reader after construction. */ private newMergeTreeSnapshotFormat; private readonly sequenceOptions; constructor(dataStoreRuntime: IFluidDataStoreRuntime, id: string, attributes: IChannelAttributes, segmentFromSpec: (spec: IJSONSegment) => ISegment, newMergeTreeSnapshotFormat?: boolean); removeRange(start: number, end: number): void; obliterateRange(start: number | InteriorSequencePlace, end: number | InteriorSequencePlace): void; groupOperation(groupOp: IMergeTreeGroupMsg): void; getContainingSegment(pos: number): { segment: T | undefined; offset: number | undefined; }; getLength(): number; getPosition(segment: ISegment): number; annotateRange(start: number, end: number, props: PropertySet): void; annotateAdjustRange(start: number, end: number, adjust: MapLike): void; getPropertiesAtPosition(pos: number): PropertySet | undefined; getRangeExtentsOfPosition(pos: number): { posStart: number | undefined; posAfterEnd: number | undefined; }; createLocalReferencePosition(segment: T | "start" | "end", offset: number | undefined, refType: ReferenceType, properties: PropertySet | undefined, slidingPreference?: SlidingPreference, canSlideToEndpoint?: boolean): LocalReferencePosition; localReferencePositionToPosition(lref: ReferencePosition): number; removeLocalReferencePosition(lref: LocalReferencePosition): LocalReferencePosition | undefined; resolveRemoteClientPosition(remoteClientPosition: number, remoteClientRefSeq: number, remoteClientId: string): number | undefined; private submitSequenceMessage; posFromRelativePos(relativePos: IRelativePosition): number; walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: TClientData, splitRange?: boolean): void; getCurrentSeq(): number; insertAtReferencePosition(pos: ReferencePosition, segment: T): void; insertFromSpec(pos: number, spec: IJSONSegment): void; getIntervalCollection(label: string): ISequenceIntervalCollection; getIntervalCollectionLabels(): IterableIterator; protected summarizeCore(serializer: IFluidSerializer, telemetryContext?: ITelemetryContext): ISummaryTreeWithStats; /** * Runs serializer over the GC data for this SharedMatrix. * All the IFluidHandle's represent routes to other objects. */ protected processGCDataCore(serializer: IFluidSerializer): void; /** * Replace the range specified from start to end with the provided segment * This is done by inserting the segment at the end of the range, followed * by removing the contents of the range * For a zero or reverse range (start \>= end), insert at end do not remove anything * @param start - The start of the range to replace * @param end - The end of the range to replace * @param segment - The segment that will replace the range */ protected replaceRange(start: number, end: number, segment: ISegment): void; protected onConnect(): void; protected onDisconnect(): void; protected reSubmitCore(content: any, localOpMetadata: unknown, squash?: boolean): void; protected reSubmitSquashed(content: unknown, localOpMetadata: unknown): void; /** * Revert an op */ protected rollback(content: any, localOpMetadata: unknown): void; protected loadCore(storage: IChannelStorageService): Promise; protected processMessagesCore(messagesCollection: IRuntimeMessageCollection): void; private processMessage; protected didAttach(): void; protected initializeLocalCore(): void; protected applyStashedOp(content: any): void; private summarizeMergeTree; /** * Processes a merge tree message for the sequence. * * @param message - Message with decoded and hydrated handles */ private processMergeTreeMsg; private processMinSequenceNumberChanged; private initializeIntervalCollections; /** * Overrides the "currently applicable reference sequence number" for the duration of the callback. * See remarks on `currentRefSeq` for more context. */ private useResubmitRefSeq; } /** * Resets the reentrancy log counter. Test-only API. */ export declare function resetReentrancyLogCounter(): void; //# sourceMappingURL=sequence.d.ts.map