@fluidframework/legacy-dds 2.50.0-345060

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 (140) hide show
  1. package/.eslintrc.cjs +11 -0
  2. package/.mocharc.cjs +12 -0
  3. package/CHANGELOG.md +13 -0
  4. package/LICENSE +21 -0
  5. package/api-extractor/api-extractor-lint-bundle.json +5 -0
  6. package/api-extractor/api-extractor-lint-legacy.cjs.json +5 -0
  7. package/api-extractor/api-extractor-lint-legacy.esm.json +5 -0
  8. package/api-extractor/api-extractor-lint-public.cjs.json +5 -0
  9. package/api-extractor/api-extractor-lint-public.esm.json +5 -0
  10. package/api-extractor/api-extractor.current.json +5 -0
  11. package/api-extractor/api-extractor.legacy.json +4 -0
  12. package/api-extractor-lint.json +4 -0
  13. package/api-extractor.json +4 -0
  14. package/api-report/legacy-dds.beta.api.md +9 -0
  15. package/api-report/legacy-dds.legacy.alpha.api.md +140 -0
  16. package/api-report/legacy-dds.legacy.public.api.md +9 -0
  17. package/api-report/legacy-dds.public.api.md +9 -0
  18. package/biome.jsonc +4 -0
  19. package/dist/array/index.d.ts +10 -0
  20. package/dist/array/index.d.ts.map +1 -0
  21. package/dist/array/index.js +16 -0
  22. package/dist/array/index.js.map +1 -0
  23. package/dist/array/interfaces.d.ts +142 -0
  24. package/dist/array/interfaces.d.ts.map +1 -0
  25. package/dist/array/interfaces.js +7 -0
  26. package/dist/array/interfaces.js.map +1 -0
  27. package/dist/array/sharedArray.d.ts +175 -0
  28. package/dist/array/sharedArray.d.ts.map +1 -0
  29. package/dist/array/sharedArray.js +652 -0
  30. package/dist/array/sharedArray.js.map +1 -0
  31. package/dist/array/sharedArrayFactory.d.ts +31 -0
  32. package/dist/array/sharedArrayFactory.d.ts.map +1 -0
  33. package/dist/array/sharedArrayFactory.js +61 -0
  34. package/dist/array/sharedArrayFactory.js.map +1 -0
  35. package/dist/array/sharedArrayOperations.d.ts +77 -0
  36. package/dist/array/sharedArrayOperations.d.ts.map +1 -0
  37. package/dist/array/sharedArrayOperations.js +19 -0
  38. package/dist/array/sharedArrayOperations.js.map +1 -0
  39. package/dist/array/sharedArrayRevertible.d.ts +17 -0
  40. package/dist/array/sharedArrayRevertible.d.ts.map +1 -0
  41. package/dist/array/sharedArrayRevertible.js +47 -0
  42. package/dist/array/sharedArrayRevertible.js.map +1 -0
  43. package/dist/index.d.ts +14 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +20 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/legacy.d.ts +31 -0
  48. package/dist/package.json +4 -0
  49. package/dist/packageVersion.d.ts +9 -0
  50. package/dist/packageVersion.d.ts.map +1 -0
  51. package/dist/packageVersion.js +12 -0
  52. package/dist/packageVersion.js.map +1 -0
  53. package/dist/public.d.ts +12 -0
  54. package/dist/signal/index.d.ts +7 -0
  55. package/dist/signal/index.d.ts.map +1 -0
  56. package/dist/signal/index.js +11 -0
  57. package/dist/signal/index.js.map +1 -0
  58. package/dist/signal/interfaces.d.ts +36 -0
  59. package/dist/signal/interfaces.d.ts.map +1 -0
  60. package/dist/signal/interfaces.js +7 -0
  61. package/dist/signal/interfaces.js.map +1 -0
  62. package/dist/signal/sharedSignal.d.ts +68 -0
  63. package/dist/signal/sharedSignal.d.ts.map +1 -0
  64. package/dist/signal/sharedSignal.js +122 -0
  65. package/dist/signal/sharedSignal.js.map +1 -0
  66. package/dist/signal/sharedSignalFactory.d.ts +24 -0
  67. package/dist/signal/sharedSignalFactory.d.ts.map +1 -0
  68. package/dist/signal/sharedSignalFactory.js +45 -0
  69. package/dist/signal/sharedSignalFactory.js.map +1 -0
  70. package/internal.d.ts +11 -0
  71. package/legacy.d.ts +11 -0
  72. package/lib/array/index.d.ts +10 -0
  73. package/lib/array/index.d.ts.map +1 -0
  74. package/lib/array/index.js +8 -0
  75. package/lib/array/index.js.map +1 -0
  76. package/lib/array/interfaces.d.ts +142 -0
  77. package/lib/array/interfaces.d.ts.map +1 -0
  78. package/lib/array/interfaces.js +6 -0
  79. package/lib/array/interfaces.js.map +1 -0
  80. package/lib/array/sharedArray.d.ts +175 -0
  81. package/lib/array/sharedArray.d.ts.map +1 -0
  82. package/lib/array/sharedArray.js +648 -0
  83. package/lib/array/sharedArray.js.map +1 -0
  84. package/lib/array/sharedArrayFactory.d.ts +31 -0
  85. package/lib/array/sharedArrayFactory.d.ts.map +1 -0
  86. package/lib/array/sharedArrayFactory.js +56 -0
  87. package/lib/array/sharedArrayFactory.js.map +1 -0
  88. package/lib/array/sharedArrayOperations.d.ts +77 -0
  89. package/lib/array/sharedArrayOperations.d.ts.map +1 -0
  90. package/lib/array/sharedArrayOperations.js +16 -0
  91. package/lib/array/sharedArrayOperations.js.map +1 -0
  92. package/lib/array/sharedArrayRevertible.d.ts +17 -0
  93. package/lib/array/sharedArrayRevertible.d.ts.map +1 -0
  94. package/lib/array/sharedArrayRevertible.js +43 -0
  95. package/lib/array/sharedArrayRevertible.js.map +1 -0
  96. package/lib/index.d.ts +14 -0
  97. package/lib/index.d.ts.map +1 -0
  98. package/lib/index.js +10 -0
  99. package/lib/index.js.map +1 -0
  100. package/lib/legacy.d.ts +31 -0
  101. package/lib/packageVersion.d.ts +9 -0
  102. package/lib/packageVersion.d.ts.map +1 -0
  103. package/lib/packageVersion.js +9 -0
  104. package/lib/packageVersion.js.map +1 -0
  105. package/lib/public.d.ts +12 -0
  106. package/lib/signal/index.d.ts +7 -0
  107. package/lib/signal/index.d.ts.map +1 -0
  108. package/lib/signal/index.js +6 -0
  109. package/lib/signal/index.js.map +1 -0
  110. package/lib/signal/interfaces.d.ts +36 -0
  111. package/lib/signal/interfaces.d.ts.map +1 -0
  112. package/lib/signal/interfaces.js +6 -0
  113. package/lib/signal/interfaces.js.map +1 -0
  114. package/lib/signal/sharedSignal.d.ts +68 -0
  115. package/lib/signal/sharedSignal.d.ts.map +1 -0
  116. package/lib/signal/sharedSignal.js +118 -0
  117. package/lib/signal/sharedSignal.js.map +1 -0
  118. package/lib/signal/sharedSignalFactory.d.ts +24 -0
  119. package/lib/signal/sharedSignalFactory.d.ts.map +1 -0
  120. package/lib/signal/sharedSignalFactory.js +41 -0
  121. package/lib/signal/sharedSignalFactory.js.map +1 -0
  122. package/lib/tsdoc-metadata.json +11 -0
  123. package/package.json +158 -0
  124. package/src/array/README.md +32 -0
  125. package/src/array/index.ts +28 -0
  126. package/src/array/interfaces.ts +169 -0
  127. package/src/array/sharedArray.ts +835 -0
  128. package/src/array/sharedArrayFactory.ts +88 -0
  129. package/src/array/sharedArrayOperations.ts +89 -0
  130. package/src/array/sharedArrayRevertible.ts +50 -0
  131. package/src/index.ts +32 -0
  132. package/src/packageVersion.ts +9 -0
  133. package/src/signal/README.md +25 -0
  134. package/src/signal/index.ts +12 -0
  135. package/src/signal/interfaces.ts +53 -0
  136. package/src/signal/sharedSignal.ts +169 -0
  137. package/src/signal/sharedSignalFactory.ts +62 -0
  138. package/tsconfig.cjs.json +7 -0
  139. package/tsconfig.json +10 -0
  140. package/tsdoc.json +4 -0
@@ -0,0 +1,648 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { assert } from "@fluidframework/core-utils/internal";
6
+ import { FileMode, MessageType, TreeEntry } from "@fluidframework/driver-definitions/internal";
7
+ import { convertToSummaryTreeWithStats } from "@fluidframework/runtime-utils/internal";
8
+ import { SharedObject } from "@fluidframework/shared-object-base/internal";
9
+ import { v4 as uuid } from "uuid";
10
+ import { SharedArrayFactory } from "./sharedArrayFactory.js";
11
+ import { OperationType } from "./sharedArrayOperations.js";
12
+ import { SharedArrayRevertible } from "./sharedArrayRevertible.js";
13
+ const snapshotFileName = "header";
14
+ /**
15
+ * Represents a shared array that allows communication between distributed clients.
16
+ *
17
+ * @internal
18
+ */
19
+ export class SharedArrayClass extends SharedObject {
20
+ /**
21
+ * Create a new shared array
22
+ *
23
+ * @param runtime - data store runtime the new shared array belongs to
24
+ * @param id - optional name of the shared array
25
+ * @returns newly create shared array (but not attached yet)
26
+ */
27
+ static create(runtime, id) {
28
+ return runtime.createChannel(id, SharedArrayFactory.Type);
29
+ }
30
+ /**
31
+ * Get a factory for SharedArray to register with the data store.
32
+ *
33
+ * @returns a factory that creates and load SharedArray
34
+ */
35
+ static getFactory() {
36
+ return new SharedArrayFactory();
37
+ }
38
+ /**
39
+ * Constructs a new shared array. If the object is non-local an id and service interfaces will
40
+ * be provided
41
+ *
42
+ * @param id - optional name of the shared array
43
+ * @param runtime - data store runtime the shared array belongs to
44
+ * @param attributes - represents the attributes of a channel/DDS.
45
+ */
46
+ constructor(id, runtime, attributes) {
47
+ super(id, runtime, attributes, "loop_sharedArray_" /* telemetryContextPrefix */);
48
+ this.sharedArray = [];
49
+ this.idToEntryMap = new Map();
50
+ }
51
+ /**
52
+ * Method that returns the ordered list of the items held in the DDS at this point in time.
53
+ * Note: This is only a snapshot of the array
54
+ */
55
+ get() {
56
+ return this.sharedArray.filter((item) => !item.isDeleted).map((entry) => entry.value);
57
+ }
58
+ summarizeCore(serializer) {
59
+ // Deep copy and unset the local flags. Needed when snapshotting is happening for runtime not attached
60
+ const dataArrayCopy = [];
61
+ for (const entry of this.sharedArray) {
62
+ dataArrayCopy.push({
63
+ entryId: entry.entryId,
64
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
65
+ value: JSON.parse(serializer.stringify(entry.value, this.handle)),
66
+ isDeleted: entry.isDeleted,
67
+ prevEntryId: entry.prevEntryId,
68
+ nextEntryId: entry.nextEntryId,
69
+ });
70
+ }
71
+ // We are snapshotting current client data so autoacking pending local.
72
+ // Assumption : This should happen only for offline client creating the array. All other scenarios should
73
+ // get to MSN - where there can be no local pending possible.
74
+ for (const entry of this.sharedArray) {
75
+ this.unsetLocalFlags(entry);
76
+ }
77
+ const contents = {
78
+ dataArray: dataArrayCopy,
79
+ };
80
+ const tree = {
81
+ entries: [
82
+ {
83
+ mode: FileMode.File,
84
+ path: snapshotFileName,
85
+ type: TreeEntry[TreeEntry.Blob],
86
+ value: {
87
+ contents: serializer.stringify(contents, this.handle),
88
+ // eslint-disable-next-line unicorn/text-encoding-identifier-case
89
+ encoding: "utf-8",
90
+ },
91
+ },
92
+ ],
93
+ };
94
+ const summaryTreeWithStats = convertToSummaryTreeWithStats(tree);
95
+ return summaryTreeWithStats;
96
+ }
97
+ insertBulkAfter(ref, values) {
98
+ let itemIndex = 0;
99
+ if (ref !== undefined) {
100
+ for (itemIndex = this.sharedArray.length - 1; itemIndex > 0; itemIndex -= 1) {
101
+ const item = this.sharedArray[itemIndex];
102
+ if (item && !item.isDeleted && item.value === ref) {
103
+ break;
104
+ }
105
+ }
106
+ // Add one since we're inserting it after this rowId. If rowId is not found, we will get -1, which after
107
+ // adding one, will be 0, which will place the new rows at the right place too
108
+ itemIndex += 1;
109
+ }
110
+ // Insert new elements
111
+ for (const value of values) {
112
+ this.insertCore(itemIndex, value);
113
+ itemIndex += 1;
114
+ }
115
+ }
116
+ insert(index, value) {
117
+ if (index < 0) {
118
+ throw new Error("Invalid input: Insertion index provided is less than 0.");
119
+ }
120
+ this.insertCore(this.findInternalInsertionIndex(index), value);
121
+ }
122
+ insertCore(indexInternal, value) {
123
+ const insertAfterEntryId = indexInternal >= 1 ? this.sharedArray[indexInternal - 1]?.entryId : undefined;
124
+ const newEntryId = this.createAddEntry(indexInternal, value);
125
+ const op = {
126
+ type: OperationType.insertEntry,
127
+ entryId: newEntryId,
128
+ value,
129
+ insertAfterEntryId,
130
+ };
131
+ this.emitValueChangedEvent(op, true /* isLocal */);
132
+ this.emitRevertibleEvent(op);
133
+ // If we are not attached, don't submit the op.
134
+ if (!this.isAttached()) {
135
+ return;
136
+ }
137
+ this.submitLocalMessage(op);
138
+ }
139
+ delete(index) {
140
+ if (index < 0) {
141
+ throw new Error("Invalid input: Deletion index provided is less than 0.");
142
+ }
143
+ const indexInternal = this.findInternalDeletionIndex(index);
144
+ const entry = this.sharedArray[indexInternal];
145
+ assert(entry !== undefined, 0xb90 /* Invalid index */);
146
+ const entryId = entry.entryId;
147
+ this.deleteCore(indexInternal);
148
+ const op = {
149
+ type: OperationType.deleteEntry,
150
+ entryId,
151
+ };
152
+ this.emitValueChangedEvent(op, true /* isLocal */);
153
+ this.emitRevertibleEvent(op);
154
+ // If we are not attached, don't submit the op.
155
+ if (!this.isAttached()) {
156
+ return;
157
+ }
158
+ this.submitLocalMessage(op);
159
+ }
160
+ rearrangeToFront(values) {
161
+ for (let toIndex = 0; toIndex < values.length; toIndex += 1) {
162
+ const value = values[toIndex];
163
+ // Can skip searching first <toIndex> indices, as they contain elements we already moved.
164
+ for (let fromIndex = toIndex; fromIndex < this.sharedArray.length; fromIndex += 1) {
165
+ const item = this.sharedArray[fromIndex];
166
+ assert(item !== undefined, 0xb91 /* Invalid index */);
167
+ if (item.value !== value) {
168
+ continue;
169
+ }
170
+ if (!item.isDeleted &&
171
+ // Moving to and from the same index makes no sense, so noOp
172
+ fromIndex !== toIndex &&
173
+ // Moving the same entry from current location to its immediate next makes no sense so noOp
174
+ toIndex !== fromIndex + 1) {
175
+ this.moveCore(fromIndex, toIndex);
176
+ }
177
+ break;
178
+ }
179
+ }
180
+ }
181
+ /**
182
+ * Moves the DDS entry from one index to another
183
+ *
184
+ * @param fromIndex - User index of the element to be moved
185
+ * @param toIndex - User index to which the element should move to
186
+ */
187
+ move(fromIndex, toIndex) {
188
+ if (fromIndex < 0) {
189
+ throw new Error("Invalid input: fromIndex value provided is less than 0");
190
+ }
191
+ if (toIndex < 0) {
192
+ throw new Error("Invalid input: toIndex value provided is less than 0");
193
+ }
194
+ if (
195
+ // Moving to and from the same index makes no sense, so noOp
196
+ fromIndex === toIndex ||
197
+ // Moving the same entry from current location to its immediate next makes no sense so noOp
198
+ toIndex === fromIndex + 1) {
199
+ return;
200
+ }
201
+ const fromIndexInternal = this.findInternalDeletionIndex(fromIndex);
202
+ const toIndexInternal = this.findInternalInsertionIndex(toIndex);
203
+ this.moveCore(fromIndexInternal, toIndexInternal);
204
+ }
205
+ moveCore(fromIndexInternal, toIndexInternal) {
206
+ const insertAfterEntryId = toIndexInternal >= 1 ? this.sharedArray[toIndexInternal - 1]?.entryId : undefined;
207
+ const entryId = this.sharedArray[fromIndexInternal]?.entryId;
208
+ assert(entryId !== undefined, 0xb92 /* Invalid index */);
209
+ const changedToEntryId = this.createMoveEntry(fromIndexInternal, toIndexInternal);
210
+ const op = {
211
+ type: OperationType.moveEntry,
212
+ entryId,
213
+ insertAfterEntryId,
214
+ changedToEntryId,
215
+ };
216
+ this.emitValueChangedEvent(op, true /* isLocal */);
217
+ this.emitRevertibleEvent(op);
218
+ // If we are not attached, don't submit the op.
219
+ if (!this.isAttached()) {
220
+ return;
221
+ }
222
+ this.submitLocalMessage(op);
223
+ }
224
+ /**
225
+ * Method used to do undo/redo operation for the given entry id. This method is
226
+ * used for undo/redo of only insert and delete operations. Move operation is NOT handled
227
+ * by this method
228
+ *
229
+ * @param entryId - Entry Id for which the the undo/redo operation is to be applied
230
+ */
231
+ toggle(entryId) {
232
+ const liveEntry = this.getLiveEntry(entryId);
233
+ const isDeleted = !liveEntry.isDeleted;
234
+ // Adding local pending counter
235
+ this.getEntryForId(entryId).isLocalPendingDelete += 1;
236
+ // Toggling the isDeleted flag to undo the last operation for the skip list payload/value
237
+ liveEntry.isDeleted = isDeleted;
238
+ const op = {
239
+ type: OperationType.toggle,
240
+ entryId,
241
+ isDeleted,
242
+ };
243
+ this.emitValueChangedEvent(op, true /* isLocal */);
244
+ this.emitRevertibleEvent(op);
245
+ // If we are not attached, don't submit the op.
246
+ if (!this.isAttached()) {
247
+ return;
248
+ }
249
+ this.submitLocalMessage(op);
250
+ }
251
+ /**
252
+ * Method to do undo/redo of move operation. All entries of the same payload/value are stored
253
+ * in the same doubly linked skip list. This skip list is updated upon every move by adding the
254
+ * new location as a new entry in the skip list and update the isDeleted flag to indicate the new
255
+ * entry is the cuurent live location for the user.
256
+ *
257
+ * @param oldEntryId - EntryId of the last live entry
258
+ * @param newEntryId - EntryId of the to be live entry
259
+ */
260
+ toggleMove(oldEntryId, newEntryId) {
261
+ if (this.getEntryForId(newEntryId).isDeleted) {
262
+ return;
263
+ }
264
+ // Adding local pending counter
265
+ this.getEntryForId(oldEntryId).isLocalPendingMove += 1;
266
+ this.updateLiveEntry(newEntryId, oldEntryId);
267
+ const op = {
268
+ type: OperationType.toggleMove,
269
+ entryId: oldEntryId,
270
+ changedToEntryId: newEntryId,
271
+ };
272
+ this.emitValueChangedEvent(op, true /* isLocal */);
273
+ this.emitRevertibleEvent(op);
274
+ // If we are not attached, don't submit the op.
275
+ if (!this.isAttached()) {
276
+ return;
277
+ }
278
+ this.submitLocalMessage(op);
279
+ }
280
+ /**
281
+ * Load share array from snapshot
282
+ *
283
+ * @param storage - the storage to get the snapshot from
284
+ * @returns - promise that resolved when the load is completed
285
+ */
286
+ async loadCore(storage) {
287
+ const header = await storage.readBlob(snapshotFileName);
288
+ // eslint-disable-next-line unicorn/text-encoding-identifier-case
289
+ const utf8 = new TextDecoder("utf-8").decode(header);
290
+ // Note: IFluidSerializer.parse() doesn't guarantee any typing; the explicit typing here is based on this code's
291
+ // knowledge of what it is deserializing.
292
+ const deserializedSharedArray = this.serializer.parse(utf8);
293
+ this.sharedArray = deserializedSharedArray.dataArray;
294
+ // Initializing the idToEntryMap optimizer data set
295
+ for (const entry of this.sharedArray) {
296
+ this.idToEntryMap.set(entry.entryId, entry);
297
+ this.unsetLocalFlags(entry);
298
+ }
299
+ }
300
+ /**
301
+ * Callback on disconnect
302
+ */
303
+ onDisconnect() { }
304
+ /**
305
+ * Tracks the doubly linked skip list for the given entry to identify local pending counter attribute.
306
+ * It signifies if a local pending operation exists for the payload/value being tracked in the skip list
307
+ *
308
+ * returns true if counterAttribute's count \> 0
309
+ * @param entryId - id for which counter attribute is to be tracked in chian.
310
+ * @param counterAttribute - flag or property name from SharedArrayEntry whose counter is to be tracked.
311
+ */
312
+ isLocalPending(entryId, counterAttribute) {
313
+ const getCounterAttributeValue = (entry, counterAttr) => {
314
+ return entry[counterAttr];
315
+ };
316
+ const inputEntry = this.getEntryForId(entryId);
317
+ let prevEntryId = inputEntry.prevEntryId;
318
+ let nextEntryId = inputEntry.nextEntryId;
319
+ if (getCounterAttributeValue(inputEntry, counterAttribute) > 0) {
320
+ return true;
321
+ }
322
+ // track back in chain
323
+ while (prevEntryId !== undefined && prevEntryId) {
324
+ const prevEntry = this.getEntryForId(prevEntryId);
325
+ if (getCounterAttributeValue(prevEntry, counterAttribute)) {
326
+ return true;
327
+ }
328
+ prevEntryId = prevEntry.prevEntryId;
329
+ }
330
+ // track forward in the chain
331
+ while (nextEntryId !== undefined && nextEntryId) {
332
+ const nextEntry = this.getEntryForId(nextEntryId);
333
+ if (getCounterAttributeValue(nextEntry, counterAttribute)) {
334
+ return true;
335
+ }
336
+ nextEntryId = nextEntry.nextEntryId;
337
+ }
338
+ return false;
339
+ }
340
+ getEntryForId(entryId) {
341
+ return this.idToEntryMap.get(entryId);
342
+ }
343
+ /**
344
+ * Process a shared array operation
345
+ *
346
+ * @param message - the message to prepare
347
+ * @param local - whether the message was sent by the local client
348
+ * @param _localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
349
+ * For messages from a remote client, this will be undefined.
350
+ */
351
+ processCore(message, local, _localOpMetadata) {
352
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
353
+ if (message.type === MessageType.Operation) {
354
+ const op = message.contents;
355
+ const opEntry = this.getEntryForId(op.entryId);
356
+ switch (op.type) {
357
+ case OperationType.insertEntry: {
358
+ this.handleInsertOp(op.entryId, op.insertAfterEntryId, local, op.value);
359
+ break;
360
+ }
361
+ case OperationType.deleteEntry: {
362
+ if (local) {
363
+ // Decrementing local pending counter as op is already applied to local state
364
+ opEntry.isLocalPendingDelete -= 1;
365
+ }
366
+ else {
367
+ // If local pending, then ignore else apply the remote op
368
+ if (!this.isLocalPending(op.entryId, "isLocalPendingDelete")) {
369
+ // last element in skip list is the most recent and live entry, so marking it deleted
370
+ this.getLiveEntry(op.entryId).isDeleted = true;
371
+ }
372
+ }
373
+ break;
374
+ }
375
+ case OperationType.moveEntry: {
376
+ this.handleInsertOp(op.changedToEntryId, op.insertAfterEntryId, local, opEntry.value);
377
+ if (local) {
378
+ // decrement the local pending move op as its already applied to local state
379
+ opEntry.isLocalPendingMove -= 1;
380
+ }
381
+ else {
382
+ const newElementEntryId = op.changedToEntryId;
383
+ const newElement = this.getEntryForId(newElementEntryId);
384
+ // If local pending then simply mark the new location dead as finally the local op will win
385
+ if (this.isLocalPending(op.entryId, "isLocalPendingDelete") ||
386
+ this.isLocalPending(op.entryId, "isLocalPendingMove")) {
387
+ this.updateDeadEntry(op.entryId, newElementEntryId);
388
+ }
389
+ else {
390
+ // move the element
391
+ const liveEntry = this.getLiveEntry(op.entryId);
392
+ const isDeleted = liveEntry.isDeleted;
393
+ this.updateLiveEntry(liveEntry.entryId, newElementEntryId);
394
+ // mark newly added element as deleted if existing live element was already deleted
395
+ if (isDeleted) {
396
+ newElement.isDeleted = isDeleted;
397
+ }
398
+ }
399
+ }
400
+ break;
401
+ }
402
+ case OperationType.toggle: {
403
+ if (local) {
404
+ // decrement the local pending delete op as its already applied to local state
405
+ if (opEntry.isLocalPendingDelete) {
406
+ opEntry.isLocalPendingDelete -= 1;
407
+ }
408
+ }
409
+ else {
410
+ if (!this.isLocalPending(op.entryId, "isLocalPendingDelete")) {
411
+ this.getLiveEntry(op.entryId).isDeleted = op.isDeleted;
412
+ }
413
+ }
414
+ break;
415
+ }
416
+ case OperationType.toggleMove: {
417
+ if (local) {
418
+ // decrement the local pending move op as its already applied to local state
419
+ if (opEntry.isLocalPendingMove) {
420
+ opEntry.isLocalPendingMove -= 1;
421
+ }
422
+ }
423
+ else if (!this.isLocalPending(op.entryId, "isLocalPendingDelete") &&
424
+ !this.isLocalPending(op.entryId, "isLocalPendingMove")) {
425
+ this.updateLiveEntry(this.getLiveEntry(op.entryId).entryId, op.entryId);
426
+ }
427
+ break;
428
+ }
429
+ default: {
430
+ throw new Error("Unknown operation");
431
+ }
432
+ }
433
+ if (!local) {
434
+ this.emitValueChangedEvent(op, local);
435
+ }
436
+ }
437
+ }
438
+ findInternalIndex(countEntries) {
439
+ if (countEntries < 0) {
440
+ throw new Error("Input count is zero");
441
+ }
442
+ let countDown = countEntries;
443
+ let entriesIterator = 0;
444
+ for (; entriesIterator < this.sharedArray.length; entriesIterator = entriesIterator + 1) {
445
+ const entry = this.sharedArray[entriesIterator];
446
+ assert(entry !== undefined, 0xb93 /* Invalid index */);
447
+ if (entry.isDeleted === false) {
448
+ if (countDown === 0) {
449
+ return entriesIterator;
450
+ }
451
+ countDown = countDown - 1;
452
+ }
453
+ }
454
+ throw new Error(`Count of live entries is less than required`);
455
+ }
456
+ findInternalInsertionIndex(index) {
457
+ return index === 0 ? index : this.findInternalIndex(index - 1) + 1;
458
+ }
459
+ findInternalDeletionIndex(index) {
460
+ return this.findInternalIndex(index);
461
+ }
462
+ createAddEntry(index, value) {
463
+ const newEntry = this.createNewEntry(uuid(), value);
464
+ this.addEntry(index, newEntry);
465
+ return newEntry.entryId;
466
+ }
467
+ addEntry(insertIndex, newEntry) {
468
+ // in scenario where we populate 100K rows, we insert them all at the end of array.
469
+ // slicing array is way slower than pushing elements.
470
+ if (insertIndex === this.sharedArray.length) {
471
+ this.sharedArray.push(newEntry);
472
+ }
473
+ else {
474
+ this.sharedArray.splice(insertIndex, 0 /* deleteCount */, newEntry);
475
+ }
476
+ // Updating the idToEntryMap optimizer data set as new entry has been added
477
+ this.idToEntryMap.set(newEntry.entryId, newEntry);
478
+ }
479
+ emitValueChangedEvent(op, isLocal) {
480
+ this.emit("valueChanged", op, isLocal, this);
481
+ }
482
+ emitRevertibleEvent(op) {
483
+ const revertible = new SharedArrayRevertible(this, op);
484
+ this.emit("revertible", revertible);
485
+ }
486
+ deleteCore(index) {
487
+ const entry = this.sharedArray[index];
488
+ assert(entry !== undefined, 0xb94 /* Invalid index */);
489
+ if (entry.isDeleted) {
490
+ throw new Error("Entry already deleted.");
491
+ }
492
+ entry.isDeleted = true;
493
+ // Adding local pending counter
494
+ entry.isLocalPendingDelete += 1;
495
+ }
496
+ createMoveEntry(oldIndex, newIndex) {
497
+ const oldEntry = this.sharedArray[oldIndex];
498
+ assert(oldEntry !== undefined, 0xb95 /* Invalid index */);
499
+ const newEntry = this.createNewEntry(uuid(), oldEntry.value, oldEntry.entryId);
500
+ oldEntry.isDeleted = true;
501
+ oldEntry.nextEntryId = newEntry.entryId;
502
+ // Adding local pending counter
503
+ oldEntry.isLocalPendingMove += 1;
504
+ this.addEntry(newIndex /* insertIndex */, newEntry);
505
+ return newEntry.entryId;
506
+ }
507
+ /**
508
+ * Creates new entry of type SharedArrayEntry interface.
509
+ * @param entryId - id for which new entry is created
510
+ * @param value - value for the new entry
511
+ * @param prevEntryId - prevEntryId if exists to update the previous pointer of double ended linked list
512
+ */
513
+ createNewEntry(entryId, value, prevEntryId) {
514
+ return {
515
+ entryId,
516
+ value,
517
+ isAckPending: true,
518
+ isDeleted: false,
519
+ prevEntryId,
520
+ nextEntryId: undefined,
521
+ isLocalPendingDelete: 0,
522
+ isLocalPendingMove: 0,
523
+ };
524
+ }
525
+ /**
526
+ * Unsets all local flags used by the DDS. This method can be used after reading from snapshott to ensure
527
+ * local flags are initialized for use by the DDS.
528
+ * @param entry - Entry for which the local flags have to be cleaned up
529
+ */
530
+ unsetLocalFlags(entry) {
531
+ entry.isAckPending = false;
532
+ entry.isLocalPendingDelete = 0;
533
+ entry.isLocalPendingMove = 0;
534
+ }
535
+ /**
536
+ * Returns the index of the first entry starting with startIndex that does not have the isAckPending flag
537
+ */
538
+ getInternalInsertIndexByIgnoringLocalPendingInserts(startIndex) {
539
+ let localOpsIterator = startIndex;
540
+ for (; localOpsIterator < this.sharedArray.length; localOpsIterator = localOpsIterator + 1) {
541
+ const entry = this.sharedArray[localOpsIterator];
542
+ assert(entry !== undefined, 0xb96 /* Invalid index */);
543
+ if (!entry.isAckPending) {
544
+ break;
545
+ }
546
+ }
547
+ return localOpsIterator;
548
+ }
549
+ handleInsertOp(entryId, insertAfterEntryId, local, value) {
550
+ let index = 0;
551
+ if (local) {
552
+ this.getEntryForId(entryId).isAckPending = false;
553
+ }
554
+ else {
555
+ if (insertAfterEntryId !== undefined) {
556
+ index = this.findIndexOfEntryId(insertAfterEntryId) + 1;
557
+ }
558
+ const newEntry = this.createNewEntry(entryId, value);
559
+ newEntry.isAckPending = false;
560
+ this.addEntry(this.getInternalInsertIndexByIgnoringLocalPendingInserts(index), newEntry);
561
+ }
562
+ }
563
+ findIndexOfEntryId(entryId) {
564
+ for (let index = 0; index < this.sharedArray.length; index = index + 1) {
565
+ if (this.sharedArray[index]?.entryId === entryId) {
566
+ return index;
567
+ }
568
+ }
569
+ return -1;
570
+ }
571
+ prepareToMakeEntryIdLive(entry) {
572
+ const prevIndex = this.findIndexOfEntryId(entry.prevEntryId);
573
+ const nextIndex = this.findIndexOfEntryId(entry.nextEntryId);
574
+ if (prevIndex !== -1) {
575
+ const prevEntry = this.sharedArray[prevIndex];
576
+ assert(prevEntry !== undefined, 0xb97 /* Invalid index */);
577
+ prevEntry.nextEntryId = entry.nextEntryId;
578
+ }
579
+ if (nextIndex !== -1) {
580
+ const nextEntry = this.sharedArray[nextIndex];
581
+ assert(nextEntry !== undefined, 0xb98 /* Invalid index */);
582
+ nextEntry.prevEntryId = entry.prevEntryId;
583
+ }
584
+ entry.prevEntryId = undefined;
585
+ entry.nextEntryId = undefined;
586
+ }
587
+ /**
588
+ * Method that returns the live entry.
589
+ * The shared array internally can store a skip list of all related entries which got created
590
+ * due to move operations for the same payload/value. However, all elements except for one element
591
+ * can have isDeleted flag as false indicating this is the live entry for the value.
592
+ * Current implementation ensures that the last element in the skip list of entries is the liveEntry/
593
+ * last live entry
594
+ *
595
+ * @param entryId - Entry id of any node in the skip list for the same payload/value
596
+ */
597
+ getLiveEntry(entryId) {
598
+ let liveEntry = this.getEntryForId(entryId);
599
+ while (liveEntry.nextEntryId !== undefined && liveEntry.nextEntryId) {
600
+ liveEntry = this.getEntryForId(liveEntry.nextEntryId);
601
+ }
602
+ return liveEntry;
603
+ }
604
+ /**
605
+ * We track sequence of moves for a entry in the shared array using doubly linked skip list.
606
+ * This utility function helps us keep track of the current position of an entry.value by marking the entry
607
+ * at previous position deleted and appending the entry at the new position at the end of the double linked
608
+ * list for that entry.value.
609
+ */
610
+ updateLiveEntry(oldLiveEntryEntryId, newLiveEntryEntryId) {
611
+ const oldLiveEntry = this.getEntryForId(oldLiveEntryEntryId);
612
+ const newLiveEntry = this.getEntryForId(newLiveEntryEntryId);
613
+ if (oldLiveEntryEntryId === newLiveEntryEntryId) {
614
+ oldLiveEntry.isDeleted = false;
615
+ }
616
+ else {
617
+ this.prepareToMakeEntryIdLive(newLiveEntry);
618
+ // Make entryId live
619
+ oldLiveEntry.nextEntryId = newLiveEntryEntryId;
620
+ newLiveEntry.prevEntryId = oldLiveEntryEntryId;
621
+ newLiveEntry.isDeleted = false;
622
+ oldLiveEntry.isDeleted = true;
623
+ }
624
+ }
625
+ /**
626
+ * We track sequence of moves for a entry in the shared array using doubly linked skip list.
627
+ * This utility function helps to insert the new entry as dead entry and reconnecting the double linked list with
628
+ * existingEntry -\> deadeEntry(appended) -\> existing chain(if any).
629
+ */
630
+ updateDeadEntry(existingEntryId, deadEntryId) {
631
+ const existingEntry = this.getEntryForId(existingEntryId);
632
+ const deadEntry = this.getEntryForId(deadEntryId);
633
+ // update dead entry's next to existingEntry's next, if existingEntry's next entry exists.
634
+ // It can be undefined if the exiting element is the last element (or only element) of chain.
635
+ if (existingEntry.nextEntryId !== undefined) {
636
+ deadEntry.nextEntryId = existingEntry.nextEntryId;
637
+ this.getEntryForId(existingEntry.nextEntryId).prevEntryId = deadEntryId;
638
+ }
639
+ // update current entry's next pointer to dead entry and vice versa.
640
+ existingEntry.nextEntryId = deadEntryId;
641
+ deadEntry.prevEntryId = existingEntryId;
642
+ deadEntry.isDeleted = true;
643
+ }
644
+ applyStashedOp(_content) {
645
+ throw new Error("Not implemented");
646
+ }
647
+ }
648
+ //# sourceMappingURL=sharedArray.js.map