From 47465f4b12056810112df30a6dad89282afc7a2d Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Fri, 17 May 2024 12:03:37 -0700 Subject: [PATCH] Add ISharedSegmentSequence (#21067) ## Description Fix that ISharedString extends SharedObject (the class). This resolves one issue preventing tagging of "legacy" DDSes as public since before this change doing so for SharedString would pull in a lot more than it should. ## Breaking Changes ISharedString no longer extends SharedSegmentSequence and instead extends the new ISharedSegmentSequence, which may be missing some APIs. Attempt to migrate off the missing APIs, but if that is not practical, request the be added to ISharedSegmentSequence and cast to SharedSegmentSequence as a workaround temporally. --- .changeset/loud-maps-fall.md | 10 + .../codemirror/src/codeMirrorView.tsx | 2 +- .../dds/sequence/api-report/sequence.api.md | 58 ++- packages/dds/sequence/package.json | 12 +- packages/dds/sequence/src/index.ts | 6 +- packages/dds/sequence/src/sequence.ts | 346 +++++++++++------- packages/dds/sequence/src/sharedString.ts | 4 +- .../validateSequencePrevious.generated.ts | 3 + .../api-report/fluid-framework.api.md | 96 ++--- .../framework/fluid-framework/src/index.ts | 2 +- .../undo-redo/api-report/undo-redo.api.md | 10 +- packages/framework/undo-redo/package.json | 6 +- .../undo-redo/src/sequenceHandler.ts | 14 +- .../validateUndoRedoPrevious.generated.ts | 1 + .../src/test/orderSequentially.spec.ts | 20 +- 15 files changed, 374 insertions(+), 216 deletions(-) create mode 100644 .changeset/loud-maps-fall.md diff --git a/.changeset/loud-maps-fall.md b/.changeset/loud-maps-fall.md new file mode 100644 index 000000000000..a3f92ca22a01 --- /dev/null +++ b/.changeset/loud-maps-fall.md @@ -0,0 +1,10 @@ +--- +"fluid-framework": minor +"@fluidframework/sequence": minor +--- + +Stop ISharedString extending SharedObject + +ISharedString no longer extends SharedSegmentSequence and instead extends the new ISharedSegmentSequence, which may be missing some APIs. + +Attempt to migrate off the missing APIs, but if that is not practical, request they be added to ISharedSegmentSequence and cast to SharedSegmentSequence as a workaround temporally. diff --git a/examples/data-objects/codemirror/src/codeMirrorView.tsx b/examples/data-objects/codemirror/src/codeMirrorView.tsx index dfb140eb2406..e270168012e1 100644 --- a/examples/data-objects/codemirror/src/codeMirrorView.tsx +++ b/examples/data-objects/codemirror/src/codeMirrorView.tsx @@ -46,7 +46,7 @@ class CodeMirrorView { // https://stackoverflow.com/questions/18828658/how-to-kill-a-codemirror-instance if (this.sequenceDeltaCb) { - this.text.removeListener("sequenceDelta", this.sequenceDeltaCb); + this.text.off("sequenceDelta", this.sequenceDeltaCb); this.sequenceDeltaCb = undefined; } diff --git a/packages/dds/sequence/api-report/sequence.api.md b/packages/dds/sequence/api-report/sequence.api.md index 4d27f1e20054..0327e6835c2c 100644 --- a/packages/dds/sequence/api-report/sequence.api.md +++ b/packages/dds/sequence/api-report/sequence.api.md @@ -23,6 +23,7 @@ import { IRelativePosition } from '@fluidframework/merge-tree/internal'; import { ISegment } from '@fluidframework/merge-tree/internal'; import { ISegmentAction } from '@fluidframework/merge-tree/internal'; import { ISequencedDocumentMessage } from '@fluidframework/protocol-definitions'; +import { ISharedObject } from '@fluidframework/shared-object-base/internal'; import { ISharedObjectEvents } from '@fluidframework/shared-object-base'; import { ISharedObjectKind } from '@fluidframework/shared-object-base/internal'; import { ISummaryTreeWithStats } from '@fluidframework/runtime-definitions/internal'; @@ -371,6 +372,43 @@ export interface ISharedIntervalCollection; } +// @alpha (undocumented) +export interface ISharedSegmentSequence extends ISharedObject, ISharedIntervalCollection, MergeTreeRevertibleDriver { + annotateRange(start: number, end: number, props: PropertySet): void; + createLocalReferencePosition(segment: T, offset: number, refType: ReferenceType, properties: PropertySet | undefined, slidingPreference?: SlidingPreference, canSlideToEndpoint?: boolean): LocalReferencePosition; + getContainingSegment(pos: number): { + segment: T | undefined; + offset: number | undefined; + }; + // (undocumented) + getCurrentSeq(): number; + getIntervalCollection(label: string): IIntervalCollection; + // (undocumented) + getIntervalCollectionLabels(): IterableIterator; + getLength(): number; + getPosition(segment: ISegment): number; + // (undocumented) + getPropertiesAtPosition(pos: number): PropertySet | undefined; + // (undocumented) + getRangeExtentsOfPosition(pos: number): { + posStart: number | undefined; + posAfterEnd: number | undefined; + }; + // @deprecated (undocumented) + groupOperation(groupOp: IMergeTreeGroupMsg): void; + initializeLocal(): void; + insertAtReferencePosition(pos: ReferencePosition, segment: T): void; + insertFromSpec(pos: number, spec: IJSONSegment): void; + localReferencePositionToPosition(lref: ReferencePosition): number; + obliterateRange(start: number, end: number): void; + posFromRelativePos(relativePos: IRelativePosition): number; + removeLocalReferencePosition(lref: LocalReferencePosition): LocalReferencePosition | undefined; + // (undocumented) + removeRange(start: number, end: number): void; + resolveRemoteClientPosition(remoteClientPosition: number, remoteClientRefSeq: number, remoteClientId: string): number | undefined; + walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: TClientData, splitRange?: boolean): void; +} + // @alpha export interface ISharedSegmentSequenceEvents extends ISharedObjectEvents { // (undocumented) @@ -382,7 +420,7 @@ export interface ISharedSegmentSequenceEvents extends ISharedObjectEvents { } // @alpha -export interface ISharedString extends SharedSegmentSequence { +export interface ISharedString extends ISharedSegmentSequence { annotateMarker(marker: Marker, props: PropertySet): void; getMarkerFromId(id: string): ISegment | undefined; getText(start?: number, end?: number): string; @@ -568,24 +606,30 @@ export class SharedIntervalCollectionFactory implements IChannelFactory { } // @alpha (undocumented) -export abstract class SharedSegmentSequence extends SharedObject implements ISharedIntervalCollection, MergeTreeRevertibleDriver { +export abstract class SharedSegmentSequence extends SharedObject implements ISharedSegmentSequence { constructor(dataStoreRuntime: IFluidDataStoreRuntime, id: string, attributes: IChannelAttributes, segmentFromSpec: (spec: IJSONSegment) => ISegment); + // (undocumented) annotateRange(start: number, end: number, props: PropertySet): void; protected applyStashedOp(content: any): void; // (undocumented) protected client: Client; + // (undocumented) createLocalReferencePosition(segment: T, offset: number, refType: ReferenceType, properties: PropertySet | undefined, slidingPreference?: SlidingPreference, canSlideToEndpoint?: boolean): LocalReferencePosition; protected didAttach(): void; + // (undocumented) getContainingSegment(pos: number): { segment: T | undefined; offset: number | undefined; }; // (undocumented) getCurrentSeq(): number; + // (undocumented) getIntervalCollection(label: string): IIntervalCollection; // (undocumented) getIntervalCollectionLabels(): IterableIterator; + // (undocumented) getLength(): number; + // (undocumented) getPosition(segment: ISegment): number; // (undocumented) getPropertiesAtPosition(pos: number): PropertySet | undefined; @@ -594,33 +638,41 @@ export abstract class SharedSegmentSequence extends SharedOb posStart: number | undefined; posAfterEnd: number | undefined; }; - // @deprecated (undocumented) + // (undocumented) groupOperation(groupOp: IMergeTreeGroupMsg): void; protected guardReentrancy: (callback: () => TRet) => TRet; // (undocumented) id: string; protected initializeLocalCore(): void; + // (undocumented) insertAtReferencePosition(pos: ReferencePosition, segment: T): void; + // (undocumented) insertFromSpec(pos: number, spec: IJSONSegment): void; protected loadCore(storage: IChannelStorageService): Promise; // @deprecated get loaded(): Promise; + // (undocumented) localReferencePositionToPosition(lref: ReferencePosition): number; + // (undocumented) obliterateRange(start: number, end: number): void; protected onConnect(): void; protected onDisconnect(): void; + // (undocumented) posFromRelativePos(relativePos: IRelativePosition): number; protected processCore(message: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown): void; protected processGCDataCore(serializer: IFluidSerializer): void; + // (undocumented) removeLocalReferencePosition(lref: LocalReferencePosition): LocalReferencePosition | undefined; // (undocumented) removeRange(start: number, end: number): void; protected replaceRange(start: number, end: number, segment: ISegment): void; + // (undocumented) resolveRemoteClientPosition(remoteClientPosition: number, remoteClientRefSeq: number, remoteClientId: string): number | undefined; protected reSubmitCore(content: any, localOpMetadata: unknown): void; // (undocumented) readonly segmentFromSpec: (spec: IJSONSegment) => ISegment; protected summarizeCore(serializer: IFluidSerializer, telemetryContext?: ITelemetryContext): ISummaryTreeWithStats; + // (undocumented) walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: TClientData, splitRange?: boolean): void; } diff --git a/packages/dds/sequence/package.json b/packages/dds/sequence/package.json index 530c276f5cf8..a2dbcbc6b4cc 100644 --- a/packages/dds/sequence/package.json +++ b/packages/dds/sequence/package.json @@ -186,6 +186,16 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "InterfaceDeclaration_ISharedString": { + "backCompat": false + }, + "TypeAliasDeclaration_SharedString": { + "backCompat": false + }, + "ClassDeclaration_SharedStringClass": { + "backCompat": false + } + } } } diff --git a/packages/dds/sequence/src/index.ts b/packages/dds/sequence/src/index.ts index c3d71130ca50..6fda83d87d06 100644 --- a/packages/dds/sequence/src/index.ts +++ b/packages/dds/sequence/src/index.ts @@ -69,7 +69,11 @@ export { revertSharedStringRevertibles, SharedStringRevertible, } from "./revertibles.js"; -export { ISharedSegmentSequenceEvents, SharedSegmentSequence } from "./sequence.js"; +export { + ISharedSegmentSequenceEvents, + SharedSegmentSequence, + ISharedSegmentSequence, +} from "./sequence.js"; export { ISequenceDeltaRange, SequenceDeltaEvent, diff --git a/packages/dds/sequence/src/sequence.ts b/packages/dds/sequence/src/sequence.ts index d7b316c3de00..babb1d807599 100644 --- a/packages/dds/sequence/src/sequence.ts +++ b/packages/dds/sequence/src/sequence.ts @@ -50,7 +50,7 @@ import { } from "@fluidframework/runtime-definitions/internal"; import { ObjectStoragePartition, SummaryTreeBuilder } from "@fluidframework/runtime-utils/internal"; import { IFluidSerializer, ISharedObjectEvents } from "@fluidframework/shared-object-base"; -import { SharedObject } from "@fluidframework/shared-object-base/internal"; +import { SharedObject, type ISharedObject } from "@fluidframework/shared-object-base/internal"; import { LoggingError, createChildLogger } from "@fluidframework/telemetry-utils/internal"; import Deque from "double-ended-queue"; @@ -113,12 +113,215 @@ export interface ISharedSegmentSequenceEvents extends ISharedObjectEvents { ): void; } +/** + * @alpha + */ +export interface ISharedSegmentSequence + extends ISharedObject, + ISharedIntervalCollection, + 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 + * @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, + offset: number, + 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): IIntervalCollection; + + /** + * Obliterate is similar to remove, but differs in that segments concurrently + * inserted into an obliterated range will also be removed + * + * @param start - The inclusive start of the range to obliterate + * @param end - The exclusive end of the range to obliterate + */ + obliterateRange(start: number, end: number): 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; + + /** + * @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; + + // #region APIs we might want to remove + /** + * 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 + */ + // eslint-disable-next-line import/no-deprecated + 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; + + // #endregion +} + /** * @alpha */ export abstract class SharedSegmentSequence extends SharedObject - implements ISharedIntervalCollection, MergeTreeRevertibleDriver + implements ISharedSegmentSequence { /** * This promise is always immediately resolved, and awaiting it has no effect. @@ -316,40 +519,19 @@ export abstract class SharedSegmentSequence ); } - /** - * @param start - The inclusive start of the range to remove - * @param end - The exclusive end of the range to remove - */ public removeRange(start: number, end: number): void { this.guardReentrancy(() => this.client.removeRangeLocal(start, end)); } - /** - * Obliterate is similar to remove, but differs in that segments concurrently - * inserted into an obliterated range will also be removed - * - * @param start - The inclusive start of the range to obliterate - * @param end - The exclusive end of the range to obliterate - */ public obliterateRange(start: number, end: number): void { this.guardReentrancy(() => this.client.obliterateRangeLocal(start, end)); } - /** - * @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 - */ // eslint-disable-next-line import/no-deprecated - public groupOperation(groupOp: IMergeTreeGroupMsg) { + public groupOperation(groupOp: IMergeTreeGroupMsg): void { this.guardReentrancy(() => this.client.localTransaction(groupOp)); } - /** - * 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. - */ public getContainingSegment(pos: number): { segment: T | undefined; offset: number | undefined; @@ -357,50 +539,29 @@ export abstract class SharedSegmentSequence return this.client.getContainingSegment(pos); } - /** - * Returns the length of the current sequence for the client - */ - public getLength() { + public getLength(): number { return this.client.getLength(); } - /** - * 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 - */ public getPosition(segment: ISegment): number { return this.client.getPosition(segment); } - /** - * 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 - * - */ public annotateRange(start: number, end: number, props: PropertySet): void { this.guardReentrancy(() => this.client.annotateRangeLocal(start, end, props)); } - public getPropertiesAtPosition(pos: number) { + public getPropertiesAtPosition(pos: number): PropertySet | undefined { return this.client.getPropertiesAtPosition(pos); } - public getRangeExtentsOfPosition(pos: number) { + public getRangeExtentsOfPosition(pos: number): { + posStart: number | undefined; + posAfterEnd: number | undefined; + } { return this.client.getRangeExtentsOfPosition(pos); } - /** - * 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 - * @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 - */ public createLocalReferencePosition( segment: T, offset: number, @@ -419,39 +580,16 @@ export abstract class SharedSegmentSequence ); } - /** - * 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`. - */ public localReferencePositionToPosition(lref: ReferencePosition): number { return this.client.localReferencePositionToPosition(lref); } - /** - * Removes a `LocalReferencePosition` from this SharedString. - */ - public removeLocalReferencePosition(lref: LocalReferencePosition) { + public removeLocalReferencePosition( + lref: LocalReferencePosition, + ): LocalReferencePosition | undefined { return this.client.removeLocalReferencePosition(lref); } - /** - * 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 - */ public resolveRemoteClientPosition( remoteClientPosition: number, remoteClientRefSeq: number, @@ -478,29 +616,10 @@ export abstract class SharedSegmentSequence this.submitLocalMessage(message, metadata); } - /** - * 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. - */ - public posFromRelativePos(relativePos: IRelativePosition) { + public posFromRelativePos(relativePos: IRelativePosition): number { return this.client.posFromRelativePos(relativePos); } - /** - * 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 - */ public walkSegments( handler: ISegmentAction, start?: number, @@ -511,52 +630,23 @@ export abstract class SharedSegmentSequence this.client.walkSegments(handler, start, end, accum as TClientData, splitRange); } - /** - * @returns The most recent sequence number which has been acked by the server and processed by this - * SharedSegmentSequence. - */ - public getCurrentSeq() { + public getCurrentSeq(): number { return this.client.getCurrentSeq(); } - /** - * Inserts a segment directly before a `ReferencePosition`. - * @param refPos - The reference position to insert the segment at - * @param segment - The segment to insert - */ public insertAtReferencePosition(pos: ReferencePosition, segment: T): void { this.guardReentrancy(() => this.client.insertAtReferencePositionLocal(pos, segment)); } - /** - * Inserts a segment - * @param start - The position to insert the segment at - * @param spec - The segment to inserts spec - */ + public insertFromSpec(pos: number, spec: IJSONSegment): void { const segment = this.segmentFromSpec(spec); this.guardReentrancy(() => this.client.insertSegmentLocal(pos, segment)); } - /** - * Retrieves the interval collection keyed on `label`. If no such interval collection exists, - * creates one. - */ public getIntervalCollection(label: string): IIntervalCollection { return this.intervalCollections.get(label); } - /** - * @returns An iterable object that enumerates the IntervalCollection labels. - * - * @example - * - * ```typescript - * const iter = this.getIntervalCollectionKeys(); - * for (key of iter) - * const collection = this.getIntervalCollection(key); - * ... - * ``` - */ public getIntervalCollectionLabels(): IterableIterator { return this.intervalCollections.keys(); } diff --git a/packages/dds/sequence/src/sharedString.ts b/packages/dds/sequence/src/sharedString.ts index 86a496f1fd33..f1943d6f1f83 100644 --- a/packages/dds/sequence/src/sharedString.ts +++ b/packages/dds/sequence/src/sharedString.ts @@ -20,14 +20,14 @@ import { refHasTileLabel, } from "@fluidframework/merge-tree/internal"; -import { SharedSegmentSequence } from "./sequence.js"; +import { SharedSegmentSequence, type ISharedSegmentSequence } from "./sequence.js"; import { SharedStringFactory } from "./sequenceFactory.js"; /** * Fluid object interface describing access methods on a SharedString * @alpha */ -export interface ISharedString extends SharedSegmentSequence { +export interface ISharedString extends ISharedSegmentSequence { /** * Inserts the text at the position. * @param pos - The position to insert the text at diff --git a/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts b/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts index 0f35a7d174e7..022465aac949 100644 --- a/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts +++ b/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts @@ -571,6 +571,7 @@ declare function get_current_InterfaceDeclaration_ISharedString(): declare function use_old_InterfaceDeclaration_ISharedString( use: TypeOnly): void; use_old_InterfaceDeclaration_ISharedString( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_ISharedString()); /* @@ -1495,6 +1496,7 @@ declare function get_current_TypeAliasDeclaration_SharedString(): declare function use_old_TypeAliasDeclaration_SharedString( use: TypeOnly): void; use_old_TypeAliasDeclaration_SharedString( + // @ts-expect-error compatibility expected to be broken get_current_TypeAliasDeclaration_SharedString()); /* @@ -1523,6 +1525,7 @@ declare function get_current_ClassDeclaration_SharedStringClass(): declare function use_old_ClassDeclaration_SharedStringClass( use: TypeOnly): void; use_old_ClassDeclaration_SharedStringClass( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_SharedStringClass()); /* diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.api.md index 8db1d5823638..820eede206e8 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.api.md @@ -632,6 +632,43 @@ export interface ISharedObjectEvents extends IErrorEvent { (event: "op", listener: (op: ISequencedDocumentMessage, local: boolean, target: IEventThisPlaceHolder) => void): any; } +// @alpha (undocumented) +export interface ISharedSegmentSequence extends ISharedObject, ISharedIntervalCollection, MergeTreeRevertibleDriver { + annotateRange(start: number, end: number, props: PropertySet): void; + createLocalReferencePosition(segment: T, offset: number, refType: ReferenceType, properties: PropertySet | undefined, slidingPreference?: SlidingPreference, canSlideToEndpoint?: boolean): LocalReferencePosition; + getContainingSegment(pos: number): { + segment: T | undefined; + offset: number | undefined; + }; + // (undocumented) + getCurrentSeq(): number; + getIntervalCollection(label: string): IIntervalCollection; + // (undocumented) + getIntervalCollectionLabels(): IterableIterator; + getLength(): number; + getPosition(segment: ISegment): number; + // (undocumented) + getPropertiesAtPosition(pos: number): PropertySet | undefined; + // (undocumented) + getRangeExtentsOfPosition(pos: number): { + posStart: number | undefined; + posAfterEnd: number | undefined; + }; + // @deprecated (undocumented) + groupOperation(groupOp: IMergeTreeGroupMsg): void; + initializeLocal(): void; + insertAtReferencePosition(pos: ReferencePosition, segment: T): void; + insertFromSpec(pos: number, spec: IJSONSegment): void; + localReferencePositionToPosition(lref: ReferencePosition): number; + obliterateRange(start: number, end: number): void; + posFromRelativePos(relativePos: IRelativePosition): number; + removeLocalReferencePosition(lref: LocalReferencePosition): LocalReferencePosition | undefined; + // (undocumented) + removeRange(start: number, end: number): void; + resolveRemoteClientPosition(remoteClientPosition: number, remoteClientRefSeq: number, remoteClientId: string): number | undefined; + walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: TClientData, splitRange?: boolean): void; +} + // @alpha export interface ISharedSegmentSequenceEvents extends ISharedObjectEvents { // (undocumented) @@ -643,7 +680,7 @@ export interface ISharedSegmentSequenceEvents extends ISharedObjectEvents { } // @alpha -export interface ISharedString extends SharedSegmentSequence { +export interface ISharedString extends ISharedSegmentSequence { annotateMarker(marker: Marker, props: PropertySet): void; getMarkerFromId(id: string): ISegment | undefined; getText(start?: number, end?: number): string; @@ -978,63 +1015,6 @@ export abstract class SharedObjectCore extends ErasedType { } -// @alpha (undocumented) -export abstract class SharedSegmentSequence extends SharedObject implements ISharedIntervalCollection, MergeTreeRevertibleDriver { - constructor(dataStoreRuntime: IFluidDataStoreRuntime, id: string, attributes: IChannelAttributes, segmentFromSpec: (spec: IJSONSegment) => ISegment); - annotateRange(start: number, end: number, props: PropertySet): void; - protected applyStashedOp(content: any): void; - // (undocumented) - protected client: Client; - createLocalReferencePosition(segment: T, offset: number, refType: ReferenceType, properties: PropertySet | undefined, slidingPreference?: SlidingPreference, canSlideToEndpoint?: boolean): LocalReferencePosition; - protected didAttach(): void; - getContainingSegment(pos: number): { - segment: T | undefined; - offset: number | undefined; - }; - // (undocumented) - getCurrentSeq(): number; - getIntervalCollection(label: string): IIntervalCollection; - // (undocumented) - getIntervalCollectionLabels(): IterableIterator; - getLength(): number; - getPosition(segment: ISegment): number; - // (undocumented) - getPropertiesAtPosition(pos: number): PropertySet | undefined; - // (undocumented) - getRangeExtentsOfPosition(pos: number): { - posStart: number | undefined; - posAfterEnd: number | undefined; - }; - // @deprecated (undocumented) - groupOperation(groupOp: IMergeTreeGroupMsg): void; - protected guardReentrancy: (callback: () => TRet) => TRet; - // (undocumented) - id: string; - protected initializeLocalCore(): void; - insertAtReferencePosition(pos: ReferencePosition, segment: T): void; - insertFromSpec(pos: number, spec: IJSONSegment): void; - protected loadCore(storage: IChannelStorageService): Promise; - // @deprecated - get loaded(): Promise; - localReferencePositionToPosition(lref: ReferencePosition): number; - obliterateRange(start: number, end: number): void; - protected onConnect(): void; - protected onDisconnect(): void; - posFromRelativePos(relativePos: IRelativePosition): number; - protected processCore(message: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown): void; - protected processGCDataCore(serializer: IFluidSerializer): void; - removeLocalReferencePosition(lref: LocalReferencePosition): LocalReferencePosition | undefined; - // (undocumented) - removeRange(start: number, end: number): void; - protected replaceRange(start: number, end: number, segment: ISegment): void; - resolveRemoteClientPosition(remoteClientPosition: number, remoteClientRefSeq: number, remoteClientId: string): number | undefined; - protected reSubmitCore(content: any, localOpMetadata: unknown): void; - // (undocumented) - readonly segmentFromSpec: (spec: IJSONSegment) => ISegment; - protected summarizeCore(serializer: IFluidSerializer, telemetryContext?: ITelemetryContext): ISummaryTreeWithStats; - walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: TClientData, splitRange?: boolean): void; -} - // @alpha export const SharedString: ISharedObjectKind & SharedObjectKind_2; diff --git a/packages/framework/fluid-framework/src/index.ts b/packages/framework/fluid-framework/src/index.ts index b3d94fba7833..6e96a6686677 100644 --- a/packages/framework/fluid-framework/src/index.ts +++ b/packages/framework/fluid-framework/src/index.ts @@ -118,8 +118,8 @@ export { SequenceEvent, SequenceInterval, SequenceMaintenanceEvent, - SharedSegmentSequence, SharedString, + type ISharedSegmentSequence, } from "@fluidframework/sequence/internal"; export type { diff --git a/packages/framework/undo-redo/api-report/undo-redo.api.md b/packages/framework/undo-redo/api-report/undo-redo.api.md index b8b8e8399cdb..1fb8d07ce017 100644 --- a/packages/framework/undo-redo/api-report/undo-redo.api.md +++ b/packages/framework/undo-redo/api-report/undo-redo.api.md @@ -6,9 +6,9 @@ import { ISegment } from '@fluidframework/merge-tree/internal'; import { ISharedMap } from '@fluidframework/map/internal'; +import { ISharedSegmentSequence } from '@fluidframework/sequence/internal'; import { IValueChanged } from '@fluidframework/map/internal'; import { SequenceDeltaEvent } from '@fluidframework/sequence/internal'; -import { SharedSegmentSequence } from '@fluidframework/sequence/internal'; // @internal (undocumented) export interface IRevertible { @@ -38,7 +38,7 @@ export class SharedMapUndoRedoHandler { // @internal export class SharedSegmentSequenceRevertible implements IRevertible { - constructor(sequence: SharedSegmentSequence); + constructor(sequence: ISharedSegmentSequence); // (undocumented) add(event: SequenceDeltaEvent): void; // (undocumented) @@ -46,16 +46,16 @@ export class SharedSegmentSequenceRevertible implements IRevertible { // (undocumented) revert(): void; // (undocumented) - readonly sequence: SharedSegmentSequence; + readonly sequence: ISharedSegmentSequence; } // @internal export class SharedSegmentSequenceUndoRedoHandler { constructor(stackManager: UndoRedoStackManager); // (undocumented) - attachSequence(sequence: SharedSegmentSequence): void; + attachSequence(sequence: ISharedSegmentSequence): void; // (undocumented) - detachSequence(sequence: SharedSegmentSequence): void; + detachSequence(sequence: ISharedSegmentSequence): void; } // @internal diff --git a/packages/framework/undo-redo/package.json b/packages/framework/undo-redo/package.json index 6f4f29cb794c..03f3cb23c964 100644 --- a/packages/framework/undo-redo/package.json +++ b/packages/framework/undo-redo/package.json @@ -124,6 +124,10 @@ "typescript": "~5.1.6" }, "typeValidation": { - "broken": {} + "broken": { + "ClassDeclaration_SharedSegmentSequenceRevertible": { + "backCompat": false + } + } } } diff --git a/packages/framework/undo-redo/src/sequenceHandler.ts b/packages/framework/undo-redo/src/sequenceHandler.ts index 47fb447bafdb..6da745e29954 100644 --- a/packages/framework/undo-redo/src/sequenceHandler.ts +++ b/packages/framework/undo-redo/src/sequenceHandler.ts @@ -10,7 +10,7 @@ import { discardMergeTreeDeltaRevertible, revertMergeTreeDeltaRevertibles, } from "@fluidframework/merge-tree/internal"; -import { SequenceDeltaEvent, SharedSegmentSequence } from "@fluidframework/sequence/internal"; +import { SequenceDeltaEvent, type ISharedSegmentSequence } from "@fluidframework/sequence/internal"; import { IRevertible, UndoRedoStackManager } from "./undoRedoStackManager.js"; @@ -21,7 +21,7 @@ import { IRevertible, UndoRedoStackManager } from "./undoRedoStackManager.js"; */ export class SharedSegmentSequenceUndoRedoHandler { private readonly sequences = new Map< - SharedSegmentSequence, + ISharedSegmentSequence, SharedSegmentSequenceRevertible | undefined >(); @@ -29,17 +29,17 @@ export class SharedSegmentSequenceUndoRedoHandler { this.stackManager.on("changePushed", () => this.sequences.clear()); } - public attachSequence(sequence: SharedSegmentSequence) { + public attachSequence(sequence: ISharedSegmentSequence) { sequence.on("sequenceDelta", this.sequenceDeltaHandler); } - public detachSequence(sequence: SharedSegmentSequence) { - sequence.removeListener("sequenceDelta", this.sequenceDeltaHandler); + public detachSequence(sequence: ISharedSegmentSequence) { + sequence.off("sequenceDelta", this.sequenceDeltaHandler); } private readonly sequenceDeltaHandler = ( event: SequenceDeltaEvent, - target: SharedSegmentSequence, + target: ISharedSegmentSequence, ) => { if (event.isLocal) { let revertible = this.sequences.get(target); @@ -60,7 +60,7 @@ export class SharedSegmentSequenceUndoRedoHandler { export class SharedSegmentSequenceRevertible implements IRevertible { private readonly revertibles: MergeTreeDeltaRevertible[]; - constructor(public readonly sequence: SharedSegmentSequence) { + constructor(public readonly sequence: ISharedSegmentSequence) { this.revertibles = []; } diff --git a/packages/framework/undo-redo/src/test/types/validateUndoRedoPrevious.generated.ts b/packages/framework/undo-redo/src/test/types/validateUndoRedoPrevious.generated.ts index 82a44c7def03..bdd715fdcd0f 100644 --- a/packages/framework/undo-redo/src/test/types/validateUndoRedoPrevious.generated.ts +++ b/packages/framework/undo-redo/src/test/types/validateUndoRedoPrevious.generated.ts @@ -151,6 +151,7 @@ declare function get_current_ClassDeclaration_SharedSegmentSequenceRevertible(): declare function use_old_ClassDeclaration_SharedSegmentSequenceRevertible( use: TypeOnly): void; use_old_ClassDeclaration_SharedSegmentSequenceRevertible( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_SharedSegmentSequenceRevertible()); /* diff --git a/packages/test/test-end-to-end-tests/src/test/orderSequentially.spec.ts b/packages/test/test-end-to-end-tests/src/test/orderSequentially.spec.ts index 9474598c8590..219e845d9fc8 100644 --- a/packages/test/test-end-to-end-tests/src/test/orderSequentially.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/orderSequentially.spec.ts @@ -12,7 +12,7 @@ import { ContainerRuntime } from "@fluidframework/container-runtime/internal"; import { ConfigTypes, IConfigProviderBase } from "@fluidframework/core-interfaces"; import { Serializable } from "@fluidframework/datastore-definitions/internal"; import type { SharedDirectory, ISharedMap, IValueChanged } from "@fluidframework/map/internal"; -import type { SharedString } from "@fluidframework/sequence/internal"; +import type { ISharedString, SharedString } from "@fluidframework/sequence/internal"; import { ChannelFactoryRegistry, DataObjectFactoryType, @@ -55,7 +55,11 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi let sharedDir: SharedDirectory; let sharedCell: ISharedCell; let sharedMap: ISharedMap; - let changedEventData: (IValueChanged | Serializable)[]; + let changedEventData: ( + | IValueChanged + | Serializable + | InstanceType + )[]; let containerRuntime: ContainerRuntime; let error: Error | undefined; @@ -75,19 +79,19 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi }; container = await provider.makeTestContainer(configWithFeatureGates); dataObject = (await container.getEntryPoint()) as ITestFluidObject; - sharedString = await dataObject.getSharedObject(stringId); - sharedString2 = await dataObject.getSharedObject(string2Id); + sharedString = await dataObject.getSharedObject(stringId); + sharedString2 = await dataObject.getSharedObject(string2Id); sharedDir = await dataObject.getSharedObject(dirId); sharedCell = await dataObject.getSharedObject(cellId); sharedMap = await dataObject.getSharedObject(mapId); containerRuntime = dataObject.context.containerRuntime as ContainerRuntime; changedEventData = []; - sharedString.on("sequenceDelta", (changed, _local, _target) => { - changedEventData.push(changed); + sharedString.on("sequenceDelta", (event, _target) => { + changedEventData.push(event); }); - sharedString2.on("sequenceDelta", (changed, _local, _target) => { - changedEventData.push(changed); + sharedString2.on("sequenceDelta", (event, _target) => { + changedEventData.push(event); }); sharedDir.on("valueChanged", (changed, _local, _target) => { changedEventData.push(changed);