Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions experimental/dds/tree/api-report/experimental-tree.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export abstract class Checkout extends EventEmitterWithErrorHandling<ICheckoutEv
openEdit(): void;
rebaseCurrentEdit(): EditValidationResult.Valid | EditValidationResult.Invalid;
revert(editId: EditId): void;
readonly tree: SharedTree;
readonly tree: ISharedTree;
protected tryApplyChangesInternal(changes: readonly ChangeInternal[]): EditStatus;
// (undocumented)
protected tryApplyChangesInternal(...changes: readonly ChangeInternal[]): EditStatus;
Expand Down Expand Up @@ -286,7 +286,7 @@ export interface EditBase<TChange> {
export interface EditCommittedEventArguments {
readonly editId: EditId;
readonly local: boolean;
readonly tree: SharedTree;
readonly tree: ISharedTree;
}

// @alpha
Expand Down Expand Up @@ -419,12 +419,41 @@ export interface IRevertible {
revert(): any;
}

// @alpha
export interface ISharedTree extends ISharedObject<ISharedTreeEvents>, NodeIdContext {
applyEdit(...changes: readonly Change[]): Edit<InternalizedChange>;
// (undocumented)
applyEdit(changes: readonly Change[]): Edit<InternalizedChange>;
applyEditInternal(editOrChanges: Edit<ChangeInternal> | readonly ChangeInternal[]): Edit<ChangeInternal>;
attributeNodeId(id: NodeId): AttributionId;
readonly attributionId: AttributionId;
// (undocumented)
readonly currentView: RevisionView;
// (undocumented)
readonly edits: OrderedEditSet<InternalizedChange>;
equals(sharedTree: ISharedTree): boolean;
getRuntime(): IFluidDataStoreRuntime;
getWriteFormat(): WriteFormat;
internalizeChange(change: Change): ChangeInternal;
loadSerializedSummary(blobData: string): ITelemetryBaseProperties;
loadSummary(summary: SharedTreeSummaryBase): void;
readonly logger: ITelemetryLoggerExt;
readonly logViewer: LogViewer;
mergeEditsFrom(other: ISharedTree, edits: Iterable<Edit<InternalizedChange>>, stableIdRemapper?: (id: StableNodeId) => StableNodeId): EditId[];
revert(editId: EditId): EditId | undefined;
revertChanges(changes: readonly InternalizedChange[], before: RevisionView): ChangeInternal[] | undefined;
saveSerializedSummary(options?: {
serializer?: IFluidSerializer;
}): string;
saveSummary(): SharedTreeSummaryBase;
}

// @alpha
export interface ISharedTreeEvents extends ISharedObjectEvents {
// (undocumented)
(event: 'committedEdit', listener: EditCommittedHandler): any;
// (undocumented)
(event: 'appliedSequencedEdit', listener: SequencedEditAppliedHandler): any;
(event: 'sequencedEditApplied', listener: SequencedEditAppliedHandler): any;
}

// @alpha
Expand Down Expand Up @@ -589,7 +618,7 @@ export interface SequencedEditAppliedEventArguments {
readonly logger: ITelemetryLoggerExt;
readonly outcome: EditApplicationOutcome;
readonly reconciliationPath: ReconciliationPath;
readonly tree: SharedTree;
readonly tree: ISharedTree;
readonly wasLocal: boolean;
}

Expand Down Expand Up @@ -736,8 +765,8 @@ export interface SharedTreeSummaryBase {
// @alpha
export class SharedTreeUndoRedoHandler {
constructor(stackManager: IUndoConsumer);
attachTree(tree: SharedTree): void;
detachTree(tree: SharedTree): void;
attachTree(tree: ISharedTree): void;
detachTree(tree: ISharedTree): void;
}

// @alpha
Expand Down Expand Up @@ -893,8 +922,7 @@ export class Transaction extends TypedEventEmitter<TransactionEvents> {
get isOpen(): boolean;
readonly startingView: TreeView;
get status(): EditStatus;
// (undocumented)
readonly tree: SharedTree;
readonly tree: ISharedTree;
}

// @alpha
Expand Down
37 changes: 25 additions & 12 deletions experimental/dds/tree/src/Checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,31 @@
* Licensed under the MIT License.
*/

import { IDisposable, IErrorEvent, ITelemetryBaseProperties } from '@fluidframework/core-interfaces';
import type { IDisposable, IErrorEvent, ITelemetryBaseProperties } from '@fluidframework/core-interfaces';
import { assert } from '@fluidframework/core-utils/internal';
import {
ITelemetryLoggerExt,
type ITelemetryLoggerExt,
EventEmitterWithErrorHandling,
createChildLogger,
} from '@fluidframework/telemetry-utils/internal';

import { Change } from './ChangeTypes.js';
import { RestOrArray, assertWithMessage, fail, unwrapRestOrArray } from './Common.js';
import type { Change } from './ChangeTypes.js';
import { type RestOrArray, assertWithMessage, fail, unwrapRestOrArray } from './Common.js';
import { newEditId } from './EditUtilities.js';
import { SharedTreeEvent } from './EventTypes.js';
import { EditId } from './Identifiers.js';
import type { ISharedTree } from './ISharedTree.js';
import type { EditId } from './Identifiers.js';
import { CachingLogViewer } from './LogViewer.js';
import { RevisionView } from './RevisionView.js';
import { EditCommittedHandler, SharedTree } from './SharedTree.js';
import { EditingResult, GenericTransaction, TransactionInternal, ValidEditingResult } from './TransactionInternal.js';
import { TreeView } from './TreeView.js';
import { ChangeInternal, Edit, EditStatus } from './persisted-types/index.js';
import type { RevisionView } from './RevisionView.js';
import type { EditCommittedHandler, SharedTree } from './SharedTree.js';
import {
type EditingResult,
type GenericTransaction,
TransactionInternal,
type ValidEditingResult,
} from './TransactionInternal.js';
import type { TreeView } from './TreeView.js';
import { type ChangeInternal, type Edit, EditStatus } from './persisted-types/index.js';

/**
* An event emitted by a `Checkout` to indicate a state change. See {@link ICheckoutEvents} for event argument information.
Expand Down Expand Up @@ -104,7 +110,13 @@ export abstract class Checkout extends EventEmitterWithErrorHandling<ICheckoutEv
/**
* The shared tree this checkout views/edits.
*/
public readonly tree: SharedTree;
public readonly tree: ISharedTree;

/**
* Concrete reference to the tree for internal operations that require
* access to SharedTree-specific implementation details (e.g. emit, instanceof checks).
*/
private readonly _concreteTree: SharedTree;

/**
* `tree`'s log viewer as a CachingLogViewer if it is one, otherwise undefined.
Expand All @@ -127,9 +139,10 @@ export abstract class Checkout extends EventEmitterWithErrorHandling<ICheckoutEv

protected constructor(tree: SharedTree, currentView: RevisionView, onEditCommitted: EditCommittedHandler) {
super((_event, error: unknown) => {
this.tree.emit('error', error);
this._concreteTree.emit('error', error);
});
this.tree = tree;
this._concreteTree = tree;
this.logger = createChildLogger({ logger: this.tree.logger, namespace: 'Checkout' });
if (tree.logViewer instanceof CachingLogViewer) {
this.cachingLogViewer = tree.logViewer;
Expand Down
138 changes: 138 additions & 0 deletions experimental/dds/tree/src/ISharedTree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*!
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/

import type { ITelemetryBaseProperties } from '@fluidframework/core-interfaces';
import type { IFluidDataStoreRuntime } from '@fluidframework/datastore-definitions/internal';
import type { ISharedObject, IFluidSerializer } from '@fluidframework/shared-object-base/internal';
import type { ITelemetryLoggerExt } from '@fluidframework/telemetry-utils/internal';

import type { Change } from './ChangeTypes.js';
import type { OrderedEditSet } from './EditLog.js';
import type { AttributionId, EditId, NodeId, StableNodeId } from './Identifiers.js';
import type { LogViewer } from './LogViewer.js';
import type { NodeIdContext } from './NodeIdUtilities.js';
import type { RevisionView } from './RevisionView.js';
import type { ISharedTreeEvents } from './SharedTree.js';
import type {
ChangeInternal,
Edit,
InternalizedChange,
SharedTreeSummaryBase,
WriteFormat,
} from './persisted-types/index.js';

/**
* A {@link https://github.com/microsoft/FluidFramework/blob/main/experimental/dds/tree/README.md | distributed tree}.
* @alpha
*/
export interface ISharedTree extends ISharedObject<ISharedTreeEvents>, NodeIdContext {
/**
* The UUID used for attribution of nodes created by this SharedTree.
*/
readonly attributionId: AttributionId;

/**
* Viewer for trees defined by the edit log. This allows access to views of the tree at different revisions.
*/
readonly logViewer: LogViewer;

/**
* Logger for SharedTree events.
*/
readonly logger: ITelemetryLoggerExt;

/**
* @returns the current view of the tree.
*/
readonly currentView: RevisionView;

/**
* @returns the edit history of the tree.
*/
readonly edits: OrderedEditSet<InternalizedChange>;

/**
* The write format version currently used by this `SharedTree`.
*/
getWriteFormat(): WriteFormat;

/**
* Applies a set of changes to this tree.
*/
applyEdit(...changes: readonly Change[]): Edit<InternalizedChange>;
applyEdit(changes: readonly Change[]): Edit<InternalizedChange>;

/**
* Applies a set of internal changes to this tree.
* This is exposed for internal use only.
*/
applyEditInternal(editOrChanges: Edit<ChangeInternal> | readonly ChangeInternal[]): Edit<ChangeInternal>;

/**
* Converts a public Change type to an internal representation.
* This is exposed for internal use only.
*/
internalizeChange(change: Change): ChangeInternal;

/**
* Merges `edits` from `other` into this SharedTree.
*/
mergeEditsFrom(
other: ISharedTree,
edits: Iterable<Edit<InternalizedChange>>,
stableIdRemapper?: (id: StableNodeId) => StableNodeId
): EditId[];

/**
* Reverts a previous edit by applying a new edit containing the inverse of the original edit's changes.
* @param editId - the edit to revert
* @returns the id of the new edit, or undefined if the original edit could not be inverted given the current tree state.
*/
revert(editId: EditId): EditId | undefined;

/**
* Revert the given changes.
* @param changes - the changes to revert
* @param before - the revision view before the changes were originally applied
* @returns the inverse of `changes` or undefined if the changes could not be inverted for the given tree state.
*/
revertChanges(changes: readonly InternalizedChange[], before: RevisionView): ChangeInternal[] | undefined;

/**
* Returns the attribution ID associated with the SharedTree that generated the given node ID.
*/
attributeNodeId(id: NodeId): AttributionId;

/**
* Compares this shared tree to another for equality.
*/
equals(sharedTree: ISharedTree): boolean;

/**
* Gets the runtime associated with this SharedTree.
*/
getRuntime(): IFluidDataStoreRuntime;

/**
* Saves this SharedTree into a deserialized summary.
*/
saveSummary(): SharedTreeSummaryBase;

/**
* Initialize shared tree with a deserialized summary.
*/
loadSummary(summary: SharedTreeSummaryBase): void;

/**
* Saves this SharedTree into a serialized summary. This is used for testing.
*/
saveSerializedSummary(options?: { serializer?: IFluidSerializer }): string;

/**
* Initialize shared tree with a serialized summary. This is used for testing.
* @returns Statistics about the loaded summary.
*/
loadSerializedSummary(blobData: string): ITelemetryBaseProperties;
}
15 changes: 8 additions & 7 deletions experimental/dds/tree/src/MergeHealth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { ITelemetryLoggerExt } from '@fluidframework/telemetry-utils/internal';
import { assertNotUndefined, fail } from './Common.js';
import { PlaceValidationResult, RangeValidationResultKind } from './EditUtilities.js';
import { SharedTreeEvent } from './EventTypes.js';
import { SequencedEditAppliedEventArguments, SharedTree } from './SharedTree.js';
import type { ISharedTree } from './ISharedTree.js';
import { SequencedEditAppliedEventArguments } from './SharedTree.js';
import { TransactionInternal } from './TransactionInternal.js';
import { EditStatus } from './persisted-types/index.js';

Expand All @@ -18,7 +19,7 @@ import { EditStatus } from './persisted-types/index.js';
* @param tree - The tree for which to log the telemetry.
* @internal
*/
export function useFailedSequencedEditTelemetry(tree: SharedTree): { disable: () => void } {
export function useFailedSequencedEditTelemetry(tree: ISharedTree): { disable: () => void } {
function onEdit({ wasLocal, logger, outcome }: SequencedEditAppliedEventArguments): void {
if (wasLocal && outcome.status !== EditStatus.Applied) {
logger.send({
Expand Down Expand Up @@ -210,14 +211,14 @@ export interface MergeHealthStats {
*/
export class SharedTreeMergeHealthTelemetryHeartbeat {
private heartbeatTimerId = 0;
private readonly treeData = new Map<SharedTree, { tally: MergeHealthStats; logger?: ITelemetryLoggerExt }>();
private readonly treeData = new Map<ISharedTree, { tally: MergeHealthStats; logger?: ITelemetryLoggerExt }>();

/**
* Adds a tree to the set of tree to log merge health telemetry for.
* Noop if such a tree was already in the set.
* @param tree - The tree to log merge health telemetry for.
*/
public attachTree(tree: SharedTree): void {
public attachTree(tree: ISharedTree): void {
if (this.treeData.has(tree) === false) {
this.resetTreeData(tree);
tree.on(SharedTreeEvent.SequencedEditApplied, this.sequencedEditHandler);
Expand All @@ -229,7 +230,7 @@ export class SharedTreeMergeHealthTelemetryHeartbeat {
* Noop if such a tree was never in the set.
* @param tree - The tree to stop logging merge health telemetry for.
*/
public detachTree(tree: SharedTree): void {
public detachTree(tree: ISharedTree): void {
if (this.treeData.has(tree)) {
tree.off(SharedTreeEvent.SequencedEditApplied, this.sequencedEditHandler);
this.treeData.delete(tree);
Expand All @@ -241,7 +242,7 @@ export class SharedTreeMergeHealthTelemetryHeartbeat {
* @param tree - The tree to get stats for.
* @returns Aggregated statistics about merge health for the given tree.
*/
public getStats(tree: SharedTree): MergeHealthStats {
public getStats(tree: ISharedTree): MergeHealthStats {
return assertNotUndefined(this.treeData.get(tree), 'No such tree was attached to the logger').tally;
}

Expand All @@ -258,7 +259,7 @@ export class SharedTreeMergeHealthTelemetryHeartbeat {
* Resets the aggregated merge health data for the given tree.
* @param tree - The tree to reset the merge health data for.
*/
public resetTreeData(tree: SharedTree): void {
public resetTreeData(tree: ISharedTree): void {
this.treeData.set(tree, {
tally: {
maxAttemptCount: 0,
Expand Down
7 changes: 4 additions & 3 deletions experimental/dds/tree/src/SharedTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
} from './EditUtilities.js';
import { SharedTreeDiagnosticEvent, SharedTreeEvent } from './EventTypes.js';
import { revert } from './HistoryEditFactory.js';
import type { ISharedTree } from './ISharedTree.js';
import { convertEditIds } from './IdConversion.js';
import {
AttributionId,
Expand Down Expand Up @@ -297,7 +298,7 @@ export interface EditCommittedEventArguments {
/** Whether or not this is a local edit. */
readonly local: boolean;
/** The tree the edit was committed on. Required for local edit events handled by SharedTreeUndoRedoHandler. */
readonly tree: SharedTree;
readonly tree: ISharedTree;
}

/**
Expand All @@ -310,7 +311,7 @@ export interface SequencedEditAppliedEventArguments {
/** Whether or not this was a local edit. */
readonly wasLocal: boolean;
/** The tree the edit was applied to. */
readonly tree: SharedTree;
readonly tree: ISharedTree;
/** The telemetry logger associated with sequenced edit application. */
readonly logger: ITelemetryLoggerExt;
/** The reconciliation path for the edit. See {@link ReconciliationPath} for details. */
Expand Down Expand Up @@ -351,7 +352,7 @@ export type EditApplicationOutcome =
*/
export interface ISharedTreeEvents extends ISharedObjectEvents {
(event: 'committedEdit', listener: EditCommittedHandler);
(event: 'appliedSequencedEdit', listener: SequencedEditAppliedHandler);
(event: 'sequencedEditApplied', listener: SequencedEditAppliedHandler);
}

/**
Expand Down
Loading
Loading