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