diff --git a/experimental/dds/attributable-map/api-report/attributable-map.api.md b/experimental/dds/attributable-map/api-report/attributable-map.api.md index d4704a4a5e7a..983d92313b5f 100644 --- a/experimental/dds/attributable-map/api-report/attributable-map.api.md +++ b/experimental/dds/attributable-map/api-report/attributable-map.api.md @@ -89,9 +89,9 @@ export interface IValueChanged { // @internal export class LocalValueMaker { - constructor(); + constructor(serializer: IFluidSerializer); fromInMemory(value: unknown): ILocalValue; - fromSerializable(serializable: ISerializableValue, serializer: IFluidSerializer, bind: IFluidHandle): ILocalValue; + fromSerializable(serializable: ISerializableValue): ILocalValue; } // @internal @sealed diff --git a/experimental/dds/attributable-map/src/localValues.ts b/experimental/dds/attributable-map/src/localValues.ts index 2c2ac686caa4..fca24357e8d8 100644 --- a/experimental/dds/attributable-map/src/localValues.ts +++ b/experimental/dds/attributable-map/src/localValues.ts @@ -114,19 +114,16 @@ export class PlainLocalValue implements ILocalValue { export class LocalValueMaker { /** * Create a new LocalValueMaker. + * @param serializer - The serializer to serialize / parse handles. */ - public constructor() {} + public constructor(private readonly serializer: IFluidSerializer) {} /** * Create a new local value from an incoming serialized value. * @param serializable - The serializable value to make local */ - public fromSerializable( - // eslint-disable-next-line import/no-deprecated - serializable: ISerializableValue, - serializer: IFluidSerializer, - bind: IFluidHandle, - ): ILocalValue { + // eslint-disable-next-line import/no-deprecated + public fromSerializable(serializable: ISerializableValue): ILocalValue { // Migrate from old shared value to handles if (serializable.type === ValueType[ValueType.Shared]) { serializable.type = ValueType[ValueType.Plain]; @@ -134,14 +131,12 @@ export class LocalValueMaker { type: "__fluid_handle__", url: serializable.value as string, }; - - // NOTE: here we require the use of `parseHandles` because the roundtrip - // through a string is necessary to resolve the absolute path of - // legacy handles (`ValueType.Shared`) - serializable.value = serializer.encode(parseHandles(handle, serializer), bind); + serializable.value = handle; } - return new PlainLocalValue(serializable.value); + const translatedValue: unknown = parseHandles(serializable.value, this.serializer); + + return new PlainLocalValue(translatedValue); } /** diff --git a/experimental/dds/attributable-map/src/mapKernel.ts b/experimental/dds/attributable-map/src/mapKernel.ts index 3a5223d91e20..0f0c8a30443f 100644 --- a/experimental/dds/attributable-map/src/mapKernel.ts +++ b/experimental/dds/attributable-map/src/mapKernel.ts @@ -4,7 +4,7 @@ */ import { IFluidHandle } from "@fluidframework/core-interfaces"; -import { IFluidSerializer, ValueType, bindHandles } from "@fluidframework/shared-object-base"; +import { IFluidSerializer, ValueType } from "@fluidframework/shared-object-base"; import { TypedEventEmitter } from "@fluid-internal/client-utils"; import { assert, unreachableCase } from "@fluidframework/core-utils"; import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; @@ -193,7 +193,7 @@ export class AttributableMapKernel { private readonly isAttached: () => boolean, private readonly eventEmitter: TypedEventEmitter, ) { - this.localValueMaker = new LocalValueMaker(); + this.localValueMaker = new LocalValueMaker(serializer); this.messageHandlers = this.getMessageHandlers(); this.attribution = new Map(); } @@ -305,6 +305,7 @@ export class AttributableMapKernel { // Create a local value and serialize it. const localValue = this.localValueMaker.fromInMemory(value); + const serializableValue = makeSerializable(localValue, this.serializer, this.handle); // Set the value and attribution locally. const previousValue = this.setCore(key, localValue, true); @@ -312,17 +313,13 @@ export class AttributableMapKernel { // If we are not attached, don't submit the op. if (!this.isAttached()) { - // this is necessary to bind the potential handles in the value - // to this DDS, as we do not walk the object normally unless we - // are attached - bindHandles(localValue.value, this.serializer, this.handle); return; } const op: IMapSetOperation = { key, type: "set", - value: { type: localValue.type, value: localValue.value as unknown }, + value: serializableValue, }; this.submitMapKeyMessage(op, previousValue); } @@ -455,9 +452,7 @@ export class AttributableMapKernel { * @param json - A JSON string containing serialized map data */ public populateFromSerializable(json: IMapDataObjectSerializable): void { - for (const [key, serializable] of Object.entries( - this.serializer.decode(json) as IMapDataObjectSerializable, - )) { + for (const [key, serializable] of Object.entries(json)) { const localValue = { key, value: this.makeLocal(key, serializable), @@ -479,6 +474,10 @@ export class AttributableMapKernel { } } + public populate(json: string): void { + this.populateFromSerializable(JSON.parse(json) as IMapDataObjectSerializable); + } + /** * Submit the given op if a handler is registered. * @param op - The operation to attempt to submit @@ -507,11 +506,7 @@ export class AttributableMapKernel { break; } case "set": { - this.set( - op.key, - this.localValueMaker.fromSerializable(op.value, this.serializer, this.handle) - .value, - ); + this.set(op.key, this.localValueMaker.fromSerializable(op.value).value); break; } default: @@ -674,11 +669,7 @@ export class AttributableMapKernel { serializable.type === ValueType[ValueType.Plain] || serializable.type === ValueType[ValueType.Shared] ) { - return this.localValueMaker.fromSerializable( - serializable, - this.serializer, - this.handle, - ); + return this.localValueMaker.fromSerializable(serializable); } else { throw new Error("Unknown local value type"); } diff --git a/experimental/dds/tree/api-report/experimental-tree.api.md b/experimental/dds/tree/api-report/experimental-tree.api.md index 92e5cd95367c..1b4eb6a209d4 100644 --- a/experimental/dds/tree/api-report/experimental-tree.api.md +++ b/experimental/dds/tree/api-report/experimental-tree.api.md @@ -974,7 +974,7 @@ export class SharedTreeFactory implements IChannelFactory { static Attributes: IChannelAttributes; get attributes(): IChannelAttributes; create(runtime: IFluidDataStoreRuntime, id: string): SharedTree; - load(runtime: IFluidDataStoreRuntime, id: string, services: IChannelServices, _channelAttributes: Readonly): Promise; + load(runtime: IFluidDataStoreRuntime, id: string, services: IChannelServices, _channelAttributes: Readonly): Promise; static Type: string; get type(): string; } diff --git a/experimental/dds/tree/src/SharedTree.ts b/experimental/dds/tree/src/SharedTree.ts index 85c6698934ea..951b31386a29 100644 --- a/experimental/dds/tree/src/SharedTree.ts +++ b/experimental/dds/tree/src/SharedTree.ts @@ -12,6 +12,7 @@ import { IChannelFactory, IChannelAttributes, IChannelServices, + IChannel, } from '@fluidframework/datastore-definitions'; import { AttachState } from '@fluidframework/container-definitions'; import { @@ -245,7 +246,7 @@ export class SharedTreeFactory implements IChannelFactory { id: string, services: IChannelServices, _channelAttributes: Readonly - ): Promise { + ): Promise { const sharedTree = this.createSharedTree(runtime, id); await sharedTree.load(services); return sharedTree; @@ -1066,14 +1067,28 @@ export class SharedTree extends SharedObject implements NodeI // TODO:Type Safety: Improve type safety around op sending/parsing (e.g. discriminated union over version field somehow) switch (op.version) { case WriteFormat.v0_0_2: - return this.encoder_0_0_2.decodeEditOp(op, (x) => x, this); + return this.encoder_0_0_2.decodeEditOp(op, this.encodeSemiSerializedEdit.bind(this), this); case WriteFormat.v0_1_1: - return this.encoder_0_1_1.decodeEditOp(op, (x) => x, this.idNormalizer, this.interner); + return this.encoder_0_1_1.decodeEditOp( + op, + this.encodeSemiSerializedEdit.bind(this), + this.idNormalizer, + this.interner + ); default: fail('Unknown op version'); } } + private encodeSemiSerializedEdit(semiSerializedEdit: Edit): Edit { + // semiSerializedEdit may have handles which have been replaced by `serializer.encode`. + // Since there is no API to un-replace them except via parse, re-stringify the edit, then parse it. + // Stringify using JSON, not IFluidSerializer since OPs use JSON directly. + // TODO:Performance:#48025: Avoid this serialization round trip. + const encodedEdit: Edit = this.serializer.parse(JSON.stringify(semiSerializedEdit)); + return encodedEdit; + } + private processSequencedEdit(edit: Edit, message: ISequencedDocumentMessage): void { const { id: editId } = edit; const wasLocalEdit = this.editLog.isLocalEdit(editId); @@ -1366,13 +1381,13 @@ export class SharedTree extends SharedObject implements NodeI if (this.isAttached()) { switch (this.writeFormat) { case WriteFormat.v0_0_2: - this.submitOp(this.encoder_0_0_2.encodeEditOp(edit, (x) => x, this)); + this.submitOp(this.encoder_0_0_2.encodeEditOp(edit, this.serializeEdit.bind(this), this)); break; case WriteFormat.v0_1_1: this.submitOp( this.encoder_0_1_1.encodeEditOp( edit, - (x) => x, + this.serializeEdit.bind(this), this.idCompressor.takeNextCreationRange(), this.idNormalizer, this.interner @@ -1385,6 +1400,10 @@ export class SharedTree extends SharedObject implements NodeI } } + private serializeEdit(preparedEdit: Edit): Edit { + return this.serializer.encode(preparedEdit, this.handle) as Edit; + } + /** A type-safe `submitLocalMessage` wrapper to enforce op format */ private submitOp(content: SharedTreeOp | SharedTreeOp_0_0_2, localOpMetadata: unknown = undefined): void { assert( @@ -1472,7 +1491,7 @@ export class SharedTree extends SharedObject implements NodeI stashedEdit = this.encoder_0_1_1.decodeEditOp( sharedTreeOp, - (x) => x, + this.encodeSemiSerializedEdit.bind(this), normalizer, this.interner ); diff --git a/experimental/dds/tree/src/migration-shim/migrationShim.ts b/experimental/dds/tree/src/migration-shim/migrationShim.ts index 330b607be373..2c28c0632d11 100644 --- a/experimental/dds/tree/src/migration-shim/migrationShim.ts +++ b/experimental/dds/tree/src/migration-shim/migrationShim.ts @@ -199,12 +199,12 @@ export class MigrationShim extends EventEmitterWithErrorHandling * {@inheritDoc ISharedCell.set} */ public set(value: Serializable): void { + // Serialize the value if required. + const operationValue: ICellValue = { + value: this.serializer.encode(value, this.handle), + }; + // Set the value locally. const previousValue = this.setCore(value); this.setAttribution(); @@ -145,10 +150,6 @@ export class SharedCell return; } - const operationValue: ICellValue = { - value, - }; - const op: ISetCellOperation = { type: "setCell", value: operationValue, @@ -225,8 +226,7 @@ export class SharedCell protected async loadCore(storage: IChannelStorageService): Promise { const content = await readAndParse(storage, snapshotFileName); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - this.data = this.serializer.decode(content.value); + this.data = this.decode(content); this.attribution = content.attribution; } @@ -250,7 +250,7 @@ export class SharedCell private applyInnerOp(content: ICellOperation): Serializable | undefined { switch (content.type) { case "setCell": { - return this.setCore(content.value.value as Serializable); + return this.setCore(this.decode(content.value)); } case "deleteCell": { @@ -322,6 +322,11 @@ export class SharedCell return previousLocalValue; } + private decode(cellValue: ICellValue): Serializable { + const value = cellValue.value; + return this.serializer.decode(value) as Serializable; + } + private createLocalOpMetadata( op: ICellOperation, previousValue?: Serializable, @@ -346,7 +351,7 @@ export class SharedCell break; } case "setCell": { - this.set(cellContent.value.value as Serializable); + this.set(this.decode(cellContent.value)); break; } default: { diff --git a/packages/dds/cell/src/cellFactory.ts b/packages/dds/cell/src/cellFactory.ts index f5b0bb99fd6d..c72ea0bd7a31 100644 --- a/packages/dds/cell/src/cellFactory.ts +++ b/packages/dds/cell/src/cellFactory.ts @@ -15,10 +15,8 @@ import { pkgVersion } from "./packageVersion.js"; /** * {@link @fluidframework/datastore-definitions#IChannelFactory} for {@link ISharedCell}. - * - * @sealed - * * @internal + * @sealed */ export class CellFactory implements IChannelFactory { /** @@ -51,6 +49,7 @@ export class CellFactory implements IChannelFactory { /** * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory.load} + * @internal */ public async load( runtime: IFluidDataStoreRuntime, @@ -65,6 +64,7 @@ export class CellFactory implements IChannelFactory { /** * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory.create} + * @internal */ public create(document: IFluidDataStoreRuntime, id: string): ISharedCell { const cell = new SharedCell(id, document, this.attributes); diff --git a/packages/dds/map/api-report/map.api.md b/packages/dds/map/api-report/map.api.md index 3404ad521524..d34bc422564a 100644 --- a/packages/dds/map/api-report/map.api.md +++ b/packages/dds/map/api-report/map.api.md @@ -185,9 +185,9 @@ export interface IValueChanged { // @alpha export class LocalValueMaker { - constructor(); + constructor(serializer: IFluidSerializer); fromInMemory(value: unknown): ILocalValue; - fromSerializable(serializable: ISerializableValue, serializer: IFluidSerializer, bind: IFluidHandle): ILocalValue; + fromSerializable(serializable: ISerializableValue): ILocalValue; } // @alpha @sealed diff --git a/packages/dds/map/src/directory.ts b/packages/dds/map/src/directory.ts index 6c6951dda5a7..7962f3978152 100644 --- a/packages/dds/map/src/directory.ts +++ b/packages/dds/map/src/directory.ts @@ -16,12 +16,7 @@ import { IChannelFactory, } from "@fluidframework/datastore-definitions"; import { ISummaryTreeWithStats, ITelemetryContext } from "@fluidframework/runtime-definitions"; -import { - IFluidSerializer, - SharedObject, - ValueType, - parseHandles, -} from "@fluidframework/shared-object-base"; +import { IFluidSerializer, SharedObject, ValueType } from "@fluidframework/shared-object-base"; import { SummaryTreeBuilder } from "@fluidframework/runtime-utils"; import path from "path-browserify"; import { RedBlackTree } from "@fluidframework/merge-tree"; @@ -526,7 +521,7 @@ export class SharedDirectory attributes: IChannelAttributes, ) { super(id, runtime, attributes, "fluid_directory_"); - this.localValueMaker = new LocalValueMaker(); + this.localValueMaker = new LocalValueMaker(this.serializer); this.setMessageHandlers(); // Mirror the containedValueChanged op on the SharedDirectory this.root.on("containedValueChanged", (changed: IValueChanged, local: boolean) => { @@ -835,8 +830,7 @@ export class SharedDirectory const localValue = this.makeLocal( key, currentSubDir.absolutePath, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - parseHandles(serializable, this.serializer), + serializable, ); currentSubDir.populateStorage(key, localValue); } @@ -900,7 +894,7 @@ export class SharedDirectory serializable.type === ValueType[ValueType.Shared], 0x1e4 /* "Unexpected serializable type" */, ); - return this.localValueMaker.fromSerializable(serializable, this.serializer, this.handle); + return this.localValueMaker.fromSerializable(serializable); } /** @@ -1072,11 +1066,7 @@ export class SharedDirectory case "set": { dir?.set( directoryOp.key, - this.localValueMaker.fromSerializable( - directoryOp.value, - this.serializer, - this.handle, - ).value, + this.localValueMaker.fromSerializable(directoryOp.value).value, ); break; } diff --git a/packages/dds/map/src/localValues.ts b/packages/dds/map/src/localValues.ts index 529e41e42d6f..cf2fe1182494 100644 --- a/packages/dds/map/src/localValues.ts +++ b/packages/dds/map/src/localValues.ts @@ -103,19 +103,16 @@ export class PlainLocalValue implements ILocalValue { export class LocalValueMaker { /** * Create a new LocalValueMaker. + * @param serializer - The serializer to serialize / parse handles. */ - public constructor() {} + public constructor(private readonly serializer: IFluidSerializer) {} /** * Create a new local value from an incoming serialized value. * @param serializable - The serializable value to make local */ - public fromSerializable( - // eslint-disable-next-line import/no-deprecated - serializable: ISerializableValue, - serializer: IFluidSerializer, - bind: IFluidHandle, - ): ILocalValue { + // eslint-disable-next-line import/no-deprecated + public fromSerializable(serializable: ISerializableValue): ILocalValue { // Migrate from old shared value to handles if (serializable.type === ValueType[ValueType.Shared]) { serializable.type = ValueType[ValueType.Plain]; @@ -123,14 +120,12 @@ export class LocalValueMaker { type: "__fluid_handle__", url: serializable.value as string, }; - // NOTE: here we require the use of `parseHandles` because the roundtrip - // through a string is necessary to resolve the absolute path of - // legacy handles (`ValueType.Shared`) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - serializable.value = serializer.encode(parseHandles(handle, serializer), bind); + serializable.value = handle; } - return new PlainLocalValue(serializable.value); + const translatedValue: unknown = parseHandles(serializable.value, this.serializer); + + return new PlainLocalValue(translatedValue); } /** diff --git a/packages/dds/map/src/mapKernel.ts b/packages/dds/map/src/mapKernel.ts index 42df96b0a999..fde229ebd149 100644 --- a/packages/dds/map/src/mapKernel.ts +++ b/packages/dds/map/src/mapKernel.ts @@ -4,7 +4,7 @@ */ import { IFluidHandle } from "@fluidframework/core-interfaces"; -import { IFluidSerializer, ValueType, bindHandles } from "@fluidframework/shared-object-base"; +import { IFluidSerializer, ValueType } from "@fluidframework/shared-object-base"; import { assert, unreachableCase } from "@fluidframework/core-utils"; import { TypedEventEmitter } from "@fluid-internal/client-utils"; // eslint-disable-next-line import/no-deprecated @@ -182,7 +182,7 @@ export class MapKernel { private readonly isAttached: () => boolean, private readonly eventEmitter: TypedEventEmitter, ) { - this.localValueMaker = new LocalValueMaker(); + this.localValueMaker = new LocalValueMaker(serializer); this.messageHandlers = this.getMessageHandlers(); } @@ -293,23 +293,20 @@ export class MapKernel { // Create a local value and serialize it. const localValue = this.localValueMaker.fromInMemory(value); + const serializableValue = makeSerializable(localValue, this.serializer, this.handle); // Set the value locally. const previousValue = this.setCore(key, localValue, true); // If we are not attached, don't submit the op. if (!this.isAttached()) { - // this is necessary to bind the potential handles in the value - // to this DDS, as we do not walk the object normally unless we - // are attached - bindHandles(localValue.value, this.serializer, this.handle); return; } const op: IMapSetOperation = { key, type: "set", - value: { type: localValue.type, value: localValue.value as unknown }, + value: serializableValue, }; this.submitMapKeyMessage(op, previousValue); } @@ -390,9 +387,7 @@ export class MapKernel { * @param data - A JSON string containing serialized map data */ public populateFromSerializable(json: IMapDataObjectSerializable): void { - for (const [key, serializable] of Object.entries( - this.serializer.decode(json) as IMapDataObjectSerializable, - )) { + for (const [key, serializable] of Object.entries(json)) { const localValue = { key, value: this.makeLocal(key, serializable), @@ -402,6 +397,10 @@ export class MapKernel { } } + public populate(json: string): void { + this.populateFromSerializable(JSON.parse(json) as IMapDataObjectSerializable); + } + /** * Submit the given op if a handler is registered. * @param op - The operation to attempt to submit @@ -592,11 +591,7 @@ export class MapKernel { serializable.type === ValueType[ValueType.Plain] || serializable.type === ValueType[ValueType.Shared] ) { - return this.localValueMaker.fromSerializable( - serializable, - this.serializer, - this.handle, - ); + return this.localValueMaker.fromSerializable(serializable); } else { throw new Error("Unknown local value type"); } diff --git a/packages/dds/matrix/api-report/matrix.api.md b/packages/dds/matrix/api-report/matrix.api.md index 8be5686880d0..620be25abaed 100644 --- a/packages/dds/matrix/api-report/matrix.api.md +++ b/packages/dds/matrix/api-report/matrix.api.md @@ -64,7 +64,7 @@ export type MatrixItem = Serializable> | undefined; // @alpha export class SharedMatrix extends SharedObject & ISharedObjectEvents> implements ISharedMatrix { constructor(runtime: IFluidDataStoreRuntime, id: string, attributes: IChannelAttributes, _isSetCellConflictResolutionPolicyFWW?: boolean); - protected applyStashedOp(_content: unknown): void; + protected applyStashedOp(content: unknown): void; // (undocumented) closeMatrix(consumer: IMatrixConsumer>): void; // (undocumented) @@ -95,7 +95,7 @@ export class SharedMatrix extends SharedObject & openMatrix(consumer: IMatrixConsumer>): IMatrixReader>; openUndo(consumer: IUndoConsumer): void; // (undocumented) - protected processCore(msg: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown): void; + protected processCore(rawMessage: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown): void; protected processGCDataCore(serializer: IFluidSerializer): void; // (undocumented) removeCols(colStart: number, count: number): void; @@ -129,7 +129,7 @@ export class SharedMatrixFactory implements IChannelFactory { // (undocumented) get attributes(): IChannelAttributes; // (undocumented) - create(document: IFluidDataStoreRuntime, id: string): SharedMatrix; + create(document: IFluidDataStoreRuntime, id: string): IChannel; load(runtime: IFluidDataStoreRuntime, id: string, services: IChannelServices, attributes: IChannelAttributes): Promise; // (undocumented) static Type: string; diff --git a/packages/dds/matrix/src/matrix.ts b/packages/dds/matrix/src/matrix.ts index cad5df76cd01..0c56b544f2da 100644 --- a/packages/dds/matrix/src/matrix.ts +++ b/packages/dds/matrix/src/matrix.ts @@ -15,6 +15,8 @@ import { import { IFluidSerializer, ISharedObjectEvents, + makeHandlesSerializable, + parseHandles, SharedObject, } from "@fluidframework/shared-object-base"; import { ISummaryTreeWithStats } from "@fluidframework/runtime-definitions"; @@ -629,7 +631,10 @@ export class SharedMatrix ); this.inFlightRefSeqs.push(this.runtime.deltaManager.lastSequenceNumber); - super.submitLocalMessage(message, localOpMetadata); + super.submitLocalMessage( + makeHandlesSerializable(message, this.serializer, this.handle), + localOpMetadata, + ); // Ensure that row/col 'localSeq' are synchronized (see 'nextLocalSeq()'). assert( @@ -805,7 +810,7 @@ export class SharedMatrix } protected processCore( - msg: ISequencedDocumentMessage, + rawMessage: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown, ) { @@ -820,6 +825,7 @@ export class SharedMatrix // See "handles stashed ops created on top of sequenced local ops" for one such test case. // assert(recordedRefSeq <= message.referenceSequenceNumber, "RefSeq mismatch"); } + const msg = parseHandles(rawMessage, this.serializer); const contents = msg.contents as MatrixSetOrVectorOp; const target = contents.target; @@ -842,10 +848,10 @@ export class SharedMatrix this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1; // If this is the first op notifying us of the policy change, then set the policy change seq number. if (this.setCellLwwToFwwPolicySwitchOpSeqNumber === -1 && fwwMode === true) { - this.setCellLwwToFwwPolicySwitchOpSeqNumber = msg.sequenceNumber; + this.setCellLwwToFwwPolicySwitchOpSeqNumber = rawMessage.sequenceNumber; } - assert(msg.clientId !== null, 0x861 /* clientId should not be null!! */); + assert(rawMessage.clientId !== null, 0x861 /* clientId should not be null!! */); if (local) { // We are receiving the ACK for a local pending set operation. const { rowHandle, colHandle, localSeq, rowsRef, colsRef } = @@ -861,12 +867,12 @@ export class SharedMatrix // If policy is not switched, then also update the tracker in case it is the latest. if ( (this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1 && - this.shouldSetCellBasedOnFWW(rowHandle, colHandle, msg)) || + this.shouldSetCellBasedOnFWW(rowHandle, colHandle, rawMessage)) || (this.setCellLwwToFwwPolicySwitchOpSeqNumber === -1 && isLatestPendingOp) ) { this.cellLastWriteTracker.setCell(rowHandle, colHandle, { - seqNum: msg.sequenceNumber, - clientId: msg.clientId, + seqNum: rawMessage.sequenceNumber, + clientId: rawMessage.clientId, }); } @@ -874,9 +880,9 @@ export class SharedMatrix this.pending.setCell(rowHandle, colHandle, undefined); } } else { - const adjustedRow = this.rows.adjustPosition(row, msg); + const adjustedRow = this.rows.adjustPosition(row, rawMessage); if (adjustedRow !== undefined) { - const adjustedCol = this.cols.adjustPosition(col, msg); + const adjustedCol = this.cols.adjustPosition(col, rawMessage); if (adjustedCol !== undefined) { const rowHandle = this.rows.getAllocatedHandle(adjustedRow); @@ -892,13 +898,13 @@ export class SharedMatrix // overwrite the cell and raise conflict if we have pending changes as our change is going to be lost. if ( !isPreviousSetCellPolicyModeFWW || - this.shouldSetCellBasedOnFWW(rowHandle, colHandle, msg) + this.shouldSetCellBasedOnFWW(rowHandle, colHandle, rawMessage) ) { const previousValue = this.cells.getCell(rowHandle, colHandle); this.cells.setCell(rowHandle, colHandle, value); this.cellLastWriteTracker.setCell(rowHandle, colHandle, { - seqNum: msg.sequenceNumber, - clientId: msg.clientId, + seqNum: rawMessage.sequenceNumber, + clientId: rawMessage.clientId, }); for (const consumer of this.consumers.values()) { consumer.cellsChanged(adjustedRow, adjustedCol, 1, 1, this); @@ -922,8 +928,8 @@ export class SharedMatrix // since it "happened before" the pending write. this.cells.setCell(rowHandle, colHandle, value); this.cellLastWriteTracker.setCell(rowHandle, colHandle, { - seqNum: msg.sequenceNumber, - clientId: msg.clientId, + seqNum: rawMessage.sequenceNumber, + clientId: rawMessage.clientId, }); for (const consumer of this.consumers.values()) { consumer.cellsChanged(adjustedRow, adjustedCol, 1, 1, this); @@ -1039,20 +1045,21 @@ export class SharedMatrix /** * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp} */ - protected applyStashedOp(_content: unknown): void { - const content = _content as MatrixSetOrVectorOp; - if (content.type === MatrixOp.set && content.target === undefined) { - if (content.fwwMode === true) { + protected applyStashedOp(content: unknown): void { + const parsedContent: MatrixSetOrVectorOp = parseHandles(content, this.serializer); + + if (parsedContent.type === MatrixOp.set && parsedContent.target === undefined) { + if (parsedContent.fwwMode === true) { this.switchSetCellPolicy(); } - this.setCell(content.row, content.col, content.value); + this.setCell(parsedContent.row, parsedContent.col, parsedContent.value); } else { - const vector = content.target === SnapshotPath.cols ? this.cols : this.rows; - vector.applyStashedOp(content); - if (content.target === SnapshotPath.cols) { - this.submitColMessage(content); + const vector = parsedContent.target === SnapshotPath.cols ? this.cols : this.rows; + vector.applyStashedOp(parsedContent); + if (parsedContent.target === SnapshotPath.cols) { + this.submitColMessage(parsedContent); } else { - this.submitRowMessage(content); + this.submitRowMessage(parsedContent); } } } diff --git a/packages/dds/matrix/src/runtime.ts b/packages/dds/matrix/src/runtime.ts index 0706573c2a21..b4c625b4bc27 100644 --- a/packages/dds/matrix/src/runtime.ts +++ b/packages/dds/matrix/src/runtime.ts @@ -48,7 +48,7 @@ export class SharedMatrixFactory implements IChannelFactory { return matrix; } - public create(document: IFluidDataStoreRuntime, id: string): SharedMatrix { + public create(document: IFluidDataStoreRuntime, id: string): IChannel { const matrix = new SharedMatrix(document, id, this.attributes); matrix.initializeLocal(); return matrix; diff --git a/packages/dds/matrix/src/test/matrix.spec.ts b/packages/dds/matrix/src/test/matrix.spec.ts index a352ad5287eb..1bed5c487a90 100644 --- a/packages/dds/matrix/src/test/matrix.spec.ts +++ b/packages/dds/matrix/src/test/matrix.spec.ts @@ -1008,7 +1008,7 @@ describe("Matrix1", () => { } it("made while detached", () => { - const matrix = createLocalMatrix("A"); + const matrix = createLocalMatrix("A") as SharedMatrix; matrix.insertRows(0, 2); matrix.insertCols(0, 2); matrix.setCell(0, 0, "val"); diff --git a/packages/dds/merge-tree/src/snapshotLoader.ts b/packages/dds/merge-tree/src/snapshotLoader.ts index a69d2e78e56b..274f37df666e 100644 --- a/packages/dds/merge-tree/src/snapshotLoader.ts +++ b/packages/dds/merge-tree/src/snapshotLoader.ts @@ -83,7 +83,7 @@ export class SnapshotLoader { // TODO: The 'Snapshot.catchupOps' tree entry is purely for backwards compatibility. // (See https://github.com/microsoft/FluidFramework/issues/84) - return this.loadCatchupOps(services.readBlob(blobs[0]), this.serializer); + return this.loadCatchupOps(services.readBlob(blobs[0])); } else if (blobs.length !== headerChunk.headerMetadata!.orderedChunkMetadata.length) { throw new Error("Unexpected blobs in snapshot"); } @@ -301,15 +301,12 @@ export class SnapshotLoader { /** * If loading from a snapshot, get the catchup messages. * @param rawMessages - The messages in original encoding - * @returns The decoded messages with parsed+hydrated handles. Matches the format that will be passed in + * @returns The decoded messages, but handles aren't parsed. Matches the format that will be passed in * SharedObject.processCore. */ private async loadCatchupOps( rawMessages: Promise, - serializer: IFluidSerializer, ): Promise { - return serializer.parse( - bufferToString(await rawMessages, "utf8"), - ) as ISequencedDocumentMessage[]; + return JSON.parse(bufferToString(await rawMessages, "utf8")) as ISequencedDocumentMessage[]; } } diff --git a/packages/dds/ordered-collection/src/consensusOrderedCollection.ts b/packages/dds/ordered-collection/src/consensusOrderedCollection.ts index 151579fd57f4..dfd3c59dea80 100644 --- a/packages/dds/ordered-collection/src/consensusOrderedCollection.ts +++ b/packages/dds/ordered-collection/src/consensusOrderedCollection.ts @@ -38,11 +38,10 @@ interface IConsensusOrderedCollectionValue { /** * An operation for consensus ordered collection */ -interface IConsensusOrderedCollectionAddOperation { +interface IConsensusOrderedCollectionAddOperation { opName: "add"; // serialized value value: string; - deserializedValue?: T; } interface IConsensusOrderedCollectionAcquireOperation { @@ -66,8 +65,8 @@ interface IConsensusOrderedCollectionReleaseOperation { acquireId: string; } -type IConsensusOrderedCollectionOperation = - | IConsensusOrderedCollectionAddOperation +type IConsensusOrderedCollectionOperation = + | IConsensusOrderedCollectionAddOperation | IConsensusOrderedCollectionAcquireOperation | IConsensusOrderedCollectionCompleteOperation | IConsensusOrderedCollectionReleaseOperation; @@ -138,10 +137,9 @@ export class ConsensusOrderedCollection return; } - await this.submit>({ + await this.submit({ opName: "add", value: valueSer, - deserializedValue: value, }); } @@ -291,15 +289,11 @@ export class ConsensusOrderedCollection localOpMetadata: unknown, ) { if (message.type === MessageType.Operation) { - const op = message.contents as IConsensusOrderedCollectionOperation; + const op = message.contents as IConsensusOrderedCollectionOperation; let value: IConsensusOrderedCollectionValue | undefined; switch (op.opName) { case "add": - if (op.deserializedValue !== undefined) { - this.addCore(op.deserializedValue); - } else { - this.addCore(this.deserializeValue(op.value, this.serializer) as T); - } + this.addCore(this.deserializeValue(op.value, this.serializer) as T); break; case "acquire": @@ -325,7 +319,7 @@ export class ConsensusOrderedCollection } } - private async submit>( + private async submit( message: TMessage, ): Promise | undefined> { assert(this.isAttached(), 0x06a /* "Trying to submit message while detached!" */); diff --git a/packages/dds/ordered-collection/src/consensusOrderedCollectionFactory.ts b/packages/dds/ordered-collection/src/consensusOrderedCollectionFactory.ts index b76f2a57db5e..8da355fb030a 100644 --- a/packages/dds/ordered-collection/src/consensusOrderedCollectionFactory.ts +++ b/packages/dds/ordered-collection/src/consensusOrderedCollectionFactory.ts @@ -14,7 +14,6 @@ import { pkgVersion } from "./packageVersion.js"; /** * The factory that defines the consensus queue - * * @internal */ export class ConsensusQueueFactory implements IConsensusOrderedCollectionFactory { diff --git a/packages/dds/register-collection/README.md b/packages/dds/register-collection/README.md index 6533ec694683..a0d212f5c187 100644 --- a/packages/dds/register-collection/README.md +++ b/packages/dds/register-collection/README.md @@ -19,7 +19,7 @@ library consumers should always prefer `^`. ### Detecting concurrency in Fluid -In distributed systems literaure, detecting concurrency requires some form of logical/phsical clock. A popular technique used in replicated databases such as dynamodb is called version vectors where each key stores a collection of `[time, value]` tuples. `time` is essentially a reference clock used to decide concurrency amongst updates. Each update to a key includes the `time`, essentially to indicate how `caught up` the replica was during that update. +In distributed systems literaure, detecting conucurrency requires some form of logical/phsical clock. A popular technique used in replicated databases such as dynamodb is called version vectors where each key stores a collection of `[time, value]` tuples. `time` is essentially a reference clock used to decide concurrency amongst updates. Each update to a key includes the `time`, essentially to indicate how `caught up` the replica was during that update. In Fluid, each operation contains a referenceSequenceNumber (`refSeq`), which essenially refers to how caught up the client was (in terms of sequence number) during that update. We can use this property to implement a similar concurreny model. Mathematically, if an update has a `refSeq N`, it can overwrite/discard any other prior values with `sequenceNumber (seq) <= N`. It is safe to do so because the client must have seen all those updates before posting it's own update. Hence this update is not concurrent with those overwritten updates. However, the update is still concurrent with any other update with `seq > N`. Therefore those versions are still kept. diff --git a/packages/dds/register-collection/src/consensusRegisterCollection.ts b/packages/dds/register-collection/src/consensusRegisterCollection.ts index a33829004209..c169ff86f146 100644 --- a/packages/dds/register-collection/src/consensusRegisterCollection.ts +++ b/packages/dds/register-collection/src/consensusRegisterCollection.ts @@ -5,23 +5,23 @@ import { bufferToString } from "@fluid-internal/client-utils"; import { assert, unreachableCase } from "@fluidframework/core-utils"; +import { ISequencedDocumentMessage, MessageType } from "@fluidframework/protocol-definitions"; import { IChannelAttributes, - IChannelStorageService, IFluidDataStoreRuntime, + IChannelStorageService, } from "@fluidframework/datastore-definitions"; -import { ISequencedDocumentMessage, MessageType } from "@fluidframework/protocol-definitions"; import { ISummaryTreeWithStats } from "@fluidframework/runtime-definitions"; import { + createSingleBlobSummary, IFluidSerializer, SharedObject, - createSingleBlobSummary, } from "@fluidframework/shared-object-base"; import { ConsensusRegisterCollectionFactory } from "./consensusRegisterCollectionFactory.js"; import { IConsensusRegisterCollection, - IConsensusRegisterCollectionEvents, ReadPolicy, + type IConsensusRegisterCollectionEvents, } from "./interfaces.js"; interface ILocalData { @@ -53,10 +53,8 @@ const newLocalRegister = (sequenceNumber: number, value: T): ILocalRegister=2.0.0-rc.2.0.0 - * - * The value stored in this op is _not_ serialized and is stored literally as `T` + * IRegisterOperation format in versions \< 0.17 */ -interface IRegisterOperationPlain { +interface IRegisterOperationOld { key: string; type: "write"; - value: { type: "Plain"; value: T; }; - - // back-compat: for clients prior to 2.0.0-rc.2.0.0, we must also pass in - // the serialized value for them to parse handles correctly. we do not have - // to pay the cost of deserializing this value in newer clients - serializedValue: string; - - // back-compat: files at rest written with runtime <= 0.13 do not have refSeq - refSeq: number | undefined; + refSeq: number; } /** Incoming ops could match any of these types */ -type IIncomingRegisterOperation = IRegisterOperationSerialized | IRegisterOperationPlain; +type IIncomingRegisterOperation = IRegisterOperation | IRegisterOperationOld; /** Distinguish between incoming op formats so we know which type it is */ -const incomingOpMatchesPlainFormat = (op): op is IRegisterOperationPlain => "value" in op; +const incomingOpMatchesCurrentFormat = (op): op is IRegisterOperation => "serializedValue" in op; /** The type of the resolve function to call after the local operation is ack'd */ type PendingResolve = (winner: boolean) => void; @@ -154,19 +142,18 @@ export class ConsensusRegisterCollection * @returns Promise if write was non-concurrent */ public async write(key: string, value: T): Promise { + const serializedValue = this.stringify(value, this.serializer); + if (!this.isAttached()) { - this.processInboundWrite(key, value, 0, 0, true); + // JSON-roundtrip value for local writes to match the behavior of going through the wire + this.processInboundWrite(key, this.parse(serializedValue, this.serializer), 0, 0, true); return true; } - const message: IRegisterOperationPlain = { + const message: IRegisterOperation = { key, type: "write", - serializedValue: this.stringify(value, this.serializer), - value: { - type: "Plain", - value, - }, + serializedValue, refSeq: this.runtime.deltaManager.lastSequenceNumber, }; @@ -258,9 +245,9 @@ export class ConsensusRegisterCollection 0x06e /* "Message's reference sequence number < op's reference sequence number!" */, ); - const value = incomingOpMatchesPlainFormat(op) - ? op.value.value - : (this.parse(op.serializedValue, this.serializer) as T); + const value = incomingOpMatchesCurrentFormat(op) + ? (this.parse(op.serializedValue, this.serializer) as T) + : op.value.value; const winner = this.processInboundWrite( op.key, value, diff --git a/packages/dds/sequence/src/defaultMap.ts b/packages/dds/sequence/src/defaultMap.ts index 49304d232197..fe5610aed7c1 100644 --- a/packages/dds/sequence/src/defaultMap.ts +++ b/packages/dds/sequence/src/defaultMap.ts @@ -5,7 +5,12 @@ import { IFluidHandle } from "@fluidframework/core-interfaces"; import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; -import { IFluidSerializer, ValueType } from "@fluidframework/shared-object-base"; +import { + IFluidSerializer, + makeHandlesSerializable, + parseHandles, + ValueType, +} from "@fluidframework/shared-object-base"; import { TypedEventEmitter } from "@fluid-internal/client-utils"; import { assert } from "@fluidframework/core-utils"; import { makeSerializable, ValueTypeLocalValue } from "./localValues.js"; @@ -98,7 +103,7 @@ export class DefaultMap = IntervalCollection = IntervalCollection = IntervalCollection = IntervalCollection = IntervalCollection { + const translatedParams = makeHandlesSerializable(params, this.serializer, this.handle); + const op: IMapValueTypeOperation = { key, type: "act", value: { opName, - value: params, + value: translatedParams, }, }; diff --git a/packages/dds/sequence/src/sequence.ts b/packages/dds/sequence/src/sequence.ts index a1f4c9066d16..b79a223c8906 100644 --- a/packages/dds/sequence/src/sequence.ts +++ b/packages/dds/sequence/src/sequence.ts @@ -48,6 +48,8 @@ import { import { ObjectStoragePartition, SummaryTreeBuilder } from "@fluidframework/runtime-utils"; import { IFluidSerializer, + makeHandlesSerializable, + parseHandles, SharedObject, ISharedObjectEvents, } from "@fluidframework/shared-object-base"; @@ -482,7 +484,7 @@ export abstract class SharedSegmentSequence } this.inFlightRefSeqs.push(this.currentRefSeq); - + const translated = makeHandlesSerializable(message, this.serializer, this.handle); const metadata = this.client.peekPendingSegmentGroups( message.type === MergeTreeDeltaType.GROUP ? message.ops.length : 1, ); @@ -491,9 +493,9 @@ export abstract class SharedSegmentSequence // local ops until loading is complete, and then // they will be present if (!this.loadedDeferred.isCompleted) { - this.loadedDeferredOutgoingOps.push(metadata ? [message, metadata] : (message as any)); + this.loadedDeferredOutgoingOps.push(metadata ? [translated, metadata] : translated); } else { - this.submitLocalMessage(message, metadata); + this.submitLocalMessage(translated, metadata); } } @@ -806,8 +808,9 @@ export abstract class SharedSegmentSequence * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp} */ protected applyStashedOp(content: any): void { - if (!this.intervalCollections.tryApplyStashedOp(content)) { - this.client.applyStashedOp(content); + const parsedContent = parseHandles(content, this.serializer); + if (!this.intervalCollections.tryApplyStashedOp(parsedContent)) { + this.client.applyStashedOp(parsedContent); } } @@ -833,11 +836,9 @@ export abstract class SharedSegmentSequence ); } - /** - * - * @param message - Message with decoded and hydrated handles - */ - private processMergeTreeMsg(message: ISequencedDocumentMessage, local?: boolean) { + private processMergeTreeMsg(rawMessage: ISequencedDocumentMessage, local?: boolean) { + const message = parseHandles(rawMessage, this.serializer); + const ops: IMergeTreeDeltaOp[] = []; function transformOps(event: SequenceDeltaEvent) { ops.push(...SharedSegmentSequence.createOpsFromDelta(event)); diff --git a/packages/dds/shared-object-base/api-report/shared-object-base.api.md b/packages/dds/shared-object-base/api-report/shared-object-base.api.md index 8941f4a79e88..909b2f3b75bd 100644 --- a/packages/dds/shared-object-base/api-report/shared-object-base.api.md +++ b/packages/dds/shared-object-base/api-report/shared-object-base.api.md @@ -23,9 +23,6 @@ import { ISummaryTreeWithStats } from '@fluidframework/runtime-definitions'; import { ITelemetryContext } from '@fluidframework/runtime-definitions'; import { ITelemetryLoggerExt } from '@fluidframework/telemetry-utils'; -// @internal -export function bindHandles(value: any, serializer: IFluidSerializer, bind: IFluidHandle): void; - // @internal export function createSingleBlobSummary(key: string, content: string | Uint8Array): ISummaryTreeWithStats; @@ -124,7 +121,6 @@ export abstract class SharedObjectCore; } diff --git a/packages/dds/shared-object-base/src/index.ts b/packages/dds/shared-object-base/src/index.ts index 76681cdc99ee..711fb9100903 100644 --- a/packages/dds/shared-object-base/src/index.ts +++ b/packages/dds/shared-object-base/src/index.ts @@ -12,6 +12,5 @@ export { makeHandlesSerializable, parseHandles, serializeHandles, - bindHandles, } from "./utils.js"; export { ValueType } from "./valueType.js"; diff --git a/packages/dds/shared-object-base/src/sharedObject.ts b/packages/dds/shared-object-base/src/sharedObject.ts index 02ebbaa05424..ea8166689481 100644 --- a/packages/dds/shared-object-base/src/sharedObject.ts +++ b/packages/dds/shared-object-base/src/sharedObject.ts @@ -37,7 +37,6 @@ import { FluidSerializer, IFluidSerializer } from "./serializer.js"; import { SharedObjectHandle } from "./handle.js"; import { SummarySerializer } from "./summarySerializer.js"; import { ISharedObject, ISharedObjectEvents } from "./types.js"; -import { makeHandlesSerializable, parseHandles } from "./utils.js"; /** * Base class from which all shared objects derive. @@ -359,15 +358,9 @@ export abstract class SharedObjectCore { - this.process( - { ...message, contents: parseHandles(message.contents, this.serializer) }, - local, - localOpMetadata, - ); + this.process(message, local, localOpMetadata); }, setConnectionState: (connected: boolean) => { this.setConnectionState(connected); @@ -474,7 +460,7 @@ export abstract class SharedObjectCore { - this.applyStashedOp(parseHandles(content, this.serializer)); + this.applyStashedOp(content); }, rollback: (content: any, localOpMetadata: unknown) => { this.rollback(content, localOpMetadata); diff --git a/packages/dds/shared-object-base/src/test/sharedObject.spec.ts b/packages/dds/shared-object-base/src/test/sharedObject.spec.ts index 17966f90c633..187d1b32f516 100644 --- a/packages/dds/shared-object-base/src/test/sharedObject.spec.ts +++ b/packages/dds/shared-object-base/src/test/sharedObject.spec.ts @@ -55,8 +55,6 @@ class MySharedObjectCore extends SharedObjectCore { ); } - protected readonly serializer = {} as any as IFluidSerializer; - protected summarizeCore(serializer: IFluidSerializer): ISummaryTreeWithStats { throw new Error("Method not implemented."); } diff --git a/packages/dds/shared-object-base/src/utils.ts b/packages/dds/shared-object-base/src/utils.ts index a9d43a1074f9..8720d610e030 100644 --- a/packages/dds/shared-object-base/src/utils.ts +++ b/packages/dds/shared-object-base/src/utils.ts @@ -78,16 +78,3 @@ export function createSingleBlobSummary( builder.addBlob(key, content); return builder.getSummaryTree(); } - -/** - * Binds all handles found in `value` to `bind`. Does not modify original input. - * - * @internal - */ -export function bindHandles(value: any, serializer: IFluidSerializer, bind: IFluidHandle): void { - // N.B. AB#7316 this could be made more efficient by writing an ad hoc - // implementation that doesn't clone at all. Today the distinction between - // this function and `encode` is purely semantic -- encoding both serializes - // handles and binds them, but sometimes we only wish to do the latter - serializer.encode(value, bind); -} diff --git a/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts b/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts index b955f1d6a16d..0432faf09119 100644 --- a/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts +++ b/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts @@ -237,7 +237,7 @@ export class SharedTreeCore extends schema: this.schemaAndPolicy ?? undefined, }, ); - this.submitLocalMessage(message); + this.submitLocalMessage(this.serializer.encode(message, this.handle)); } protected processCore( @@ -245,9 +245,9 @@ export class SharedTreeCore extends local: boolean, localOpMetadata: unknown, ) { + const contents: unknown = this.serializer.decode(message.contents); // Empty context object is passed in, as our decode function is schema-agnostic. - const { commit, sessionId } = this.messageCodec.decode(message.contents, {}); - + const { commit, sessionId } = this.messageCodec.decode(contents, {}); this.editManager.addSequencedChange( { ...commit, sessionId }, brand(message.sequenceNumber), diff --git a/packages/framework/attributor/src/test/attribution/documents/default/attributor-lz4-and-delta-snap.json b/packages/framework/attributor/src/test/attribution/documents/default/attributor-lz4-and-delta-snap.json index 6dbf38fc8087..6ca476046453 100644 --- a/packages/framework/attributor/src/test/attribution/documents/default/attributor-lz4-and-delta-snap.json +++ b/packages/framework/attributor/src/test/attribution/documents/default/attributor-lz4-and-delta-snap.json @@ -10,7 +10,7 @@ }, "catchupOps": { "type": 2, - "content": "[{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":27,\"contents\":{\"pos1\":80,\"pos2\":102,\"type\":1},\"referenceSequenceNumber\":76,\"type\":\"op\",\"sequenceNumber\":77,\"minimumSequenceNumber\":76,\"timestamp\":1665600035000},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":25,\"contents\":{\"pos1\":31,\"seg\":\"BPBs\",\"type\":0},\"referenceSequenceNumber\":77,\"type\":\"op\",\"sequenceNumber\":78,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":26,\"contents\":{\"pos1\":31,\"pos2\":57,\"type\":1},\"referenceSequenceNumber\":78,\"type\":\"op\",\"sequenceNumber\":79,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":27,\"contents\":{\"ops\":[{\"pos1\":21,\"pos2\":40,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2}],\"type\":3},\"referenceSequenceNumber\":79,\"type\":\"op\",\"sequenceNumber\":80,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":28,\"contents\":{\"pos1\":31,\"pos2\":36,\"type\":1},\"referenceSequenceNumber\":80,\"type\":\"op\",\"sequenceNumber\":81,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":28,\"contents\":{\"ops\":[{\"pos1\":30,\"pos2\":40,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":35,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":38,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":40,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2}],\"type\":3},\"referenceSequenceNumber\":81,\"type\":\"op\",\"sequenceNumber\":82,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":29,\"contents\":{\"pos1\":31,\"seg\":\"yc5Bz\",\"type\":0},\"referenceSequenceNumber\":82,\"type\":\"op\",\"sequenceNumber\":83,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":30,\"contents\":{\"pos1\":36,\"seg\":\"mQny\",\"type\":0},\"referenceSequenceNumber\":83,\"type\":\"op\",\"sequenceNumber\":84,\"minimumSequenceNumber\":76},{\"clientId\":\"25adaa92-9220-49e2-a852-3c7e97ac9d1c\",\"clientSequenceNumber\":24,\"contents\":{\"ops\":[],\"type\":3},\"referenceSequenceNumber\":84,\"type\":\"op\",\"sequenceNumber\":85,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":29,\"contents\":{\"pos1\":4,\"seg\":\"x2NAdmR\",\"type\":0},\"referenceSequenceNumber\":85,\"type\":\"op\",\"sequenceNumber\":86,\"minimumSequenceNumber\":76,\"timestamp\":1665600040000}]" + "content": "[{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":27,\"contents\":{\"pos1\":80,\"pos2\":102,\"type\":1},\"referenceSequenceNumber\":76,\"type\":\"op\",\"sequenceNumber\":77,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":25,\"contents\":{\"pos1\":31,\"seg\":\"BPBs\",\"type\":0},\"referenceSequenceNumber\":77,\"type\":\"op\",\"sequenceNumber\":78,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":26,\"contents\":{\"pos1\":31,\"pos2\":57,\"type\":1},\"referenceSequenceNumber\":78,\"type\":\"op\",\"sequenceNumber\":79,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":27,\"contents\":{\"ops\":[{\"pos1\":21,\"pos2\":40,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2}],\"type\":3},\"referenceSequenceNumber\":79,\"type\":\"op\",\"sequenceNumber\":80,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":28,\"contents\":{\"pos1\":31,\"pos2\":36,\"type\":1},\"referenceSequenceNumber\":80,\"type\":\"op\",\"sequenceNumber\":81,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":28,\"contents\":{\"ops\":[{\"pos1\":30,\"pos2\":40,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":35,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":38,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":40,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2}],\"type\":3},\"referenceSequenceNumber\":81,\"type\":\"op\",\"sequenceNumber\":82,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":29,\"contents\":{\"pos1\":31,\"seg\":\"yc5Bz\",\"type\":0},\"referenceSequenceNumber\":82,\"type\":\"op\",\"sequenceNumber\":83,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":30,\"contents\":{\"pos1\":36,\"seg\":\"mQny\",\"type\":0},\"referenceSequenceNumber\":83,\"type\":\"op\",\"sequenceNumber\":84,\"minimumSequenceNumber\":76},{\"clientId\":\"25adaa92-9220-49e2-a852-3c7e97ac9d1c\",\"clientSequenceNumber\":24,\"contents\":{\"ops\":[],\"type\":3},\"referenceSequenceNumber\":84,\"type\":\"op\",\"sequenceNumber\":85,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":29,\"contents\":{\"pos1\":4,\"seg\":\"x2NAdmR\",\"type\":0},\"referenceSequenceNumber\":85,\"type\":\"op\",\"sequenceNumber\":86,\"minimumSequenceNumber\":76}]" } } } diff --git a/packages/framework/attributor/src/test/attribution/documents/default/attributor-lz4-compression-snap.json b/packages/framework/attributor/src/test/attribution/documents/default/attributor-lz4-compression-snap.json index e41f406480f7..4f59bbc6b844 100644 --- a/packages/framework/attributor/src/test/attribution/documents/default/attributor-lz4-compression-snap.json +++ b/packages/framework/attributor/src/test/attribution/documents/default/attributor-lz4-compression-snap.json @@ -10,7 +10,7 @@ }, "catchupOps": { "type": 2, - "content": "[{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":27,\"contents\":{\"pos1\":80,\"pos2\":102,\"type\":1},\"referenceSequenceNumber\":76,\"type\":\"op\",\"sequenceNumber\":77,\"minimumSequenceNumber\":76,\"timestamp\":1665600035000},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":25,\"contents\":{\"pos1\":31,\"seg\":\"BPBs\",\"type\":0},\"referenceSequenceNumber\":77,\"type\":\"op\",\"sequenceNumber\":78,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":26,\"contents\":{\"pos1\":31,\"pos2\":57,\"type\":1},\"referenceSequenceNumber\":78,\"type\":\"op\",\"sequenceNumber\":79,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":27,\"contents\":{\"ops\":[{\"pos1\":21,\"pos2\":40,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2}],\"type\":3},\"referenceSequenceNumber\":79,\"type\":\"op\",\"sequenceNumber\":80,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":28,\"contents\":{\"pos1\":31,\"pos2\":36,\"type\":1},\"referenceSequenceNumber\":80,\"type\":\"op\",\"sequenceNumber\":81,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":28,\"contents\":{\"ops\":[{\"pos1\":30,\"pos2\":40,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":35,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":38,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":40,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2}],\"type\":3},\"referenceSequenceNumber\":81,\"type\":\"op\",\"sequenceNumber\":82,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":29,\"contents\":{\"pos1\":31,\"seg\":\"yc5Bz\",\"type\":0},\"referenceSequenceNumber\":82,\"type\":\"op\",\"sequenceNumber\":83,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":30,\"contents\":{\"pos1\":36,\"seg\":\"mQny\",\"type\":0},\"referenceSequenceNumber\":83,\"type\":\"op\",\"sequenceNumber\":84,\"minimumSequenceNumber\":76},{\"clientId\":\"25adaa92-9220-49e2-a852-3c7e97ac9d1c\",\"clientSequenceNumber\":24,\"contents\":{\"ops\":[],\"type\":3},\"referenceSequenceNumber\":84,\"type\":\"op\",\"sequenceNumber\":85,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":29,\"contents\":{\"pos1\":4,\"seg\":\"x2NAdmR\",\"type\":0},\"referenceSequenceNumber\":85,\"type\":\"op\",\"sequenceNumber\":86,\"minimumSequenceNumber\":76,\"timestamp\":1665600040000}]" + "content": "[{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":27,\"contents\":{\"pos1\":80,\"pos2\":102,\"type\":1},\"referenceSequenceNumber\":76,\"type\":\"op\",\"sequenceNumber\":77,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":25,\"contents\":{\"pos1\":31,\"seg\":\"BPBs\",\"type\":0},\"referenceSequenceNumber\":77,\"type\":\"op\",\"sequenceNumber\":78,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":26,\"contents\":{\"pos1\":31,\"pos2\":57,\"type\":1},\"referenceSequenceNumber\":78,\"type\":\"op\",\"sequenceNumber\":79,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":27,\"contents\":{\"ops\":[{\"pos1\":21,\"pos2\":40,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2}],\"type\":3},\"referenceSequenceNumber\":79,\"type\":\"op\",\"sequenceNumber\":80,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":28,\"contents\":{\"pos1\":31,\"pos2\":36,\"type\":1},\"referenceSequenceNumber\":80,\"type\":\"op\",\"sequenceNumber\":81,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":28,\"contents\":{\"ops\":[{\"pos1\":30,\"pos2\":40,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":35,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":38,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":40,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2}],\"type\":3},\"referenceSequenceNumber\":81,\"type\":\"op\",\"sequenceNumber\":82,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":29,\"contents\":{\"pos1\":31,\"seg\":\"yc5Bz\",\"type\":0},\"referenceSequenceNumber\":82,\"type\":\"op\",\"sequenceNumber\":83,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":30,\"contents\":{\"pos1\":36,\"seg\":\"mQny\",\"type\":0},\"referenceSequenceNumber\":83,\"type\":\"op\",\"sequenceNumber\":84,\"minimumSequenceNumber\":76},{\"clientId\":\"25adaa92-9220-49e2-a852-3c7e97ac9d1c\",\"clientSequenceNumber\":24,\"contents\":{\"ops\":[],\"type\":3},\"referenceSequenceNumber\":84,\"type\":\"op\",\"sequenceNumber\":85,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":29,\"contents\":{\"pos1\":4,\"seg\":\"x2NAdmR\",\"type\":0},\"referenceSequenceNumber\":85,\"type\":\"op\",\"sequenceNumber\":86,\"minimumSequenceNumber\":76}]" } } } diff --git a/packages/framework/attributor/src/test/attribution/documents/default/attributor-no-compression-snap.json b/packages/framework/attributor/src/test/attribution/documents/default/attributor-no-compression-snap.json index b3ca80fe0105..bfd1be6642c6 100644 --- a/packages/framework/attributor/src/test/attribution/documents/default/attributor-no-compression-snap.json +++ b/packages/framework/attributor/src/test/attribution/documents/default/attributor-no-compression-snap.json @@ -10,7 +10,7 @@ }, "catchupOps": { "type": 2, - "content": "[{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":27,\"contents\":{\"pos1\":80,\"pos2\":102,\"type\":1},\"referenceSequenceNumber\":76,\"type\":\"op\",\"sequenceNumber\":77,\"minimumSequenceNumber\":76,\"timestamp\":1665600035000},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":25,\"contents\":{\"pos1\":31,\"seg\":\"BPBs\",\"type\":0},\"referenceSequenceNumber\":77,\"type\":\"op\",\"sequenceNumber\":78,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":26,\"contents\":{\"pos1\":31,\"pos2\":57,\"type\":1},\"referenceSequenceNumber\":78,\"type\":\"op\",\"sequenceNumber\":79,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":27,\"contents\":{\"ops\":[{\"pos1\":21,\"pos2\":40,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2}],\"type\":3},\"referenceSequenceNumber\":79,\"type\":\"op\",\"sequenceNumber\":80,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":28,\"contents\":{\"pos1\":31,\"pos2\":36,\"type\":1},\"referenceSequenceNumber\":80,\"type\":\"op\",\"sequenceNumber\":81,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":28,\"contents\":{\"ops\":[{\"pos1\":30,\"pos2\":40,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":35,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":38,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":40,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2}],\"type\":3},\"referenceSequenceNumber\":81,\"type\":\"op\",\"sequenceNumber\":82,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":29,\"contents\":{\"pos1\":31,\"seg\":\"yc5Bz\",\"type\":0},\"referenceSequenceNumber\":82,\"type\":\"op\",\"sequenceNumber\":83,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":30,\"contents\":{\"pos1\":36,\"seg\":\"mQny\",\"type\":0},\"referenceSequenceNumber\":83,\"type\":\"op\",\"sequenceNumber\":84,\"minimumSequenceNumber\":76},{\"clientId\":\"25adaa92-9220-49e2-a852-3c7e97ac9d1c\",\"clientSequenceNumber\":24,\"contents\":{\"ops\":[],\"type\":3},\"referenceSequenceNumber\":84,\"type\":\"op\",\"sequenceNumber\":85,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":29,\"contents\":{\"pos1\":4,\"seg\":\"x2NAdmR\",\"type\":0},\"referenceSequenceNumber\":85,\"type\":\"op\",\"sequenceNumber\":86,\"minimumSequenceNumber\":76,\"timestamp\":1665600040000}]" + "content": "[{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":27,\"contents\":{\"pos1\":80,\"pos2\":102,\"type\":1},\"referenceSequenceNumber\":76,\"type\":\"op\",\"sequenceNumber\":77,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":25,\"contents\":{\"pos1\":31,\"seg\":\"BPBs\",\"type\":0},\"referenceSequenceNumber\":77,\"type\":\"op\",\"sequenceNumber\":78,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":26,\"contents\":{\"pos1\":31,\"pos2\":57,\"type\":1},\"referenceSequenceNumber\":78,\"type\":\"op\",\"sequenceNumber\":79,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":27,\"contents\":{\"ops\":[{\"pos1\":21,\"pos2\":40,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"v1BgG\",\"prop1\":\"m4xRB\",\"prop3\":\"8e12Q\"},\"type\":2}],\"type\":3},\"referenceSequenceNumber\":79,\"type\":\"op\",\"sequenceNumber\":80,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":28,\"contents\":{\"pos1\":31,\"pos2\":36,\"type\":1},\"referenceSequenceNumber\":80,\"type\":\"op\",\"sequenceNumber\":81,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":28,\"contents\":{\"ops\":[{\"pos1\":30,\"pos2\":40,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":35,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":32,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":34,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":33,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":38,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2},{\"pos1\":31,\"pos2\":40,\"props\":{\"prop2\":\"dHWn1\",\"prop3\":\"5KoWp\"},\"type\":2}],\"type\":3},\"referenceSequenceNumber\":81,\"type\":\"op\",\"sequenceNumber\":82,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":29,\"contents\":{\"pos1\":31,\"seg\":\"yc5Bz\",\"type\":0},\"referenceSequenceNumber\":82,\"type\":\"op\",\"sequenceNumber\":83,\"minimumSequenceNumber\":76},{\"clientId\":\"d3fdea22-7906-496f-9fc5-0bf988954452\",\"clientSequenceNumber\":30,\"contents\":{\"pos1\":36,\"seg\":\"mQny\",\"type\":0},\"referenceSequenceNumber\":83,\"type\":\"op\",\"sequenceNumber\":84,\"minimumSequenceNumber\":76},{\"clientId\":\"25adaa92-9220-49e2-a852-3c7e97ac9d1c\",\"clientSequenceNumber\":24,\"contents\":{\"ops\":[],\"type\":3},\"referenceSequenceNumber\":84,\"type\":\"op\",\"sequenceNumber\":85,\"minimumSequenceNumber\":76},{\"clientId\":\"a2df62bb-461f-4c2e-a3ad-fc5df463c464\",\"clientSequenceNumber\":29,\"contents\":{\"pos1\":4,\"seg\":\"x2NAdmR\",\"type\":0},\"referenceSequenceNumber\":85,\"type\":\"op\",\"sequenceNumber\":86,\"minimumSequenceNumber\":76}]" } } } diff --git a/packages/test/functional-tests/package.json b/packages/test/functional-tests/package.json index e568618e6cd2..47d86ece0248 100644 --- a/packages/test/functional-tests/package.json +++ b/packages/test/functional-tests/package.json @@ -54,14 +54,12 @@ "temp-directory": "nyc/.nyc_output" }, "devDependencies": { - "@fluid-experimental/tree": "workspace:~", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/test-dds-utils": "workspace:~", "@fluid-private/test-loader-utils": "workspace:~", "@fluid-tools/build-cli": "^0.34.0", "@fluidframework/build-common": "^2.0.3", "@fluidframework/build-tools": "^0.34.0", - "@fluidframework/cell": "workspace:~", "@fluidframework/container-loader": "workspace:~", "@fluidframework/container-runtime": "workspace:~", "@fluidframework/datastore-definitions": "workspace:~", diff --git a/packages/test/functional-tests/src/test/ddsHandleEncoding.spec.ts b/packages/test/functional-tests/src/test/ddsHandleEncoding.spec.ts index 786661c30dd4..481777c52397 100644 --- a/packages/test/functional-tests/src/test/ddsHandleEncoding.spec.ts +++ b/packages/test/functional-tests/src/test/ddsHandleEncoding.spec.ts @@ -4,30 +4,16 @@ */ import { strict as assert } from "assert"; -import { - type BuildNode, - Change, - SharedTree as LegacySharedTree, - StablePlace, - type TraitLabel, - MigrationShimFactory, -} from "@fluid-experimental/tree"; import { MockFluidDataStoreRuntime, MockStorage, MockDeltaConnection, MockHandle, } from "@fluidframework/test-runtime-utils"; -import { CellFactory } from "@fluidframework/cell"; import { DirectoryFactory, IDirectory, MapFactory } from "@fluidframework/map"; -import { SharedMatrixFactory, SharedMatrix } from "@fluidframework/matrix"; -import { SharedTree, SchemaFactory, ITree, TreeConfiguration } from "@fluidframework/tree"; -import { ConsensusQueueFactory } from "@fluidframework/ordered-collection"; -import { ReferenceType, SharedStringFactory } from "@fluidframework/sequence"; import { IChannel, IChannelFactory } from "@fluidframework/datastore-definitions"; import { ConsensusRegisterCollectionFactory } from "@fluidframework/register-collection"; import { detectOutboundReferences } from "@fluidframework/container-runtime"; -import { SessionId, createIdCompressor } from "@fluidframework/id-compressor"; /** * The purpose of these tests is to demonstrate that DDSes do not do opaque encoding of handles @@ -75,14 +61,11 @@ describe("DDS Handle Encoding", () => { factory: IChannelFactoryWithCreatedType, addHandleToDDS: (dds: T) => void, expectedHandles: string[], - nameOverride?: string, ): ITestCase { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const name = nameOverride ?? factory.type.split("/").pop()!; + const name = factory.type.split("/").pop()!; - const dataStoreRuntime = new MockFluidDataStoreRuntime({ - idCompressor: createIdCompressor("173cb232-53a2-4327-b690-afa954397989" as SessionId), - }); + const dataStoreRuntime = new MockFluidDataStoreRuntime(); const deltaConnection = new MockDeltaConnection( /* submitFn: */ (message) => { messages.push(message); @@ -107,7 +90,7 @@ describe("DDS Handle Encoding", () => { const testCases: ITestCase[] = [ createTestCase( new MapFactory(), - (dds) => { + (dds: Map) => { dds.set("whatever", handle); }, [handle.absolutePath] /* expectedHandles */, @@ -119,145 +102,25 @@ describe("DDS Handle Encoding", () => { }, [handle.absolutePath] /* expectedHandles */, ), - createTestCase( - new SharedStringFactory(), - (dds) => { - dds.insertMarker(0, ReferenceType.Simple, { marker: handle }); - }, - [handle.absolutePath] /* expectedHandles */, - ), - createTestCase( - new SharedMatrixFactory(), - (dds: SharedMatrix) => { - dds.insertRows(0, 1); - dds.insertCols(0, 1); - - dds.setCell(0, 0, handle); - }, - [handle.absolutePath] /* expectedHandles */, - ), - createTestCase( - SharedTree.getFactory() as any, - (dds: ITree) => { - const builder = new SchemaFactory("test"); - class Bar extends builder.object("bar", { - h: builder.optional(builder.handle), - }) {} - - const config = new TreeConfiguration(Bar, () => ({ - h: undefined, - })); - - const treeView = dds.schematize(config); - - treeView.root.h = handle; - }, - [handle.absolutePath] /* expectedHandles */, - "tree2", - ), - createTestCase( - LegacySharedTree.getFactory(), - (tree) => { - const legacyNodeId: TraitLabel = "inventory" as TraitLabel; - - const handleNode: BuildNode = { - definition: legacyNodeId, - traits: { - handle: { - definition: "handle", - payload: 0, - }, - }, - }; - tree.applyEdit( - Change.insertTree( - handleNode, - StablePlace.atStartOf({ - parent: tree.currentView.root, - label: legacyNodeId, - }), - ), - ); - - const rootNode = tree.currentView.getViewNode(tree.currentView.root); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const nodeId = rootNode.traits.get(legacyNodeId)![0]; - const change: Change = Change.setPayload(nodeId, handle); - tree.applyEdit(change); - }, - [handle.absolutePath] /* expectedHandles */, - "legacy-shared-tree", - ), createTestCase( new ConsensusRegisterCollectionFactory(), (dds) => { - dds.write("whatever", handle).catch(() => {}); + dds.write("whatever", handle).catch(() => { + // We only care about errors before message submission, which will fail the message.length assert below. + }); }, - [handle.absolutePath] /* expectedHandles */, - ), - createTestCase( - new ConsensusQueueFactory(), - (dds) => { - dds.add(handle).catch(() => {}); - }, - [handle.absolutePath] /* expectedHandles */, - ), - createTestCase( - new CellFactory(), - (dds) => { - dds.set(handle); - }, - [handle.absolutePath] /* expectedHandles */, - ), - createTestCase( - new MigrationShimFactory( - LegacySharedTree.getFactory(), - SharedTree.getFactory(), - (legacyTree, newTree) => { - throw new Error("unreachable"); - }, - ), - (shim) => { - const tree = shim.currentTree as LegacySharedTree; - const legacyNodeId: TraitLabel = "inventory" as TraitLabel; - - const handleNode: BuildNode = { - definition: legacyNodeId, - traits: { - handle: { - definition: "handle", - payload: 0, - }, - }, - }; - tree.applyEdit( - Change.insertTree( - handleNode, - StablePlace.atStartOf({ - parent: tree.currentView.root, - label: legacyNodeId, - }), - ), - ); - - const rootNode = tree.currentView.getViewNode(tree.currentView.root); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const nodeId = rootNode.traits.get(legacyNodeId)![0]; - const change: Change = Change.setPayload(nodeId, { handle }); - tree.applyEdit(change); - }, - [handle.absolutePath] /* expectedHandles */, - "migration-shim", + [] /* expectedHandles */, ), ]; testCases.forEach((testCase) => { - const shouldOrShouldNot = testCase.expectedHandles.length > 0 ? "should not" : "should"; + const shouldOrShouldNot = testCase.expectedHandles.length > 0 ? "should" : "should not"; it(`${shouldOrShouldNot} obscure handles in ${testCase.name} message contents`, async () => { testCase.addHandleToDDS(); + assert.equal(messages.length, 1, "Expected a single message to be submitted"); assert.deepEqual( - messages.flatMap((m) => findAllHandles(m)), + findAllHandles(messages[0]), testCase.expectedHandles, `The handle ${shouldOrShouldNot} be detected`, ); diff --git a/packages/test/test-end-to-end-tests/src/test/detachedContainerTests.spec.ts b/packages/test/test-end-to-end-tests/src/test/detachedContainerTests.spec.ts index 4e8648b40627..e93663dfdf2a 100644 --- a/packages/test/test-end-to-end-tests/src/test/detachedContainerTests.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/detachedContainerTests.spec.ts @@ -512,10 +512,6 @@ describeCompat("Detached Container", "FullCompat", (getTestObjectProvider, apis) key: "1", type: "write", serializedValue: JSON.stringify("b"), - value: { - type: "Plain", - value: "b", - }, refSeq: detachedContainerRefSeqNumber, }; const defPromise = new Deferred(); @@ -538,24 +534,19 @@ describeCompat("Detached Container", "FullCompat", (getTestObjectProvider, apis) crcId, "Address should be consensus register collection", ); - const receivedOp = ( - ( - (message.contents as { contents: unknown }).contents as { - content: unknown; - } - ).content as { contents?: unknown } - ).contents as any; - assert.strictEqual(op.key, receivedOp.key, "Op key should be same"); - assert.strictEqual(op.type, receivedOp.type, "Op type should be same"); assert.strictEqual( - op.serializedValue, - receivedOp.serializedValue, - "Op serializedValue should be same", + JSON.stringify( + ( + ( + (message.contents as { contents: unknown }).contents as { + content: unknown; + } + ).content as { contents?: unknown } + ).contents, + ), + JSON.stringify(op), + "Op should be same", ); - assert.strictEqual(op.refSeq, receivedOp.refSeq, "Op refSeq should be same"); - if (receivedOp.value) { - assert.deepEqual(op.value, receivedOp.value, "Op value should be same"); - } defPromise.resolve(); return 0; }); @@ -682,7 +673,7 @@ describeCompat("Detached Container", "FullCompat", (getTestObjectProvider, apis) }); it("Fire ops during container attach for consensus ordered collection", async () => { - const op = { opName: "add", value: JSON.stringify("s"), deserializedValue: "s" }; + const op = { opName: "add", value: JSON.stringify("s") }; const defPromise = new Deferred(); const container = await loader.createDetachedContainer(provider.defaultCodeDetails); @@ -702,22 +693,19 @@ describeCompat("Detached Container", "FullCompat", (getTestObjectProvider, apis) cocId, "Address should be consensus queue", ); - const receivedOp = ( - ( - (message.contents as { contents: unknown }).contents as { - content: unknown; - } - ).content as { contents?: unknown } - ).contents as any; - assert.strictEqual(op.opName, receivedOp.opName, "Op name should be same"); - assert.strictEqual(op.value, receivedOp.value, "Op value should be same"); - if (receivedOp.deserializedValue) { - assert.strictEqual( - op.deserializedValue, - receivedOp.deserializedValue, - "Op deserializedValue should be same", - ); - } + assert.strictEqual( + JSON.stringify( + ( + ( + (message.contents as { contents: unknown }).contents as { + content: unknown; + } + ).content as { contents?: unknown } + ).contents, + ), + JSON.stringify(op), + "Op should be same", + ); defPromise.resolve(); return 0; }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72ba74937694..db8ace6ed563 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9686,14 +9686,12 @@ importers: packages/test/functional-tests: specifiers: - '@fluid-experimental/tree': workspace:~ '@fluid-internal/mocha-test-setup': workspace:~ '@fluid-private/test-dds-utils': workspace:~ '@fluid-private/test-loader-utils': workspace:~ '@fluid-tools/build-cli': ^0.34.0 '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.34.0 - '@fluidframework/cell': workspace:~ '@fluidframework/container-loader': workspace:~ '@fluidframework/container-runtime': workspace:~ '@fluidframework/datastore-definitions': workspace:~ @@ -9728,14 +9726,12 @@ importers: webpack: ^5.82.0 webpack-cli: ^4.9.2 devDependencies: - '@fluid-experimental/tree': link:../../../experimental/dds/tree '@fluid-internal/mocha-test-setup': link:../mocha-test-setup '@fluid-private/test-dds-utils': link:../../dds/test-dds-utils '@fluid-private/test-loader-utils': link:../../loader/test-loader-utils '@fluid-tools/build-cli': 0.34.0_wct7gbpa3rmr6xmk4uo7pbrani '@fluidframework/build-common': 2.0.3 '@fluidframework/build-tools': 0.34.0_h467wi3sy6j67ifywrn7x7qf4m - '@fluidframework/cell': link:../../dds/cell '@fluidframework/container-loader': link:../../loader/container-loader '@fluidframework/container-runtime': link:../../runtime/container-runtime '@fluidframework/datastore-definitions': link:../../runtime/datastore-definitions