@fluidframework/sequence 2.0.0-internal.1.0.0.83139 → 2.0.0-internal.1.1.1

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.
@@ -147,6 +147,9 @@ export class SharedString extends SharedSegmentSequence {
147
147
  findTile(startPos, tileLabel, preceding = true) {
148
148
  return this.client.findTile(startPos, tileLabel, preceding);
149
149
  }
150
+ /**
151
+ * @deprecated - use the free function `getTextAndMarkers` exported by this package instead.
152
+ */
150
153
  getTextAndMarkers(label) {
151
154
  const segmentWindow = this.client.getCollabWindow();
152
155
  return this.mergeTreeTextHelper.getTextAndMarkers(segmentWindow.currentSeq, segmentWindow.clientId, label);
@@ -164,13 +167,15 @@ export class SharedString extends SharedSegmentSequence {
164
167
  /**
165
168
  * Adds spaces for markers and handles, so that position calculations account for them.
166
169
  */
167
- getTextWithPlaceholders() {
170
+ getTextWithPlaceholders(start, end) {
168
171
  const segmentWindow = this.client.getCollabWindow();
169
- return this.mergeTreeTextHelper.getText(segmentWindow.currentSeq, segmentWindow.clientId, " ");
172
+ return this.mergeTreeTextHelper.getText(segmentWindow.currentSeq, segmentWindow.clientId, " ", start, end);
170
173
  }
174
+ /**
175
+ * @deprecated - use `getTextWithPlaceholders` instead.
176
+ */
171
177
  getTextRangeWithPlaceholders(start, end) {
172
- const segmentWindow = this.client.getCollabWindow();
173
- return this.mergeTreeTextHelper.getText(segmentWindow.currentSeq, segmentWindow.clientId, " ", start, end);
178
+ return this.getTextWithPlaceholders(start, end);
174
179
  }
175
180
  getTextRangeWithMarkers(start, end) {
176
181
  const segmentWindow = this.client.getCollabWindow();
@@ -191,4 +196,22 @@ export class SharedString extends SharedSegmentSequence {
191
196
  }
192
197
  }
193
198
  }
199
+ /**
200
+ * Splits the text into regions ending with markers with the given `label`.
201
+ * @param sharedString - String to retrieve text and markers from
202
+ * @param label - label to split on
203
+ * @returns Two parallel lists of text and markers, split by markers with the provided `label`.
204
+ *
205
+ * For example:
206
+ * ```typescript
207
+ * // Say sharedstring has contents "hello<paragraph marker 1>world<paragraph marker 2>missing".
208
+ * const { parallelText, parallelMarkers } = getTextAndMarkers(sharedString, "paragraph");
209
+ * // parallelText === ["hello", "world"]
210
+ * // parallelMarkers === [<paragraph marker 1 object>, <paragraph marker 2 object>]
211
+ * // Note parallelText does not include "missing".
212
+ * ```
213
+ */
214
+ export function getTextAndMarkers(sharedString, label) {
215
+ return sharedString.getTextAndMarkers(label);
216
+ }
194
217
  //# sourceMappingURL=sharedString.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sharedString.js","sourceRoot":"","sources":["../src/sharedString.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 {\n ICombiningOp,\n IMergeTreeInsertMsg,\n IMergeTreeRemoveMsg,\n IMergeTreeTextHelper,\n IRelativePosition,\n ISegment,\n Marker,\n PropertySet,\n ReferencePosition,\n ReferenceType,\n TextSegment,\n} from \"@fluidframework/merge-tree\";\nimport { IFluidDataStoreRuntime, IChannelAttributes } from \"@fluidframework/datastore-definitions\";\nimport { SharedSegmentSequence } from \"./sequence\";\nimport { SharedStringFactory } from \"./sequenceFactory\";\n\n/**\n * Fluid object interface describing access methods on a SharedString\n */\nexport interface ISharedString extends SharedSegmentSequence<SharedStringSegment> {\n /**\n * Inserts the text at the position.\n * @param pos - The position to insert the text at\n * @param text - The text to insert\n * @param props - The properties of the text\n */\n insertText(pos: number, text: string, props?: PropertySet): void;\n\n /**\n * Inserts a marker at the position.\n * @param pos - The position to insert the marker at\n * @param refType - The reference type of the marker\n * @param props - The properties of the marker\n */\n insertMarker(pos: number, refType: ReferenceType, props?: PropertySet): IMergeTreeInsertMsg;\n\n /**\n * {@inheritDoc SharedSegmentSequence.posFromRelativePos}\n */\n posFromRelativePos(relativePos: IRelativePosition): number;\n}\n\nexport type SharedStringSegment = TextSegment | Marker;\n\n/**\n * The Shared String is a specialized data structure for handling collaborative\n * text. It is based on a more general Sequence data structure but has\n * additional features that make working with text easier.\n *\n * In addition to text, a Shared String can also contain markers. Markers can be\n * used to store metadata at positions within the text, like the details of an\n * image or Fluid object that should be rendered with the text.\n *\n */\nexport class SharedString extends SharedSegmentSequence<SharedStringSegment> implements ISharedString {\n /**\n * Create a new shared string.\n * @param runtime - data store runtime the new shared string belongs to\n * @param id - optional name of the shared string\n * @returns newly create shared string (but not attached yet)\n */\n public static create(runtime: IFluidDataStoreRuntime, id?: string) {\n return runtime.createChannel(id, SharedStringFactory.Type) as SharedString;\n }\n\n /**\n * Get a factory for SharedString to register with the data store.\n * @returns a factory that creates and load SharedString\n */\n public static getFactory() {\n return new SharedStringFactory();\n }\n\n public get ISharedString(): ISharedString {\n return this;\n }\n\n private readonly mergeTreeTextHelper: IMergeTreeTextHelper;\n\n constructor(document: IFluidDataStoreRuntime, public id: string, attributes: IChannelAttributes) {\n super(document, id, attributes, SharedStringFactory.segmentFromSpec);\n this.mergeTreeTextHelper = this.client.createTextHelper();\n }\n\n /**\n * Inserts a marker at a relative position.\n * @param relativePos1 - The relative position to insert the marker at\n * @param refType - The reference type of the marker\n * @param props - The properties of the marker\n */\n public insertMarkerRelative(\n relativePos1: IRelativePosition,\n refType: ReferenceType,\n props?: PropertySet) {\n const segment = new Marker(refType);\n if (props) {\n segment.addProperties(props);\n }\n\n const pos = this.posFromRelativePos(relativePos1);\n const insertOp = this.client.insertSegmentLocal(pos, segment);\n if (insertOp) {\n this.submitSequenceMessage(insertOp);\n }\n }\n\n /**\n * {@inheritDoc ISharedString.insertMarker}\n */\n public insertMarker(\n pos: number,\n refType: ReferenceType,\n props?: PropertySet): IMergeTreeInsertMsg {\n const segment = new Marker(refType);\n if (props) {\n segment.addProperties(props);\n }\n\n const insertOp = this.client.insertSegmentLocal(pos, segment);\n if (insertOp) {\n this.submitSequenceMessage(insertOp);\n }\n return insertOp;\n }\n\n /**\n * Inserts the text at the position.\n * @param relativePos1 - The relative position to insert the text at\n * @param text - The text to insert\n * @param props - The properties of text\n */\n public insertTextRelative(relativePos1: IRelativePosition, text: string, props?: PropertySet) {\n const segment = new TextSegment(text);\n if (props) {\n segment.addProperties(props);\n }\n\n const pos = this.posFromRelativePos(relativePos1);\n const insertOp = this.client.insertSegmentLocal(pos, segment);\n if (insertOp) {\n this.submitSequenceMessage(insertOp);\n }\n }\n\n /**\n * {@inheritDoc ISharedString.insertText}\n */\n public insertText(pos: number, text: string, props?: PropertySet) {\n const segment = new TextSegment(text);\n if (props) {\n segment.addProperties(props);\n }\n\n const insertOp = this.client.insertSegmentLocal(pos, segment);\n if (insertOp) {\n this.submitSequenceMessage(insertOp);\n }\n }\n\n /**\n * Replaces a range with the provided text.\n * @param start - The inclusive start of the range to replace\n * @param end - The exclusive end of the range to replace\n * @param text - The text to replace the range with\n * @param props - Optional. The properties of the replacement text\n */\n public replaceText(start: number, end: number, text: string, props?: PropertySet) {\n this.replaceRange(start, end, TextSegment.make(text, props));\n }\n\n /**\n * Removes the text in the given range.\n * @param start - The inclusive start of the range to remove\n * @param end - The exclusive end of the range to replace\n * @returns the message sent.\n */\n public removeText(start: number, end: number): IMergeTreeRemoveMsg {\n return this.removeRange(start, end);\n }\n\n /**\n * Annotates the marker with the provided properties and calls the callback on consensus.\n * @param marker - The marker to annotate\n * @param props - The properties to annotate the marker with\n * @param consensusCallback - The callback called when consensus is reached\n */\n public annotateMarkerNotifyConsensus(\n marker: Marker,\n props: PropertySet,\n callback: (m: Marker) => void) {\n const annotateOp = this.client.annotateMarkerNotifyConsensus(marker, props, callback);\n if (annotateOp) {\n this.submitSequenceMessage(annotateOp);\n }\n }\n\n /**\n * Annotates the marker with the provided properties.\n * @param marker - The marker to annotate\n * @param props - The properties to annotate the marker with\n * @param combiningOp - Optional. Specifies how to combine values for the property, such as \"incr\" for increment.\n */\n public annotateMarker(\n marker: Marker,\n props: PropertySet,\n combiningOp?: ICombiningOp) {\n const annotateOp = this.client.annotateMarker(marker, props, combiningOp);\n if (annotateOp) {\n this.submitSequenceMessage(annotateOp);\n }\n }\n\n public findTile(startPos: number | undefined, tileLabel: string, preceding = true): {\n tile: ReferencePosition;\n pos: number;\n } {\n return this.client.findTile(startPos, tileLabel, preceding);\n }\n\n public getTextAndMarkers(label: string) {\n const segmentWindow = this.client.getCollabWindow();\n return this.mergeTreeTextHelper.getTextAndMarkers(segmentWindow.currentSeq, segmentWindow.clientId, label);\n }\n\n /**\n * Retrieve text from the SharedString in string format.\n * @param start - The starting index of the text to retrieve, or 0 if omitted.\n * @param end - The ending index of the text to retrieve, or the end of the string if omitted\n * @returns The requested text content as a string.\n */\n public getText(start?: number, end?: number) {\n const segmentWindow = this.client.getCollabWindow();\n return this.mergeTreeTextHelper.getText(segmentWindow.currentSeq, segmentWindow.clientId, \"\", start, end);\n }\n\n /**\n * Adds spaces for markers and handles, so that position calculations account for them.\n */\n public getTextWithPlaceholders() {\n const segmentWindow = this.client.getCollabWindow();\n return this.mergeTreeTextHelper.getText(segmentWindow.currentSeq, segmentWindow.clientId, \" \");\n }\n\n public getTextRangeWithPlaceholders(start: number, end: number) {\n const segmentWindow = this.client.getCollabWindow();\n return this.mergeTreeTextHelper.getText(segmentWindow.currentSeq, segmentWindow.clientId, \" \", start, end);\n }\n\n public getTextRangeWithMarkers(start: number, end: number) {\n const segmentWindow = this.client.getCollabWindow();\n return this.mergeTreeTextHelper.getText(segmentWindow.currentSeq, segmentWindow.clientId, \"*\", start, end);\n }\n\n public getMarkerFromId(id: string): ISegment {\n return this.client.getMarkerFromId(id);\n }\n\n /**\n * Revert an op\n */\n protected rollback(content: any, localOpMetadata: unknown): void {\n if (this.client.rollback !== undefined) {\n this.client.rollback(content, localOpMetadata);\n } else {\n super.rollback(content, localOpMetadata);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"sharedString.js","sourceRoot":"","sources":["../src/sharedString.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 {\n ICombiningOp,\n IMergeTreeInsertMsg,\n IMergeTreeRemoveMsg,\n IMergeTreeTextHelper,\n IRelativePosition,\n ISegment,\n Marker,\n PropertySet,\n ReferencePosition,\n ReferenceType,\n TextSegment,\n} from \"@fluidframework/merge-tree\";\nimport { IFluidDataStoreRuntime, IChannelAttributes } from \"@fluidframework/datastore-definitions\";\nimport { SharedSegmentSequence } from \"./sequence\";\nimport { SharedStringFactory } from \"./sequenceFactory\";\n\n/**\n * Fluid object interface describing access methods on a SharedString\n */\nexport interface ISharedString extends SharedSegmentSequence<SharedStringSegment> {\n /**\n * Inserts the text at the position.\n * @param pos - The position to insert the text at\n * @param text - The text to insert\n * @param props - The properties of the text\n */\n insertText(pos: number, text: string, props?: PropertySet): void;\n\n /**\n * Inserts a marker at the position.\n * @param pos - The position to insert the marker at\n * @param refType - The reference type of the marker\n * @param props - The properties of the marker\n */\n insertMarker(pos: number, refType: ReferenceType, props?: PropertySet): IMergeTreeInsertMsg;\n\n /**\n * {@inheritDoc SharedSegmentSequence.posFromRelativePos}\n */\n posFromRelativePos(relativePos: IRelativePosition): number;\n}\n\nexport type SharedStringSegment = TextSegment | Marker;\n\n/**\n * The Shared String is a specialized data structure for handling collaborative\n * text. It is based on a more general Sequence data structure but has\n * additional features that make working with text easier.\n *\n * In addition to text, a Shared String can also contain markers. Markers can be\n * used to store metadata at positions within the text, like the details of an\n * image or Fluid object that should be rendered with the text.\n *\n */\nexport class SharedString extends SharedSegmentSequence<SharedStringSegment> implements ISharedString {\n /**\n * Create a new shared string.\n * @param runtime - data store runtime the new shared string belongs to\n * @param id - optional name of the shared string\n * @returns newly create shared string (but not attached yet)\n */\n public static create(runtime: IFluidDataStoreRuntime, id?: string) {\n return runtime.createChannel(id, SharedStringFactory.Type) as SharedString;\n }\n\n /**\n * Get a factory for SharedString to register with the data store.\n * @returns a factory that creates and load SharedString\n */\n public static getFactory() {\n return new SharedStringFactory();\n }\n\n public get ISharedString(): ISharedString {\n return this;\n }\n\n private readonly mergeTreeTextHelper: IMergeTreeTextHelper;\n\n constructor(document: IFluidDataStoreRuntime, public id: string, attributes: IChannelAttributes) {\n super(document, id, attributes, SharedStringFactory.segmentFromSpec);\n this.mergeTreeTextHelper = this.client.createTextHelper();\n }\n\n /**\n * Inserts a marker at a relative position.\n * @param relativePos1 - The relative position to insert the marker at\n * @param refType - The reference type of the marker\n * @param props - The properties of the marker\n */\n public insertMarkerRelative(\n relativePos1: IRelativePosition,\n refType: ReferenceType,\n props?: PropertySet) {\n const segment = new Marker(refType);\n if (props) {\n segment.addProperties(props);\n }\n\n const pos = this.posFromRelativePos(relativePos1);\n const insertOp = this.client.insertSegmentLocal(pos, segment);\n if (insertOp) {\n this.submitSequenceMessage(insertOp);\n }\n }\n\n /**\n * {@inheritDoc ISharedString.insertMarker}\n */\n public insertMarker(\n pos: number,\n refType: ReferenceType,\n props?: PropertySet): IMergeTreeInsertMsg {\n const segment = new Marker(refType);\n if (props) {\n segment.addProperties(props);\n }\n\n const insertOp = this.client.insertSegmentLocal(pos, segment);\n if (insertOp) {\n this.submitSequenceMessage(insertOp);\n }\n return insertOp;\n }\n\n /**\n * Inserts the text at the position.\n * @param relativePos1 - The relative position to insert the text at\n * @param text - The text to insert\n * @param props - The properties of text\n */\n public insertTextRelative(relativePos1: IRelativePosition, text: string, props?: PropertySet) {\n const segment = new TextSegment(text);\n if (props) {\n segment.addProperties(props);\n }\n\n const pos = this.posFromRelativePos(relativePos1);\n const insertOp = this.client.insertSegmentLocal(pos, segment);\n if (insertOp) {\n this.submitSequenceMessage(insertOp);\n }\n }\n\n /**\n * {@inheritDoc ISharedString.insertText}\n */\n public insertText(pos: number, text: string, props?: PropertySet) {\n const segment = new TextSegment(text);\n if (props) {\n segment.addProperties(props);\n }\n\n const insertOp = this.client.insertSegmentLocal(pos, segment);\n if (insertOp) {\n this.submitSequenceMessage(insertOp);\n }\n }\n\n /**\n * Replaces a range with the provided text.\n * @param start - The inclusive start of the range to replace\n * @param end - The exclusive end of the range to replace\n * @param text - The text to replace the range with\n * @param props - Optional. The properties of the replacement text\n */\n public replaceText(start: number, end: number, text: string, props?: PropertySet) {\n this.replaceRange(start, end, TextSegment.make(text, props));\n }\n\n /**\n * Removes the text in the given range.\n * @param start - The inclusive start of the range to remove\n * @param end - The exclusive end of the range to replace\n * @returns the message sent.\n */\n public removeText(start: number, end: number): IMergeTreeRemoveMsg {\n return this.removeRange(start, end);\n }\n\n /**\n * Annotates the marker with the provided properties and calls the callback on consensus.\n * @param marker - The marker to annotate\n * @param props - The properties to annotate the marker with\n * @param consensusCallback - The callback called when consensus is reached\n */\n public annotateMarkerNotifyConsensus(\n marker: Marker,\n props: PropertySet,\n callback: (m: Marker) => void) {\n const annotateOp = this.client.annotateMarkerNotifyConsensus(marker, props, callback);\n if (annotateOp) {\n this.submitSequenceMessage(annotateOp);\n }\n }\n\n /**\n * Annotates the marker with the provided properties.\n * @param marker - The marker to annotate\n * @param props - The properties to annotate the marker with\n * @param combiningOp - Optional. Specifies how to combine values for the property, such as \"incr\" for increment.\n */\n public annotateMarker(\n marker: Marker,\n props: PropertySet,\n combiningOp?: ICombiningOp) {\n const annotateOp = this.client.annotateMarker(marker, props, combiningOp);\n if (annotateOp) {\n this.submitSequenceMessage(annotateOp);\n }\n }\n\n public findTile(startPos: number | undefined, tileLabel: string, preceding = true): {\n tile: ReferencePosition;\n pos: number;\n } {\n return this.client.findTile(startPos, tileLabel, preceding);\n }\n\n /**\n * @deprecated - use the free function `getTextAndMarkers` exported by this package instead.\n */\n public getTextAndMarkers(label: string) {\n const segmentWindow = this.client.getCollabWindow();\n return this.mergeTreeTextHelper.getTextAndMarkers(segmentWindow.currentSeq, segmentWindow.clientId, label);\n }\n\n /**\n * Retrieve text from the SharedString in string format.\n * @param start - The starting index of the text to retrieve, or 0 if omitted.\n * @param end - The ending index of the text to retrieve, or the end of the string if omitted\n * @returns The requested text content as a string.\n */\n public getText(start?: number, end?: number) {\n const segmentWindow = this.client.getCollabWindow();\n return this.mergeTreeTextHelper.getText(segmentWindow.currentSeq, segmentWindow.clientId, \"\", start, end);\n }\n\n /**\n * Adds spaces for markers and handles, so that position calculations account for them.\n */\n public getTextWithPlaceholders(start?: number, end?: number) {\n const segmentWindow = this.client.getCollabWindow();\n return this.mergeTreeTextHelper.getText(segmentWindow.currentSeq, segmentWindow.clientId, \" \", start, end);\n }\n\n /**\n * @deprecated - use `getTextWithPlaceholders` instead.\n */\n public getTextRangeWithPlaceholders(start: number, end: number) {\n return this.getTextWithPlaceholders(start, end);\n }\n\n public getTextRangeWithMarkers(start: number, end: number) {\n const segmentWindow = this.client.getCollabWindow();\n return this.mergeTreeTextHelper.getText(segmentWindow.currentSeq, segmentWindow.clientId, \"*\", start, end);\n }\n\n public getMarkerFromId(id: string): ISegment {\n return this.client.getMarkerFromId(id);\n }\n\n /**\n * Revert an op\n */\n protected rollback(content: any, localOpMetadata: unknown): void {\n if (this.client.rollback !== undefined) {\n this.client.rollback(content, localOpMetadata);\n } else {\n super.rollback(content, localOpMetadata);\n }\n }\n}\n\n/**\n * Splits the text into regions ending with markers with the given `label`.\n * @param sharedString - String to retrieve text and markers from\n * @param label - label to split on\n * @returns Two parallel lists of text and markers, split by markers with the provided `label`.\n *\n * For example:\n * ```typescript\n * // Say sharedstring has contents \"hello<paragraph marker 1>world<paragraph marker 2>missing\".\n * const { parallelText, parallelMarkers } = getTextAndMarkers(sharedString, \"paragraph\");\n * // parallelText === [\"hello\", \"world\"]\n * // parallelMarkers === [<paragraph marker 1 object>, <paragraph marker 2 object>]\n * // Note parallelText does not include \"missing\".\n * ```\n */\nexport function getTextAndMarkers(sharedString: SharedString, label: string): {\n parallelText: string[];\n parallelMarkers: Marker[];\n} {\n return sharedString.getTextAndMarkers(label);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/sequence",
3
- "version": "2.0.0-internal.1.0.0.83139",
3
+ "version": "2.0.0-internal.1.1.1",
4
4
  "description": "Distributed sequence",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -30,11 +30,15 @@
30
30
  "eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
31
31
  "lint": "npm run eslint",
32
32
  "lint:fix": "npm run eslint:fix",
33
+ "postpack": "cd dist && tar -cvf ../sequence.test-files.tar ./test",
33
34
  "test": "npm run test:mocha",
34
35
  "test:coverage": "nyc npm test -- --reporter xunit --reporter-option output=nyc/junit-report.xml",
35
- "test:mocha": "mocha --recursive dist/test/**/*.spec.js -r node_modules/@fluidframework/mocha-test-setup --unhandled-rejections=strict",
36
+ "test:memory": "mocha --config src/test/memory/.mocharc.js",
37
+ "test:memory-profiling:report": "mocha --config src/test/memory/.mocharc.js",
38
+ "test:mocha": "mocha --ignore 'dist/test/memory/**/*' --recursive 'dist/test/**/*.spec.js' -r node_modules/@fluidframework/mocha-test-setup --unhandled-rejections=strict",
36
39
  "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
37
40
  "test:newsnapfiles": "node dist/test/createSnapshotFiles.js",
41
+ "test:stress": "cross-env FUZZ_TEST_COUNT=100 FUZZ_STRESS_RUN=true npm run test:mocha",
38
42
  "testfarm": "node dist/test/testFarm.js",
39
43
  "tsc": "tsc",
40
44
  "tsfmt": "tsfmt --verify",
@@ -63,29 +67,30 @@
63
67
  },
64
68
  "dependencies": {
65
69
  "@fluidframework/common-definitions": "^0.20.1",
66
- "@fluidframework/common-utils": "^0.32.1",
67
- "@fluidframework/container-utils": "2.0.0-internal.1.0.0.83139",
68
- "@fluidframework/core-interfaces": "2.0.0-internal.1.0.0.83139",
69
- "@fluidframework/datastore-definitions": "2.0.0-internal.1.0.0.83139",
70
- "@fluidframework/merge-tree": "2.0.0-internal.1.0.0.83139",
71
- "@fluidframework/protocol-definitions": "^0.1029.1000-0",
72
- "@fluidframework/runtime-definitions": "2.0.0-internal.1.0.0.83139",
73
- "@fluidframework/runtime-utils": "2.0.0-internal.1.0.0.83139",
74
- "@fluidframework/shared-object-base": "2.0.0-internal.1.0.0.83139",
75
- "@fluidframework/telemetry-utils": "2.0.0-internal.1.0.0.83139",
70
+ "@fluidframework/common-utils": "^1.0.0",
71
+ "@fluidframework/container-utils": "^2.0.0-internal.1.1.1",
72
+ "@fluidframework/core-interfaces": "^2.0.0-internal.1.1.1",
73
+ "@fluidframework/datastore-definitions": "^2.0.0-internal.1.1.1",
74
+ "@fluidframework/merge-tree": "^2.0.0-internal.1.1.1",
75
+ "@fluidframework/protocol-definitions": "^1.0.0",
76
+ "@fluidframework/runtime-definitions": "^2.0.0-internal.1.1.1",
77
+ "@fluidframework/runtime-utils": "^2.0.0-internal.1.1.1",
78
+ "@fluidframework/shared-object-base": "^2.0.0-internal.1.1.1",
79
+ "@fluidframework/telemetry-utils": "^2.0.0-internal.1.1.1",
76
80
  "uuid": "^8.3.1"
77
81
  },
78
82
  "devDependencies": {
79
- "@fluid-internal/stochastic-test-utils": "2.0.0-internal.1.0.0.83139",
80
- "@fluid-internal/test-dds-utils": "2.0.0-internal.1.0.0.83139",
83
+ "@fluid-internal/stochastic-test-utils": "^2.0.0-internal.1.1.1",
84
+ "@fluid-internal/test-dds-utils": "^2.0.0-internal.1.1.1",
85
+ "@fluid-tools/benchmark": "^0.43.0",
81
86
  "@fluidframework/build-common": "^0.24.0",
82
- "@fluidframework/build-tools": "^0.3.0-0",
87
+ "@fluidframework/build-tools": "^0.3.1000",
83
88
  "@fluidframework/eslint-config-fluid": "^0.28.2000",
84
- "@fluidframework/gitresources": "^0.1037.1000-0",
85
- "@fluidframework/mocha-test-setup": "2.0.0-internal.1.0.0.83139",
89
+ "@fluidframework/gitresources": "^0.1037.1000",
90
+ "@fluidframework/mocha-test-setup": "^2.0.0-internal.1.1.1",
86
91
  "@fluidframework/sequence-previous": "npm:@fluidframework/sequence@^1.0.0",
87
- "@fluidframework/server-services-client": "^0.1037.1000-0",
88
- "@fluidframework/test-runtime-utils": "2.0.0-internal.1.0.0.83139",
92
+ "@fluidframework/server-services-client": "^0.1037.1000",
93
+ "@fluidframework/test-runtime-utils": "^2.0.0-internal.1.1.1",
89
94
  "@microsoft/api-extractor": "^7.22.2",
90
95
  "@rushstack/eslint-config": "^2.5.1",
91
96
  "@types/diff": "^3.5.1",
@@ -93,7 +98,7 @@
93
98
  "@types/node": "^14.18.0",
94
99
  "@types/random-js": "^1.0.31",
95
100
  "concurrently": "^6.2.0",
96
- "copyfiles": "^2.1.0",
101
+ "copyfiles": "^2.4.1",
97
102
  "cross-env": "^7.0.2",
98
103
  "diff": "^3.5.0",
99
104
  "eslint": "~8.6.0",
package/src/index.ts CHANGED
@@ -22,6 +22,8 @@ export {
22
22
  Interval,
23
23
  IntervalCollection,
24
24
  IntervalCollectionIterator,
25
+ IntervalLocator,
26
+ intervalLocatorFromEndpoint,
25
27
  IntervalType,
26
28
  ISerializableInterval,
27
29
  ISerializedInterval,
@@ -32,6 +32,7 @@ import {
32
32
  UnassignedSequenceNumber,
33
33
  maxReferencePosition,
34
34
  createDetachedLocalReferencePosition,
35
+ DetachedReferencePosition,
35
36
  } from "@fluidframework/merge-tree";
36
37
  import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
37
38
  import { LoggingError } from "@fluidframework/telemetry-utils";
@@ -99,7 +100,7 @@ function decompressInterval(interval: CompressedSerializedInterval, label?: stri
99
100
  end: interval[1],
100
101
  sequenceNumber: interval[2],
101
102
  intervalType: interval[3],
102
- properties: { ...interval[4], [reservedRangeLabelsKey]: label },
103
+ properties: { ...interval[4], [reservedRangeLabelsKey]: [label] },
103
104
  };
104
105
  }
105
106
 
@@ -191,11 +192,7 @@ export class Interval implements ISerializableInterval {
191
192
  }
192
193
 
193
194
  public serialize(client: Client): ISerializedInterval {
194
- let seq = 0;
195
- if (client) {
196
- seq = client.getCurrentSeq();
197
- }
198
-
195
+ const seq = client?.getCurrentSeq() ?? 0;
199
196
  const serializedInterval: ISerializedInterval = {
200
197
  end: this.end,
201
198
  intervalType: 0,
@@ -431,7 +428,7 @@ export class SequenceInterval implements ISerializableInterval {
431
428
  return (endPos > bstart) && (startPos < bend);
432
429
  }
433
430
 
434
- public modify(label: string, start: number, end: number, op?: ISequencedDocumentMessage) {
431
+ public modify(label: string, start: number, end: number, op?: ISequencedDocumentMessage, localSeq?: number) {
435
432
  const getRefType = (baseType: ReferenceType): ReferenceType => {
436
433
  let refType = baseType;
437
434
  if (op === undefined) {
@@ -443,13 +440,17 @@ export class SequenceInterval implements ISerializableInterval {
443
440
 
444
441
  let startRef = this.start;
445
442
  if (start !== undefined) {
446
- startRef = createPositionReference(this.client, start, getRefType(this.start.refType), op);
443
+ startRef = createPositionReference(
444
+ this.client, start, getRefType(this.start.refType), op, undefined, localSeq,
445
+ );
447
446
  startRef.addProperties(this.start.properties);
448
447
  }
449
448
 
450
449
  let endRef = this.end;
451
450
  if (end !== undefined) {
452
- endRef = createPositionReference(this.client, end, getRefType(this.end.refType), op);
451
+ endRef = createPositionReference(
452
+ this.client, end, getRefType(this.end.refType), op, undefined, localSeq,
453
+ );
453
454
  endRef.addProperties(this.end.properties);
454
455
  }
455
456
 
@@ -479,11 +480,13 @@ function createPositionReferenceFromSegoff(
479
480
  if (segoff.segment) {
480
481
  const ref = client.createLocalReferencePosition(segoff.segment, segoff.offset, refType, undefined);
481
482
  return ref;
482
- } else {
483
- if (!op && !refTypeIncludesFlag(refType, ReferenceType.Transient)) {
484
- throw new UsageError("Non-transient references need segment");
485
- }
486
483
  }
484
+
485
+ if (!op && !refTypeIncludesFlag(refType, ReferenceType.Transient)) {
486
+ // reference to segment that dne locally
487
+ throw new UsageError("Non-transient references need segment");
488
+ }
489
+
487
490
  return createDetachedLocalReferencePosition(refType);
488
491
  }
489
492
 
@@ -492,7 +495,9 @@ function createPositionReference(
492
495
  pos: number,
493
496
  refType: ReferenceType,
494
497
  op?: ISequencedDocumentMessage,
495
- fromSnapshot?: boolean): LocalReferencePosition {
498
+ fromSnapshot?: boolean,
499
+ localSeq?: number,
500
+ ): LocalReferencePosition {
496
501
  let segoff;
497
502
  if (op) {
498
503
  assert((refType & ReferenceType.SlideOnRemove) !== 0, 0x2f5 /* op create references must be SlideOnRemove */);
@@ -501,7 +506,7 @@ function createPositionReference(
501
506
  } else {
502
507
  assert((refType & ReferenceType.SlideOnRemove) === 0 || fromSnapshot,
503
508
  0x2f6 /* SlideOnRemove references must be op created */);
504
- segoff = client.getContainingSegment(pos);
509
+ segoff = client.getContainingSegment(pos, undefined, localSeq);
505
510
  }
506
511
  return createPositionReferenceFromSegoff(client, segoff, refType, op);
507
512
  }
@@ -793,18 +798,23 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
793
798
  if (!interval.properties) {
794
799
  interval.properties = createMap<any>();
795
800
  }
801
+
796
802
  if (props) {
797
803
  interval.addProperties(props);
798
804
  }
799
- if (interval.properties[reservedIntervalIdKey] === undefined) {
800
- // Create a new ID.
801
- interval.properties[reservedIntervalIdKey] = uuid();
802
- }
805
+ interval.properties[reservedIntervalIdKey] ??= uuid();
803
806
  this.add(interval);
804
807
  }
805
808
  return interval;
806
809
  }
807
810
 
811
+ private linkEndpointsToInterval(interval: TInterval): void {
812
+ if (interval instanceof SequenceInterval) {
813
+ interval.start.addProperties({ interval });
814
+ interval.end.addProperties({ interval });
815
+ }
816
+ }
817
+
808
818
  private addIntervalToIndex(interval: TInterval) {
809
819
  const id = interval.getIntervalId();
810
820
  assert(id !== undefined, 0x2c0 /* "ID must be created before adding interval to collection" */);
@@ -820,6 +830,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
820
830
  }
821
831
 
822
832
  public add(interval: TInterval) {
833
+ this.linkEndpointsToInterval(interval);
823
834
  this.addIntervalToIndex(interval);
824
835
  this.addIntervalListeners(interval);
825
836
  }
@@ -828,8 +839,14 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
828
839
  return this.intervalIdMap.get(id);
829
840
  }
830
841
 
831
- public changeInterval(interval: TInterval, start: number, end: number, op?: ISequencedDocumentMessage) {
832
- const newInterval = interval.modify(this.label, start, end, op) as TInterval | undefined;
842
+ public changeInterval(
843
+ interval: TInterval,
844
+ start: number,
845
+ end: number,
846
+ op?: ISequencedDocumentMessage,
847
+ localSeq?: number,
848
+ ) {
849
+ const newInterval = interval.modify(this.label, start, end, op, localSeq) as TInterval | undefined;
833
850
  if (newInterval) {
834
851
  this.removeExistingInterval(interval);
835
852
  this.add(newInterval);
@@ -980,6 +997,11 @@ function makeOpsMap<T extends ISerializableInterval>(): Map<string, IValueOperat
980
997
  "add",
981
998
  {
982
999
  process: (collection, params, local, op) => {
1000
+ // if params is undefined, the interval was deleted during
1001
+ // rebasing
1002
+ if (!params) {
1003
+ return;
1004
+ }
983
1005
  collection.ackAdd(params, local, op);
984
1006
  },
985
1007
  rebase,
@@ -1001,6 +1023,11 @@ function makeOpsMap<T extends ISerializableInterval>(): Map<string, IValueOperat
1001
1023
  "change",
1002
1024
  {
1003
1025
  process: (collection, params, local, op) => {
1026
+ // if params is undefined, the interval was deleted during
1027
+ // rebasing
1028
+ if (!params) {
1029
+ return;
1030
+ }
1004
1031
  collection.ackChange(params, local, op);
1005
1032
  },
1006
1033
  rebase,
@@ -1063,7 +1090,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1063
1090
  private savedSerializedIntervals?: ISerializedInterval[];
1064
1091
  private localCollection: LocalIntervalCollection<TInterval>;
1065
1092
  private onDeserialize: DeserializeCallback | undefined;
1066
- private client: Client;
1093
+ private client: Client | undefined;
1067
1094
  private readonly pendingChangesStart: Map<string, ISerializedInterval[]> = new Map<string, ISerializedInterval[]>();
1068
1095
  private readonly pendingChangesEnd: Map<string, ISerializedInterval[]> = new Map<string, ISerializedInterval[]>();
1069
1096
 
@@ -1129,7 +1156,11 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1129
1156
  * Gets the next local sequence number, modifying this client's collab window in doing so.
1130
1157
  */
1131
1158
  private getNextLocalSeq(): number {
1132
- return ++this.client.getCollabWindow().localSeq;
1159
+ if (this.client) {
1160
+ return ++this.client.getCollabWindow().localSeq;
1161
+ }
1162
+
1163
+ return 0;
1133
1164
  }
1134
1165
 
1135
1166
  public getIntervalById(id: string) {
@@ -1411,12 +1442,22 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1411
1442
  });
1412
1443
  }
1413
1444
 
1414
- /** @internal */
1445
+ /**
1446
+ * @internal
1447
+ *
1448
+ * Returns new interval after rebasing. If undefined, the interval was
1449
+ * deleted as a result of rebasing. This can occur if the interval applies
1450
+ * to a range that no longer exists, and the interval was unable to slide.
1451
+ */
1415
1452
  public rebaseLocalInterval(
1416
1453
  opName: string,
1417
1454
  serializedInterval: ISerializedInterval,
1418
1455
  localSeq: number,
1419
- ) {
1456
+ ): ISerializedInterval | undefined {
1457
+ if (!this.client) {
1458
+ // If there's no associated mergeTree client, the originally submitted op is still correct.
1459
+ return serializedInterval;
1460
+ }
1420
1461
  if (!this.attached) {
1421
1462
  throw new LoggingError("attachSequence must be called");
1422
1463
  }
@@ -1428,6 +1469,8 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1428
1469
  this.client.rebasePosition(end, sequenceNumber, localSeq);
1429
1470
 
1430
1471
  const intervalId = properties?.[reservedIntervalIdKey];
1472
+ const localInterval = this.localCollection.getIntervalById(intervalId);
1473
+
1431
1474
  const rebased: ISerializedInterval = {
1432
1475
  start: startRebased,
1433
1476
  end: endRebased,
@@ -1435,10 +1478,44 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1435
1478
  sequenceNumber: this.client?.getCurrentSeq() ?? 0,
1436
1479
  properties,
1437
1480
  };
1481
+
1438
1482
  if (opName === "change" && (this.hasPendingChangeStart(intervalId) || this.hasPendingChangeEnd(intervalId))) {
1439
1483
  this.removePendingChange(serializedInterval);
1440
1484
  this.addPendingChange(intervalId, rebased);
1441
1485
  }
1486
+
1487
+ // if the interval slid off the string, rebase the op to be a noop and
1488
+ // delete the interval
1489
+ if (startRebased === DetachedReferencePosition || endRebased === DetachedReferencePosition) {
1490
+ if (localInterval) {
1491
+ this.localCollection.removeExistingInterval(localInterval);
1492
+ }
1493
+ return undefined;
1494
+ }
1495
+
1496
+ if (!localInterval) {
1497
+ return rebased;
1498
+ }
1499
+
1500
+ // we know we must be using `SequenceInterval` because `this.client` exists
1501
+ assert(
1502
+ localInterval instanceof SequenceInterval,
1503
+ 0x3a0 /* localInterval must be `SequenceInterval` when used with client */,
1504
+ );
1505
+
1506
+ const startSegment = this.getSlideToSegment(localInterval.start);
1507
+ const endSegment = this.getSlideToSegment(localInterval.end);
1508
+
1509
+ // we need to slide because the reference has been removed
1510
+ if (startSegment || endSegment) {
1511
+ const newStart =
1512
+ startSegment && this.client.getPosition(startSegment.segment, localSeq) + startSegment.offset;
1513
+ const newEnd =
1514
+ endSegment && this.client.getPosition(endSegment.segment, localSeq) + endSegment.offset;
1515
+
1516
+ this.localCollection.changeInterval(localInterval, newStart, newEnd, undefined, localSeq);
1517
+ }
1518
+
1442
1519
  return rebased;
1443
1520
  }
1444
1521
 
@@ -1458,7 +1535,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1458
1535
  }
1459
1536
 
1460
1537
  private ackInterval(interval: TInterval, op: ISequencedDocumentMessage) {
1461
- // in current usage, interval is always a SequenceInterval
1538
+ // Only SequenceIntervals need potential sliding
1462
1539
  if (!(interval instanceof SequenceInterval)) {
1463
1540
  return;
1464
1541
  }
@@ -1653,3 +1730,31 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1653
1730
  return this.localCollection.nextInterval(pos);
1654
1731
  }
1655
1732
  }
1733
+
1734
+ /**
1735
+ * Information that identifies an interval within a `Sequence`.
1736
+ */
1737
+ export interface IntervalLocator {
1738
+ /**
1739
+ * Label for the collection the interval is a part of
1740
+ */
1741
+ label: string;
1742
+ /**
1743
+ * Interval within that collection
1744
+ */
1745
+ interval: SequenceInterval;
1746
+ }
1747
+
1748
+ /**
1749
+ * Returns an object that can be used to find the interval a given LocalReferencePosition belongs to.
1750
+ * @returns undefined if the reference position is not the endpoint of any interval (e.g. it was created
1751
+ * on the merge tree directly by app code), otherwise an {@link IntervalLocator} for the interval this
1752
+ * endpoint is a part of.
1753
+ */
1754
+ export function intervalLocatorFromEndpoint(potentialEndpoint: LocalReferencePosition): IntervalLocator | undefined {
1755
+ const {
1756
+ interval,
1757
+ [reservedRangeLabelsKey]: collectionNameArray,
1758
+ } = potentialEndpoint.properties ?? {};
1759
+ return (interval && collectionNameArray?.length === 1) ? { label: collectionNameArray[0], interval } : undefined;
1760
+ }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/sequence";
9
- export const pkgVersion = "2.0.0-internal.1.0.0.83139";
9
+ export const pkgVersion = "2.0.0-internal.1.1.1";
@@ -223,6 +223,9 @@ export class SharedString extends SharedSegmentSequence<SharedStringSegment> imp
223
223
  return this.client.findTile(startPos, tileLabel, preceding);
224
224
  }
225
225
 
226
+ /**
227
+ * @deprecated - use the free function `getTextAndMarkers` exported by this package instead.
228
+ */
226
229
  public getTextAndMarkers(label: string) {
227
230
  const segmentWindow = this.client.getCollabWindow();
228
231
  return this.mergeTreeTextHelper.getTextAndMarkers(segmentWindow.currentSeq, segmentWindow.clientId, label);
@@ -242,14 +245,16 @@ export class SharedString extends SharedSegmentSequence<SharedStringSegment> imp
242
245
  /**
243
246
  * Adds spaces for markers and handles, so that position calculations account for them.
244
247
  */
245
- public getTextWithPlaceholders() {
248
+ public getTextWithPlaceholders(start?: number, end?: number) {
246
249
  const segmentWindow = this.client.getCollabWindow();
247
- return this.mergeTreeTextHelper.getText(segmentWindow.currentSeq, segmentWindow.clientId, " ");
250
+ return this.mergeTreeTextHelper.getText(segmentWindow.currentSeq, segmentWindow.clientId, " ", start, end);
248
251
  }
249
252
 
253
+ /**
254
+ * @deprecated - use `getTextWithPlaceholders` instead.
255
+ */
250
256
  public getTextRangeWithPlaceholders(start: number, end: number) {
251
- const segmentWindow = this.client.getCollabWindow();
252
- return this.mergeTreeTextHelper.getText(segmentWindow.currentSeq, segmentWindow.clientId, " ", start, end);
257
+ return this.getTextWithPlaceholders(start, end);
253
258
  }
254
259
 
255
260
  public getTextRangeWithMarkers(start: number, end: number) {
@@ -272,3 +277,25 @@ export class SharedString extends SharedSegmentSequence<SharedStringSegment> imp
272
277
  }
273
278
  }
274
279
  }
280
+
281
+ /**
282
+ * Splits the text into regions ending with markers with the given `label`.
283
+ * @param sharedString - String to retrieve text and markers from
284
+ * @param label - label to split on
285
+ * @returns Two parallel lists of text and markers, split by markers with the provided `label`.
286
+ *
287
+ * For example:
288
+ * ```typescript
289
+ * // Say sharedstring has contents "hello<paragraph marker 1>world<paragraph marker 2>missing".
290
+ * const { parallelText, parallelMarkers } = getTextAndMarkers(sharedString, "paragraph");
291
+ * // parallelText === ["hello", "world"]
292
+ * // parallelMarkers === [<paragraph marker 1 object>, <paragraph marker 2 object>]
293
+ * // Note parallelText does not include "missing".
294
+ * ```
295
+ */
296
+ export function getTextAndMarkers(sharedString: SharedString, label: string): {
297
+ parallelText: string[];
298
+ parallelMarkers: Marker[];
299
+ } {
300
+ return sharedString.getTextAndMarkers(label);
301
+ }