@fluidframework/sequence 2.0.0-internal.3.0.2 → 2.0.0-internal.3.2.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 (94) hide show
  1. package/.eslintrc.js +9 -12
  2. package/.mocharc.js +2 -2
  3. package/.vscode/launch.json +15 -14
  4. package/README.md +188 -179
  5. package/api-extractor.json +2 -2
  6. package/dist/defaultMap.d.ts.map +1 -1
  7. package/dist/defaultMap.js +5 -4
  8. package/dist/defaultMap.js.map +1 -1
  9. package/dist/defaultMapInterfaces.d.ts.map +1 -1
  10. package/dist/defaultMapInterfaces.js.map +1 -1
  11. package/dist/index.d.ts +2 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/intervalCollection.d.ts.map +1 -1
  15. package/dist/intervalCollection.js +50 -36
  16. package/dist/intervalCollection.js.map +1 -1
  17. package/dist/intervalTree.d.ts.map +1 -1
  18. package/dist/intervalTree.js.map +1 -1
  19. package/dist/localValues.d.ts.map +1 -1
  20. package/dist/localValues.js.map +1 -1
  21. package/dist/packageVersion.d.ts +1 -1
  22. package/dist/packageVersion.js +1 -1
  23. package/dist/packageVersion.js.map +1 -1
  24. package/dist/sequence.d.ts +1 -1
  25. package/dist/sequence.d.ts.map +1 -1
  26. package/dist/sequence.js +13 -17
  27. package/dist/sequence.js.map +1 -1
  28. package/dist/sequenceDeltaEvent.d.ts.map +1 -1
  29. package/dist/sequenceDeltaEvent.js.map +1 -1
  30. package/dist/sequenceFactory.d.ts.map +1 -1
  31. package/dist/sequenceFactory.js.map +1 -1
  32. package/dist/sharedIntervalCollection.d.ts.map +1 -1
  33. package/dist/sharedIntervalCollection.js.map +1 -1
  34. package/dist/sharedSequence.d.ts.map +1 -1
  35. package/dist/sharedSequence.js +3 -3
  36. package/dist/sharedSequence.js.map +1 -1
  37. package/dist/sharedString.d.ts.map +1 -1
  38. package/dist/sharedString.js +5 -4
  39. package/dist/sharedString.js.map +1 -1
  40. package/lib/defaultMap.d.ts.map +1 -1
  41. package/lib/defaultMap.js +6 -5
  42. package/lib/defaultMap.js.map +1 -1
  43. package/lib/defaultMapInterfaces.d.ts.map +1 -1
  44. package/lib/defaultMapInterfaces.js.map +1 -1
  45. package/lib/index.d.ts +2 -2
  46. package/lib/index.d.ts.map +1 -1
  47. package/lib/index.js +2 -2
  48. package/lib/index.js.map +1 -1
  49. package/lib/intervalCollection.d.ts.map +1 -1
  50. package/lib/intervalCollection.js +50 -36
  51. package/lib/intervalCollection.js.map +1 -1
  52. package/lib/intervalTree.d.ts.map +1 -1
  53. package/lib/intervalTree.js.map +1 -1
  54. package/lib/localValues.d.ts.map +1 -1
  55. package/lib/localValues.js +1 -1
  56. package/lib/localValues.js.map +1 -1
  57. package/lib/packageVersion.d.ts +1 -1
  58. package/lib/packageVersion.js +1 -1
  59. package/lib/packageVersion.js.map +1 -1
  60. package/lib/sequence.d.ts +1 -1
  61. package/lib/sequence.d.ts.map +1 -1
  62. package/lib/sequence.js +15 -19
  63. package/lib/sequence.js.map +1 -1
  64. package/lib/sequenceDeltaEvent.d.ts.map +1 -1
  65. package/lib/sequenceDeltaEvent.js.map +1 -1
  66. package/lib/sequenceFactory.d.ts.map +1 -1
  67. package/lib/sequenceFactory.js +1 -1
  68. package/lib/sequenceFactory.js.map +1 -1
  69. package/lib/sharedIntervalCollection.d.ts.map +1 -1
  70. package/lib/sharedIntervalCollection.js.map +1 -1
  71. package/lib/sharedSequence.d.ts.map +1 -1
  72. package/lib/sharedSequence.js +4 -4
  73. package/lib/sharedSequence.js.map +1 -1
  74. package/lib/sharedString.d.ts.map +1 -1
  75. package/lib/sharedString.js +5 -4
  76. package/lib/sharedString.js.map +1 -1
  77. package/package.json +55 -55
  78. package/prettier.config.cjs +1 -1
  79. package/src/defaultMap.ts +406 -405
  80. package/src/defaultMapInterfaces.ts +120 -115
  81. package/src/index.ts +27 -17
  82. package/src/intervalCollection.ts +2198 -1997
  83. package/src/intervalTree.ts +139 -139
  84. package/src/localValues.ts +64 -73
  85. package/src/packageVersion.ts +1 -1
  86. package/src/sequence.ts +739 -694
  87. package/src/sequenceDeltaEvent.ts +143 -137
  88. package/src/sequenceFactory.ts +48 -46
  89. package/src/sharedIntervalCollection.ts +150 -136
  90. package/src/sharedSequence.ts +165 -160
  91. package/src/sharedString.ts +385 -343
  92. package/tsconfig.esnext.json +6 -6
  93. package/tsconfig.json +8 -12
  94. package/.editorconfig +0 -7
package/src/defaultMap.ts CHANGED
@@ -6,76 +6,73 @@
6
6
  import { IFluidHandle } from "@fluidframework/core-interfaces";
7
7
  import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
8
8
  import {
9
- IFluidSerializer,
10
- makeHandlesSerializable,
11
- parseHandles,
12
- ValueType,
9
+ IFluidSerializer,
10
+ makeHandlesSerializable,
11
+ parseHandles,
12
+ ValueType,
13
13
  } from "@fluidframework/shared-object-base";
14
14
  import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
15
+ import { makeSerializable, ValueTypeLocalValue } from "./localValues";
15
16
  import {
16
- makeSerializable,
17
- ValueTypeLocalValue,
18
- } from "./localValues";
19
- import {
20
- ISerializableValue,
21
- ISerializedValue,
22
- IValueChanged,
23
- IValueOpEmitter,
24
- IValueType,
25
- IValueTypeOperationValue,
26
- ISharedDefaultMapEvents,
27
- IMapMessageLocalMetadata,
17
+ ISerializableValue,
18
+ ISerializedValue,
19
+ IValueChanged,
20
+ IValueOpEmitter,
21
+ IValueType,
22
+ IValueTypeOperationValue,
23
+ ISharedDefaultMapEvents,
24
+ IMapMessageLocalMetadata,
28
25
  } from "./defaultMapInterfaces";
29
26
 
30
27
  /**
31
28
  * Defines the means to process and submit a given op on a map.
32
29
  */
33
30
  interface IMapMessageHandler {
34
- /**
35
- * Apply the given operation.
36
- * @param op - The map operation to apply
37
- * @param local - Whether the message originated from the local client
38
- * @param message - The full message. Not provided for stashed ops.
39
- * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
40
- * For messages from a remote client, this will be undefined.
41
- */
42
- process(
43
- op: IMapOperation,
44
- local: boolean,
45
- message: ISequencedDocumentMessage | undefined,
46
- localOpMetadata: IMapMessageLocalMetadata,
47
- ): void;
48
-
49
- /**
50
- * Communicate the operation to remote clients.
51
- * @param op - The map operation to submit
52
- */
53
- submit(op: IMapOperation, localOpMetadata: IMapMessageLocalMetadata): void;
54
-
55
- resubmit(op: IMapOperation, localOpMetadata: IMapMessageLocalMetadata): void;
56
-
57
- getStashedOpLocalMetadata(op: IMapOperation): unknown;
31
+ /**
32
+ * Apply the given operation.
33
+ * @param op - The map operation to apply
34
+ * @param local - Whether the message originated from the local client
35
+ * @param message - The full message. Not provided for stashed ops.
36
+ * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
37
+ * For messages from a remote client, this will be undefined.
38
+ */
39
+ process(
40
+ op: IMapOperation,
41
+ local: boolean,
42
+ message: ISequencedDocumentMessage | undefined,
43
+ localOpMetadata: IMapMessageLocalMetadata,
44
+ ): void;
45
+
46
+ /**
47
+ * Communicate the operation to remote clients.
48
+ * @param op - The map operation to submit
49
+ */
50
+ submit(op: IMapOperation, localOpMetadata: IMapMessageLocalMetadata): void;
51
+
52
+ resubmit(op: IMapOperation, localOpMetadata: IMapMessageLocalMetadata): void;
53
+
54
+ getStashedOpLocalMetadata(op: IMapOperation): unknown;
58
55
  }
59
56
 
60
57
  /**
61
58
  * Describes an operation specific to a value type.
62
59
  */
63
60
  export interface IMapValueTypeOperation {
64
- /**
65
- * String identifier of the operation type.
66
- */
67
- type: "act";
68
-
69
- /**
70
- * Map key being modified.
71
- */
72
- key: string;
73
-
74
- /**
75
- * Value of the operation, specific to the value type.
76
- * @alpha
77
- */
78
- value: IValueTypeOperationValue;
61
+ /**
62
+ * String identifier of the operation type.
63
+ */
64
+ type: "act";
65
+
66
+ /**
67
+ * Map key being modified.
68
+ */
69
+ key: string;
70
+
71
+ /**
72
+ * Value of the operation, specific to the value type.
73
+ * @alpha
74
+ */
75
+ value: IValueTypeOperationValue;
79
76
  }
80
77
 
81
78
  /**
@@ -88,11 +85,11 @@ export type IMapOperation = IMapValueTypeOperation;
88
85
  * Directly used in JSON.stringify, direct result from JSON.parse
89
86
  */
90
87
  export interface IMapDataObjectSerializable {
91
- [key: string]: ISerializableValue;
88
+ [key: string]: ISerializableValue;
92
89
  }
93
90
 
94
91
  export interface IMapDataObjectSerialized {
95
- [key: string]: ISerializedValue;
92
+ [key: string]: ISerializedValue;
96
93
  }
97
94
 
98
95
  /**
@@ -103,352 +100,356 @@ export interface IMapDataObjectSerialized {
103
100
  * a collection that wasn't previously known)
104
101
  */
105
102
  export class DefaultMap<T> {
106
- /**
107
- * The number of key/value pairs stored in the map.
108
- */
109
- public get size(): number {
110
- return this.data.size;
111
- }
112
-
113
- /**
114
- * Mapping of op types to message handlers.
115
- */
116
- private readonly messageHandlers: ReadonlyMap<string, IMapMessageHandler> = new Map();
117
-
118
- /**
119
- * The in-memory data the map is storing.
120
- */
121
- private readonly data = new Map<string, ValueTypeLocalValue<T>>();
122
-
123
- /**
124
- * Create a new default map.
125
- * @param serializer - The serializer to serialize / parse handles
126
- * @param handle - The handle of the shared object using the kernel
127
- * @param submitMessage - A callback to submit a message through the shared object
128
- * @param type - The value type to create at values of this map
129
- * @param eventEmitter - The object that will emit map events
130
- */
131
- constructor(
132
- private readonly serializer: IFluidSerializer,
133
- private readonly handle: IFluidHandle,
134
- private readonly submitMessage: (op: any, localOpMetadata: IMapMessageLocalMetadata) => void,
135
- private readonly type: IValueType<T>,
136
- public readonly eventEmitter = new TypedEventEmitter<ISharedDefaultMapEvents>(),
137
- ) {
138
- this.messageHandlers = this.getMessageHandlers();
139
- }
140
-
141
- /**
142
- * Get an iterator over the keys in this map.
143
- * @returns The iterator
144
- */
145
- public keys(): IterableIterator<string> {
146
- return this.data.keys();
147
- }
148
-
149
- /**
150
- * Get an iterator over the entries in this map.
151
- * @returns The iterator
152
- */
153
- public entries(): IterableIterator<[string, any]> {
154
- const localEntriesIterator = this.data.entries();
155
- const iterator = {
156
- next(): IteratorResult<[string, any]> {
157
- const nextVal = localEntriesIterator.next();
158
- return nextVal.done
159
- ? { value: undefined, done: true }
160
- : { value: [nextVal.value[0], nextVal.value[1].value], done: false }; // Unpack the stored value
161
- },
162
- [Symbol.iterator]() {
163
- return this;
164
- },
165
- };
166
- return iterator;
167
- }
168
-
169
- /**
170
- * Get an iterator over the values in this map.
171
- * @returns The iterator
172
- */
173
- public values(): IterableIterator<any> {
174
- const localValuesIterator = this.data.values();
175
- const iterator = {
176
- next(): IteratorResult<any> {
177
- const nextVal = localValuesIterator.next();
178
- return nextVal.done
179
- ? { value: undefined, done: true }
180
- : { value: nextVal.value.value, done: false }; // Unpack the stored value
181
- },
182
- [Symbol.iterator]() {
183
- return this;
184
- },
185
- };
186
- return iterator;
187
- }
188
-
189
- /**
190
- * Get an iterator over the entries in this map.
191
- * @returns The iterator
192
- */
193
- public [Symbol.iterator](): IterableIterator<[string, any]> {
194
- return this.entries();
195
- }
196
-
197
- /**
198
- * Executes the given callback on each entry in the map.
199
- * @param callbackFn - Callback function
200
- */
201
- public forEach(callbackFn: (value: any, key: string, map: Map<string, any>) => void): void {
202
- this.data.forEach((localValue, key, m) => {
203
- callbackFn(localValue.value, key, m);
204
- });
205
- }
206
-
207
- /**
208
- * {@inheritDoc ISharedMap.get}
209
- */
210
- public get(key: string): T {
211
- const localValue = this.data.get(key) ?? this.createCore(key, true);
212
-
213
- return localValue.value;
214
- }
215
-
216
- /**
217
- * Check if a key exists in the map.
218
- * @param key - The key to check
219
- * @returns True if the key exists, false otherwise
220
- */
221
- public has(key: string): boolean {
222
- return this.data.has(key);
223
- }
224
-
225
- /**
226
- * Serializes the data stored in the shared map to a JSON string
227
- * @param serializer - The serializer to use to serialize handles in its values.
228
- * @returns A JSON string containing serialized map data
229
- */
230
- public getSerializedStorage(serializer: IFluidSerializer): IMapDataObjectSerialized {
231
- const serializableMapData: IMapDataObjectSerialized = {};
232
- this.data.forEach((localValue, key) => {
233
- serializableMapData[key] = localValue.makeSerialized(serializer, this.handle);
234
- });
235
- return serializableMapData;
236
- }
237
-
238
- public getSerializableStorage(serializer: IFluidSerializer): IMapDataObjectSerializable {
239
- const serializableMapData: IMapDataObjectSerializable = {};
240
- this.data.forEach((localValue, key) => {
241
- serializableMapData[key] = makeSerializable(localValue, serializer, this.handle);
242
- });
243
- return serializableMapData;
244
- }
245
-
246
- public serialize(serializer: IFluidSerializer): string {
247
- return JSON.stringify(this.getSerializableStorage(serializer));
248
- }
249
-
250
- /**
251
- * Populate the kernel with the given map data.
252
- * @param data - A JSON string containing serialized map data
253
- */
254
- public populateFromSerializable(json: IMapDataObjectSerializable): void {
255
- for (const [key, serializable] of Object.entries(json)) {
256
- // Back-compat: legacy documents may have handles to an intervalCollection map kernel.
257
- // These collections should be empty, and ValueTypes are no longer supported.
258
- if (serializable.type === ValueType[ValueType.Plain]
259
- || serializable.type === ValueType[ValueType.Shared]) {
260
- continue;
261
- }
262
-
263
- // Back-compat: Sequence previously arbitrarily prefixed all interval collection keys with
264
- // "intervalCollections/". This would burden users trying to iterate the collection and
265
- // access its value, as well as those trying to match a create message to its underlying
266
- // collection. See https://github.com/microsoft/FluidFramework/issues/10557 for more context.
267
- const normalizedKey = key.startsWith("intervalCollections/") ? key.substring(20) : key;
268
-
269
- const localValue = {
270
- key: normalizedKey,
271
- value: this.makeLocal(key, serializable),
272
- };
273
-
274
- this.data.set(localValue.key, localValue.value);
275
- }
276
- }
277
-
278
- public populate(json: string): void {
279
- this.populateFromSerializable(JSON.parse(json) as IMapDataObjectSerializable);
280
- }
281
-
282
- /**
283
- * Submit the given op if a handler is registered.
284
- * @param op - The operation to attempt to submit
285
- * @param localOpMetadata - The local metadata associated with the op. This is kept locally by the runtime
286
- * and not sent to the server. This will be sent back when this message is received back from the server. This is
287
- * also sent if we are asked to resubmit the message.
288
- * @returns True if the operation was submitted, false otherwise.
289
- */
290
- public tryResubmitMessage(op: any, localOpMetadata: IMapMessageLocalMetadata): boolean {
291
- const type: string = op.type;
292
- const handler = this.messageHandlers.get(type);
293
- if (handler !== undefined) {
294
- handler.resubmit(op as IMapOperation, localOpMetadata);
295
- return true;
296
- }
297
- return false;
298
- }
299
-
300
- public tryGetStashedOpLocalMetadata(op: any): unknown {
301
- const type: string = op.type;
302
- if (this.messageHandlers.has(type)) {
303
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
304
- return this.messageHandlers.get(type)!.getStashedOpLocalMetadata(op as IMapOperation);
305
- }
306
- throw new Error("no apply stashed op handler");
307
- }
308
-
309
- /**
310
- * Process the given op if a handler is registered.
311
- * @param message - The message to process
312
- * @param local - Whether the message originated from the local client
313
- * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
314
- * For messages from a remote client, this will be undefined.
315
- * @returns True if the operation was processed, false otherwise.
316
- */
317
- public tryProcessMessage(
318
- op: IMapOperation,
319
- local: boolean,
320
- message: ISequencedDocumentMessage | undefined,
321
- localOpMetadata: unknown,
322
- ): boolean {
323
- const handler = this.messageHandlers.get(op.type);
324
- if (handler !== undefined) {
325
- handler.process(op, local, message, localOpMetadata as IMapMessageLocalMetadata);
326
- return true;
327
- }
328
- return false;
329
- }
330
-
331
- /**
332
- * Initializes a default ValueType at the provided key.
333
- * Should be used when a map operation incurs creation.
334
- * @param key - The key being initialized
335
- * @param local - Whether the message originated from the local client
336
- */
337
- private createCore(key: string, local: boolean): ValueTypeLocalValue<T> {
338
- const localValue = new ValueTypeLocalValue(
339
- this.type.factory.load(this.makeMapValueOpEmitter(key), undefined),
340
- this.type,
341
- );
342
- const previousValue = this.data.get(key);
343
- this.data.set(key, localValue);
344
- const event: IValueChanged = { key, previousValue };
345
- this.eventEmitter.emit("create", event, local, this.eventEmitter);
346
- return localValue;
347
- }
348
-
349
- /**
350
- * The remote ISerializableValue we're receiving (either as a result of a load or an incoming set op) will
351
- * have the information we need to create a real object, but will not be the real object yet. For example,
352
- * we might know it's a map and the map's ID but not have the actual map or its data yet. makeLocal's
353
- * job is to convert that information into a real object for local usage.
354
- * @param key - The key that the caller intends to store the local value into (used for ops later). But
355
- * doesn't actually store the local value into that key. So better not lie!
356
- * @param serializable - The remote information that we can convert into a real object
357
- * @returns The local value that was produced
358
- */
359
- private makeLocal(key: string, serializable: ISerializableValue): ValueTypeLocalValue<T> {
360
- assert(serializable.type !== ValueType[ValueType.Plain] && serializable.type !== ValueType[ValueType.Shared],
361
- 0x2e1 /* "Support for plain value types removed." */);
362
-
363
- serializable.value = parseHandles(serializable.value, this.serializer);
364
- const localValue = this.type.factory.load(
365
- this.makeMapValueOpEmitter(key),
366
- serializable.value,
367
- );
368
- return new ValueTypeLocalValue(localValue, this.type);
369
- }
370
-
371
- /**
372
- * Get the message handlers for the map.
373
- * @returns A map of string op names to IMapMessageHandlers for those ops
374
- */
375
- private getMessageHandlers() {
376
- const messageHandlers = new Map<string, IMapMessageHandler>();
377
- // Ops with type "act" describe actions taken by custom value type handlers of whatever item is
378
- // being addressed. These custom handlers can be retrieved from the ValueTypeLocalValue which has
379
- // stashed its valueType (and therefore its handlers). We also emit a valueChanged for anyone
380
- // watching for manipulations of that item.
381
- messageHandlers.set(
382
- "act",
383
- {
384
- process: (op: IMapValueTypeOperation, local, message, localOpMetadata) => {
385
- const localValue = this.data.get(op.key) ?? this.createCore(op.key, local);
386
- const handler = localValue.getOpHandler(op.value.opName);
387
- const previousValue = localValue.value;
388
- const translatedValue = parseHandles(
389
- op.value.value,
390
- this.serializer);
391
- handler.process(previousValue, translatedValue, local, message, localOpMetadata);
392
- const event: IValueChanged = { key: op.key, previousValue };
393
- this.eventEmitter.emit("valueChanged", event, local, message, this.eventEmitter);
394
- },
395
- submit: (op: IMapValueTypeOperation, localOpMetadata: IMapMessageLocalMetadata) => {
396
- this.submitMessage(
397
- op,
398
- localOpMetadata,
399
- );
400
- },
401
- resubmit: (op: IMapValueTypeOperation, localOpMetadata: IMapMessageLocalMetadata) => {
402
- const localValue = this.data.get(op.key);
403
-
404
- assert(localValue !== undefined, 0x3f8 /* Local value expected on resubmission */);
405
-
406
- const handler = localValue.getOpHandler(op.value.opName);
407
- const {
408
- rebasedOp,
409
- rebasedLocalOpMetadata,
410
- } = handler.rebase(localValue.value, op.value, localOpMetadata);
411
- this.submitMessage(
412
- { ...op, value: rebasedOp },
413
- rebasedLocalOpMetadata,
414
- );
415
- },
416
- getStashedOpLocalMetadata: (op: IMapValueTypeOperation) => {
417
- assert(false, 0x016 /* "apply stashed op not implemented for custom value type ops" */);
418
- },
419
- });
420
-
421
- return messageHandlers;
422
- }
423
-
424
- /**
425
- * Create an emitter for a value type to emit ops from the given key.
426
- * @alpha
427
- * @param key - The key of the map that the value type will be stored on
428
- * @returns A value op emitter for the given key
429
- */
430
- private makeMapValueOpEmitter(key: string): IValueOpEmitter {
431
- const emit = (opName: string, previousValue: any, params: any, localOpMetadata: IMapMessageLocalMetadata) => {
432
- const translatedParams = makeHandlesSerializable(
433
- params,
434
- this.serializer,
435
- this.handle);
436
-
437
- const op: IMapValueTypeOperation = {
438
- key,
439
- type: "act",
440
- value: {
441
- opName,
442
- value: translatedParams,
443
- },
444
- };
445
-
446
- this.submitMessage(op, localOpMetadata);
447
-
448
- const event: IValueChanged = { key, previousValue };
449
- this.eventEmitter.emit("valueChanged", event, true, null, this.eventEmitter);
450
- };
451
-
452
- return { emit };
453
- }
103
+ /**
104
+ * The number of key/value pairs stored in the map.
105
+ */
106
+ public get size(): number {
107
+ return this.data.size;
108
+ }
109
+
110
+ /**
111
+ * Mapping of op types to message handlers.
112
+ */
113
+ private readonly messageHandlers: ReadonlyMap<string, IMapMessageHandler> = new Map();
114
+
115
+ /**
116
+ * The in-memory data the map is storing.
117
+ */
118
+ private readonly data = new Map<string, ValueTypeLocalValue<T>>();
119
+
120
+ /**
121
+ * Create a new default map.
122
+ * @param serializer - The serializer to serialize / parse handles
123
+ * @param handle - The handle of the shared object using the kernel
124
+ * @param submitMessage - A callback to submit a message through the shared object
125
+ * @param type - The value type to create at values of this map
126
+ * @param eventEmitter - The object that will emit map events
127
+ */
128
+ constructor(
129
+ private readonly serializer: IFluidSerializer,
130
+ private readonly handle: IFluidHandle,
131
+ private readonly submitMessage: (
132
+ op: any,
133
+ localOpMetadata: IMapMessageLocalMetadata,
134
+ ) => void,
135
+ private readonly type: IValueType<T>,
136
+ public readonly eventEmitter = new TypedEventEmitter<ISharedDefaultMapEvents>(),
137
+ ) {
138
+ this.messageHandlers = this.getMessageHandlers();
139
+ }
140
+
141
+ /**
142
+ * Get an iterator over the keys in this map.
143
+ * @returns The iterator
144
+ */
145
+ public keys(): IterableIterator<string> {
146
+ return this.data.keys();
147
+ }
148
+
149
+ /**
150
+ * Get an iterator over the entries in this map.
151
+ * @returns The iterator
152
+ */
153
+ public entries(): IterableIterator<[string, any]> {
154
+ const localEntriesIterator = this.data.entries();
155
+ const iterator = {
156
+ next(): IteratorResult<[string, any]> {
157
+ const nextVal = localEntriesIterator.next();
158
+ return nextVal.done
159
+ ? { value: undefined, done: true }
160
+ : { value: [nextVal.value[0], nextVal.value[1].value], done: false }; // Unpack the stored value
161
+ },
162
+ [Symbol.iterator]() {
163
+ return this;
164
+ },
165
+ };
166
+ return iterator;
167
+ }
168
+
169
+ /**
170
+ * Get an iterator over the values in this map.
171
+ * @returns The iterator
172
+ */
173
+ public values(): IterableIterator<any> {
174
+ const localValuesIterator = this.data.values();
175
+ const iterator = {
176
+ next(): IteratorResult<any> {
177
+ const nextVal = localValuesIterator.next();
178
+ return nextVal.done
179
+ ? { value: undefined, done: true }
180
+ : { value: nextVal.value.value, done: false }; // Unpack the stored value
181
+ },
182
+ [Symbol.iterator]() {
183
+ return this;
184
+ },
185
+ };
186
+ return iterator;
187
+ }
188
+
189
+ /**
190
+ * Get an iterator over the entries in this map.
191
+ * @returns The iterator
192
+ */
193
+ public [Symbol.iterator](): IterableIterator<[string, any]> {
194
+ return this.entries();
195
+ }
196
+
197
+ /**
198
+ * Executes the given callback on each entry in the map.
199
+ * @param callbackFn - Callback function
200
+ */
201
+ public forEach(callbackFn: (value: any, key: string, map: Map<string, any>) => void): void {
202
+ this.data.forEach((localValue, key, m) => {
203
+ callbackFn(localValue.value, key, m);
204
+ });
205
+ }
206
+
207
+ /**
208
+ * {@inheritDoc ISharedMap.get}
209
+ */
210
+ public get(key: string): T {
211
+ const localValue = this.data.get(key) ?? this.createCore(key, true);
212
+
213
+ return localValue.value;
214
+ }
215
+
216
+ /**
217
+ * Check if a key exists in the map.
218
+ * @param key - The key to check
219
+ * @returns True if the key exists, false otherwise
220
+ */
221
+ public has(key: string): boolean {
222
+ return this.data.has(key);
223
+ }
224
+
225
+ /**
226
+ * Serializes the data stored in the shared map to a JSON string
227
+ * @param serializer - The serializer to use to serialize handles in its values.
228
+ * @returns A JSON string containing serialized map data
229
+ */
230
+ public getSerializedStorage(serializer: IFluidSerializer): IMapDataObjectSerialized {
231
+ const serializableMapData: IMapDataObjectSerialized = {};
232
+ this.data.forEach((localValue, key) => {
233
+ serializableMapData[key] = localValue.makeSerialized(serializer, this.handle);
234
+ });
235
+ return serializableMapData;
236
+ }
237
+
238
+ public getSerializableStorage(serializer: IFluidSerializer): IMapDataObjectSerializable {
239
+ const serializableMapData: IMapDataObjectSerializable = {};
240
+ this.data.forEach((localValue, key) => {
241
+ serializableMapData[key] = makeSerializable(localValue, serializer, this.handle);
242
+ });
243
+ return serializableMapData;
244
+ }
245
+
246
+ public serialize(serializer: IFluidSerializer): string {
247
+ return JSON.stringify(this.getSerializableStorage(serializer));
248
+ }
249
+
250
+ /**
251
+ * Populate the kernel with the given map data.
252
+ * @param data - A JSON string containing serialized map data
253
+ */
254
+ public populateFromSerializable(json: IMapDataObjectSerializable): void {
255
+ for (const [key, serializable] of Object.entries(json)) {
256
+ // Back-compat: legacy documents may have handles to an intervalCollection map kernel.
257
+ // These collections should be empty, and ValueTypes are no longer supported.
258
+ if (
259
+ serializable.type === ValueType[ValueType.Plain] ||
260
+ serializable.type === ValueType[ValueType.Shared]
261
+ ) {
262
+ continue;
263
+ }
264
+
265
+ // Back-compat: Sequence previously arbitrarily prefixed all interval collection keys with
266
+ // "intervalCollections/". This would burden users trying to iterate the collection and
267
+ // access its value, as well as those trying to match a create message to its underlying
268
+ // collection. See https://github.com/microsoft/FluidFramework/issues/10557 for more context.
269
+ const normalizedKey = key.startsWith("intervalCollections/") ? key.substring(20) : key;
270
+
271
+ const localValue = {
272
+ key: normalizedKey,
273
+ value: this.makeLocal(key, serializable),
274
+ };
275
+
276
+ this.data.set(localValue.key, localValue.value);
277
+ }
278
+ }
279
+
280
+ public populate(json: string): void {
281
+ this.populateFromSerializable(JSON.parse(json) as IMapDataObjectSerializable);
282
+ }
283
+
284
+ /**
285
+ * Submit the given op if a handler is registered.
286
+ * @param op - The operation to attempt to submit
287
+ * @param localOpMetadata - The local metadata associated with the op. This is kept locally by the runtime
288
+ * and not sent to the server. This will be sent back when this message is received back from the server. This is
289
+ * also sent if we are asked to resubmit the message.
290
+ * @returns True if the operation was submitted, false otherwise.
291
+ */
292
+ public tryResubmitMessage(op: any, localOpMetadata: IMapMessageLocalMetadata): boolean {
293
+ const type: string = op.type;
294
+ const handler = this.messageHandlers.get(type);
295
+ if (handler !== undefined) {
296
+ handler.resubmit(op as IMapOperation, localOpMetadata);
297
+ return true;
298
+ }
299
+ return false;
300
+ }
301
+
302
+ public tryGetStashedOpLocalMetadata(op: any): unknown {
303
+ const type: string = op.type;
304
+ if (this.messageHandlers.has(type)) {
305
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
306
+ return this.messageHandlers.get(type)!.getStashedOpLocalMetadata(op as IMapOperation);
307
+ }
308
+ throw new Error("no apply stashed op handler");
309
+ }
310
+
311
+ /**
312
+ * Process the given op if a handler is registered.
313
+ * @param message - The message to process
314
+ * @param local - Whether the message originated from the local client
315
+ * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
316
+ * For messages from a remote client, this will be undefined.
317
+ * @returns True if the operation was processed, false otherwise.
318
+ */
319
+ public tryProcessMessage(
320
+ op: IMapOperation,
321
+ local: boolean,
322
+ message: ISequencedDocumentMessage | undefined,
323
+ localOpMetadata: unknown,
324
+ ): boolean {
325
+ const handler = this.messageHandlers.get(op.type);
326
+ if (handler !== undefined) {
327
+ handler.process(op, local, message, localOpMetadata as IMapMessageLocalMetadata);
328
+ return true;
329
+ }
330
+ return false;
331
+ }
332
+
333
+ /**
334
+ * Initializes a default ValueType at the provided key.
335
+ * Should be used when a map operation incurs creation.
336
+ * @param key - The key being initialized
337
+ * @param local - Whether the message originated from the local client
338
+ */
339
+ private createCore(key: string, local: boolean): ValueTypeLocalValue<T> {
340
+ const localValue = new ValueTypeLocalValue(
341
+ this.type.factory.load(this.makeMapValueOpEmitter(key), undefined),
342
+ this.type,
343
+ );
344
+ const previousValue = this.data.get(key);
345
+ this.data.set(key, localValue);
346
+ const event: IValueChanged = { key, previousValue };
347
+ this.eventEmitter.emit("create", event, local, this.eventEmitter);
348
+ return localValue;
349
+ }
350
+
351
+ /**
352
+ * The remote ISerializableValue we're receiving (either as a result of a load or an incoming set op) will
353
+ * have the information we need to create a real object, but will not be the real object yet. For example,
354
+ * we might know it's a map and the map's ID but not have the actual map or its data yet. makeLocal's
355
+ * job is to convert that information into a real object for local usage.
356
+ * @param key - The key that the caller intends to store the local value into (used for ops later). But
357
+ * doesn't actually store the local value into that key. So better not lie!
358
+ * @param serializable - The remote information that we can convert into a real object
359
+ * @returns The local value that was produced
360
+ */
361
+ private makeLocal(key: string, serializable: ISerializableValue): ValueTypeLocalValue<T> {
362
+ assert(
363
+ serializable.type !== ValueType[ValueType.Plain] &&
364
+ serializable.type !== ValueType[ValueType.Shared],
365
+ 0x2e1 /* "Support for plain value types removed." */,
366
+ );
367
+
368
+ serializable.value = parseHandles(serializable.value, this.serializer);
369
+ const localValue = this.type.factory.load(
370
+ this.makeMapValueOpEmitter(key),
371
+ serializable.value,
372
+ );
373
+ return new ValueTypeLocalValue(localValue, this.type);
374
+ }
375
+
376
+ /**
377
+ * Get the message handlers for the map.
378
+ * @returns A map of string op names to IMapMessageHandlers for those ops
379
+ */
380
+ private getMessageHandlers() {
381
+ const messageHandlers = new Map<string, IMapMessageHandler>();
382
+ // Ops with type "act" describe actions taken by custom value type handlers of whatever item is
383
+ // being addressed. These custom handlers can be retrieved from the ValueTypeLocalValue which has
384
+ // stashed its valueType (and therefore its handlers). We also emit a valueChanged for anyone
385
+ // watching for manipulations of that item.
386
+ messageHandlers.set("act", {
387
+ process: (op: IMapValueTypeOperation, local, message, localOpMetadata) => {
388
+ const localValue = this.data.get(op.key) ?? this.createCore(op.key, local);
389
+ const handler = localValue.getOpHandler(op.value.opName);
390
+ const previousValue = localValue.value;
391
+ const translatedValue = parseHandles(op.value.value, this.serializer);
392
+ handler.process(previousValue, translatedValue, local, message, localOpMetadata);
393
+ const event: IValueChanged = { key: op.key, previousValue };
394
+ this.eventEmitter.emit("valueChanged", event, local, message, this.eventEmitter);
395
+ },
396
+ submit: (op: IMapValueTypeOperation, localOpMetadata: IMapMessageLocalMetadata) => {
397
+ this.submitMessage(op, localOpMetadata);
398
+ },
399
+ resubmit: (op: IMapValueTypeOperation, localOpMetadata: IMapMessageLocalMetadata) => {
400
+ const localValue = this.data.get(op.key);
401
+
402
+ assert(localValue !== undefined, 0x3f8 /* Local value expected on resubmission */);
403
+
404
+ const handler = localValue.getOpHandler(op.value.opName);
405
+ const { rebasedOp, rebasedLocalOpMetadata } = handler.rebase(
406
+ localValue.value,
407
+ op.value,
408
+ localOpMetadata,
409
+ );
410
+ this.submitMessage({ ...op, value: rebasedOp }, rebasedLocalOpMetadata);
411
+ },
412
+ getStashedOpLocalMetadata: (op: IMapValueTypeOperation) => {
413
+ assert(
414
+ false,
415
+ 0x016 /* "apply stashed op not implemented for custom value type ops" */,
416
+ );
417
+ },
418
+ });
419
+
420
+ return messageHandlers;
421
+ }
422
+
423
+ /**
424
+ * Create an emitter for a value type to emit ops from the given key.
425
+ * @alpha
426
+ * @param key - The key of the map that the value type will be stored on
427
+ * @returns A value op emitter for the given key
428
+ */
429
+ private makeMapValueOpEmitter(key: string): IValueOpEmitter {
430
+ const emit = (
431
+ opName: string,
432
+ previousValue: any,
433
+ params: any,
434
+ localOpMetadata: IMapMessageLocalMetadata,
435
+ ) => {
436
+ const translatedParams = makeHandlesSerializable(params, this.serializer, this.handle);
437
+
438
+ const op: IMapValueTypeOperation = {
439
+ key,
440
+ type: "act",
441
+ value: {
442
+ opName,
443
+ value: translatedParams,
444
+ },
445
+ };
446
+
447
+ this.submitMessage(op, localOpMetadata);
448
+
449
+ const event: IValueChanged = { key, previousValue };
450
+ this.eventEmitter.emit("valueChanged", event, true, null, this.eventEmitter);
451
+ };
452
+
453
+ return { emit };
454
+ }
454
455
  }