@fluidframework/sequence 0.59.3003 → 0.59.4000

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 (80) hide show
  1. package/dist/{mapKernel.d.ts → defaultMap.d.ts} +21 -145
  2. package/dist/defaultMap.d.ts.map +1 -0
  3. package/dist/defaultMap.js +317 -0
  4. package/dist/defaultMap.js.map +1 -0
  5. package/dist/{mapKernelInterfaces.d.ts → defaultMapInterfaces.d.ts} +9 -51
  6. package/dist/defaultMapInterfaces.d.ts.map +1 -0
  7. package/dist/{mapKernelInterfaces.js → defaultMapInterfaces.js} +1 -1
  8. package/dist/defaultMapInterfaces.js.map +1 -0
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/intervalCollection.d.ts +12 -3
  13. package/dist/intervalCollection.d.ts.map +1 -1
  14. package/dist/intervalCollection.js +44 -39
  15. package/dist/intervalCollection.js.map +1 -1
  16. package/dist/localValues.d.ts +7 -86
  17. package/dist/localValues.d.ts.map +1 -1
  18. package/dist/localValues.js +1 -131
  19. package/dist/localValues.js.map +1 -1
  20. package/dist/packageVersion.d.ts +1 -1
  21. package/dist/packageVersion.js +1 -1
  22. package/dist/packageVersion.js.map +1 -1
  23. package/dist/sequence.d.ts +5 -1
  24. package/dist/sequence.d.ts.map +1 -1
  25. package/dist/sequence.js +23 -23
  26. package/dist/sequence.js.map +1 -1
  27. package/dist/sharedIntervalCollection.d.ts +13 -5
  28. package/dist/sharedIntervalCollection.d.ts.map +1 -1
  29. package/dist/sharedIntervalCollection.js +16 -12
  30. package/dist/sharedIntervalCollection.js.map +1 -1
  31. package/lib/{mapKernel.d.ts → defaultMap.d.ts} +21 -145
  32. package/lib/defaultMap.d.ts.map +1 -0
  33. package/lib/defaultMap.js +313 -0
  34. package/lib/defaultMap.js.map +1 -0
  35. package/lib/{mapKernelInterfaces.d.ts → defaultMapInterfaces.d.ts} +9 -51
  36. package/lib/defaultMapInterfaces.d.ts.map +1 -0
  37. package/lib/{mapKernelInterfaces.js → defaultMapInterfaces.js} +1 -1
  38. package/lib/defaultMapInterfaces.js.map +1 -0
  39. package/lib/index.d.ts +1 -1
  40. package/lib/index.d.ts.map +1 -1
  41. package/lib/index.js.map +1 -1
  42. package/lib/intervalCollection.d.ts +12 -3
  43. package/lib/intervalCollection.d.ts.map +1 -1
  44. package/lib/intervalCollection.js +44 -39
  45. package/lib/intervalCollection.js.map +1 -1
  46. package/lib/localValues.d.ts +7 -86
  47. package/lib/localValues.d.ts.map +1 -1
  48. package/lib/localValues.js +1 -129
  49. package/lib/localValues.js.map +1 -1
  50. package/lib/packageVersion.d.ts +1 -1
  51. package/lib/packageVersion.js +1 -1
  52. package/lib/packageVersion.js.map +1 -1
  53. package/lib/sequence.d.ts +5 -1
  54. package/lib/sequence.d.ts.map +1 -1
  55. package/lib/sequence.js +23 -23
  56. package/lib/sequence.js.map +1 -1
  57. package/lib/sharedIntervalCollection.d.ts +13 -5
  58. package/lib/sharedIntervalCollection.d.ts.map +1 -1
  59. package/lib/sharedIntervalCollection.js +16 -12
  60. package/lib/sharedIntervalCollection.js.map +1 -1
  61. package/package.json +23 -18
  62. package/src/defaultMap.ts +453 -0
  63. package/src/{mapKernelInterfaces.ts → defaultMapInterfaces.ts} +14 -59
  64. package/src/index.ts +1 -1
  65. package/src/intervalCollection.ts +58 -43
  66. package/src/localValues.ts +6 -154
  67. package/src/packageVersion.ts +1 -1
  68. package/src/sequence.ts +32 -33
  69. package/src/sharedIntervalCollection.ts +22 -25
  70. package/dist/mapKernel.d.ts.map +0 -1
  71. package/dist/mapKernel.js +0 -599
  72. package/dist/mapKernel.js.map +0 -1
  73. package/dist/mapKernelInterfaces.d.ts.map +0 -1
  74. package/dist/mapKernelInterfaces.js.map +0 -1
  75. package/lib/mapKernel.d.ts.map +0 -1
  76. package/lib/mapKernel.js +0 -595
  77. package/lib/mapKernel.js.map +0 -1
  78. package/lib/mapKernelInterfaces.d.ts.map +0 -1
  79. package/lib/mapKernelInterfaces.js.map +0 -1
  80. package/src/mapKernel.ts +0 -850
package/src/mapKernel.ts DELETED
@@ -1,850 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
-
6
- import { IFluidHandle } from "@fluidframework/core-interfaces";
7
- import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
8
- import { IFluidSerializer, makeHandlesSerializable, parseHandles, ValueType } from "@fluidframework/shared-object-base";
9
- import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
10
- import {
11
- ILocalValue,
12
- LocalValueMaker,
13
- makeSerializable,
14
- ValueTypeLocalValue,
15
- } from "./localValues";
16
- import {
17
- ISerializableValue,
18
- ISerializedValue,
19
- IValueChanged,
20
- IValueOpEmitter,
21
- IValueType,
22
- IValueTypeCreator,
23
- IValueTypeOperationValue,
24
- ISharedMapEvents,
25
- } from "./mapKernelInterfaces";
26
-
27
- /**
28
- * Defines the means to process and submit a given op on a map.
29
- */
30
- interface IMapMessageHandler {
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
- * @param localOpMetadata - The metadata to be submitted with the message.
50
- */
51
- submit(op: IMapOperation): void;
52
-
53
- getStashedOpLocalMetadata(op: IMapOperation): unknown;
54
- }
55
-
56
- interface IMapMessageLocalMetadata{
57
- pendingClearMessageId?: number;
58
- pendingMessageId?: number;
59
- lastProcessedSeq: number;
60
- }
61
-
62
- /**
63
- * Describes an operation specific to a value type.
64
- */
65
- export interface IMapValueTypeOperation {
66
- /**
67
- * String identifier of the operation type.
68
- */
69
- type: "act";
70
-
71
- /**
72
- * Map key being modified.
73
- */
74
- key: string;
75
-
76
- /**
77
- * Value of the operation, specific to the value type.
78
- * @alpha
79
- */
80
- value: IValueTypeOperationValue;
81
- }
82
-
83
- /**
84
- * Operation indicating a value should be set for a key.
85
- */
86
- export interface IMapSetOperation {
87
- /**
88
- * String identifier of the operation type.
89
- */
90
- type: "set";
91
-
92
- /**
93
- * Map key being modified.
94
- */
95
- key: string;
96
-
97
- /**
98
- * Value to be set on the key.
99
- */
100
- value: ISerializableValue;
101
- }
102
-
103
- /**
104
- * Operation indicating a key should be deleted from the map.
105
- */
106
- export interface IMapDeleteOperation {
107
- /**
108
- * String identifier of the operation type.
109
- */
110
- type: "delete";
111
-
112
- /**
113
- * Map key being modified.
114
- */
115
- key: string;
116
- }
117
-
118
- /**
119
- * Map key operations are one of several types.
120
- */
121
- export type IMapKeyOperation = IMapValueTypeOperation | IMapSetOperation | IMapDeleteOperation;
122
-
123
- /**
124
- * Operation indicating the map should be cleared.
125
- */
126
- export interface IMapClearOperation {
127
- /**
128
- * String identifier of the operation type.
129
- */
130
- type: "clear";
131
- }
132
-
133
- /**
134
- * Description of a map delta operation
135
- */
136
- export type IMapOperation = IMapKeyOperation | IMapClearOperation;
137
-
138
- /**
139
- * Defines the in-memory object structure to be used for the conversion to/from serialized.
140
- * Directly used in JSON.stringify, direct result from JSON.parse
141
- */
142
- export interface IMapDataObjectSerializable {
143
- [key: string]: ISerializableValue;
144
- }
145
-
146
- export interface IMapDataObjectSerialized {
147
- [key: string]: ISerializedValue;
148
- }
149
-
150
- /**
151
- * A SharedMap is a map-like distributed data structure.
152
- */
153
- export class MapKernel implements IValueTypeCreator {
154
- /**
155
- * The number of key/value pairs stored in the map.
156
- */
157
- public get size(): number {
158
- return this.data.size;
159
- }
160
-
161
- /**
162
- * Mapping of op types to message handlers.
163
- */
164
- private readonly messageHandlers: ReadonlyMap<string, IMapMessageHandler> = new Map();
165
-
166
- /**
167
- * The in-memory data the map is storing.
168
- */
169
- private readonly data = new Map<string, ILocalValue>();
170
-
171
- /**
172
- * Keys that have been modified locally but not yet ack'd from the server.
173
- */
174
- private readonly pendingKeys: Map<string, number> = new Map();
175
-
176
- /**
177
- * This is used to assign a unique id to every outgoing operation and helps in tracking unacked ops.
178
- */
179
- private pendingMessageId: number = -1;
180
-
181
- /**
182
- * If a clear has been performed locally but not yet ack'd from the server, then this stores the pending id
183
- * of that clear operation. Otherwise, is -1.
184
- */
185
- private pendingClearMessageId: number = -1;
186
-
187
- /**
188
- * Object to create encapsulations of the values stored in the map.
189
- */
190
- private readonly localValueMaker: LocalValueMaker;
191
-
192
- private lastProcessedSeq: number = -1;
193
-
194
- /**
195
- * Create a new shared map kernel.
196
- * @param serializer - The serializer to serialize / parse handles
197
- * @param handle - The handle of the shared object using the kernel
198
- * @param submitMessage - A callback to submit a message through the shared object
199
- * @param isAttached - To query whether the shared object should generate ops
200
- * @param valueTypes - The value types to register
201
- * @param eventEmitter - The object that will emit map events
202
- */
203
- constructor(
204
- private readonly serializer: IFluidSerializer,
205
- private readonly handle: IFluidHandle,
206
- private readonly submitMessage: (op: any, localOpMetadata: IMapMessageLocalMetadata) => void,
207
- private readonly isAttached: () => boolean,
208
- valueTypes: Readonly<IValueType<any>[]>,
209
- public readonly eventEmitter = new TypedEventEmitter<ISharedMapEvents>(),
210
- ) {
211
- this.localValueMaker = new LocalValueMaker(serializer);
212
- this.messageHandlers = this.getMessageHandlers();
213
- for (const type of valueTypes) {
214
- this.localValueMaker.registerValueType(type);
215
- }
216
- }
217
-
218
- /**
219
- * Get an iterator over the keys in this map.
220
- * @returns The iterator
221
- */
222
- public keys(): IterableIterator<string> {
223
- return this.data.keys();
224
- }
225
-
226
- /**
227
- * Get an iterator over the entries in this map.
228
- * @returns The iterator
229
- */
230
- public entries(): IterableIterator<[string, any]> {
231
- const localEntriesIterator = this.data.entries();
232
- const iterator = {
233
- next(): IteratorResult<[string, any]> {
234
- const nextVal = localEntriesIterator.next();
235
- if (nextVal.done) {
236
- return { value: undefined, done: true };
237
- } else {
238
- // Unpack the stored value
239
- return { value: [nextVal.value[0], nextVal.value[1].value], done: false };
240
- }
241
- },
242
- [Symbol.iterator]() {
243
- return this;
244
- },
245
- };
246
- return iterator;
247
- }
248
-
249
- /**
250
- * Get an iterator over the values in this map.
251
- * @returns The iterator
252
- */
253
- public values(): IterableIterator<any> {
254
- const localValuesIterator = this.data.values();
255
- const iterator = {
256
- next(): IteratorResult<any> {
257
- const nextVal = localValuesIterator.next();
258
- if (nextVal.done) {
259
- return { value: undefined, done: true };
260
- } else {
261
- // Unpack the stored value
262
- return { value: nextVal.value.value, done: false };
263
- }
264
- },
265
- [Symbol.iterator]() {
266
- return this;
267
- },
268
- };
269
- return iterator;
270
- }
271
-
272
- /**
273
- * Get an iterator over the entries in this map.
274
- * @returns The iterator
275
- */
276
- public [Symbol.iterator](): IterableIterator<[string, any]> {
277
- return this.entries();
278
- }
279
-
280
- /**
281
- * Executes the given callback on each entry in the map.
282
- * @param callbackFn - Callback function
283
- */
284
- public forEach(callbackFn: (value: any, key: string, map: Map<string, any>) => void): void {
285
- this.data.forEach((localValue, key, m) => {
286
- callbackFn(localValue.value, key, m);
287
- });
288
- }
289
-
290
- /**
291
- * {@inheritDoc ISharedMap.get}
292
- */
293
- public get<T = any>(key: string): T | undefined {
294
- if (!this.data.has(key)) {
295
- return undefined;
296
- }
297
-
298
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
299
- const localValue = this.data.get(key)!;
300
-
301
- return localValue.value as T;
302
- }
303
-
304
- /**
305
- * {@inheritDoc ISharedMap.wait}
306
- */
307
- public async wait<T = any>(key: string): Promise<T> {
308
- // Return immediately if the value already exists
309
- if (this.has(key)) {
310
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
311
- return this.get<T>(key)!;
312
- }
313
-
314
- // Otherwise subscribe to changes
315
- return new Promise<T>((resolve) => {
316
- const callback = (changed: IValueChanged) => {
317
- if (key === changed.key) {
318
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
319
- resolve(this.get<T>(changed.key)!);
320
- this.eventEmitter.removeListener("create", callback);
321
- }
322
- };
323
-
324
- this.eventEmitter.on("create", callback);
325
- });
326
- }
327
-
328
- /**
329
- * Check if a key exists in the map.
330
- * @param key - The key to check
331
- * @returns True if the key exists, false otherwise
332
- */
333
- public has(key: string): boolean {
334
- return this.data.has(key);
335
- }
336
-
337
- /**
338
- * {@inheritDoc ISharedMap.set}
339
- */
340
- public set(key: string, value: any) {
341
- // Undefined/null keys can't be serialized to JSON in the manner we currently snapshot.
342
- if (key === undefined || key === null) {
343
- throw new Error("Undefined and null keys are not supported");
344
- }
345
-
346
- // Create a local value and serialize it.
347
- const localValue = this.localValueMaker.fromInMemory(value);
348
- const serializableValue = makeSerializable(
349
- localValue,
350
- this.serializer,
351
- this.handle);
352
-
353
- // Set the value locally.
354
- this.setCore(
355
- key,
356
- localValue,
357
- true,
358
- undefined,
359
- );
360
-
361
- // If we are not attached, don't submit the op.
362
- if (!this.isAttached()) {
363
- return;
364
- }
365
-
366
- const op: IMapSetOperation = {
367
- key,
368
- type: "set",
369
- value: serializableValue,
370
- };
371
- this.submitMapKeyMessage(op);
372
- }
373
-
374
- /**
375
- * {@inheritDoc IValueTypeCreator.createValueType}
376
- */
377
- public createValueType(key: string, type: string, params: any) {
378
- // Create a local value and serialize it.
379
- const localValue = this.localValueMaker.makeValueType(type, this.makeMapValueOpEmitter(key), params);
380
-
381
- // TODO ideally we could use makeSerialized in this case as well. But the interval
382
- // collection has assumptions of attach being called prior. Given the IFluidSerializer it
383
- // may be possible to remove custom value type serialization entirely.
384
- const transformedValue = makeHandlesSerializable(
385
- params,
386
- this.serializer,
387
- this.handle);
388
-
389
- // Set the value locally.
390
- this.setCore(
391
- key,
392
- localValue,
393
- true,
394
- undefined,
395
- );
396
-
397
- // If we are not attached, don't submit the op.
398
- if (!this.isAttached()) {
399
- return this;
400
- }
401
-
402
- // This is a special form of serialized valuetype only used for set, containing info for initialization.
403
- // After initialization, the serialized form will need to come from the .store of the value type's factory.
404
- const serializableValue = { type, value: transformedValue };
405
- const op: IMapSetOperation = {
406
- key,
407
- type: "set",
408
- value: serializableValue,
409
- };
410
- this.submitMapKeyMessage(op);
411
- return this;
412
- }
413
-
414
- /**
415
- * Delete a key from the map.
416
- * @param key - Key to delete
417
- * @returns True if the key existed and was deleted, false if it did not exist
418
- */
419
- public delete(key: string): boolean {
420
- // Delete the key locally first.
421
- const successfullyRemoved = this.deleteCore(key, true, undefined);
422
-
423
- // If we are not attached, don't submit the op.
424
- if (!this.isAttached()) {
425
- return successfullyRemoved;
426
- }
427
-
428
- const op: IMapDeleteOperation = {
429
- key,
430
- type: "delete",
431
- };
432
- this.submitMapKeyMessage(op);
433
-
434
- return successfullyRemoved;
435
- }
436
-
437
- /**
438
- * Clear all data from the map.
439
- */
440
- public clear(): void {
441
- // Clear the data locally first.
442
- this.clearCore(true, undefined);
443
-
444
- // If we are not attached, don't submit the op.
445
- if (!this.isAttached()) {
446
- return;
447
- }
448
-
449
- const op: IMapClearOperation = {
450
- type: "clear",
451
- };
452
- this.submitMapClearMessage(op);
453
- }
454
-
455
- /**
456
- * Serializes the data stored in the shared map to a JSON string
457
- * @param serializer - The serializer to use to serialize handles in its values.
458
- * @returns A JSON string containing serialized map data
459
- */
460
- public getSerializedStorage(serializer: IFluidSerializer): IMapDataObjectSerialized {
461
- const serializableMapData: IMapDataObjectSerialized = {};
462
- this.data.forEach((localValue, key) => {
463
- serializableMapData[key] = localValue.makeSerialized(serializer, this.handle);
464
- });
465
- return serializableMapData;
466
- }
467
-
468
- public getSerializableStorage(serializer: IFluidSerializer): IMapDataObjectSerializable {
469
- const serializableMapData: IMapDataObjectSerializable = {};
470
- this.data.forEach((localValue, key) => {
471
- serializableMapData[key] = makeSerializable(localValue, serializer, this.handle);
472
- });
473
- return serializableMapData;
474
- }
475
-
476
- public serialize(serializer: IFluidSerializer): string {
477
- return JSON.stringify(this.getSerializableStorage(serializer));
478
- }
479
-
480
- /**
481
- * Populate the kernel with the given map data.
482
- * @param data - A JSON string containing serialized map data
483
- */
484
- public populateFromSerializable(json: IMapDataObjectSerializable): void {
485
- for (const [key, serializable] of Object.entries(json)) {
486
- const localValue = {
487
- key,
488
- value: this.makeLocal(key, serializable),
489
- };
490
-
491
- this.data.set(localValue.key, localValue.value);
492
- }
493
- }
494
-
495
- public populate(json: string): void {
496
- this.populateFromSerializable(JSON.parse(json) as IMapDataObjectSerializable);
497
- }
498
-
499
- /**
500
- * Submit the given op if a handler is registered.
501
- * @param op - The operation to attempt to submit
502
- * @param localOpMetadata - The local metadata associated with the op. This is kept locally by the runtime
503
- * and not sent to the server. This will be sent back when this message is received back from the server. This is
504
- * also sent if we are asked to resubmit the message.
505
- * @returns True if the operation was submitted, false otherwise.
506
- */
507
- public trySubmitMessage(op: any, localOpMetadata: unknown): boolean {
508
- const type: string = op.type;
509
- if (this.messageHandlers.has(type)) {
510
- const mapLocalMetadata: Partial<IMapMessageLocalMetadata> = localOpMetadata;
511
- // we don't know how to rebase these operations, so if any other op has come in
512
- // we will fail.
513
- if (this.lastProcessedSeq !== mapLocalMetadata?.lastProcessedSeq) {
514
- throw new Error("SharedInterval does not support reconnect in presence of external changes");
515
- }
516
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
517
- this.messageHandlers.get(type)!.submit(op as IMapOperation);
518
- return true;
519
- }
520
- return false;
521
- }
522
-
523
- public tryGetStashedOpLocalMetadata(op: any): unknown {
524
- const type: string = op.type;
525
- if (this.messageHandlers.has(type)) {
526
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
527
- return this.messageHandlers.get(type)!.getStashedOpLocalMetadata(op as IMapOperation);
528
- }
529
- throw new Error("no apply stashed op handler");
530
- }
531
-
532
- /**
533
- * Process the given op if a handler is registered.
534
- * @param message - The message to process
535
- * @param local - Whether the message originated from the local client
536
- * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
537
- * For messages from a remote client, this will be undefined.
538
- * @returns True if the operation was processed, false otherwise.
539
- */
540
- public tryProcessMessage(
541
- op: IMapOperation,
542
- local: boolean,
543
- message: ISequencedDocumentMessage | undefined,
544
- localOpMetadata: unknown,
545
- ): boolean {
546
- // track the seq of every incoming message, so we can detect if any
547
- // changes happened during a resubmit
548
- this.lastProcessedSeq = message.sequenceNumber;
549
- if (this.messageHandlers.has(op.type)) {
550
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
551
- this.messageHandlers
552
- .get(op.type)!
553
- .process(op, local, message, localOpMetadata as IMapMessageLocalMetadata);
554
- return true;
555
- }
556
- return false;
557
- }
558
-
559
- /**
560
- * Set implementation used for both locally sourced sets as well as incoming remote sets.
561
- * @param key - The key being set
562
- * @param value - The value being set
563
- * @param local - Whether the message originated from the local client
564
- * @param op - The message if from a remote set, or null if from a local set
565
- */
566
- private setCore(key: string, value: ILocalValue, local: boolean, op: ISequencedDocumentMessage | undefined): void {
567
- const previousValue = this.get(key);
568
- this.data.set(key, value);
569
- const event: IValueChanged = { key, previousValue };
570
- this.eventEmitter.emit("create", event, local, op, this.eventEmitter);
571
- }
572
-
573
- /**
574
- * Clear implementation used for both locally sourced clears as well as incoming remote clears.
575
- * @param local - Whether the message originated from the local client
576
- * @param op - The message if from a remote clear, or null if from a local clear
577
- */
578
- private clearCore(local: boolean, op: ISequencedDocumentMessage | undefined): void {
579
- this.data.clear();
580
- this.eventEmitter.emit("clear", local, op, this.eventEmitter);
581
- }
582
-
583
- /**
584
- * Delete implementation used for both locally sourced deletes as well as incoming remote deletes.
585
- * @param key - The key being deleted
586
- * @param local - Whether the message originated from the local client
587
- * @param op - The message if from a remote delete, or null if from a local delete
588
- * @returns True if the key existed and was deleted, false if it did not exist
589
- */
590
- private deleteCore(key: string, local: boolean, op: ISequencedDocumentMessage | undefined): boolean {
591
- const previousValue = this.get(key);
592
- const successfullyRemoved = this.data.delete(key);
593
- if (successfullyRemoved) {
594
- const event: IValueChanged = { key, previousValue };
595
- this.eventEmitter.emit("valueChanged", event, local, op, this.eventEmitter);
596
- }
597
- return successfullyRemoved;
598
- }
599
-
600
- /**
601
- * Clear all keys in memory in response to a remote clear, but retain keys we have modified but not yet been ack'd.
602
- */
603
- private clearExceptPendingKeys(): void {
604
- // Assuming the pendingKeys is small and the map is large
605
- // we will get the value for the pendingKeys and clear the map
606
- const temp = new Map<string, ILocalValue>();
607
- this.pendingKeys.forEach((value, key) => {
608
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
609
- temp.set(key, this.data.get(key)!);
610
- });
611
- this.data.clear();
612
- temp.forEach((value, key) => {
613
- this.data.set(key, value);
614
- });
615
- }
616
-
617
- /**
618
- * The remote ISerializableValue we're receiving (either as a result of a load or an incoming set op) will
619
- * have the information we need to create a real object, but will not be the real object yet. For example,
620
- * we might know it's a map and the map's ID but not have the actual map or its data yet. makeLocal's
621
- * job is to convert that information into a real object for local usage.
622
- * @param key - The key that the caller intends to store the local value into (used for ops later). But
623
- * doesn't actually store the local value into that key. So better not lie!
624
- * @param serializable - The remote information that we can convert into a real object
625
- * @returns The local value that was produced
626
- */
627
- private makeLocal(key: string, serializable: ISerializableValue): ILocalValue {
628
- if (serializable.type === ValueType[ValueType.Plain] || serializable.type === ValueType[ValueType.Shared]) {
629
- return this.localValueMaker.fromSerializable(serializable);
630
- } else {
631
- return this.localValueMaker.fromSerializableValueType(
632
- serializable,
633
- this.makeMapValueOpEmitter(key),
634
- );
635
- }
636
- }
637
-
638
- /**
639
- * If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
640
- * not process the incoming operation.
641
- * @param op - Operation to check
642
- * @param local - Whether the message originated from the local client
643
- * @param message - The message
644
- * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
645
- * For messages from a remote client, this will be undefined.
646
- * @returns True if the operation should be processed, false otherwise
647
- */
648
- private needProcessKeyOperation(
649
- op: IMapKeyOperation,
650
- local: boolean,
651
- localOpMetadata: IMapMessageLocalMetadata,
652
- ): boolean {
653
- if (this.pendingClearMessageId !== -1) {
654
- if (local) {
655
- assert(localOpMetadata?.pendingClearMessageId !== undefined
656
- && localOpMetadata.pendingClearMessageId < this.pendingClearMessageId,
657
- 0x1f1 /* "Received out of order op when there is an unacked clear message" */);
658
- }
659
- // If we have an unacked clear, we can ignore all ops.
660
- return false;
661
- }
662
-
663
- if (this.pendingKeys.has(op.key)) {
664
- // Found an unacked op. Clear it from the map if the pendingMessageId in the map matches this message's
665
- // and don't process the op.
666
- if (local) {
667
- assert(localOpMetadata !== undefined,
668
- 0x1f2 /* `pendingMessageId is missing from the local client's ${op.type} operation` */);
669
- const pendingMessageId = localOpMetadata.pendingMessageId;
670
- const pendingKeyMessageId = this.pendingKeys.get(op.key);
671
- if (pendingKeyMessageId === pendingMessageId) {
672
- this.pendingKeys.delete(op.key);
673
- }
674
- }
675
- return false;
676
- }
677
-
678
- // If we don't have a NACK op on the key, we need to process the remote ops.
679
- return !local;
680
- }
681
-
682
- /**
683
- * Get the message handlers for the map.
684
- * @returns A map of string op names to IMapMessageHandlers for those ops
685
- */
686
- private getMessageHandlers() {
687
- const messageHandlers = new Map<string, IMapMessageHandler>();
688
- messageHandlers.set(
689
- "clear",
690
- {
691
- process: (op: IMapClearOperation, local, message, localOpMetadata) => {
692
- if (local) {
693
- assert(localOpMetadata !== undefined,
694
- 0x1f3 /* "pendingMessageId is missing from the local client's clear operation" */);
695
- const pendingMessageId = localOpMetadata?.pendingMessageId;
696
- if (this.pendingClearMessageId === pendingMessageId) {
697
- this.pendingClearMessageId = -1;
698
- }
699
- return;
700
- }
701
- if (this.pendingKeys.size !== 0) {
702
- this.clearExceptPendingKeys();
703
- return;
704
- }
705
- this.clearCore(local, message);
706
- },
707
- submit: (op: IMapClearOperation) => {
708
- // We don't reuse the metadata but send a new one on each submit.
709
- this.submitMapClearMessage(op);
710
- },
711
- getStashedOpLocalMetadata: (op: IMapClearOperation) => {
712
- // We don't reuse the metadata but send a new one on each submit.
713
- return this.getMapClearMessageLocalMetadata(op);
714
- },
715
- });
716
- messageHandlers.set(
717
- "delete",
718
- {
719
- process: (op: IMapDeleteOperation, local, message, localOpMetadata) => {
720
- if (!this.needProcessKeyOperation(op, local, localOpMetadata)) {
721
- return;
722
- }
723
- this.deleteCore(op.key, local, message);
724
- },
725
- submit: (op: IMapDeleteOperation) => {
726
- // We don't reuse the metadata but send a new one on each submit.
727
- this.submitMapKeyMessage(op);
728
- },
729
- getStashedOpLocalMetadata: (op: IMapDeleteOperation) => {
730
- // We don't reuse the metadata but send a new one on each submit.
731
- return this.getMapKeyMessageLocalMetadata(op);
732
- },
733
- });
734
- messageHandlers.set(
735
- "set",
736
- {
737
- process: (op: IMapSetOperation, local, message, localOpMetadata) => {
738
- if (!this.needProcessKeyOperation(op, local, localOpMetadata)) {
739
- return;
740
- }
741
-
742
- // needProcessKeyOperation should have returned false if local is true
743
- const context = this.makeLocal(op.key, op.value);
744
- this.setCore(op.key, context, local, message);
745
- },
746
- submit: (op: IMapSetOperation) => {
747
- // We don't reuse the metadata but send a new one on each submit.
748
- this.submitMapKeyMessage(op);
749
- },
750
- getStashedOpLocalMetadata: (op: IMapSetOperation) => {
751
- // We don't reuse the metadata but send a new one on each submit.
752
- return this.getMapKeyMessageLocalMetadata(op);
753
- },
754
- });
755
-
756
- // Ops with type "act" describe actions taken by custom value type handlers of whatever item is
757
- // being addressed. These custom handlers can be retrieved from the ValueTypeLocalValue which has
758
- // stashed its valueType (and therefore its handlers). We also emit a valueChanged for anyone
759
- // watching for manipulations of that item.
760
- messageHandlers.set(
761
- "act",
762
- {
763
- process: (op: IMapValueTypeOperation, local, message, localOpMetadata) => {
764
- // Local value might not exist if we deleted it
765
- const localValue = this.data.get(op.key) as ValueTypeLocalValue;
766
- if (!localValue) {
767
- return;
768
- }
769
-
770
- const handler = localValue.getOpHandler(op.value.opName);
771
- const previousValue = localValue.value;
772
- const translatedValue = parseHandles(
773
- op.value.value,
774
- this.serializer);
775
- handler.process(previousValue, translatedValue, local, message);
776
- const event: IValueChanged = { key: op.key, previousValue };
777
- this.eventEmitter.emit("valueChanged", event, local, message, this.eventEmitter);
778
- },
779
- submit: (op: IMapValueTypeOperation) => {
780
- this.submitMessage(op, { lastProcessedSeq: this.lastProcessedSeq });
781
- },
782
- getStashedOpLocalMetadata: (op: IMapValueTypeOperation) => {
783
- assert(false, 0x016 /* "apply stashed op not implemented for custom value type ops" */);
784
- },
785
- });
786
-
787
- return messageHandlers;
788
- }
789
-
790
- private getMapClearMessageLocalMetadata(op: IMapClearOperation): number {
791
- const pendingMessageId = ++this.pendingMessageId;
792
- this.pendingClearMessageId = pendingMessageId;
793
- return pendingMessageId;
794
- }
795
-
796
- /**
797
- * Submit a clear message to remote clients.
798
- * @param op - The clear message
799
- */
800
- private submitMapClearMessage(op: IMapClearOperation): void {
801
- const pendingClearMessageId = this.getMapClearMessageLocalMetadata(op);
802
- this.submitMessage(op, { pendingClearMessageId, lastProcessedSeq: this.lastProcessedSeq });
803
- }
804
-
805
- private getMapKeyMessageLocalMetadata(op: IMapKeyOperation): number {
806
- const pendingMessageId = ++this.pendingMessageId;
807
- this.pendingKeys.set(op.key, pendingMessageId);
808
- return pendingMessageId;
809
- }
810
-
811
- /**
812
- * Submit a map key message to remote clients.
813
- * @param op - The map key message
814
- */
815
- private submitMapKeyMessage(op: IMapKeyOperation): void {
816
- const pendingMessageId = this.getMapKeyMessageLocalMetadata(op);
817
- this.submitMessage(op, { pendingMessageId, lastProcessedSeq: this.lastProcessedSeq });
818
- }
819
-
820
- /**
821
- * Create an emitter for a value type to emit ops from the given key.
822
- * @alpha
823
- * @param key - The key of the map that the value type will be stored on
824
- * @returns A value op emitter for the given key
825
- */
826
- private makeMapValueOpEmitter(key: string): IValueOpEmitter {
827
- const emit = (opName: string, previousValue: any, params: any) => {
828
- const translatedParams = makeHandlesSerializable(
829
- params,
830
- this.serializer,
831
- this.handle);
832
-
833
- const op: IMapValueTypeOperation = {
834
- key,
835
- type: "act",
836
- value: {
837
- opName,
838
- value: translatedParams,
839
- },
840
- };
841
- // Send the localOpMetadata as undefined because we don't care about the ack.
842
- this.submitMessage(op, { lastProcessedSeq: this.lastProcessedSeq });
843
-
844
- const event: IValueChanged = { key, previousValue };
845
- this.eventEmitter.emit("valueChanged", event, true, null, this.eventEmitter);
846
- };
847
-
848
- return { emit };
849
- }
850
- }