@fluidframework/merge-tree 0.58.2002 → 0.59.1000
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -1
- package/dist/collections.d.ts.map +1 -1
- package/dist/collections.js +1 -0
- package/dist/collections.js.map +1 -1
- package/dist/mergeTree.d.ts +5 -8
- package/dist/mergeTree.d.ts.map +1 -1
- package/dist/mergeTree.js +35 -48
- package/dist/mergeTree.js.map +1 -1
- package/dist/partialLengths.d.ts.map +1 -1
- package/dist/partialLengths.js +19 -18
- package/dist/partialLengths.js.map +1 -1
- package/dist/snapshotChunks.d.ts +4 -0
- package/dist/snapshotChunks.d.ts.map +1 -1
- package/dist/snapshotChunks.js.map +1 -1
- package/dist/snapshotLoader.d.ts.map +1 -1
- package/dist/snapshotLoader.js +9 -1
- package/dist/snapshotLoader.js.map +1 -1
- package/dist/snapshotV1.d.ts.map +1 -1
- package/dist/snapshotV1.js +7 -2
- package/dist/snapshotV1.js.map +1 -1
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +2 -0
- package/lib/client.js.map +1 -1
- package/lib/collections.d.ts.map +1 -1
- package/lib/collections.js +1 -0
- package/lib/collections.js.map +1 -1
- package/lib/mergeTree.d.ts +5 -8
- package/lib/mergeTree.d.ts.map +1 -1
- package/lib/mergeTree.js +33 -47
- package/lib/mergeTree.js.map +1 -1
- package/lib/partialLengths.d.ts.map +1 -1
- package/lib/partialLengths.js +20 -19
- package/lib/partialLengths.js.map +1 -1
- package/lib/snapshotChunks.d.ts +4 -0
- package/lib/snapshotChunks.d.ts.map +1 -1
- package/lib/snapshotChunks.js.map +1 -1
- package/lib/snapshotLoader.d.ts.map +1 -1
- package/lib/snapshotLoader.js +9 -1
- package/lib/snapshotLoader.js.map +1 -1
- package/lib/snapshotV1.d.ts.map +1 -1
- package/lib/snapshotV1.js +7 -2
- package/lib/snapshotV1.js.map +1 -1
- package/package.json +19 -13
- package/src/client.ts +2 -0
- package/src/collections.ts +2 -0
- package/src/mergeTree.ts +37 -55
- package/src/partialLengths.ts +23 -21
- package/src/snapshotChunks.ts +4 -0
- package/src/snapshotLoader.ts +9 -1
- package/src/snapshotV1.ts +8 -2
package/lib/snapshotV1.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshotV1.js","sourceRoot":"","sources":["../src/snapshotV1.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { IFluidHandle } from \"@fluidframework/core-interfaces\";\nimport { IFluidSerializer } from \"@fluidframework/shared-object-base\";\nimport { assert, bufferToString } from \"@fluidframework/common-utils\";\nimport { ChildLogger } from \"@fluidframework/telemetry-utils\";\nimport { IChannelStorageService } from \"@fluidframework/datastore-definitions\";\nimport { ISummaryTreeWithStats } from \"@fluidframework/runtime-definitions\";\nimport { SummaryTreeBuilder } from \"@fluidframework/runtime-utils\";\nimport { UnassignedSequenceNumber } from \"./constants\";\nimport {\n ISegment,\n MergeTree,\n} from \"./mergeTree\";\nimport {\n matchProperties,\n PropertySet,\n} from \"./properties\";\nimport {\n IJSONSegmentWithMergeInfo,\n JsonSegmentSpecs,\n MergeTreeHeaderMetadata,\n MergeTreeChunkV1,\n toLatestVersion,\n serializeAsMaxSupportedVersion,\n} from \"./snapshotChunks\";\nimport { SnapshotLegacy } from \"./snapshotlegacy\";\n\nexport class SnapshotV1 {\n // Split snapshot into two entries - headers (small) and body (overflow) for faster loading initial content\n // Please note that this number has no direct relationship to anything other than size of raw text (characters).\n // As we produce json for the blob (and then send over the wire compressed), this number\n // is really hard to correlate with any actual metric that matters (like bytes over the wire).\n // For test with small number of chunks it would be closer to blob size,\n // for very chunky text, blob size can easily be 4x-8x of that number.\n public static readonly chunkSize: number = 10000;\n\n private readonly header: MergeTreeHeaderMetadata;\n private readonly segments: JsonSegmentSpecs[];\n private readonly segmentLengths: number[];\n private readonly logger: ITelemetryLogger;\n private readonly chunkSize: number;\n\n constructor(\n public mergeTree: MergeTree,\n logger: ITelemetryLogger,\n private readonly getLongClientId: (id: number) => string,\n public filename?: string,\n public onCompletion?: () => void,\n ) {\n this.logger = ChildLogger.create(logger, \"Snapshot\");\n this.chunkSize = mergeTree?.options?.mergeTreeSnapshotChunkSize ?? SnapshotV1.chunkSize;\n\n const { currentSeq, minSeq } = mergeTree.getCollabWindow();\n this.header = {\n minSequenceNumber: minSeq,\n sequenceNumber: currentSeq,\n orderedChunkMetadata: [],\n totalLength: 0,\n totalSegmentCount: 0,\n };\n\n this.segments = [];\n this.segmentLengths = [];\n }\n\n private getSeqLengthSegs(\n allSegments: JsonSegmentSpecs[],\n allLengths: number[],\n approxSequenceLength: number,\n startIndex = 0): MergeTreeChunkV1 {\n const segments: JsonSegmentSpecs[] = [];\n let length = 0;\n let segmentCount = 0;\n while ((length < approxSequenceLength) && ((startIndex + segmentCount) < allSegments.length)) {\n const pseg = allSegments[startIndex + segmentCount];\n segments.push(pseg);\n length += allLengths[startIndex + segmentCount];\n segmentCount++;\n }\n return {\n version: \"1\",\n segmentCount,\n length,\n segments,\n startIndex,\n headerMetadata: undefined,\n };\n }\n\n /**\n * Emits the snapshot to an ISummarizeResult. If provided the optional IFluidSerializer will be used when\n * serializing the summary data rather than JSON.stringify.\n */\n emit(\n serializer: IFluidSerializer,\n bind: IFluidHandle,\n ): ISummaryTreeWithStats {\n const chunks: MergeTreeChunkV1[] = [];\n this.header.totalSegmentCount = 0;\n this.header.totalLength = 0;\n do {\n const chunk = this.getSeqLengthSegs(\n this.segments,\n this.segmentLengths,\n this.chunkSize,\n this.header.totalSegmentCount);\n chunks.push(chunk);\n this.header.totalSegmentCount += chunk.segmentCount;\n this.header.totalLength += chunk.length;\n } while (this.header.totalSegmentCount < this.segments.length);\n\n // The do while loop should have added at least one chunk\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const headerChunk = chunks.shift()!;\n headerChunk.headerMetadata = this.header;\n headerChunk.headerMetadata.orderedChunkMetadata = [{ id: SnapshotLegacy.header }];\n const blobs: [key: string, content: string][] = [];\n chunks.forEach((chunk, index) => {\n const id = `${SnapshotLegacy.body}_${index}`;\n this.header.orderedChunkMetadata.push({ id });\n blobs.push([id, serializeAsMaxSupportedVersion(\n id,\n chunk,\n this.logger,\n this.mergeTree.options,\n serializer,\n bind)]);\n });\n\n const builder = new SummaryTreeBuilder();\n builder.addBlob(SnapshotLegacy.header, serializeAsMaxSupportedVersion(\n SnapshotLegacy.header,\n headerChunk,\n this.logger,\n this.mergeTree.options,\n serializer,\n bind));\n blobs.forEach((value) => {\n builder.addBlob(value[0], value[1]);\n });\n\n return builder.getSummaryTree();\n }\n\n extractSync() {\n const mergeTree = this.mergeTree;\n const minSeq = this.header.minSequenceNumber;\n\n // Helper to add the given `MergeTreeChunkV0SegmentSpec` to the snapshot.\n const pushSegRaw = (json: JsonSegmentSpecs, length: number) => {\n this.segments.push(json);\n this.segmentLengths.push(length);\n };\n\n // Helper to serialize the given `segment` and add it to the snapshot (if a segment is provided).\n const pushSeg = (segment?: ISegment) => {\n if (segment) { pushSegRaw(segment.toJSONObject(), segment.cachedLength); }\n };\n\n let prev: ISegment | undefined;\n const extractSegment = (segment: ISegment) => {\n // Elide segments that do not need to be included in the snapshot. A segment may be elided if\n // either condition is true:\n // a) The segment has not yet been ACKed. We do not need to snapshot unACKed segments because\n // there is a pending insert op that will deliver the segment on reconnection.\n // b) The segment was removed at or below the MSN. Pending ops can no longer reference this\n // segment, and therefore we can discard it.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n if (segment.seq === UnassignedSequenceNumber || segment.removedSeq! <= minSeq) {\n return true;\n }\n\n // Next determine if the snapshot needs to preserve information required for merging the segment\n // (seq, client, etc.) This information is only needed if the segment is above the MSN (and doesn't\n // have a pending remove.)\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n if ((segment.seq! <= minSeq) // Segment is below the MSN, and...\n && (segment.removedSeq === undefined // .. Segment has not been removed, or...\n || segment.removedSeq === UnassignedSequenceNumber) // .. Removal op to be delivered on reconnect\n ) {\n // This segment is below the MSN, which means that future ops will not reference it. Attempt to\n // coalesce the new segment with the previous (if any).\n if (!prev) {\n // We do not have a previous candidate for coalescing. Make the current segment the new candidate.\n prev = segment;\n } else if (prev.canAppend(segment) && matchProperties(prev.properties, segment.properties)) {\n // We have a compatible pair. Replace `prev` with the coalesced segment. Clone to avoid\n // modifying the segment instances currently in the MergeTree.\n prev = prev.clone();\n prev.append(segment.clone());\n } else {\n // The segment pair could not be coalesced. Record the `prev` segment in the snapshot\n // and make the current segment the new candidate for coalescing.\n pushSeg(prev);\n prev = segment;\n }\n } else {\n // This segment needs to preserve it's metadata as it may be referenced by future ops. It's ineligible\n // for coalescing, so emit the 'prev' segment now (if any).\n pushSeg(prev);\n prev = undefined;\n\n const raw: IJSONSegmentWithMergeInfo = { json: segment.toJSONObject() };\n // If the segment insertion is above the MSN, record the insertion merge info.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n if (segment.seq! > minSeq) {\n raw.seq = segment.seq;\n raw.client = this.getLongClientId(segment.clientId);\n }\n // We have already dispensed with removed segments below the MSN and removed segments with unassigned\n // sequence numbers. Any remaining removal info should be preserved.\n if (segment.removedSeq !== undefined) {\n assert(segment.removedSeq !== UnassignedSequenceNumber && segment.removedSeq > minSeq,\n 0x065 /* \"On removal info preservation, segment has invalid removed sequence number!\" */);\n raw.removedSeq = segment.removedSeq;\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n raw.removedClient = this.getLongClientId(segment.removedClientId!);\n }\n\n // Sanity check that we are preserving either the seq < minSeq or a removed segment's info.\n assert(raw.seq !== undefined && raw.client !== undefined\n || raw.removedSeq !== undefined && raw.removedClient !== undefined,\n 0x066 /* \"Corrupted preservation of segment metadata!\" */);\n\n // Record the segment with it's required metadata.\n pushSegRaw(raw, segment.cachedLength);\n }\n return true;\n };\n\n mergeTree.walkAllSegments(mergeTree.root, extractSegment, this);\n\n // If the last segment in the walk was coalescable, push it now.\n pushSeg(prev);\n\n return this.segments;\n }\n\n public static async loadChunk(\n storage: IChannelStorageService,\n path: string,\n logger: ITelemetryLogger,\n options: PropertySet | undefined,\n serializer?: IFluidSerializer,\n ): Promise<MergeTreeChunkV1> {\n const blob = await storage.readBlob(path);\n const chunkAsString = bufferToString(blob, \"utf8\");\n return SnapshotV1.processChunk(path, chunkAsString, logger, options, serializer);\n }\n\n public static processChunk(\n path: string,\n chunk: string,\n logger: ITelemetryLogger,\n options: PropertySet | undefined,\n serializer?: IFluidSerializer,\n ): MergeTreeChunkV1 {\n const chunkObj = serializer ? serializer.parse(chunk) : JSON.parse(chunk);\n return toLatestVersion(path, chunkObj, logger, options);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"snapshotV1.js","sourceRoot":"","sources":["../src/snapshotV1.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { IFluidHandle } from \"@fluidframework/core-interfaces\";\nimport { IFluidSerializer } from \"@fluidframework/shared-object-base\";\nimport { assert, bufferToString } from \"@fluidframework/common-utils\";\nimport { ChildLogger } from \"@fluidframework/telemetry-utils\";\nimport { IChannelStorageService } from \"@fluidframework/datastore-definitions\";\nimport { ISummaryTreeWithStats } from \"@fluidframework/runtime-definitions\";\nimport { SummaryTreeBuilder } from \"@fluidframework/runtime-utils\";\nimport { UnassignedSequenceNumber } from \"./constants\";\nimport {\n ISegment,\n MergeTree,\n} from \"./mergeTree\";\nimport {\n matchProperties,\n PropertySet,\n} from \"./properties\";\nimport {\n IJSONSegmentWithMergeInfo,\n JsonSegmentSpecs,\n MergeTreeHeaderMetadata,\n MergeTreeChunkV1,\n toLatestVersion,\n serializeAsMaxSupportedVersion,\n} from \"./snapshotChunks\";\nimport { SnapshotLegacy } from \"./snapshotlegacy\";\n\nexport class SnapshotV1 {\n // Split snapshot into two entries - headers (small) and body (overflow) for faster loading initial content\n // Please note that this number has no direct relationship to anything other than size of raw text (characters).\n // As we produce json for the blob (and then send over the wire compressed), this number\n // is really hard to correlate with any actual metric that matters (like bytes over the wire).\n // For test with small number of chunks it would be closer to blob size,\n // for very chunky text, blob size can easily be 4x-8x of that number.\n public static readonly chunkSize: number = 10000;\n\n private readonly header: MergeTreeHeaderMetadata;\n private readonly segments: JsonSegmentSpecs[];\n private readonly segmentLengths: number[];\n private readonly logger: ITelemetryLogger;\n private readonly chunkSize: number;\n\n constructor(\n public mergeTree: MergeTree,\n logger: ITelemetryLogger,\n private readonly getLongClientId: (id: number) => string,\n public filename?: string,\n public onCompletion?: () => void,\n ) {\n this.logger = ChildLogger.create(logger, \"Snapshot\");\n this.chunkSize = mergeTree?.options?.mergeTreeSnapshotChunkSize ?? SnapshotV1.chunkSize;\n\n const { currentSeq, minSeq } = mergeTree.getCollabWindow();\n this.header = {\n minSequenceNumber: minSeq,\n sequenceNumber: currentSeq,\n orderedChunkMetadata: [],\n totalLength: 0,\n totalSegmentCount: 0,\n };\n\n this.segments = [];\n this.segmentLengths = [];\n }\n\n private getSeqLengthSegs(\n allSegments: JsonSegmentSpecs[],\n allLengths: number[],\n approxSequenceLength: number,\n startIndex = 0): MergeTreeChunkV1 {\n const segments: JsonSegmentSpecs[] = [];\n let length = 0;\n let segmentCount = 0;\n while ((length < approxSequenceLength) && ((startIndex + segmentCount) < allSegments.length)) {\n const pseg = allSegments[startIndex + segmentCount];\n segments.push(pseg);\n length += allLengths[startIndex + segmentCount];\n segmentCount++;\n }\n return {\n version: \"1\",\n segmentCount,\n length,\n segments,\n startIndex,\n headerMetadata: undefined,\n };\n }\n\n /**\n * Emits the snapshot to an ISummarizeResult. If provided the optional IFluidSerializer will be used when\n * serializing the summary data rather than JSON.stringify.\n */\n emit(\n serializer: IFluidSerializer,\n bind: IFluidHandle,\n ): ISummaryTreeWithStats {\n const chunks: MergeTreeChunkV1[] = [];\n this.header.totalSegmentCount = 0;\n this.header.totalLength = 0;\n do {\n const chunk = this.getSeqLengthSegs(\n this.segments,\n this.segmentLengths,\n this.chunkSize,\n this.header.totalSegmentCount);\n chunks.push(chunk);\n this.header.totalSegmentCount += chunk.segmentCount;\n this.header.totalLength += chunk.length;\n } while (this.header.totalSegmentCount < this.segments.length);\n\n // The do while loop should have added at least one chunk\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const headerChunk = chunks.shift()!;\n headerChunk.headerMetadata = this.header;\n headerChunk.headerMetadata.orderedChunkMetadata = [{ id: SnapshotLegacy.header }];\n const blobs: [key: string, content: string][] = [];\n chunks.forEach((chunk, index) => {\n const id = `${SnapshotLegacy.body}_${index}`;\n this.header.orderedChunkMetadata.push({ id });\n blobs.push([id, serializeAsMaxSupportedVersion(\n id,\n chunk,\n this.logger,\n this.mergeTree.options,\n serializer,\n bind)]);\n });\n\n const builder = new SummaryTreeBuilder();\n builder.addBlob(SnapshotLegacy.header, serializeAsMaxSupportedVersion(\n SnapshotLegacy.header,\n headerChunk,\n this.logger,\n this.mergeTree.options,\n serializer,\n bind));\n blobs.forEach((value) => {\n builder.addBlob(value[0], value[1]);\n });\n\n return builder.getSummaryTree();\n }\n\n extractSync() {\n const mergeTree = this.mergeTree;\n const minSeq = this.header.minSequenceNumber;\n\n // Helper to add the given `MergeTreeChunkV0SegmentSpec` to the snapshot.\n const pushSegRaw = (json: JsonSegmentSpecs, length: number) => {\n this.segments.push(json);\n this.segmentLengths.push(length);\n };\n\n // Helper to serialize the given `segment` and add it to the snapshot (if a segment is provided).\n const pushSeg = (segment?: ISegment) => {\n if (segment) { pushSegRaw(segment.toJSONObject(), segment.cachedLength); }\n };\n\n let prev: ISegment | undefined;\n const extractSegment = (segment: ISegment) => {\n // Elide segments that do not need to be included in the snapshot. A segment may be elided if\n // either condition is true:\n // a) The segment has not yet been ACKed. We do not need to snapshot unACKed segments because\n // there is a pending insert op that will deliver the segment on reconnection.\n // b) The segment was removed at or below the MSN. Pending ops can no longer reference this\n // segment, and therefore we can discard it.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n if (segment.seq === UnassignedSequenceNumber || segment.removedSeq! <= minSeq) {\n return true;\n }\n\n // Next determine if the snapshot needs to preserve information required for merging the segment\n // (seq, client, etc.) This information is only needed if the segment is above the MSN (and doesn't\n // have a pending remove.)\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n if ((segment.seq! <= minSeq) // Segment is below the MSN, and...\n && (segment.removedSeq === undefined // .. Segment has not been removed, or...\n || segment.removedSeq === UnassignedSequenceNumber) // .. Removal op to be delivered on reconnect\n ) {\n // This segment is below the MSN, which means that future ops will not reference it. Attempt to\n // coalesce the new segment with the previous (if any).\n if (!prev) {\n // We do not have a previous candidate for coalescing. Make the current segment the new candidate.\n prev = segment;\n } else if (prev.canAppend(segment) && matchProperties(prev.properties, segment.properties)) {\n // We have a compatible pair. Replace `prev` with the coalesced segment. Clone to avoid\n // modifying the segment instances currently in the MergeTree.\n prev = prev.clone();\n prev.append(segment.clone());\n } else {\n // The segment pair could not be coalesced. Record the `prev` segment in the snapshot\n // and make the current segment the new candidate for coalescing.\n pushSeg(prev);\n prev = segment;\n }\n } else {\n // This segment needs to preserve it's metadata as it may be referenced by future ops. It's ineligible\n // for coalescing, so emit the 'prev' segment now (if any).\n pushSeg(prev);\n prev = undefined;\n\n const raw: IJSONSegmentWithMergeInfo = { json: segment.toJSONObject() };\n // If the segment insertion is above the MSN, record the insertion merge info.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n if (segment.seq! > minSeq) {\n raw.seq = segment.seq;\n raw.client = this.getLongClientId(segment.clientId);\n }\n // We have already dispensed with removed segments below the MSN and removed segments with unassigned\n // sequence numbers. Any remaining removal info should be preserved.\n if (segment.removedSeq !== undefined) {\n assert(segment.removedSeq !== UnassignedSequenceNumber && segment.removedSeq > minSeq,\n 0x065 /* \"On removal info preservation, segment has invalid removed sequence number!\" */);\n raw.removedSeq = segment.removedSeq;\n\n // back compat for when we split overlap and removed client\n raw.removedClient =\n segment.removedClientIds !== undefined\n ? this.getLongClientId(segment.removedClientIds[0])\n : undefined;\n\n raw.removedClientIds = segment.removedClientIds?.map((id)=>this.getLongClientId(id));\n }\n\n // Sanity check that we are preserving either the seq < minSeq or a removed segment's info.\n assert(raw.seq !== undefined && raw.client !== undefined\n || raw.removedSeq !== undefined && raw.removedClient !== undefined,\n 0x066 /* \"Corrupted preservation of segment metadata!\" */);\n\n // Record the segment with it's required metadata.\n pushSegRaw(raw, segment.cachedLength);\n }\n return true;\n };\n\n mergeTree.walkAllSegments(mergeTree.root, extractSegment, this);\n\n // If the last segment in the walk was coalescable, push it now.\n pushSeg(prev);\n\n return this.segments;\n }\n\n public static async loadChunk(\n storage: IChannelStorageService,\n path: string,\n logger: ITelemetryLogger,\n options: PropertySet | undefined,\n serializer?: IFluidSerializer,\n ): Promise<MergeTreeChunkV1> {\n const blob = await storage.readBlob(path);\n const chunkAsString = bufferToString(blob, \"utf8\");\n return SnapshotV1.processChunk(path, chunkAsString, logger, options, serializer);\n }\n\n public static processChunk(\n path: string,\n chunk: string,\n logger: ITelemetryLogger,\n options: PropertySet | undefined,\n serializer?: IFluidSerializer,\n ): MergeTreeChunkV1 {\n const chunkObj = serializer ? serializer.parse(chunk) : JSON.parse(chunk);\n return toLatestVersion(path, chunkObj, logger, options);\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/merge-tree",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.1000",
|
|
4
4
|
"description": "Merge tree",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"lint:fix": "npm run eslint:fix",
|
|
32
32
|
"test": "npm run test:mocha",
|
|
33
33
|
"test:coverage": "nyc npm test -- --reporter xunit --reporter-option output=nyc/junit-report.xml",
|
|
34
|
-
"test:mocha": "mocha --recursive dist/test --exit -r node_modules/@fluidframework/mocha-test-setup -r source-map-support/register --unhandled-rejections=strict",
|
|
34
|
+
"test:mocha": "mocha --ignore 'dist/test/types/*' --recursive dist/test --exit -r node_modules/@fluidframework/mocha-test-setup -r source-map-support/register --unhandled-rejections=strict",
|
|
35
35
|
"test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
|
|
36
36
|
"tsc": "tsc",
|
|
37
37
|
"tsfmt": "tsfmt --verify",
|
|
@@ -60,20 +60,21 @@
|
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@fluidframework/common-definitions": "^0.20.1",
|
|
62
62
|
"@fluidframework/common-utils": "^0.32.1",
|
|
63
|
-
"@fluidframework/container-definitions": "^0.
|
|
64
|
-
"@fluidframework/core-interfaces": "^0.
|
|
65
|
-
"@fluidframework/datastore-definitions": "^0.
|
|
66
|
-
"@fluidframework/protocol-definitions": "^0.
|
|
67
|
-
"@fluidframework/runtime-definitions": "^0.
|
|
68
|
-
"@fluidframework/runtime-utils": "^0.
|
|
69
|
-
"@fluidframework/shared-object-base": "^0.
|
|
70
|
-
"@fluidframework/telemetry-utils": "^0.
|
|
63
|
+
"@fluidframework/container-definitions": "^0.48.1000",
|
|
64
|
+
"@fluidframework/core-interfaces": "^0.43.1000",
|
|
65
|
+
"@fluidframework/datastore-definitions": "^0.59.1000",
|
|
66
|
+
"@fluidframework/protocol-definitions": "^0.1028.1000",
|
|
67
|
+
"@fluidframework/runtime-definitions": "^0.59.1000",
|
|
68
|
+
"@fluidframework/runtime-utils": "^0.59.1000",
|
|
69
|
+
"@fluidframework/shared-object-base": "^0.59.1000",
|
|
70
|
+
"@fluidframework/telemetry-utils": "^0.59.1000"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@fluidframework/build-common": "^0.23.0",
|
|
74
|
-
"@fluidframework/eslint-config-fluid": "^0.
|
|
75
|
-
"@fluidframework/
|
|
76
|
-
"@fluidframework/test-
|
|
74
|
+
"@fluidframework/eslint-config-fluid": "^0.28.1000",
|
|
75
|
+
"@fluidframework/merge-tree-previous": "npm:@fluidframework/merge-tree@^0.58.0",
|
|
76
|
+
"@fluidframework/mocha-test-setup": "^0.59.1000",
|
|
77
|
+
"@fluidframework/test-runtime-utils": "^0.59.1000",
|
|
77
78
|
"@microsoft/api-extractor": "^7.16.1",
|
|
78
79
|
"@rushstack/eslint-config": "^2.5.1",
|
|
79
80
|
"@types/diff": "^3.5.1",
|
|
@@ -100,5 +101,10 @@
|
|
|
100
101
|
"source-map-support": "^0.5.16",
|
|
101
102
|
"typescript": "~4.1.3",
|
|
102
103
|
"typescript-formatter": "7.1.0"
|
|
104
|
+
},
|
|
105
|
+
"typeValidation": {
|
|
106
|
+
"version": "0.59.1000",
|
|
107
|
+
"broken": {},
|
|
108
|
+
"disabled": true
|
|
103
109
|
}
|
|
104
110
|
}
|
package/src/client.ts
CHANGED
|
@@ -545,6 +545,8 @@ export class Client {
|
|
|
545
545
|
}
|
|
546
546
|
}
|
|
547
547
|
|
|
548
|
+
// start and end are guaranteed to be non-null here, otherwise we throw above.
|
|
549
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
548
550
|
return { start, end } as IIntegerRange;
|
|
549
551
|
}
|
|
550
552
|
|
package/src/collections.ts
CHANGED
|
@@ -190,6 +190,7 @@ export class Heap<T> {
|
|
|
190
190
|
this.fixup(this.count());
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
/* eslint-disable no-bitwise */
|
|
193
194
|
private fixup(k: number) {
|
|
194
195
|
let _k = k;
|
|
195
196
|
while (_k > 1 && (this.comp.compare(this.L[_k >> 1], this.L[_k]) > 0)) {
|
|
@@ -216,6 +217,7 @@ export class Heap<T> {
|
|
|
216
217
|
_k = j;
|
|
217
218
|
}
|
|
218
219
|
}
|
|
220
|
+
/* eslint-enable no-bitwise */
|
|
219
221
|
}
|
|
220
222
|
|
|
221
223
|
export const enum RBColor {
|
package/src/mergeTree.ts
CHANGED
|
@@ -104,15 +104,21 @@ export interface IHierBlock extends IMergeBlock {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
export interface IRemovalInfo {
|
|
107
|
-
removedSeq
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
removedSeq: number;
|
|
108
|
+
removedClientIds: number[];
|
|
109
|
+
}
|
|
110
|
+
export function toRemovalInfo(maybe: Partial<IRemovalInfo> | undefined): IRemovalInfo | undefined {
|
|
111
|
+
if (maybe?.removedClientIds !== undefined && maybe?.removedSeq !== undefined) {
|
|
112
|
+
return maybe as IRemovalInfo;
|
|
113
|
+
}
|
|
114
|
+
assert(maybe?.removedClientIds === undefined && maybe?.removedSeq === undefined,
|
|
115
|
+
0x2bf /* "both removedClientIds and removedSeq should be set or not set" */);
|
|
110
116
|
}
|
|
111
117
|
|
|
112
118
|
/**
|
|
113
119
|
* A segment representing a portion of the merge tree.
|
|
114
120
|
*/
|
|
115
|
-
export interface ISegment extends IMergeNodeCommon, IRemovalInfo {
|
|
121
|
+
export interface ISegment extends IMergeNodeCommon, Partial<IRemovalInfo> {
|
|
116
122
|
readonly type: string;
|
|
117
123
|
readonly segmentGroups: SegmentGroupCollection;
|
|
118
124
|
readonly trackingCollection: TrackingGroupCollection;
|
|
@@ -482,8 +488,7 @@ export abstract class BaseSegment extends MergeNode implements ISegment {
|
|
|
482
488
|
public clientId: number = LocalClientId;
|
|
483
489
|
public seq: number = UniversalSequenceNumber;
|
|
484
490
|
public removedSeq?: number;
|
|
485
|
-
public
|
|
486
|
-
public removedClientOverlap?: number[];
|
|
491
|
+
public removedClientIds?: number[];
|
|
487
492
|
public readonly segmentGroups: SegmentGroupCollection = new SegmentGroupCollection(this);
|
|
488
493
|
public readonly trackingCollection: TrackingGroupCollection = new TrackingGroupCollection(this);
|
|
489
494
|
public propertyManager?: PropertiesManager;
|
|
@@ -521,7 +526,7 @@ export abstract class BaseSegment extends MergeNode implements ISegment {
|
|
|
521
526
|
b.clientId = this.clientId;
|
|
522
527
|
// TODO: deep clone properties
|
|
523
528
|
b.properties = clone(this.properties);
|
|
524
|
-
b.
|
|
529
|
+
b.removedClientIds = this.removedClientIds?.slice();
|
|
525
530
|
// TODO: copy removed client overlap and branch removal info
|
|
526
531
|
b.removedSeq = this.removedSeq;
|
|
527
532
|
b.seq = this.seq;
|
|
@@ -555,10 +560,9 @@ export abstract class BaseSegment extends MergeNode implements ISegment {
|
|
|
555
560
|
return true;
|
|
556
561
|
|
|
557
562
|
case MergeTreeDeltaType.REMOVE:
|
|
558
|
-
|
|
559
|
-
const removalInfo: IRemovalInfo = this;
|
|
560
|
-
assert(
|
|
561
|
-
assert(!!removalInfo.removedSeq, 0x047 /* "On remove ack, missing removed sequence number!" */);
|
|
563
|
+
|
|
564
|
+
const removalInfo: IRemovalInfo | undefined = toRemovalInfo(this);
|
|
565
|
+
assert(removalInfo !== undefined, 0x046 /* "On remove ack, missing removal info!" */);
|
|
562
566
|
this.localRemovedSeq = undefined;
|
|
563
567
|
if (removalInfo.removedSeq === UnassignedSequenceNumber) {
|
|
564
568
|
removalInfo.removedSeq = opArgs.sequencedMessage!.sequenceNumber;
|
|
@@ -584,15 +588,12 @@ export abstract class BaseSegment extends MergeNode implements ISegment {
|
|
|
584
588
|
// but this ordinal meets all the necessary invariants for now.
|
|
585
589
|
leafSegment.ordinal = this.ordinal + String.fromCharCode(0);
|
|
586
590
|
|
|
587
|
-
leafSegment.
|
|
591
|
+
leafSegment.removedClientIds = this.removedClientIds?.slice();
|
|
588
592
|
leafSegment.removedSeq = this.removedSeq;
|
|
589
593
|
leafSegment.localRemovedSeq = this.localRemovedSeq;
|
|
590
594
|
leafSegment.seq = this.seq;
|
|
591
595
|
leafSegment.localSeq = this.localSeq;
|
|
592
596
|
leafSegment.clientId = this.clientId;
|
|
593
|
-
if (this.removedClientOverlap) {
|
|
594
|
-
leafSegment.removedClientOverlap = [...this.removedClientOverlap];
|
|
595
|
-
}
|
|
596
597
|
this.segmentGroups.copyTo(leafSegment);
|
|
597
598
|
this.trackingCollection.copyTo(leafSegment);
|
|
598
599
|
if (this.localRefs) {
|
|
@@ -1035,10 +1036,6 @@ export class MergeTree {
|
|
|
1035
1036
|
|
|
1036
1037
|
private static readonly initBlockUpdateActions: BlockUpdateActions;
|
|
1037
1038
|
private static readonly theUnfinishedNode = <IMergeBlock>{ childCount: -1 };
|
|
1038
|
-
// WARNING:
|
|
1039
|
-
// Setting blockUpdateMarkers to false will result in eventual consistency issues
|
|
1040
|
-
// for property updates on markers when loading from snapshots
|
|
1041
|
-
private static readonly blockUpdateMarkers = true;
|
|
1042
1039
|
|
|
1043
1040
|
root: IMergeBlock;
|
|
1044
1041
|
private readonly blockUpdateActions: BlockUpdateActions = MergeTree.initBlockUpdateActions;
|
|
@@ -1059,12 +1056,7 @@ export class MergeTree {
|
|
|
1059
1056
|
}
|
|
1060
1057
|
|
|
1061
1058
|
private makeBlock(childCount: number) {
|
|
1062
|
-
|
|
1063
|
-
if (MergeTree.blockUpdateMarkers) {
|
|
1064
|
-
block = new HierMergeBlock(childCount);
|
|
1065
|
-
} else {
|
|
1066
|
-
block = new MergeBlock(childCount);
|
|
1067
|
-
}
|
|
1059
|
+
const block: MergeBlock = new HierMergeBlock(childCount);
|
|
1068
1060
|
block.ordinal = "";
|
|
1069
1061
|
return block;
|
|
1070
1062
|
}
|
|
@@ -1100,8 +1092,8 @@ export class MergeTree {
|
|
|
1100
1092
|
}
|
|
1101
1093
|
|
|
1102
1094
|
public localNetLength(segment: ISegment) {
|
|
1103
|
-
const removalInfo
|
|
1104
|
-
if (removalInfo
|
|
1095
|
+
const removalInfo = toRemovalInfo(segment);
|
|
1096
|
+
if (removalInfo !== undefined) {
|
|
1105
1097
|
return 0;
|
|
1106
1098
|
} else {
|
|
1107
1099
|
return segment.cachedLength;
|
|
@@ -1467,9 +1459,9 @@ export class MergeTree {
|
|
|
1467
1459
|
return node.partialLengths!.getPartialLength(refSeq, clientId);
|
|
1468
1460
|
} else {
|
|
1469
1461
|
const segment = node;
|
|
1470
|
-
const removalInfo
|
|
1462
|
+
const removalInfo = toRemovalInfo(segment);
|
|
1471
1463
|
|
|
1472
|
-
if(removalInfo
|
|
1464
|
+
if(removalInfo !== undefined
|
|
1473
1465
|
&& removalInfo.removedSeq !== UnassignedSequenceNumber
|
|
1474
1466
|
&& removalInfo.removedSeq <= refSeq) {
|
|
1475
1467
|
// this segment is a tombstone eligible for zamboni
|
|
@@ -1480,12 +1472,8 @@ export class MergeTree {
|
|
|
1480
1472
|
if (((segment.clientId === clientId) ||
|
|
1481
1473
|
((segment.seq !== UnassignedSequenceNumber) && (segment.seq! <= refSeq)))) {
|
|
1482
1474
|
// Segment happened by reference sequence number or segment from requesting client
|
|
1483
|
-
if (removalInfo
|
|
1484
|
-
if (
|
|
1485
|
-
removalInfo.removedClientId === clientId
|
|
1486
|
-
|| (removalInfo.removedClientOverlap
|
|
1487
|
-
&& removalInfo.removedClientOverlap.includes(clientId))
|
|
1488
|
-
) {
|
|
1475
|
+
if (removalInfo !== undefined) {
|
|
1476
|
+
if (removalInfo.removedClientIds.includes(clientId)) {
|
|
1489
1477
|
return 0;
|
|
1490
1478
|
} else {
|
|
1491
1479
|
return segment.cachedLength;
|
|
@@ -1497,7 +1485,7 @@ export class MergeTree {
|
|
|
1497
1485
|
// the segment was inserted and removed before the
|
|
1498
1486
|
// this context, so it will never exist for this
|
|
1499
1487
|
// context
|
|
1500
|
-
if(removalInfo
|
|
1488
|
+
if(removalInfo !== undefined
|
|
1501
1489
|
&& removalInfo.removedSeq !== UnassignedSequenceNumber) {
|
|
1502
1490
|
return undefined;
|
|
1503
1491
|
}
|
|
@@ -2305,13 +2293,6 @@ export class MergeTree {
|
|
|
2305
2293
|
}
|
|
2306
2294
|
}
|
|
2307
2295
|
|
|
2308
|
-
private addOverlappingClient(removalInfo: IRemovalInfo, clientId: number) {
|
|
2309
|
-
if (!removalInfo.removedClientOverlap) {
|
|
2310
|
-
removalInfo.removedClientOverlap = <number[]>[];
|
|
2311
|
-
}
|
|
2312
|
-
removalInfo.removedClientOverlap.push(clientId);
|
|
2313
|
-
}
|
|
2314
|
-
|
|
2315
2296
|
/**
|
|
2316
2297
|
* Annotate a range with properties
|
|
2317
2298
|
* @param start - The inclusive start position of the range to annotate
|
|
@@ -2382,21 +2363,24 @@ export class MergeTree {
|
|
|
2382
2363
|
const savedLocalRefs: LocalReferenceCollection[] = [];
|
|
2383
2364
|
const localSeq = seq === UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined;
|
|
2384
2365
|
const markRemoved = (segment: ISegment, pos: number, _start: number, _end: number) => {
|
|
2385
|
-
const
|
|
2386
|
-
if (
|
|
2366
|
+
const existingRemovalInfo = toRemovalInfo(segment);
|
|
2367
|
+
if (existingRemovalInfo !== undefined) {
|
|
2387
2368
|
_overwrite = true;
|
|
2388
|
-
if (
|
|
2389
|
-
//
|
|
2390
|
-
|
|
2391
|
-
|
|
2369
|
+
if (existingRemovalInfo.removedSeq === UnassignedSequenceNumber) {
|
|
2370
|
+
// we removed this locally, but someone else removed it first
|
|
2371
|
+
// so put them at the head of the list
|
|
2372
|
+
// the list isn't ordered, but we
|
|
2373
|
+
// keep first removal at the head.
|
|
2374
|
+
existingRemovalInfo.removedClientIds.unshift(clientId);
|
|
2375
|
+
existingRemovalInfo.removedSeq = seq;
|
|
2392
2376
|
segment.localRemovedSeq = undefined;
|
|
2393
2377
|
} else {
|
|
2394
2378
|
// Do not replace earlier sequence number for remove
|
|
2395
|
-
|
|
2379
|
+
existingRemovalInfo.removedClientIds.push(clientId);
|
|
2396
2380
|
}
|
|
2397
2381
|
} else {
|
|
2398
|
-
|
|
2399
|
-
|
|
2382
|
+
segment.removedClientIds = [clientId];
|
|
2383
|
+
segment.removedSeq = seq;
|
|
2400
2384
|
segment.localRemovedSeq = localSeq;
|
|
2401
2385
|
|
|
2402
2386
|
removedSegments.push({ segment });
|
|
@@ -2408,9 +2392,7 @@ export class MergeTree {
|
|
|
2408
2392
|
|
|
2409
2393
|
// Save segment so can assign removed sequence number when acked by server
|
|
2410
2394
|
if (this.collabWindow.collaborating) {
|
|
2411
|
-
|
|
2412
|
-
const _removalInfo: IRemovalInfo = segment;
|
|
2413
|
-
if (_removalInfo.removedSeq === UnassignedSequenceNumber && clientId === this.collabWindow.clientId) {
|
|
2395
|
+
if (segment.removedSeq === UnassignedSequenceNumber && clientId === this.collabWindow.clientId) {
|
|
2414
2396
|
segmentGroup = this.addToPendingList(segment, segmentGroup, localSeq);
|
|
2415
2397
|
} else {
|
|
2416
2398
|
if (MergeTree.options.zamboniSegments) {
|
package/src/partialLengths.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
IRemovalInfo,
|
|
15
15
|
ISegment,
|
|
16
16
|
MergeTree,
|
|
17
|
+
toRemovalInfo,
|
|
17
18
|
} from "./mergeTree";
|
|
18
19
|
|
|
19
20
|
interface IOverlapClient {
|
|
@@ -81,7 +82,7 @@ export class PartialSequenceLengths {
|
|
|
81
82
|
collabWindow: CollaborationWindow,
|
|
82
83
|
recur = false) {
|
|
83
84
|
let combinedPartialLengths = new PartialSequenceLengths(collabWindow.minSeq);
|
|
84
|
-
PartialSequenceLengths.fromLeaves(
|
|
85
|
+
PartialSequenceLengths.fromLeaves(combinedPartialLengths, block, collabWindow);
|
|
85
86
|
let prevPartial: PartialSequenceLength | undefined;
|
|
86
87
|
|
|
87
88
|
function cloneOverlapRemoveClients(oldTree: RedBlackTree<number, IOverlapClient> | undefined) {
|
|
@@ -209,13 +210,13 @@ export class PartialSequenceLengths {
|
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
private static fromLeaves(
|
|
212
|
-
|
|
213
|
+
combinedPartialLengths: PartialSequenceLengths,
|
|
213
214
|
block: IMergeBlock, collabWindow: CollaborationWindow) {
|
|
214
215
|
combinedPartialLengths.minLength = 0;
|
|
215
216
|
combinedPartialLengths.segmentCount = block.childCount;
|
|
216
217
|
|
|
217
|
-
function seqLTE(seq: number, minSeq: number) {
|
|
218
|
-
return
|
|
218
|
+
function seqLTE(seq: number | undefined, minSeq: number) {
|
|
219
|
+
return seq !== undefined && seq !== UnassignedSequenceNumber && seq <= minSeq;
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
for (let i = 0; i < block.childCount; i++) {
|
|
@@ -223,21 +224,19 @@ export class PartialSequenceLengths {
|
|
|
223
224
|
if (child.isLeaf()) {
|
|
224
225
|
// Leaf segment
|
|
225
226
|
const segment = child;
|
|
226
|
-
|
|
227
|
-
if (seqLTE(segment.seq!, collabWindow.minSeq)) {
|
|
227
|
+
if (seqLTE(segment.seq, collabWindow.minSeq)) {
|
|
228
228
|
combinedPartialLengths.minLength += segment.cachedLength;
|
|
229
229
|
} else {
|
|
230
230
|
if (segment.seq !== UnassignedSequenceNumber) {
|
|
231
231
|
PartialSequenceLengths.insertSegment(combinedPartialLengths, segment);
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
|
-
const removalInfo
|
|
235
|
-
|
|
236
|
-
if (seqLTE(removalInfo.removedSeq!, collabWindow.minSeq)) {
|
|
234
|
+
const removalInfo = toRemovalInfo(segment);
|
|
235
|
+
if (seqLTE(removalInfo?.removedSeq, collabWindow.minSeq)) {
|
|
237
236
|
combinedPartialLengths.minLength -= segment.cachedLength;
|
|
238
237
|
} else {
|
|
239
|
-
if (
|
|
240
|
-
|
|
238
|
+
if (removalInfo !== undefined
|
|
239
|
+
&& removalInfo.removedSeq !== UnassignedSequenceNumber) {
|
|
241
240
|
PartialSequenceLengths.insertSegment(
|
|
242
241
|
combinedPartialLengths,
|
|
243
242
|
segment,
|
|
@@ -300,14 +299,15 @@ export class PartialSequenceLengths {
|
|
|
300
299
|
let removeClientOverlap: number[] | undefined;
|
|
301
300
|
|
|
302
301
|
if (removalInfo) {
|
|
303
|
-
|
|
304
|
-
seq = removalInfo.removedSeq!;
|
|
302
|
+
seq = removalInfo.removedSeq;
|
|
305
303
|
segmentLen = -segmentLen;
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
304
|
+
// this code still assume removed client id and
|
|
305
|
+
// overlap clients are separate. so we need to pull
|
|
306
|
+
// then apart first.
|
|
307
|
+
clientId = removalInfo.removedClientIds[0];
|
|
308
|
+
removeClientOverlap = removalInfo.removedClientIds.length > 1
|
|
309
|
+
? removalInfo.removedClientIds.slice(1)
|
|
310
|
+
: undefined;
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
const seqPartials = combinedPartialLengths.partialLengths;
|
|
@@ -366,6 +366,8 @@ export class PartialSequenceLengths {
|
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
if (seqPartialLen === undefined) {
|
|
369
|
+
// len will be assigned below, making this assertion true.
|
|
370
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
369
371
|
seqPartialLen = {
|
|
370
372
|
clientId,
|
|
371
373
|
seglen: seqSeglen,
|
|
@@ -419,14 +421,14 @@ export class PartialSequenceLengths {
|
|
|
419
421
|
segCount += branchPartialLengths.segmentCount;
|
|
420
422
|
} else {
|
|
421
423
|
const segment = child;
|
|
422
|
-
const removalInfo
|
|
424
|
+
const removalInfo = toRemovalInfo(segment);
|
|
423
425
|
|
|
424
426
|
if (segment.seq === seq) {
|
|
425
|
-
if (removalInfo
|
|
427
|
+
if (removalInfo?.removedSeq !== seq) {
|
|
426
428
|
seqSeglen += segment.cachedLength;
|
|
427
429
|
}
|
|
428
430
|
} else {
|
|
429
|
-
if (removalInfo
|
|
431
|
+
if (removalInfo?.removedSeq === seq) {
|
|
430
432
|
seqSeglen -= segment.cachedLength;
|
|
431
433
|
}
|
|
432
434
|
}
|
package/src/snapshotChunks.ts
CHANGED
|
@@ -62,7 +62,11 @@ export interface IJSONSegmentWithMergeInfo {
|
|
|
62
62
|
json: IJSONSegment;
|
|
63
63
|
client?: string;
|
|
64
64
|
seq?: number;
|
|
65
|
+
/**
|
|
66
|
+
* @deprecated - use removedClientIds instead. this only exists for back-compat
|
|
67
|
+
*/
|
|
65
68
|
removedClient?: string;
|
|
69
|
+
removedClientIds?: string[];
|
|
66
70
|
removedSeq?: number;
|
|
67
71
|
}
|
|
68
72
|
|
package/src/snapshotLoader.ts
CHANGED
|
@@ -102,8 +102,16 @@ export class SnapshotLoader {
|
|
|
102
102
|
if (spec.removedSeq !== undefined) {
|
|
103
103
|
seg.removedSeq = spec.removedSeq;
|
|
104
104
|
}
|
|
105
|
+
// this format had a bug where it didn't store all the overlap clients
|
|
106
|
+
// this is for back compat, so we change the singular id to an array
|
|
107
|
+
// this will only cause problems if there is an overlapping delete
|
|
108
|
+
// spanning the snapshot, which should be rare
|
|
105
109
|
if (spec.removedClient !== undefined) {
|
|
106
|
-
seg.
|
|
110
|
+
seg.removedClientIds = [this.client.getOrAddShortClientId(spec.removedClient)];
|
|
111
|
+
}
|
|
112
|
+
if (spec.removedClientIds !== undefined) {
|
|
113
|
+
seg.removedClientIds = spec.removedClientIds?.map(
|
|
114
|
+
(sid)=> this.client.getOrAddShortClientId(sid));
|
|
107
115
|
}
|
|
108
116
|
} else {
|
|
109
117
|
seg = this.client.specToSegment(spec);
|
package/src/snapshotV1.ts
CHANGED
|
@@ -218,8 +218,14 @@ export class SnapshotV1 {
|
|
|
218
218
|
assert(segment.removedSeq !== UnassignedSequenceNumber && segment.removedSeq > minSeq,
|
|
219
219
|
0x065 /* "On removal info preservation, segment has invalid removed sequence number!" */);
|
|
220
220
|
raw.removedSeq = segment.removedSeq;
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
|
|
222
|
+
// back compat for when we split overlap and removed client
|
|
223
|
+
raw.removedClient =
|
|
224
|
+
segment.removedClientIds !== undefined
|
|
225
|
+
? this.getLongClientId(segment.removedClientIds[0])
|
|
226
|
+
: undefined;
|
|
227
|
+
|
|
228
|
+
raw.removedClientIds = segment.removedClientIds?.map((id)=>this.getLongClientId(id));
|
|
223
229
|
}
|
|
224
230
|
|
|
225
231
|
// Sanity check that we are preserving either the seq < minSeq or a removed segment's info.
|