@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.
Files changed (52) hide show
  1. package/dist/client.d.ts.map +1 -1
  2. package/dist/client.js +2 -0
  3. package/dist/client.js.map +1 -1
  4. package/dist/collections.d.ts.map +1 -1
  5. package/dist/collections.js +1 -0
  6. package/dist/collections.js.map +1 -1
  7. package/dist/mergeTree.d.ts +5 -8
  8. package/dist/mergeTree.d.ts.map +1 -1
  9. package/dist/mergeTree.js +35 -48
  10. package/dist/mergeTree.js.map +1 -1
  11. package/dist/partialLengths.d.ts.map +1 -1
  12. package/dist/partialLengths.js +19 -18
  13. package/dist/partialLengths.js.map +1 -1
  14. package/dist/snapshotChunks.d.ts +4 -0
  15. package/dist/snapshotChunks.d.ts.map +1 -1
  16. package/dist/snapshotChunks.js.map +1 -1
  17. package/dist/snapshotLoader.d.ts.map +1 -1
  18. package/dist/snapshotLoader.js +9 -1
  19. package/dist/snapshotLoader.js.map +1 -1
  20. package/dist/snapshotV1.d.ts.map +1 -1
  21. package/dist/snapshotV1.js +7 -2
  22. package/dist/snapshotV1.js.map +1 -1
  23. package/lib/client.d.ts.map +1 -1
  24. package/lib/client.js +2 -0
  25. package/lib/client.js.map +1 -1
  26. package/lib/collections.d.ts.map +1 -1
  27. package/lib/collections.js +1 -0
  28. package/lib/collections.js.map +1 -1
  29. package/lib/mergeTree.d.ts +5 -8
  30. package/lib/mergeTree.d.ts.map +1 -1
  31. package/lib/mergeTree.js +33 -47
  32. package/lib/mergeTree.js.map +1 -1
  33. package/lib/partialLengths.d.ts.map +1 -1
  34. package/lib/partialLengths.js +20 -19
  35. package/lib/partialLengths.js.map +1 -1
  36. package/lib/snapshotChunks.d.ts +4 -0
  37. package/lib/snapshotChunks.d.ts.map +1 -1
  38. package/lib/snapshotChunks.js.map +1 -1
  39. package/lib/snapshotLoader.d.ts.map +1 -1
  40. package/lib/snapshotLoader.js +9 -1
  41. package/lib/snapshotLoader.js.map +1 -1
  42. package/lib/snapshotV1.d.ts.map +1 -1
  43. package/lib/snapshotV1.js +7 -2
  44. package/lib/snapshotV1.js.map +1 -1
  45. package/package.json +19 -13
  46. package/src/client.ts +2 -0
  47. package/src/collections.ts +2 -0
  48. package/src/mergeTree.ts +37 -55
  49. package/src/partialLengths.ts +23 -21
  50. package/src/snapshotChunks.ts +4 -0
  51. package/src/snapshotLoader.ts +9 -1
  52. package/src/snapshotV1.ts +8 -2
@@ -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.58.2002",
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.47.1000",
64
- "@fluidframework/core-interfaces": "^0.42.0",
65
- "@fluidframework/datastore-definitions": "^0.58.2002",
66
- "@fluidframework/protocol-definitions": "^0.1027.1000",
67
- "@fluidframework/runtime-definitions": "^0.58.2002",
68
- "@fluidframework/runtime-utils": "^0.58.2002",
69
- "@fluidframework/shared-object-base": "^0.58.2002",
70
- "@fluidframework/telemetry-utils": "^0.58.2002"
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.27.0",
75
- "@fluidframework/mocha-test-setup": "^0.58.2002",
76
- "@fluidframework/test-runtime-utils": "^0.58.2002",
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
 
@@ -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?: number;
108
- removedClientId?: number;
109
- removedClientOverlap?: number[];
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 removedClientId?: number;
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.removedClientId = this.removedClientId;
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
- // eslint-disable-next-line @typescript-eslint/no-this-alias
559
- const removalInfo: IRemovalInfo = this;
560
- assert(!!removalInfo, 0x046 /* "On remove ack, missing removal info!" */);
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.removedClientId = this.removedClientId;
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
- let block: MergeBlock;
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: IRemovalInfo = segment;
1104
- if (removalInfo.removedSeq !== undefined) {
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: IRemovalInfo = segment;
1462
+ const removalInfo = toRemovalInfo(segment);
1471
1463
 
1472
- if(removalInfo.removedSeq !== undefined
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.removedSeq !== undefined) {
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.removedSeq !== undefined
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 removalInfo: IRemovalInfo = segment;
2386
- if (removalInfo.removedSeq !== undefined) {
2366
+ const existingRemovalInfo = toRemovalInfo(segment);
2367
+ if (existingRemovalInfo !== undefined) {
2387
2368
  _overwrite = true;
2388
- if (removalInfo.removedSeq === UnassignedSequenceNumber) {
2389
- // replace because comes later
2390
- removalInfo.removedClientId = clientId;
2391
- removalInfo.removedSeq = seq;
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
- this.addOverlappingClient(removalInfo, clientId);
2379
+ existingRemovalInfo.removedClientIds.push(clientId);
2396
2380
  }
2397
2381
  } else {
2398
- removalInfo.removedClientId = clientId;
2399
- removalInfo.removedSeq = seq;
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
- // Use removal information
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) {
@@ -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(mergeTree, combinedPartialLengths, block, collabWindow);
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
- mergeTree: MergeTree, combinedPartialLengths: PartialSequenceLengths,
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 (seq !== UnassignedSequenceNumber) && (seq <= minSeq);
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
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
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: IRemovalInfo = segment;
235
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
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 ((removalInfo.removedSeq !== undefined) &&
240
- (removalInfo.removedSeq !== UnassignedSequenceNumber)) {
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
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
304
- seq = removalInfo.removedSeq!;
302
+ seq = removalInfo.removedSeq;
305
303
  segmentLen = -segmentLen;
306
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
307
- clientId = removalInfo.removedClientId!;
308
- if (removalInfo.removedClientOverlap) {
309
- removeClientOverlap = removalInfo.removedClientOverlap;
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: IRemovalInfo = segment;
424
+ const removalInfo = toRemovalInfo(segment);
423
425
 
424
426
  if (segment.seq === seq) {
425
- if (removalInfo.removedSeq !== seq) {
427
+ if (removalInfo?.removedSeq !== seq) {
426
428
  seqSeglen += segment.cachedLength;
427
429
  }
428
430
  } else {
429
- if (removalInfo.removedSeq === seq) {
431
+ if (removalInfo?.removedSeq === seq) {
430
432
  seqSeglen -= segment.cachedLength;
431
433
  }
432
434
  }
@@ -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
 
@@ -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.removedClientId = this.client.getOrAddShortClientId(spec.removedClient);
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
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
222
- raw.removedClient = this.getLongClientId(segment.removedClientId!);
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.