@fluidframework/register-collection 1.4.0-115997 → 2.0.0-dev-rc.1.0.0.224419

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 (83) hide show
  1. package/.eslintrc.js +5 -7
  2. package/.mocharc.js +12 -0
  3. package/CHANGELOG.md +117 -0
  4. package/README.md +29 -8
  5. package/api-extractor-lint.json +4 -0
  6. package/api-extractor.json +2 -2
  7. package/api-report/register-collection.api.md +90 -0
  8. package/dist/{consensusRegisterCollection.js → consensusRegisterCollection.cjs} +31 -29
  9. package/dist/consensusRegisterCollection.cjs.map +1 -0
  10. package/dist/consensusRegisterCollection.d.ts +2 -1
  11. package/dist/consensusRegisterCollection.d.ts.map +1 -1
  12. package/dist/{consensusRegisterCollectionFactory.js → consensusRegisterCollectionFactory.cjs} +5 -4
  13. package/dist/consensusRegisterCollectionFactory.cjs.map +1 -0
  14. package/dist/consensusRegisterCollectionFactory.d.ts +2 -1
  15. package/dist/consensusRegisterCollectionFactory.d.ts.map +1 -1
  16. package/dist/index.cjs +14 -0
  17. package/dist/index.cjs.map +1 -0
  18. package/dist/index.d.ts +3 -3
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/{interfaces.js → interfaces.cjs} +3 -2
  21. package/dist/interfaces.cjs.map +1 -0
  22. package/dist/interfaces.d.ts +9 -5
  23. package/dist/interfaces.d.ts.map +1 -1
  24. package/dist/{packageVersion.js → packageVersion.cjs} +2 -2
  25. package/dist/packageVersion.cjs.map +1 -0
  26. package/dist/packageVersion.d.ts +1 -1
  27. package/dist/packageVersion.d.ts.map +1 -1
  28. package/dist/register-collection-alpha.d.ts +163 -0
  29. package/dist/register-collection-beta.d.ts +25 -0
  30. package/dist/register-collection-public.d.ts +25 -0
  31. package/dist/register-collection-untrimmed.d.ts +163 -0
  32. package/dist/tsdoc-metadata.json +11 -0
  33. package/lib/{consensusRegisterCollection.d.ts → consensusRegisterCollection.d.mts} +4 -3
  34. package/lib/consensusRegisterCollection.d.mts.map +1 -0
  35. package/lib/{consensusRegisterCollection.js → consensusRegisterCollection.mjs} +27 -25
  36. package/lib/consensusRegisterCollection.mjs.map +1 -0
  37. package/lib/{consensusRegisterCollectionFactory.d.ts → consensusRegisterCollectionFactory.d.mts} +3 -2
  38. package/lib/consensusRegisterCollectionFactory.d.mts.map +1 -0
  39. package/lib/{consensusRegisterCollectionFactory.js → consensusRegisterCollectionFactory.mjs} +5 -4
  40. package/lib/consensusRegisterCollectionFactory.mjs.map +1 -0
  41. package/lib/index.d.mts +8 -0
  42. package/lib/index.d.mts.map +1 -0
  43. package/lib/index.mjs +8 -0
  44. package/lib/index.mjs.map +1 -0
  45. package/lib/{interfaces.d.ts → interfaces.d.mts} +9 -5
  46. package/lib/interfaces.d.mts.map +1 -0
  47. package/lib/{interfaces.js → interfaces.mjs} +2 -1
  48. package/lib/interfaces.mjs.map +1 -0
  49. package/lib/{packageVersion.d.ts → packageVersion.d.mts} +1 -1
  50. package/lib/{packageVersion.d.ts.map → packageVersion.d.mts.map} +1 -1
  51. package/lib/{packageVersion.js → packageVersion.mjs} +2 -2
  52. package/lib/packageVersion.mjs.map +1 -0
  53. package/lib/register-collection-alpha.d.mts +163 -0
  54. package/lib/register-collection-beta.d.mts +25 -0
  55. package/lib/register-collection-public.d.mts +25 -0
  56. package/lib/register-collection-untrimmed.d.mts +163 -0
  57. package/package.json +97 -62
  58. package/prettier.config.cjs +8 -0
  59. package/src/consensusRegisterCollection.ts +307 -284
  60. package/src/consensusRegisterCollectionFactory.ts +39 -33
  61. package/src/index.ts +8 -3
  62. package/src/interfaces.ts +52 -43
  63. package/src/packageVersion.ts +1 -1
  64. package/tsc-multi.test.json +4 -0
  65. package/tsconfig.json +11 -13
  66. package/dist/consensusRegisterCollection.js.map +0 -1
  67. package/dist/consensusRegisterCollectionFactory.js.map +0 -1
  68. package/dist/index.js +0 -20
  69. package/dist/index.js.map +0 -1
  70. package/dist/interfaces.js.map +0 -1
  71. package/dist/packageVersion.js.map +0 -1
  72. package/lib/consensusRegisterCollection.d.ts.map +0 -1
  73. package/lib/consensusRegisterCollection.js.map +0 -1
  74. package/lib/consensusRegisterCollectionFactory.d.ts.map +0 -1
  75. package/lib/consensusRegisterCollectionFactory.js.map +0 -1
  76. package/lib/index.d.ts +0 -8
  77. package/lib/index.d.ts.map +0 -1
  78. package/lib/index.js +0 -8
  79. package/lib/index.js.map +0 -1
  80. package/lib/interfaces.d.ts.map +0 -1
  81. package/lib/interfaces.js.map +0 -1
  82. package/lib/packageVersion.js.map +0 -1
  83. package/tsconfig.esnext.json +0 -7
@@ -3,71 +3,80 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { assert, bufferToString, unreachableCase } from "@fluidframework/common-utils";
6
+ import { bufferToString } from "@fluid-internal/client-utils";
7
+ import { assert, unreachableCase } from "@fluidframework/core-utils";
7
8
  import { ISequencedDocumentMessage, MessageType } from "@fluidframework/protocol-definitions";
8
9
  import {
9
- IChannelAttributes,
10
- IFluidDataStoreRuntime,
11
- IChannelStorageService,
10
+ IChannelAttributes,
11
+ IFluidDataStoreRuntime,
12
+ IChannelStorageService,
12
13
  } from "@fluidframework/datastore-definitions";
13
14
  import { ISummaryTreeWithStats } from "@fluidframework/runtime-definitions";
14
- import { createSingleBlobSummary, IFluidSerializer, SharedObject } from "@fluidframework/shared-object-base";
15
+ import {
16
+ createSingleBlobSummary,
17
+ IFluidSerializer,
18
+ SharedObject,
19
+ } from "@fluidframework/shared-object-base";
15
20
  import { ConsensusRegisterCollectionFactory } from "./consensusRegisterCollectionFactory";
16
- import { IConsensusRegisterCollection, ReadPolicy, IConsensusRegisterCollectionEvents } from "./interfaces";
21
+ import {
22
+ IConsensusRegisterCollection,
23
+ ReadPolicy,
24
+ IConsensusRegisterCollectionEvents,
25
+ } from "./interfaces";
17
26
 
18
27
  interface ILocalData<T> {
19
- // Atomic version
20
- atomic: ILocalRegister<T>;
28
+ // Atomic version
29
+ atomic: ILocalRegister<T>;
21
30
 
22
- // All concurrent versions awaiting consensus
23
- versions: ILocalRegister<T>[];
31
+ // All concurrent versions awaiting consensus
32
+ versions: ILocalRegister<T>[];
24
33
  }
25
34
 
26
35
  interface ILocalRegister<T> {
27
- // Register value, wrapped for backwards compatibility with < 0.17
28
- value: {
29
- type: "Plain";
30
- value: T;
31
- };
32
-
33
- // The sequence number when last consensus was reached
34
- sequenceNumber: number;
36
+ // Register value, wrapped for backwards compatibility with < 0.17
37
+ value: {
38
+ type: "Plain";
39
+ value: T;
40
+ };
41
+
42
+ // The sequence number when last consensus was reached
43
+ sequenceNumber: number;
35
44
  }
36
45
 
37
46
  const newLocalRegister = <T>(sequenceNumber: number, value: T): ILocalRegister<T> => ({
38
- sequenceNumber,
39
- value: {
40
- type: "Plain",
41
- value,
42
- },
47
+ sequenceNumber,
48
+ value: {
49
+ type: "Plain",
50
+ value,
51
+ },
43
52
  });
44
53
 
45
54
  /**
46
55
  * An operation for consensus register collection
47
56
  */
48
57
  interface IRegisterOperation {
49
- key: string;
50
- type: "write";
51
- serializedValue: string;
52
-
53
- // Message can be delivered with delay - resubmitted on reconnect.
54
- // As such, refSeq needs to reference seq # at the time op was created,
55
- // not when op was actually sent over wire (ISequencedDocumentMessage.referenceSequenceNumber),
56
- // as client can ingest ops in between.
57
- refSeq: number | undefined;
58
+ key: string;
59
+ type: "write";
60
+ serializedValue: string;
61
+
62
+ // Message can be delivered with delay - resubmitted on reconnect.
63
+ // As such, refSeq needs to reference seq # at the time op was created,
64
+ // not when op was actually sent over wire (ISequencedDocumentMessage.referenceSequenceNumber),
65
+ // as client can ingest ops in between.
66
+ refSeq: number | undefined;
58
67
  }
59
68
 
60
69
  /**
61
70
  * IRegisterOperation format in versions \< 0.17
62
71
  */
63
72
  interface IRegisterOperationOld<T> {
64
- key: string;
65
- type: "write";
66
- value: {
67
- type: "Plain";
68
- value: T;
69
- };
70
- refSeq: number;
73
+ key: string;
74
+ type: "write";
75
+ value: {
76
+ type: "Plain";
77
+ value: T;
78
+ };
79
+ refSeq: number;
71
80
  }
72
81
 
73
82
  /** Incoming ops could match any of these types */
@@ -82,251 +91,265 @@ type PendingResolve = (winner: boolean) => void;
82
91
  const snapshotFileName = "header";
83
92
 
84
93
  /**
85
- * Implementation of a consensus register collection
94
+ * {@inheritDoc IConsensusRegisterCollection}
95
+ * @alpha
86
96
  */
87
97
  export class ConsensusRegisterCollection<T>
88
- extends SharedObject<IConsensusRegisterCollectionEvents> implements IConsensusRegisterCollection<T> {
89
- /**
90
- * Create a new consensus register collection
91
- *
92
- * @param runtime - data store runtime the new consensus register collection belongs to
93
- * @param id - optional name of the consensus register collection
94
- * @returns newly create consensus register collection (but not attached yet)
95
- */
96
- // eslint-disable-next-line @typescript-eslint/no-shadow
97
- public static create<T>(runtime: IFluidDataStoreRuntime, id?: string) {
98
- return runtime.createChannel(id, ConsensusRegisterCollectionFactory.Type) as ConsensusRegisterCollection<T>;
99
- }
100
-
101
- /**
102
- * Get a factory for ConsensusRegisterCollection to register with the data store.
103
- *
104
- * @returns a factory that creates and load ConsensusRegisterCollection
105
- */
106
- public static getFactory() {
107
- return new ConsensusRegisterCollectionFactory();
108
- }
109
-
110
- private readonly data = new Map<string, ILocalData<T>>();
111
-
112
- /**
113
- * Constructs a new consensus register collection. If the object is non-local an id and service interfaces will
114
- * be provided
115
- */
116
- public constructor(
117
- id: string,
118
- runtime: IFluidDataStoreRuntime,
119
- attributes: IChannelAttributes,
120
- ) {
121
- super(id, runtime, attributes, "fluid_consensusRegisterCollection_");
122
- }
123
-
124
- /**
125
- * Creates a new register or writes a new value.
126
- * Returns a promise that will resolve when the write is acked.
127
- *
128
- * @returns Promise<true> if write was non-concurrent
129
- */
130
- public async write(key: string, value: T): Promise<boolean> {
131
- const serializedValue = this.stringify(value, this.serializer);
132
-
133
- if (!this.isAttached()) {
134
- // JSON-roundtrip value for local writes to match the behavior of going through the wire
135
- this.processInboundWrite(key, this.parse(serializedValue, this.serializer), 0, 0, true);
136
- return true;
137
- }
138
-
139
- const message: IRegisterOperation = {
140
- key,
141
- type: "write",
142
- serializedValue,
143
- refSeq: this.runtime.deltaManager.lastSequenceNumber,
144
- };
145
-
146
- return this.newAckBasedPromise<boolean>((resolve) => {
147
- // Send the resolve function as the localOpMetadata. This will be provided back to us when the
148
- // op is ack'd.
149
- this.submitLocalMessage(message, resolve);
150
- // If we fail due to runtime being disposed, it's better to return false then unhandled exception.
151
- }).catch((error) => false);
152
- }
153
-
154
- /**
155
- * Returns the most recent local value of a register.
156
- * @param key - The key to read
157
- * @param readPolicy - The ReadPolicy to apply. Defaults to Atomic.
158
- */
159
- public read(key: string, readPolicy: ReadPolicy = ReadPolicy.Atomic): T | undefined {
160
- if (readPolicy === ReadPolicy.Atomic) {
161
- return this.readAtomic(key);
162
- }
163
-
164
- const versions = this.readVersions(key);
165
-
166
- if (versions !== undefined) {
167
- // We don't support deletion. So there should be at least one value.
168
- assert(versions.length > 0, 0x06c /* "Value should be undefined or non-empty" */);
169
-
170
- return versions[versions.length - 1];
171
- }
172
- }
173
-
174
- public readVersions(key: string): T[] | undefined {
175
- const data = this.data.get(key);
176
- return data?.versions.map((element: ILocalRegister<T>) => element.value.value);
177
- }
178
-
179
- public keys(): string[] {
180
- return [...this.data.keys()];
181
- }
182
-
183
- protected summarizeCore(serializer: IFluidSerializer): ISummaryTreeWithStats {
184
- const dataObj: { [key: string]: ILocalData<T>; } = {};
185
- this.data.forEach((v, k) => { dataObj[k] = v; });
186
-
187
- return createSingleBlobSummary(snapshotFileName, this.stringify(dataObj, serializer));
188
- }
189
-
190
- /**
191
- * {@inheritDoc @fluidframework/shared-object-base#SharedObject.loadCore}
192
- */
193
- protected async loadCore(storage: IChannelStorageService): Promise<void> {
194
- const blob = await storage.readBlob(snapshotFileName);
195
- const header = bufferToString(blob, "utf8");
196
- const dataObj = this.parse(header, this.serializer);
197
-
198
- for (const key of Object.keys(dataObj)) {
199
- assert(dataObj[key].atomic?.value.type !== "Shared",
200
- // eslint-disable-next-line max-len
201
- 0x06d /* "SharedObjects contained in ConsensusRegisterCollection can no longer be deserialized as of 0.17" */);
202
-
203
- this.data.set(key, dataObj[key]);
204
- }
205
- }
206
-
207
- protected onDisconnect() {}
208
-
209
- protected processCore(message: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown) {
210
- if (message.type === MessageType.Operation) {
211
- const op: IIncomingRegisterOperation<T> = message.contents;
212
- switch (op.type) {
213
- case "write": {
214
- // backward compatibility: File at rest written with runtime <= 0.13 do not have refSeq
215
- // when the refSeq property didn't exist
216
- if (op.refSeq === undefined) {
217
- op.refSeq = message.referenceSequenceNumber;
218
- }
219
- // Message can be delivered with delay - e.g. resubmitted on reconnect.
220
- // Use the refSeq from when the op was created, not when it was transmitted
221
- const refSeqWhenCreated = op.refSeq;
222
- assert(refSeqWhenCreated <= message.referenceSequenceNumber,
223
- 0x06e /* "Message's reference sequence number < op's reference sequence number!" */);
224
-
225
- const value = incomingOpMatchesCurrentFormat(op)
226
- ? this.parse(op.serializedValue, this.serializer) as T
227
- : op.value.value;
228
- const winner = this.processInboundWrite(
229
- op.key,
230
- value,
231
- refSeqWhenCreated,
232
- message.sequenceNumber,
233
- local);
234
- if (local) {
235
- // Resolve the pending promise for this operation now that we have received an ack for it.
236
- const resolve = localOpMetadata as PendingResolve;
237
- resolve(winner);
238
- }
239
- break;
240
- }
241
- default: unreachableCase(op.type);
242
- }
243
- }
244
- }
245
-
246
- private readAtomic(key: string): T | undefined {
247
- const data = this.data.get(key);
248
- return data?.atomic.value.value;
249
- }
250
-
251
- /**
252
- * Process an inbound write op
253
- * @param key - Key that was written to
254
- * @param value - Incoming value
255
- * @param refSeq - RefSeq at the time of write on the remote client
256
- * @param sequenceNumber - Sequence Number of this write op
257
- * @param local - Did this write originate on this client
258
- */
259
- private processInboundWrite(
260
- key: string,
261
- value: T,
262
- refSeq: number,
263
- sequenceNumber: number,
264
- local: boolean,
265
- ): boolean {
266
- let data = this.data.get(key);
267
- // Atomic update if it's a new register or the write was not concurrent,
268
- // meaning our state was known to the remote client at the time of write
269
- const winner = data === undefined || refSeq >= data.atomic.sequenceNumber;
270
- if (winner) {
271
- const atomicUpdate = newLocalRegister<T>(
272
- sequenceNumber,
273
- value,
274
- );
275
- if (data === undefined) {
276
- data = {
277
- atomic: atomicUpdate,
278
- versions: [], // we'll update versions next, leave it empty for now
279
- };
280
- this.data.set(key, data);
281
- } else {
282
- data.atomic = atomicUpdate;
283
- }
284
- } else {
285
- assert(!!data, 0x06f /* "data missing for non-atomic inbound update!" */);
286
- }
287
-
288
- // Remove versions that were known to the remote client at the time of write
289
- while (data.versions.length > 0 && refSeq >= data.versions[0].sequenceNumber) {
290
- data.versions.shift();
291
- }
292
-
293
- const versionUpdate = newLocalRegister<T>(
294
- sequenceNumber,
295
- value,
296
- );
297
-
298
- // Asserts for data integrity
299
- if (!this.isAttached()) {
300
- assert(refSeq === 0 && sequenceNumber === 0,
301
- 0x070 /* "sequence numbers are expected to be 0 when unattached" */);
302
- } else if (data.versions.length > 0) {
303
- assert(sequenceNumber > data.versions[data.versions.length - 1].sequenceNumber,
304
- 0x071 /* "Versions should naturally be ordered by sequenceNumber" */);
305
- }
306
-
307
- // Push the new element.
308
- data.versions.push(versionUpdate);
309
-
310
- // Raise events at the end, to avoid reentrancy issues
311
- if (winner) {
312
- this.emit("atomicChanged", key, value, local);
313
- }
314
- this.emit("versionChanged", key, value, local);
315
-
316
- return winner;
317
- }
318
-
319
- private stringify(value: any, serializer: IFluidSerializer): string {
320
- return serializer.stringify(value, this.handle);
321
- }
322
-
323
- private parse(content: string, serializer: IFluidSerializer): any {
324
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
325
- return serializer.parse(content);
326
- }
327
-
328
- protected applyStashedOp() {
329
- // empty implementation
330
- return () => { };
331
- }
98
+ extends SharedObject<IConsensusRegisterCollectionEvents>
99
+ implements IConsensusRegisterCollection<T>
100
+ {
101
+ /**
102
+ * Create a new consensus register collection
103
+ *
104
+ * @param runtime - data store runtime the new consensus register collection belongs to
105
+ * @param id - optional name of the consensus register collection
106
+ * @returns newly create consensus register collection (but not attached yet)
107
+ */
108
+ public static create<T>(runtime: IFluidDataStoreRuntime, id?: string) {
109
+ return runtime.createChannel(
110
+ id,
111
+ ConsensusRegisterCollectionFactory.Type,
112
+ ) as ConsensusRegisterCollection<T>;
113
+ }
114
+
115
+ /**
116
+ * Get a factory for ConsensusRegisterCollection to register with the data store.
117
+ *
118
+ * @returns a factory that creates and load ConsensusRegisterCollection
119
+ */
120
+ public static getFactory() {
121
+ return new ConsensusRegisterCollectionFactory();
122
+ }
123
+
124
+ private readonly data = new Map<string, ILocalData<T>>();
125
+
126
+ /**
127
+ * Constructs a new consensus register collection. If the object is non-local an id and service interfaces will
128
+ * be provided
129
+ */
130
+ public constructor(
131
+ id: string,
132
+ runtime: IFluidDataStoreRuntime,
133
+ attributes: IChannelAttributes,
134
+ ) {
135
+ super(id, runtime, attributes, "fluid_consensusRegisterCollection_");
136
+ }
137
+
138
+ /**
139
+ * Creates a new register or writes a new value.
140
+ * Returns a promise that will resolve when the write is acked.
141
+ *
142
+ * @returns Promise<true> if write was non-concurrent
143
+ */
144
+ public async write(key: string, value: T): Promise<boolean> {
145
+ const serializedValue = this.stringify(value, this.serializer);
146
+
147
+ if (!this.isAttached()) {
148
+ // JSON-roundtrip value for local writes to match the behavior of going through the wire
149
+ this.processInboundWrite(key, this.parse(serializedValue, this.serializer), 0, 0, true);
150
+ return true;
151
+ }
152
+
153
+ const message: IRegisterOperation = {
154
+ key,
155
+ type: "write",
156
+ serializedValue,
157
+ refSeq: this.runtime.deltaManager.lastSequenceNumber,
158
+ };
159
+
160
+ return this.newAckBasedPromise<boolean>((resolve) => {
161
+ // Send the resolve function as the localOpMetadata. This will be provided back to us when the
162
+ // op is ack'd.
163
+ this.submitLocalMessage(message, resolve);
164
+ // If we fail due to runtime being disposed, it's better to return false then unhandled exception.
165
+ }).catch((error) => false);
166
+ }
167
+
168
+ /**
169
+ * Returns the most recent local value of a register.
170
+ * @param key - The key to read
171
+ * @param readPolicy - The ReadPolicy to apply. Defaults to Atomic.
172
+ */
173
+ public read(key: string, readPolicy: ReadPolicy = ReadPolicy.Atomic): T | undefined {
174
+ if (readPolicy === ReadPolicy.Atomic) {
175
+ return this.readAtomic(key);
176
+ }
177
+
178
+ const versions = this.readVersions(key);
179
+
180
+ if (versions !== undefined) {
181
+ // We don't support deletion. So there should be at least one value.
182
+ assert(versions.length > 0, 0x06c /* "Value should be undefined or non-empty" */);
183
+
184
+ return versions[versions.length - 1];
185
+ }
186
+ }
187
+
188
+ public readVersions(key: string): T[] | undefined {
189
+ const data = this.data.get(key);
190
+ return data?.versions.map((element: ILocalRegister<T>) => element.value.value);
191
+ }
192
+
193
+ public keys(): string[] {
194
+ return [...this.data.keys()];
195
+ }
196
+
197
+ protected summarizeCore(serializer: IFluidSerializer): ISummaryTreeWithStats {
198
+ const dataObj: { [key: string]: ILocalData<T> } = {};
199
+ this.data.forEach((v, k) => {
200
+ dataObj[k] = v;
201
+ });
202
+
203
+ return createSingleBlobSummary(snapshotFileName, this.stringify(dataObj, serializer));
204
+ }
205
+
206
+ /**
207
+ * {@inheritDoc @fluidframework/shared-object-base#SharedObject.loadCore}
208
+ */
209
+ protected async loadCore(storage: IChannelStorageService): Promise<void> {
210
+ const blob = await storage.readBlob(snapshotFileName);
211
+ const header = bufferToString(blob, "utf8");
212
+ const dataObj = this.parse(header, this.serializer);
213
+
214
+ for (const key of Object.keys(dataObj)) {
215
+ assert(
216
+ dataObj[key].atomic?.value.type !== "Shared",
217
+ 0x06d /* "SharedObjects contained in ConsensusRegisterCollection can no longer be deserialized as of 0.17" */,
218
+ );
219
+
220
+ this.data.set(key, dataObj[key]);
221
+ }
222
+ }
223
+
224
+ protected onDisconnect() {}
225
+
226
+ protected processCore(
227
+ message: ISequencedDocumentMessage,
228
+ local: boolean,
229
+ localOpMetadata: unknown,
230
+ ) {
231
+ if (message.type === MessageType.Operation) {
232
+ const op = message.contents as IIncomingRegisterOperation<T>;
233
+ switch (op.type) {
234
+ case "write": {
235
+ // backward compatibility: File at rest written with runtime <= 0.13 do not have refSeq
236
+ // when the refSeq property didn't exist
237
+ if (op.refSeq === undefined) {
238
+ op.refSeq = message.referenceSequenceNumber;
239
+ }
240
+ // Message can be delivered with delay - e.g. resubmitted on reconnect.
241
+ // Use the refSeq from when the op was created, not when it was transmitted
242
+ const refSeqWhenCreated = op.refSeq;
243
+ assert(
244
+ refSeqWhenCreated <= message.referenceSequenceNumber,
245
+ 0x06e /* "Message's reference sequence number < op's reference sequence number!" */,
246
+ );
247
+
248
+ const value = incomingOpMatchesCurrentFormat(op)
249
+ ? (this.parse(op.serializedValue, this.serializer) as T)
250
+ : op.value.value;
251
+ const winner = this.processInboundWrite(
252
+ op.key,
253
+ value,
254
+ refSeqWhenCreated,
255
+ message.sequenceNumber,
256
+ local,
257
+ );
258
+ if (local) {
259
+ // Resolve the pending promise for this operation now that we have received an ack for it.
260
+ const resolve = localOpMetadata as PendingResolve;
261
+ resolve(winner);
262
+ }
263
+ break;
264
+ }
265
+ default:
266
+ unreachableCase(op.type);
267
+ }
268
+ }
269
+ }
270
+
271
+ private readAtomic(key: string): T | undefined {
272
+ const data = this.data.get(key);
273
+ return data?.atomic.value.value;
274
+ }
275
+
276
+ /**
277
+ * Process an inbound write op
278
+ * @param key - Key that was written to
279
+ * @param value - Incoming value
280
+ * @param refSeq - RefSeq at the time of write on the remote client
281
+ * @param sequenceNumber - Sequence Number of this write op
282
+ * @param local - Did this write originate on this client
283
+ */
284
+ private processInboundWrite(
285
+ key: string,
286
+ value: T,
287
+ refSeq: number,
288
+ sequenceNumber: number,
289
+ local: boolean,
290
+ ): boolean {
291
+ let data = this.data.get(key);
292
+ // Atomic update if it's a new register or the write was not concurrent,
293
+ // meaning our state was known to the remote client at the time of write
294
+ const winner = data === undefined || refSeq >= data.atomic.sequenceNumber;
295
+ if (winner) {
296
+ const atomicUpdate = newLocalRegister<T>(sequenceNumber, value);
297
+ if (data === undefined) {
298
+ data = {
299
+ atomic: atomicUpdate,
300
+ versions: [], // we'll update versions next, leave it empty for now
301
+ };
302
+ this.data.set(key, data);
303
+ } else {
304
+ data.atomic = atomicUpdate;
305
+ }
306
+ } else {
307
+ assert(!!data, 0x06f /* "data missing for non-atomic inbound update!" */);
308
+ }
309
+
310
+ // Remove versions that were known to the remote client at the time of write
311
+ while (data.versions.length > 0 && refSeq >= data.versions[0].sequenceNumber) {
312
+ data.versions.shift();
313
+ }
314
+
315
+ const versionUpdate = newLocalRegister<T>(sequenceNumber, value);
316
+
317
+ // Asserts for data integrity
318
+ if (!this.isAttached()) {
319
+ assert(
320
+ refSeq === 0 && sequenceNumber === 0,
321
+ 0x070 /* "sequence numbers are expected to be 0 when unattached" */,
322
+ );
323
+ } else if (data.versions.length > 0) {
324
+ assert(
325
+ // seqNum should always be increasing, except for the case of grouped batches (seqNum will be the same)
326
+ sequenceNumber >= data.versions[data.versions.length - 1].sequenceNumber,
327
+ 0x071 /* "Versions should naturally be ordered by sequenceNumber" */,
328
+ );
329
+ }
330
+
331
+ // Push the new element.
332
+ data.versions.push(versionUpdate);
333
+
334
+ // Raise events at the end, to avoid reentrancy issues
335
+ if (winner) {
336
+ this.emit("atomicChanged", key, value, local);
337
+ }
338
+ this.emit("versionChanged", key, value, local);
339
+
340
+ return winner;
341
+ }
342
+
343
+ private stringify(value: any, serializer: IFluidSerializer): string {
344
+ return serializer.stringify(value, this.handle);
345
+ }
346
+
347
+ private parse(content: string, serializer: IFluidSerializer): any {
348
+ return serializer.parse(content);
349
+ }
350
+
351
+ protected applyStashedOp() {
352
+ // empty implementation
353
+ return () => {};
354
+ }
332
355
  }