@fluidframework/merge-tree 2.23.0 → 2.30.0

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 (53) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/mergeTree.d.ts +2 -0
  3. package/dist/mergeTree.d.ts.map +1 -1
  4. package/dist/mergeTree.js +15 -10
  5. package/dist/mergeTree.js.map +1 -1
  6. package/dist/perspective.d.ts +92 -54
  7. package/dist/perspective.d.ts.map +1 -1
  8. package/dist/perspective.js +145 -84
  9. package/dist/perspective.js.map +1 -1
  10. package/dist/stamps.d.ts +90 -0
  11. package/dist/stamps.d.ts.map +1 -0
  12. package/dist/stamps.js +90 -0
  13. package/dist/stamps.js.map +1 -0
  14. package/dist/test/perspective.spec.d.ts +6 -0
  15. package/dist/test/perspective.spec.d.ts.map +1 -0
  16. package/dist/test/perspective.spec.js +119 -0
  17. package/dist/test/perspective.spec.js.map +1 -0
  18. package/dist/test/stamps.spec.d.ts +6 -0
  19. package/dist/test/stamps.spec.d.ts.map +1 -0
  20. package/dist/test/stamps.spec.js +130 -0
  21. package/dist/test/stamps.spec.js.map +1 -0
  22. package/dist/test/testClientLogger.d.ts +9 -0
  23. package/dist/test/testClientLogger.d.ts.map +1 -1
  24. package/dist/test/testClientLogger.js +64 -45
  25. package/dist/test/testClientLogger.js.map +1 -1
  26. package/lib/mergeTree.d.ts +2 -0
  27. package/lib/mergeTree.d.ts.map +1 -1
  28. package/lib/mergeTree.js +16 -11
  29. package/lib/mergeTree.js.map +1 -1
  30. package/lib/perspective.d.ts +92 -54
  31. package/lib/perspective.d.ts.map +1 -1
  32. package/lib/perspective.js +119 -80
  33. package/lib/perspective.js.map +1 -1
  34. package/lib/stamps.d.ts +90 -0
  35. package/lib/stamps.d.ts.map +1 -0
  36. package/lib/stamps.js +77 -0
  37. package/lib/stamps.js.map +1 -0
  38. package/lib/test/perspective.spec.d.ts +6 -0
  39. package/lib/test/perspective.spec.d.ts.map +1 -0
  40. package/lib/test/perspective.spec.js +117 -0
  41. package/lib/test/perspective.spec.js.map +1 -0
  42. package/lib/test/stamps.spec.d.ts +6 -0
  43. package/lib/test/stamps.spec.d.ts.map +1 -0
  44. package/lib/test/stamps.spec.js +105 -0
  45. package/lib/test/stamps.spec.js.map +1 -0
  46. package/lib/test/testClientLogger.d.ts +9 -0
  47. package/lib/test/testClientLogger.d.ts.map +1 -1
  48. package/lib/test/testClientLogger.js +65 -46
  49. package/lib/test/testClientLogger.js.map +1 -1
  50. package/package.json +17 -17
  51. package/src/mergeTree.ts +32 -11
  52. package/src/perspective.ts +184 -108
  53. package/src/stamps.ts +164 -0
package/lib/stamps.js ADDED
@@ -0,0 +1,77 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { UnassignedSequenceNumber } from "./constants.js";
6
+ export function lessThan(a, b) {
7
+ if (a.seq === UnassignedSequenceNumber) {
8
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
9
+ return b.seq === UnassignedSequenceNumber && a.localSeq < b.localSeq;
10
+ }
11
+ if (b.seq === UnassignedSequenceNumber) {
12
+ return true;
13
+ }
14
+ return a.seq < b.seq;
15
+ }
16
+ export function gte(a, b) {
17
+ return !lessThan(a, b);
18
+ }
19
+ export function greaterThan(a, b) {
20
+ if (a.seq === UnassignedSequenceNumber) {
21
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
22
+ return b.seq !== UnassignedSequenceNumber || a.localSeq > b.localSeq;
23
+ }
24
+ if (b.seq === UnassignedSequenceNumber) {
25
+ return false;
26
+ }
27
+ return a.seq > b.seq;
28
+ }
29
+ export function lte(a, b) {
30
+ return !greaterThan(a, b);
31
+ }
32
+ export function equal(a, b) {
33
+ return a.seq === b.seq && a.clientId === b.clientId && a.localSeq === b.localSeq;
34
+ }
35
+ export function isLocal(a) {
36
+ return a.seq === UnassignedSequenceNumber;
37
+ }
38
+ export function isAcked(a) {
39
+ return a.seq !== UnassignedSequenceNumber;
40
+ }
41
+ /**
42
+ * Inserts a stamp into a sorted list of stamps in the correct (sorted) position.
43
+ *
44
+ * Beware that this uses Array.splice, thus requires asymptotics considerations.
45
+ * If inserting a variable number of timestamp, consider just pushing them and sorting the list
46
+ * after using {@link compare} instead.
47
+ */
48
+ export function spliceIntoList(list, stamp) {
49
+ if (isLocal(stamp) || list.length === 0) {
50
+ list.push(stamp);
51
+ }
52
+ else {
53
+ for (let i = list.length - 1; i >= 0; i--) {
54
+ if (greaterThan(stamp, list[i])) {
55
+ list.splice(i + 1, 0, stamp);
56
+ return;
57
+ }
58
+ }
59
+ // Less than all stamps in the list: put it at the beginning.
60
+ list.unshift(stamp);
61
+ }
62
+ }
63
+ export function hasAnyAckedOperation(list) {
64
+ return list.some((ts) => isAcked(ts));
65
+ }
66
+ export function compare(a, b) {
67
+ if (greaterThan(a, b)) {
68
+ return 1;
69
+ }
70
+ else if (lessThan(a, b)) {
71
+ return -1;
72
+ }
73
+ else {
74
+ return 0;
75
+ }
76
+ }
77
+ //# sourceMappingURL=stamps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stamps.js","sourceRoot":"","sources":["../src/stamps.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 { UnassignedSequenceNumber } from \"./constants.js\";\n\n/**\n * A stamp that identifies provenance of an operation performed on the MergeTree.\n *\n * Stamps identify a point in time (`seq`/`localSeq`) as well as the source (`clientId`) for the operation.\n * This provides enough information to linearize all known applied operations: acked operations happen before\n * local+unacked ones, with acked operations ordered by their sequence numbers and local+unacked operations\n * ordered by their localSeq.\n *\n * By including `clientId`, it also provides enough information to resolve whether segments are visible\n * from alternative perspectives: a remote client will have seen all of its own previous operations as well as\n * those at or below the op's reference sequence number.\n *\n * @remarks - As the `readonly` identifies suggest, these stamps should be treated as immutable.\n * New operations applied to a merge-tree should create new stamps rather than modify existing ones (e.g. when\n * a change's ack happens).\n * @internal\n */\nexport interface OperationStamp {\n\t/**\n\t * The sequence number at which this operation was applied.\n\t */\n\treadonly seq: number;\n\n\t/**\n\t * Short clientId for the client that performed this operation.\n\t */\n\treadonly clientId: number;\n\n\t/**\n\t * Local seq at which this operation was applied.\n\t * This is defined if and only if the operation is pending an ack, i.e. `seq` is UnassignedSequenceNumber.\n\t *\n\t * @privateRemarks\n\t * See {@link CollaborationWindow.localSeq} for more information on the semantics of localSeq.\n\t */\n\treadonly localSeq?: number;\n}\n\n/**\n * {@link OperationStamp} for an 'insert' operation.\n */\nexport interface InsertOperationStamp extends OperationStamp {\n\treadonly type: \"insert\";\n}\n\n/**\n * {@link OperationStamp} for a 'set remove' operation. This aligns with the `markRangeRemoved` API in MergeTree.\n *\n * @remarks The terminology here comes from the fact that the removal should affect only the *set* of nodes that were\n * specified at the time the local client issued the remove, and not any nodes that were inserted concurrently.\n *\n * Not using \"remove\" and \"obliterate\" here allows us to unambiguously use the term \"remove\" elsewhere in code to mean\n * \"removed from the tree, either by MergeTree.obliterateRange or MergeTree.removeRange\". This is convenient as the vast majority\n * of merge-tree code only cares about segment visibility and not the specific operation that caused a segment to be removed.\n */\nexport interface SetRemoveOperationStamp extends OperationStamp {\n\treadonly type: \"setRemove\";\n}\n\n/**\n * {@link OperationStamp} for a 'set remove' operation. This aligns with the `obliterateRange` API in MergeTree.\n *\n * @remarks The terminology here comes from the fact that the removal should affect the *slice* of nodes between the\n * start and end point specified by the local client, which includes any nodes that were inserted concurrently.\n *\n * Not using \"remove\" and \"obliterate\" here allows us to unambiguously use the term \"remove\" elsewhere in code to mean\n * \"removed from the tree, either by MergeTree.obliterateRange or MergeTree.removeRange\". This is convenient as the vast majority\n * of merge-tree code only cares about segment visibility and not the specific operation that caused a segment to be removed.\n */\nexport interface SliceRemoveOperationStamp extends OperationStamp {\n\treadonly type: \"sliceRemove\";\n}\n\nexport type RemoveOperationStamp = SetRemoveOperationStamp | SliceRemoveOperationStamp;\n\nexport function lessThan(a: OperationStamp, b: OperationStamp): boolean {\n\tif (a.seq === UnassignedSequenceNumber) {\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\treturn b.seq === UnassignedSequenceNumber && a.localSeq! < b.localSeq!;\n\t}\n\n\tif (b.seq === UnassignedSequenceNumber) {\n\t\treturn true;\n\t}\n\n\treturn a.seq < b.seq;\n}\n\nexport function gte(a: OperationStamp, b: OperationStamp): boolean {\n\treturn !lessThan(a, b);\n}\n\nexport function greaterThan(a: OperationStamp, b: OperationStamp): boolean {\n\tif (a.seq === UnassignedSequenceNumber) {\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\treturn b.seq !== UnassignedSequenceNumber || a.localSeq! > b.localSeq!;\n\t}\n\n\tif (b.seq === UnassignedSequenceNumber) {\n\t\treturn false;\n\t}\n\n\treturn a.seq > b.seq;\n}\n\nexport function lte(a: OperationStamp, b: OperationStamp): boolean {\n\treturn !greaterThan(a, b);\n}\n\nexport function equal(a: OperationStamp, b: OperationStamp): boolean {\n\treturn a.seq === b.seq && a.clientId === b.clientId && a.localSeq === b.localSeq;\n}\n\nexport function isLocal(a: OperationStamp): boolean {\n\treturn a.seq === UnassignedSequenceNumber;\n}\n\nexport function isAcked(a: OperationStamp): boolean {\n\treturn a.seq !== UnassignedSequenceNumber;\n}\n\n/**\n * Inserts a stamp into a sorted list of stamps in the correct (sorted) position.\n *\n * Beware that this uses Array.splice, thus requires asymptotics considerations.\n * If inserting a variable number of timestamp, consider just pushing them and sorting the list\n * after using {@link compare} instead.\n */\nexport function spliceIntoList(list: OperationStamp[], stamp: OperationStamp): void {\n\tif (isLocal(stamp) || list.length === 0) {\n\t\tlist.push(stamp);\n\t} else {\n\t\tfor (let i = list.length - 1; i >= 0; i--) {\n\t\t\tif (greaterThan(stamp, list[i])) {\n\t\t\t\tlist.splice(i + 1, 0, stamp);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Less than all stamps in the list: put it at the beginning.\n\t\tlist.unshift(stamp);\n\t}\n}\n\nexport function hasAnyAckedOperation(list: OperationStamp[]): boolean {\n\treturn list.some((ts) => isAcked(ts));\n}\n\nexport function compare(a: OperationStamp, b: OperationStamp): number {\n\tif (greaterThan(a, b)) {\n\t\treturn 1;\n\t} else if (lessThan(a, b)) {\n\t\treturn -1;\n\t} else {\n\t\treturn 0;\n\t}\n}\n"]}
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=perspective.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"perspective.spec.d.ts","sourceRoot":"","sources":["../../src/test/perspective.spec.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,117 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { strict as assert } from "node:assert";
6
+ import { UnassignedSequenceNumber } from "../constants.js";
7
+ import { LocalDefaultPerspective, LocalReconnectingPerspective, PriorPerspective, RemoteObliteratePerspective, } from "../perspective.js";
8
+ const clientId = 17;
9
+ describe("PriorPerspective", () => {
10
+ const refSeq = 10;
11
+ const perspective = new PriorPerspective(refSeq, clientId);
12
+ it("sees operations from the same client", () => {
13
+ const stamp = { clientId, seq: 1 };
14
+ assert.ok(perspective.hasOccurred(stamp));
15
+ });
16
+ it("sees operations at or below the refSeq", () => {
17
+ for (let seq = 0; seq <= refSeq; seq++) {
18
+ const stamp = { clientId, seq };
19
+ assert.ok(perspective.hasOccurred(stamp), `Failed for seq ${seq}`);
20
+ }
21
+ });
22
+ it("Does not see operations from other clients above the refSeq", () => {
23
+ const stamp = { clientId: clientId + 1, seq: refSeq + 1 };
24
+ assert.ok(!perspective.hasOccurred(stamp));
25
+ });
26
+ });
27
+ describe("LocalReconnectingPerspective", () => {
28
+ const refSeq = 10;
29
+ const localSeq = 20;
30
+ const perspective = new LocalReconnectingPerspective(refSeq, clientId, localSeq);
31
+ it("sees operations from the same client at or below localSeq", () => {
32
+ for (let i = 0; i <= localSeq; i++) {
33
+ const stamp = { seq: UnassignedSequenceNumber, clientId, localSeq: i };
34
+ assert.ok(perspective.hasOccurred(stamp), `Failed for localSeq ${i}`);
35
+ }
36
+ });
37
+ it("does not see operations from the same client above localSeq", () => {
38
+ const stamp = {
39
+ seq: UnassignedSequenceNumber,
40
+ clientId,
41
+ localSeq: localSeq + 1,
42
+ };
43
+ assert.ok(!perspective.hasOccurred(stamp));
44
+ });
45
+ it("sees operations at or below refSeq", () => {
46
+ for (let seq = 0; seq <= refSeq; seq++) {
47
+ const stamp = {
48
+ seq,
49
+ clientId: seq % 3 === 0 ? clientId : clientId + 1,
50
+ };
51
+ assert.ok(perspective.hasOccurred(stamp), `Failed for seq ${seq}`);
52
+ }
53
+ });
54
+ it("does not see operations from other clients above refSeq", () => {
55
+ const stamp = { seq: refSeq + 1, clientId: clientId + 1 };
56
+ assert.ok(!perspective.hasOccurred(stamp));
57
+ });
58
+ });
59
+ describe("LocalDefaultPerspective", () => {
60
+ const perspective = new LocalDefaultPerspective(clientId);
61
+ it("sees all operations", () => {
62
+ for (const id of [0, 1, 2, 3, clientId]) {
63
+ for (const refSeq of [0, 1, 5, 100, 1000]) {
64
+ const stamp = { seq: 1, clientId: id };
65
+ assert.ok(perspective.hasOccurred(stamp), `Failed for clientId ${id} and refSeq ${refSeq}`);
66
+ }
67
+ }
68
+ for (const localSeq of [0, 1, 5, 100, 1000]) {
69
+ const stamp = { seq: UnassignedSequenceNumber, clientId, localSeq };
70
+ assert.ok(perspective.hasOccurred(stamp), `Failed for localSeq ${localSeq}`);
71
+ }
72
+ });
73
+ });
74
+ describe("RemoteObliteratePerspective", () => {
75
+ const perspective = new RemoteObliteratePerspective(clientId);
76
+ it("Sees all inserts", () => {
77
+ for (const id of [0, 1, 2, 3, clientId]) {
78
+ for (const refSeq of [0, 1, 5, 100, 1000]) {
79
+ const stamp = { type: "insert", seq: 1, clientId: id };
80
+ assert.ok(perspective.hasOccurred(stamp), `Failed for clientId ${id} and refSeq ${refSeq}`);
81
+ }
82
+ }
83
+ for (const localSeq of [0, 1, 5, 100, 1000]) {
84
+ const stamp = {
85
+ type: "insert",
86
+ seq: UnassignedSequenceNumber,
87
+ clientId,
88
+ localSeq,
89
+ };
90
+ assert.ok(perspective.hasOccurred(stamp), `Failed for localSeq ${localSeq}`);
91
+ }
92
+ });
93
+ it("Sees remote removes", () => {
94
+ for (const id of [0, 1, 2, 3, clientId]) {
95
+ for (const refSeq of [0, 1, 5, 100, 1000]) {
96
+ for (const type of ["setRemove", "sliceRemove"]) {
97
+ const stamp = { type, seq: 1, clientId: id };
98
+ assert.ok(perspective.hasOccurred(stamp), `Failed for clientId ${id} and refSeq ${refSeq} with ${type}`);
99
+ }
100
+ }
101
+ }
102
+ });
103
+ it("Does not see local removes", () => {
104
+ for (const localSeq of [0, 1, 5, 100, 1000]) {
105
+ for (const type of ["setRemove", "sliceRemove"]) {
106
+ const stamp = {
107
+ type,
108
+ seq: UnassignedSequenceNumber,
109
+ clientId,
110
+ localSeq,
111
+ };
112
+ assert.ok(!perspective.hasOccurred(stamp), `Failed for localSeq ${localSeq}`);
113
+ }
114
+ }
115
+ });
116
+ });
117
+ //# sourceMappingURL=perspective.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"perspective.spec.js","sourceRoot":"","sources":["../../src/test/perspective.spec.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 { strict as assert } from \"node:assert\";\n\nimport { UnassignedSequenceNumber } from \"../constants.js\";\nimport {\n\tLocalDefaultPerspective,\n\tLocalReconnectingPerspective,\n\tPriorPerspective,\n\tRemoteObliteratePerspective,\n} from \"../perspective.js\";\nimport type { InsertOperationStamp, OperationStamp, RemoveOperationStamp } from \"../stamps.js\";\n\nconst clientId = 17;\ndescribe(\"PriorPerspective\", () => {\n\tconst refSeq = 10;\n\tconst perspective = new PriorPerspective(refSeq, clientId);\n\tit(\"sees operations from the same client\", () => {\n\t\tconst stamp: OperationStamp = { clientId, seq: 1 };\n\t\tassert.ok(perspective.hasOccurred(stamp));\n\t});\n\n\tit(\"sees operations at or below the refSeq\", () => {\n\t\tfor (let seq = 0; seq <= refSeq; seq++) {\n\t\t\tconst stamp: OperationStamp = { clientId, seq };\n\t\t\tassert.ok(perspective.hasOccurred(stamp), `Failed for seq ${seq}`);\n\t\t}\n\t});\n\n\tit(\"Does not see operations from other clients above the refSeq\", () => {\n\t\tconst stamp: OperationStamp = { clientId: clientId + 1, seq: refSeq + 1 };\n\t\tassert.ok(!perspective.hasOccurred(stamp));\n\t});\n});\n\ndescribe(\"LocalReconnectingPerspective\", () => {\n\tconst refSeq = 10;\n\tconst localSeq = 20;\n\tconst perspective = new LocalReconnectingPerspective(refSeq, clientId, localSeq);\n\tit(\"sees operations from the same client at or below localSeq\", () => {\n\t\tfor (let i = 0; i <= localSeq; i++) {\n\t\t\tconst stamp: OperationStamp = { seq: UnassignedSequenceNumber, clientId, localSeq: i };\n\t\t\tassert.ok(perspective.hasOccurred(stamp), `Failed for localSeq ${i}`);\n\t\t}\n\t});\n\n\tit(\"does not see operations from the same client above localSeq\", () => {\n\t\tconst stamp: OperationStamp = {\n\t\t\tseq: UnassignedSequenceNumber,\n\t\t\tclientId,\n\t\t\tlocalSeq: localSeq + 1,\n\t\t};\n\t\tassert.ok(!perspective.hasOccurred(stamp));\n\t});\n\n\tit(\"sees operations at or below refSeq\", () => {\n\t\tfor (let seq = 0; seq <= refSeq; seq++) {\n\t\t\tconst stamp: OperationStamp = {\n\t\t\t\tseq,\n\t\t\t\tclientId: seq % 3 === 0 ? clientId : clientId + 1,\n\t\t\t};\n\t\t\tassert.ok(perspective.hasOccurred(stamp), `Failed for seq ${seq}`);\n\t\t}\n\t});\n\n\tit(\"does not see operations from other clients above refSeq\", () => {\n\t\tconst stamp: OperationStamp = { seq: refSeq + 1, clientId: clientId + 1 };\n\t\tassert.ok(!perspective.hasOccurred(stamp));\n\t});\n});\n\ndescribe(\"LocalDefaultPerspective\", () => {\n\tconst perspective = new LocalDefaultPerspective(clientId);\n\tit(\"sees all operations\", () => {\n\t\tfor (const id of [0, 1, 2, 3, clientId]) {\n\t\t\tfor (const refSeq of [0, 1, 5, 100, 1000]) {\n\t\t\t\tconst stamp: OperationStamp = { seq: 1, clientId: id };\n\t\t\t\tassert.ok(\n\t\t\t\t\tperspective.hasOccurred(stamp),\n\t\t\t\t\t`Failed for clientId ${id} and refSeq ${refSeq}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tfor (const localSeq of [0, 1, 5, 100, 1000]) {\n\t\t\tconst stamp: OperationStamp = { seq: UnassignedSequenceNumber, clientId, localSeq };\n\t\t\tassert.ok(perspective.hasOccurred(stamp), `Failed for localSeq ${localSeq}`);\n\t\t}\n\t});\n});\n\ndescribe(\"RemoteObliteratePerspective\", () => {\n\tconst perspective = new RemoteObliteratePerspective(clientId);\n\tit(\"Sees all inserts\", () => {\n\t\tfor (const id of [0, 1, 2, 3, clientId]) {\n\t\t\tfor (const refSeq of [0, 1, 5, 100, 1000]) {\n\t\t\t\tconst stamp: InsertOperationStamp = { type: \"insert\", seq: 1, clientId: id };\n\t\t\t\tassert.ok(\n\t\t\t\t\tperspective.hasOccurred(stamp),\n\t\t\t\t\t`Failed for clientId ${id} and refSeq ${refSeq}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tfor (const localSeq of [0, 1, 5, 100, 1000]) {\n\t\t\tconst stamp: InsertOperationStamp = {\n\t\t\t\ttype: \"insert\",\n\t\t\t\tseq: UnassignedSequenceNumber,\n\t\t\t\tclientId,\n\t\t\t\tlocalSeq,\n\t\t\t};\n\t\t\tassert.ok(perspective.hasOccurred(stamp), `Failed for localSeq ${localSeq}`);\n\t\t}\n\t});\n\n\tit(\"Sees remote removes\", () => {\n\t\tfor (const id of [0, 1, 2, 3, clientId]) {\n\t\t\tfor (const refSeq of [0, 1, 5, 100, 1000]) {\n\t\t\t\tfor (const type of [\"setRemove\", \"sliceRemove\"] as const) {\n\t\t\t\t\tconst stamp: RemoveOperationStamp = { type, seq: 1, clientId: id };\n\t\t\t\t\tassert.ok(\n\t\t\t\t\t\tperspective.hasOccurred(stamp),\n\t\t\t\t\t\t`Failed for clientId ${id} and refSeq ${refSeq} with ${type}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\n\tit(\"Does not see local removes\", () => {\n\t\tfor (const localSeq of [0, 1, 5, 100, 1000]) {\n\t\t\tfor (const type of [\"setRemove\", \"sliceRemove\"] as const) {\n\t\t\t\tconst stamp: RemoveOperationStamp = {\n\t\t\t\t\ttype,\n\t\t\t\t\tseq: UnassignedSequenceNumber,\n\t\t\t\t\tclientId,\n\t\t\t\t\tlocalSeq,\n\t\t\t\t};\n\t\t\t\tassert.ok(!perspective.hasOccurred(stamp), `Failed for localSeq ${localSeq}`);\n\t\t\t}\n\t\t}\n\t});\n});\n"]}
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=stamps.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stamps.spec.d.ts","sourceRoot":"","sources":["../../src/test/stamps.spec.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,105 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { strict as assert } from "node:assert";
6
+ import { UnassignedSequenceNumber } from "../constants.js";
7
+ import * as opstampUtils from "../stamps.js";
8
+ function lessThan(a, b) {
9
+ const result = opstampUtils.lessThan(a, b);
10
+ // Validate that this gives a consistent result with some other ways to compute the same thing
11
+ const fromComparison = opstampUtils.compare(a, b) === -1;
12
+ const fromGte = !opstampUtils.gte(a, b);
13
+ assert.strictEqual(result, fromComparison);
14
+ assert.strictEqual(result, fromGte);
15
+ return result;
16
+ }
17
+ function greaterThan(a, b) {
18
+ const result = opstampUtils.greaterThan(a, b);
19
+ // Validate that this gives a consistent result with some other ways to compute the same thing
20
+ const fromComparison = opstampUtils.compare(a, b) === 1;
21
+ const fromLte = !opstampUtils.lte(a, b);
22
+ assert.strictEqual(result, fromComparison);
23
+ assert.strictEqual(result, fromLte);
24
+ return result;
25
+ }
26
+ /**
27
+ * Validate that a list of operation stamps is in strictly increasing order in several different ways using the comparison
28
+ * operation stamp utility methods.
29
+ */
30
+ function expectStrictlyIncreasing(list) {
31
+ for (let i = 0; i < list.length - 1; i++) {
32
+ assert.ok(lessThan(list[i], list[i + 1]), `List not strictly increasing by lessThan: ${JSON.stringify(list[i])} >= ${JSON.stringify(list[i + 1])}`);
33
+ assert.ok(greaterThan(list[i + 1], list[i]), `List not strictly increasing by greaterThan: ${JSON.stringify(list[i + 1])} <= ${JSON.stringify(list[i])}`);
34
+ }
35
+ }
36
+ describe("opstampUtils", () => {
37
+ const acked1 = { clientId: 1, seq: 1 };
38
+ const acked2 = { clientId: 2, seq: 2 };
39
+ const acked3 = { clientId: 1, seq: 3 };
40
+ const local1 = { clientId: 1, seq: UnassignedSequenceNumber, localSeq: 1 };
41
+ const local2 = { clientId: 1, seq: UnassignedSequenceNumber, localSeq: 2 };
42
+ describe("equality", () => {
43
+ it("returns true for reference equal stamps", () => {
44
+ for (const stamp of [acked1, acked2, acked3, local1, local2]) {
45
+ assert.ok(opstampUtils.equal(stamp, stamp));
46
+ }
47
+ });
48
+ it("returns true for equal stamps", () => {
49
+ for (const stamp of [acked1, acked2, acked3, local1, local2]) {
50
+ assert.ok(opstampUtils.equal(stamp, { ...stamp }));
51
+ }
52
+ });
53
+ it("returns false for different stamps", () => {
54
+ assert.ok(!opstampUtils.equal(acked1, acked2));
55
+ assert.ok(!opstampUtils.equal(acked1, acked3));
56
+ assert.ok(!opstampUtils.equal(acked1, local1));
57
+ assert.ok(!opstampUtils.equal(acked1, local2));
58
+ assert.ok(!opstampUtils.equal(local1, local2));
59
+ });
60
+ });
61
+ describe("comparison", () => {
62
+ it("orders stamps correctly", () => {
63
+ expectStrictlyIncreasing([acked1, acked2, acked3, local1, local2]);
64
+ });
65
+ it("compare can sort lists", () => {
66
+ const list = [acked3, local1, acked1, local2, acked2];
67
+ list.sort(opstampUtils.compare);
68
+ assert.deepEqual(list, [acked1, acked2, acked3, local1, local2]);
69
+ });
70
+ });
71
+ describe("spliceIntoList", () => {
72
+ it("inserts unacked into empty list", () => {
73
+ const list = [];
74
+ opstampUtils.spliceIntoList(list, local1);
75
+ assert.deepStrictEqual(list, [local1]);
76
+ });
77
+ it("inserts acked into empty list", () => {
78
+ const list = [];
79
+ opstampUtils.spliceIntoList(list, acked1);
80
+ assert.deepStrictEqual(list, [acked1]);
81
+ });
82
+ it("inserts unacked after acked", () => {
83
+ const list = [acked1];
84
+ opstampUtils.spliceIntoList(list, local1);
85
+ assert.deepStrictEqual(list, [acked1, local1]);
86
+ });
87
+ it("inserts acked before unacked", () => {
88
+ const list = [acked1, acked2, local1];
89
+ opstampUtils.spliceIntoList(list, acked3);
90
+ assert.deepStrictEqual(list, [acked1, acked2, acked3, local1]);
91
+ });
92
+ it("inserts acked before single unacked", () => {
93
+ const list = [local1];
94
+ opstampUtils.spliceIntoList(list, acked2);
95
+ assert.deepStrictEqual(list, [acked2, local1]);
96
+ });
97
+ it("inserts local seqs at end", () => {
98
+ const list = [acked1, acked2];
99
+ opstampUtils.spliceIntoList(list, local1);
100
+ opstampUtils.spliceIntoList(list, local2);
101
+ assert.deepStrictEqual(list, [acked1, acked2, local1, local2]);
102
+ });
103
+ });
104
+ });
105
+ //# sourceMappingURL=stamps.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stamps.spec.js","sourceRoot":"","sources":["../../src/test/stamps.spec.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 { strict as assert } from \"node:assert\";\n\nimport { UnassignedSequenceNumber } from \"../constants.js\";\nimport * as opstampUtils from \"../stamps.js\";\nimport type { OperationStamp } from \"../stamps.js\";\n\nfunction lessThan(a: OperationStamp, b: OperationStamp): boolean {\n\tconst result = opstampUtils.lessThan(a, b);\n\t// Validate that this gives a consistent result with some other ways to compute the same thing\n\tconst fromComparison = opstampUtils.compare(a, b) === -1;\n\tconst fromGte = !opstampUtils.gte(a, b);\n\tassert.strictEqual(result, fromComparison);\n\tassert.strictEqual(result, fromGte);\n\treturn result;\n}\n\nfunction greaterThan(a: OperationStamp, b: OperationStamp): boolean {\n\tconst result = opstampUtils.greaterThan(a, b);\n\t// Validate that this gives a consistent result with some other ways to compute the same thing\n\tconst fromComparison = opstampUtils.compare(a, b) === 1;\n\tconst fromLte = !opstampUtils.lte(a, b);\n\tassert.strictEqual(result, fromComparison);\n\tassert.strictEqual(result, fromLte);\n\treturn result;\n}\n\n/**\n * Validate that a list of operation stamps is in strictly increasing order in several different ways using the comparison\n * operation stamp utility methods.\n */\nfunction expectStrictlyIncreasing(list: OperationStamp[]): void {\n\tfor (let i = 0; i < list.length - 1; i++) {\n\t\tassert.ok(\n\t\t\tlessThan(list[i], list[i + 1]),\n\t\t\t`List not strictly increasing by lessThan: ${JSON.stringify(list[i])} >= ${JSON.stringify(list[i + 1])}`,\n\t\t);\n\t\tassert.ok(\n\t\t\tgreaterThan(list[i + 1], list[i]),\n\t\t\t`List not strictly increasing by greaterThan: ${JSON.stringify(list[i + 1])} <= ${JSON.stringify(list[i])}`,\n\t\t);\n\t}\n}\n\ndescribe(\"opstampUtils\", () => {\n\tconst acked1: OperationStamp = { clientId: 1, seq: 1 };\n\tconst acked2: OperationStamp = { clientId: 2, seq: 2 };\n\tconst acked3: OperationStamp = { clientId: 1, seq: 3 };\n\tconst local1: OperationStamp = { clientId: 1, seq: UnassignedSequenceNumber, localSeq: 1 };\n\tconst local2: OperationStamp = { clientId: 1, seq: UnassignedSequenceNumber, localSeq: 2 };\n\tdescribe(\"equality\", () => {\n\t\tit(\"returns true for reference equal stamps\", () => {\n\t\t\tfor (const stamp of [acked1, acked2, acked3, local1, local2]) {\n\t\t\t\tassert.ok(opstampUtils.equal(stamp, stamp));\n\t\t\t}\n\t\t});\n\n\t\tit(\"returns true for equal stamps\", () => {\n\t\t\tfor (const stamp of [acked1, acked2, acked3, local1, local2]) {\n\t\t\t\tassert.ok(opstampUtils.equal(stamp, { ...stamp }));\n\t\t\t}\n\t\t});\n\n\t\tit(\"returns false for different stamps\", () => {\n\t\t\tassert.ok(!opstampUtils.equal(acked1, acked2));\n\t\t\tassert.ok(!opstampUtils.equal(acked1, acked3));\n\t\t\tassert.ok(!opstampUtils.equal(acked1, local1));\n\t\t\tassert.ok(!opstampUtils.equal(acked1, local2));\n\t\t\tassert.ok(!opstampUtils.equal(local1, local2));\n\t\t});\n\t});\n\n\tdescribe(\"comparison\", () => {\n\t\tit(\"orders stamps correctly\", () => {\n\t\t\texpectStrictlyIncreasing([acked1, acked2, acked3, local1, local2]);\n\t\t});\n\n\t\tit(\"compare can sort lists\", () => {\n\t\t\tconst list = [acked3, local1, acked1, local2, acked2];\n\t\t\tlist.sort(opstampUtils.compare);\n\t\t\tassert.deepEqual(list, [acked1, acked2, acked3, local1, local2]);\n\t\t});\n\t});\n\n\tdescribe(\"spliceIntoList\", () => {\n\t\tit(\"inserts unacked into empty list\", () => {\n\t\t\tconst list: OperationStamp[] = [];\n\t\t\topstampUtils.spliceIntoList(list, local1);\n\t\t\tassert.deepStrictEqual(list, [local1]);\n\t\t});\n\n\t\tit(\"inserts acked into empty list\", () => {\n\t\t\tconst list: OperationStamp[] = [];\n\t\t\topstampUtils.spliceIntoList(list, acked1);\n\t\t\tassert.deepStrictEqual(list, [acked1]);\n\t\t});\n\n\t\tit(\"inserts unacked after acked\", () => {\n\t\t\tconst list: OperationStamp[] = [acked1];\n\t\t\topstampUtils.spliceIntoList(list, local1);\n\t\t\tassert.deepStrictEqual(list, [acked1, local1]);\n\t\t});\n\n\t\tit(\"inserts acked before unacked\", () => {\n\t\t\tconst list: OperationStamp[] = [acked1, acked2, local1];\n\t\t\topstampUtils.spliceIntoList(list, acked3);\n\t\t\tassert.deepStrictEqual(list, [acked1, acked2, acked3, local1]);\n\t\t});\n\n\t\tit(\"inserts acked before single unacked\", () => {\n\t\t\tconst list: OperationStamp[] = [local1];\n\t\t\topstampUtils.spliceIntoList(list, acked2);\n\t\t\tassert.deepStrictEqual(list, [acked2, local1]);\n\t\t});\n\n\t\tit(\"inserts local seqs at end\", () => {\n\t\t\tconst list: OperationStamp[] = [acked1, acked2];\n\t\t\topstampUtils.spliceIntoList(list, local1);\n\t\t\topstampUtils.spliceIntoList(list, local2);\n\t\t\tassert.deepStrictEqual(list, [acked1, acked2, local1, local2]);\n\t\t});\n\t});\n});\n"]}
@@ -38,6 +38,15 @@ export declare class TestClientLogger {
38
38
  static validate(clients: readonly TestClient[], title?: string): string;
39
39
  toString(excludeHeader?: boolean): string;
40
40
  addLogsToError(e: unknown): Error;
41
+ /**
42
+ * Get a string representation of the merge-tree state.
43
+ *
44
+ * @privateRemarks
45
+ * This function is a dominating bottleneck in operation runner tests, as they log multiple client states per operation performed.
46
+ * This takes more than 50% of the overall test time, so some inner-loop elements of this favor performance over readability/safety
47
+ * (e.g. precomputing removed string representations, casting to the more internal segment interfaces to view insertion/removal info
48
+ * rather than import typeguards, etc.)
49
+ */
41
50
  private static getSegString;
42
51
  }
43
52
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"testClientLogger.d.ts","sourceRoot":"","sources":["../../src/test/testClientLogger.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAShD,OAAO,EAAE,WAAW,EAAmB,MAAM,kBAAkB,CAAC;AAIhE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AA4C7C,KAAK,SAAS,CAAC,WAAW,SAAS,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;AAEtF,wBAAgB,2BAA2B,CAC1C,QAAQ,SAAS,SAAS,CAAC,WAAW,CAAC,EACvC,WAAW,SAAS,MAAM,GAAG,MAAM,GAAG,MAAM,QAAQ,EAEpD,IAAI,EAAE;IACL,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,iBAAiB,GAAG,WAAW,CAAC;CAC1C,EACD,GAAG,SAAS,EAAE,WAAW,EAAE,GACzB,MAAM,CAAC,MAAM,QAAQ,EAAE,UAAU,CAAC,GAAG;IAAE,GAAG,EAAE,UAAU,EAAE,CAAA;CAAE,CAuB5D;AACD,qBAAa,gBAAgB;IA0C3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;WA1CV,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM;IAiB9D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IAExC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkB;IAEhD,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,SAAS,CAAgB;IAEjC,OAAO,CAAC,aAAa,CAAoC;IAEzD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsB;IAEvD;;;OAGG;IACI,OAAO,IAAI,IAAI;gBAQJ,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,KAAK,CAAC,oBAAQ;IA4DhC,OAAO,CAAC,aAAa;IA+Bd,QAAQ,CAAC,IAAI,CAAC,EAAE;QACtB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,MAAM;IA+DV,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM;IAOhE,QAAQ,CAAC,aAAa,GAAE,OAAe,GAAG,MAAM;IAyBhD,cAAc,CAAC,CAAC,EAAE,OAAO,GAAG,KAAK;IASxC,OAAO,CAAC,MAAM,CAAC,YAAY;CAmD3B"}
1
+ {"version":3,"file":"testClientLogger.d.ts","sourceRoot":"","sources":["../../src/test/testClientLogger.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAShD,OAAO,EAAE,WAAW,EAAmB,MAAM,kBAAkB,CAAC;AAIhE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AA4C7C,KAAK,SAAS,CAAC,WAAW,SAAS,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;AAEtF,wBAAgB,2BAA2B,CAC1C,QAAQ,SAAS,SAAS,CAAC,WAAW,CAAC,EACvC,WAAW,SAAS,MAAM,GAAG,MAAM,GAAG,MAAM,QAAQ,EAEpD,IAAI,EAAE;IACL,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,iBAAiB,GAAG,WAAW,CAAC;CAC1C,EACD,GAAG,SAAS,EAAE,WAAW,EAAE,GACzB,MAAM,CAAC,MAAM,QAAQ,EAAE,UAAU,CAAC,GAAG;IAAE,GAAG,EAAE,UAAU,EAAE,CAAA;CAAE,CAuB5D;AACD,qBAAa,gBAAgB;IA0C3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;WA1CV,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM;IAiB9D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IAExC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkB;IAEhD,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,SAAS,CAAgB;IAEjC,OAAO,CAAC,aAAa,CAAoC;IAEzD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsB;IAEvD;;;OAGG;IACI,OAAO,IAAI,IAAI;gBAQJ,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,KAAK,CAAC,oBAAQ;IA4DhC,OAAO,CAAC,aAAa;IA+Bd,QAAQ,CAAC,IAAI,CAAC,EAAE;QACtB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,MAAM;IA+DV,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM;IAOhE,QAAQ,CAAC,aAAa,GAAE,OAAe,GAAG,MAAM;IAyBhD,cAAc,CAAC,CAAC,EAAE,OAAO,GAAG,KAAK;IASxC;;;;;;;;OAQG;IACH,OAAO,CAAC,MAAM,CAAC,YAAY;CAqD3B"}
@@ -4,14 +4,14 @@
4
4
  */
5
5
  import { strict as assert } from "node:assert";
6
6
  import { LoggingError } from "@fluidframework/telemetry-utils/internal";
7
+ import { DoublyLinkedList } from "../collections/index.js";
7
8
  import { UnassignedSequenceNumber } from "../constants.js";
8
9
  import { MergeTreeMaintenanceType, } from "../mergeTreeDeltaCallback.js";
9
10
  import { depthFirstNodeWalk } from "../mergeTreeNodeWalk.js";
10
11
  import { Marker, seqLTE } from "../mergeTreeNodes.js";
11
12
  import { MergeTreeDeltaType } from "../ops.js";
12
13
  import { matchProperties } from "../properties.js";
13
- import { toInsertionInfo, toMoveInfo, toRemovalInfo } from "../segmentInfos.js";
14
- import { TextSegment } from "../textSegment.js";
14
+ import { TextSegment, TextSegmentGranularity } from "../textSegment.js";
15
15
  import { TestClient } from "./testClient.js";
16
16
  function getOpString(msg) {
17
17
  if (msg === undefined) {
@@ -235,69 +235,88 @@ export class TestClientLogger {
235
235
  }
236
236
  return new LoggingError(`${e}\n${this.toString()}`);
237
237
  }
238
+ /**
239
+ * Get a string representation of the merge-tree state.
240
+ *
241
+ * @privateRemarks
242
+ * This function is a dominating bottleneck in operation runner tests, as they log multiple client states per operation performed.
243
+ * This takes more than 50% of the overall test time, so some inner-loop elements of this favor performance over readability/safety
244
+ * (e.g. precomputing removed string representations, casting to the more internal segment interfaces to view insertion/removal info
245
+ * rather than import typeguards, etc.)
246
+ */
238
247
  static getSegString(client) {
239
248
  let acked = "";
240
249
  let local = "";
241
- const nodes = [...client.mergeTree.root.children];
242
- let parent = nodes[0]?.parent;
250
+ const nodes = new DoublyLinkedList([client.mergeTree.root]);
251
+ let parent;
252
+ const { minSeq } = client.getCollabWindow();
243
253
  while (nodes.length > 0) {
244
- const node = nodes.shift();
245
- if (node) {
246
- if (node.isLeaf()) {
247
- if (node.parent !== parent) {
248
- if (acked.length > 0) {
249
- acked += " ";
250
- local += " ";
254
+ // Safe due to length check above
255
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
256
+ const node = nodes.shift().data;
257
+ if (node.isLeaf()) {
258
+ if (node.parent !== parent) {
259
+ if (acked.length > 0) {
260
+ acked += " ";
261
+ local += " ";
262
+ }
263
+ parent = node.parent;
264
+ }
265
+ const text = TextSegment.is(node) ? node.text : Marker.is(node) ? "¶" : undefined;
266
+ if (text !== undefined) {
267
+ const insertionSeq = node?.seq;
268
+ const removedNode = toMoveOrRemove(node);
269
+ if (removedNode === undefined) {
270
+ if (insertionSeq === UnassignedSequenceNumber) {
271
+ acked += underscores[text.length];
272
+ local += text;
273
+ }
274
+ else {
275
+ acked += text;
276
+ local += spaces[text.length];
251
277
  }
252
- parent = node.parent;
253
278
  }
254
- const text = TextSegment.is(node) ? node.text : Marker.is(node) ? "¶" : undefined;
255
- const insertionSeq = toInsertionInfo(node)?.seq;
256
- if (text !== undefined) {
257
- const removedNode = toMoveOrRemove(node);
258
- if (removedNode === undefined) {
259
- if (insertionSeq === UnassignedSequenceNumber) {
260
- acked += "_".repeat(text.length);
261
- local += text;
262
- }
263
- else {
264
- acked += text;
265
- local += " ".repeat(text.length);
266
- }
279
+ else {
280
+ if (removedNode.seq === UnassignedSequenceNumber) {
281
+ acked += underscores[text.length];
282
+ local +=
283
+ insertionSeq === UnassignedSequenceNumber
284
+ ? asterisks[text.length]
285
+ : dashes[text.length];
267
286
  }
268
287
  else {
269
- if (removedNode.seq === UnassignedSequenceNumber) {
270
- acked += "_".repeat(text.length);
271
- local +=
272
- insertionSeq === UnassignedSequenceNumber
273
- ? "*".repeat(text.length)
274
- : "-".repeat(text.length);
275
- }
276
- else {
277
- const removedSymbol = seqLTE(removedNode.seq, client.getCollabWindow().minSeq)
278
- ? "~"
279
- : "-";
280
- acked += removedSymbol.repeat(text.length);
281
- local += " ".repeat(text.length);
282
- }
288
+ acked += seqLTE(removedNode.seq, minSeq)
289
+ ? tildes[text.length]
290
+ : dashes[text.length];
291
+ local += spaces[text.length];
283
292
  }
284
293
  }
285
294
  }
286
- else {
287
- nodes.push(...node.children);
295
+ }
296
+ else {
297
+ for (let i = 0; i < node.childCount; i++) {
298
+ nodes.push(node.children[i]);
288
299
  }
289
300
  }
290
301
  }
291
302
  return { acked, local };
292
303
  }
293
304
  }
305
+ const maxSegmentLength = TextSegmentGranularity * 2;
306
+ const underscores = Array.from({ length: maxSegmentLength }, (_, i) => "_".repeat(i));
307
+ const spaces = Array.from({ length: maxSegmentLength }, (_, i) => " ".repeat(i));
308
+ const dashes = Array.from({ length: maxSegmentLength }, (_, i) => "-".repeat(i));
309
+ const asterisks = Array.from({ length: maxSegmentLength }, (_, i) => "*".repeat(i));
310
+ const tildes = Array.from({ length: maxSegmentLength }, (_, i) => "~".repeat(i));
294
311
  function toMoveOrRemove(segment) {
295
- const mi = toMoveInfo(segment);
296
- const ri = toRemovalInfo(segment);
297
- if (mi !== undefined || ri !== undefined) {
312
+ if (segment.movedSeq !== undefined) {
313
+ return {
314
+ seq: segment.movedSeq,
315
+ };
316
+ }
317
+ if (segment.removedSeq !== undefined) {
298
318
  return {
299
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
300
- seq: mi?.movedSeq ?? ri?.removedSeq,
319
+ seq: segment.removedSeq,
301
320
  };
302
321
  }
303
322
  }