@fluidframework/cell 2.0.0-internal.2.1.1 → 2.0.0-internal.2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  module.exports = {
7
7
  "extends": [
8
- require.resolve("@fluidframework/eslint-config-fluid")
8
+ require.resolve("@fluidframework/eslint-config-fluid"), "prettier"
9
9
  ],
10
10
  "parserOptions": {
11
11
  "project": ["./tsconfig.json", "./src/test/tsconfig.json"]
package/dist/cell.d.ts CHANGED
@@ -83,6 +83,7 @@ export declare class SharedCell<T = any> extends SharedObject<ISharedCellEvents<
83
83
  * we a message is ack'd with it's messageId.
84
84
  */
85
85
  private messageIdObserved;
86
+ private readonly pendingMessageIds;
86
87
  /**
87
88
  * Constructs a new shared cell. If the object is non-local an id and service interfaces will
88
89
  * be provided
@@ -142,10 +143,23 @@ export declare class SharedCell<T = any> extends SharedObject<ISharedCellEvents<
142
143
  private setCore;
143
144
  private deleteCore;
144
145
  private decode;
146
+ private createLocalOpMetadata;
145
147
  /**
146
148
  * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}
147
149
  * @internal
148
150
  */
149
151
  protected applyStashedOp(content: unknown): unknown;
152
+ /**
153
+ * Rollback a local op
154
+ * @param content - The operation to rollback
155
+ * @param localOpMetadata - The local metadata associated with the op.
156
+ */
157
+ protected rollback(content: any, localOpMetadata: unknown): void;
158
+ /**
159
+ * Submit a cell message to remote clients.
160
+ * @param op - The cell message
161
+ * @param previousValue - The value of the cell before this op
162
+ */
163
+ private submitCellMessage;
150
164
  }
151
165
  //# sourceMappingURL=cell.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cell.d.ts","sourceRoot":"","sources":["../src/cell.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,yBAAyB,EAAe,MAAM,sCAAsC,CAAC;AAC9F,OAAO,EACH,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,eAAe,EACf,YAAY,EACf,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAE5E,OAAO,EAA2B,gBAAgB,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAE7G,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAuB9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,qBAAa,UAAU,CAAC,CAAC,GAAG,GAAG,CAAE,SAAQ,YAAY,CAAC,iBAAiB,CAAC,CAAC,CAAC,CACtE,YAAW,WAAW,CAAC,CAAC,CAAC;IACzB;;;;;;OAMG;WACW,MAAM,CAAC,OAAO,EAAE,sBAAsB,EAAE,EAAE,CAAC,EAAE,MAAM;IAIjE;;;;OAIG;WACW,UAAU,IAAI,eAAe;IAG3C;;OAEG;IACH,OAAO,CAAC,IAAI,CAA8B;IAE1C;;;OAGG;IACH,OAAO,CAAC,SAAS,CAAc;IAE/B;;;OAGG;IACH,OAAO,CAAC,iBAAiB,CAAc;IAEvC;;;;;;OAMG;gBACS,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,kBAAkB;IAIvF;;OAEG;IACI,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS;IAIzC;;OAEG;IACI,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;IAqBjC;;OAEG;IACI,MAAM;IAeb;;OAEG;IACI,KAAK;IAIZ;;;;OAIG;IACH,SAAS,CAAC,aAAa,CAAC,UAAU,EAAE,gBAAgB,GAAG,qBAAqB;IAK5E;;OAEG;cACa,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxE;;OAEG;IACH,SAAS,CAAC,mBAAmB;IAI7B;;OAEG;IACH,SAAS,CAAC,YAAY;IAEtB;;;OAGG;IACH,OAAO,CAAC,YAAY;IAepB;;;;;;;OAOG;IACH,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,yBAAyB,EAAE,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO;IAoBlG,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,MAAM;IAMd;;;OAGG;IACH,SAAS,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO;CAMtD"}
1
+ {"version":3,"file":"cell.d.ts","sourceRoot":"","sources":["../src/cell.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,yBAAyB,EAAe,MAAM,sCAAsC,CAAC;AAC9F,OAAO,EACH,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,eAAe,EACf,YAAY,EACf,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAE5E,OAAO,EAA2B,gBAAgB,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAE7G,OAAO,EACH,WAAW,EACX,iBAAiB,EAEpB,MAAM,cAAc,CAAC;AAuBtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,qBAAa,UAAU,CAAC,CAAC,GAAG,GAAG,CAAE,SAAQ,YAAY,CAAC,iBAAiB,CAAC,CAAC,CAAC,CACtE,YAAW,WAAW,CAAC,CAAC,CAAC;IACzB;;;;;;OAMG;WACW,MAAM,CAAC,OAAO,EAAE,sBAAsB,EAAE,EAAE,CAAC,EAAE,MAAM;IAIjE;;;;OAIG;WACW,UAAU,IAAI,eAAe;IAG3C;;OAEG;IACH,OAAO,CAAC,IAAI,CAA8B;IAE1C;;;OAGG;IACH,OAAO,CAAC,SAAS,CAAc;IAE/B;;;OAGG;IACH,OAAO,CAAC,iBAAiB,CAAc;IAEvC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAgB;IAElD;;;;;;OAMG;gBACS,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,kBAAkB;IAIvF;;OAEG;IACI,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS;IAIzC;;OAEG;IACI,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;IAqBjC;;OAEG;IACI,MAAM;IAeb;;OAEG;IACI,KAAK;IAIZ;;;;OAIG;IACH,SAAS,CAAC,aAAa,CAAC,UAAU,EAAE,gBAAgB,GAAG,qBAAqB;IAK5E;;OAEG;cACa,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxE;;OAEG;IACH,SAAS,CAAC,mBAAmB;IAI7B;;OAEG;IACH,SAAS,CAAC,YAAY;IAEtB;;;OAGG;IACH,OAAO,CAAC,YAAY;IAapB;;;;;;;OAOG;IACH,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,yBAAyB,EAAE,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO;IAwBlG,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,qBAAqB;IAS7B;;;OAGG;IACH,SAAS,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO;IAMnD;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,eAAe,EAAE,OAAO;IAkBxD;;;;MAIE;IACH,OAAO,CAAC,iBAAiB;CAI5B"}
package/dist/cell.js CHANGED
@@ -78,6 +78,7 @@ class SharedCell extends shared_object_base_1.SharedObject {
78
78
  * we a message is ack'd with it's messageId.
79
79
  */
80
80
  this.messageIdObserved = -1;
81
+ this.pendingMessageIds = [];
81
82
  }
82
83
  /**
83
84
  * Create a new shared cell
@@ -112,7 +113,7 @@ class SharedCell extends shared_object_base_1.SharedObject {
112
113
  value: this.serializer.encode(value, this.handle),
113
114
  };
114
115
  // Set the value locally.
115
- this.setCore(value);
116
+ const previousValue = this.setCore(value);
116
117
  // If we are not attached, don't submit the op.
117
118
  if (!this.isAttached()) {
118
119
  return;
@@ -121,14 +122,14 @@ class SharedCell extends shared_object_base_1.SharedObject {
121
122
  type: "setCell",
122
123
  value: operationValue,
123
124
  };
124
- this.submitLocalMessage(op, ++this.messageId);
125
+ this.submitCellMessage(op, previousValue);
125
126
  }
126
127
  /**
127
128
  * {@inheritDoc ISharedCell.delete}
128
129
  */
129
130
  delete() {
130
131
  // Delete the value locally.
131
- this.deleteCore();
132
+ const previousValue = this.deleteCore();
132
133
  // If we are not attached, don't submit the op.
133
134
  if (!this.isAttached()) {
134
135
  return;
@@ -136,7 +137,7 @@ class SharedCell extends shared_object_base_1.SharedObject {
136
137
  const op = {
137
138
  type: "deleteCell",
138
139
  };
139
- this.submitLocalMessage(op, ++this.messageId);
140
+ this.submitCellMessage(op, previousValue);
140
141
  }
141
142
  /**
142
143
  * {@inheritDoc ISharedCell.empty}
@@ -177,11 +178,9 @@ class SharedCell extends shared_object_base_1.SharedObject {
177
178
  applyInnerOp(content) {
178
179
  switch (content.type) {
179
180
  case "setCell":
180
- this.setCore(this.decode(content.value));
181
- break;
181
+ return this.setCore(this.decode(content.value));
182
182
  case "deleteCell":
183
- this.deleteCore();
184
- break;
183
+ return this.deleteCore();
185
184
  default:
186
185
  throw new Error("Unknown operation");
187
186
  }
@@ -195,13 +194,17 @@ class SharedCell extends shared_object_base_1.SharedObject {
195
194
  * For messages from a remote client, this will be undefined.
196
195
  */
197
196
  processCore(message, local, localOpMetadata) {
197
+ const cellOpMetadata = localOpMetadata;
198
198
  if (this.messageId !== this.messageIdObserved) {
199
199
  // We are waiting for an ACK on our change to this cell - we will ignore all messages until we get it.
200
200
  if (local) {
201
- const messageIdReceived = localOpMetadata;
201
+ const messageIdReceived = cellOpMetadata.pendingMessageId;
202
202
  (0, common_utils_1.assert)(messageIdReceived !== undefined && messageIdReceived <= this.messageId, 0x00c /* "messageId is incorrect from from the local client's ACK" */);
203
+ (0, common_utils_1.assert)(this.pendingMessageIds !== undefined &&
204
+ this.pendingMessageIds[0] === cellOpMetadata.pendingMessageId, 0x471 /* Unexpected pending message received */);
205
+ this.pendingMessageIds.shift();
203
206
  // We got an ACK. Update messageIdObserved.
204
- this.messageIdObserved = localOpMetadata;
207
+ this.messageIdObserved = cellOpMetadata.pendingMessageId;
205
208
  }
206
209
  return;
207
210
  }
@@ -211,27 +214,70 @@ class SharedCell extends shared_object_base_1.SharedObject {
211
214
  }
212
215
  }
213
216
  setCore(value) {
217
+ const previousLocalValue = this.get();
214
218
  this.data = value;
215
219
  this.emit("valueChanged", value);
220
+ return previousLocalValue;
216
221
  }
217
222
  deleteCore() {
223
+ const previousLocalValue = this.get();
218
224
  this.data = undefined;
219
225
  this.emit("delete");
226
+ return previousLocalValue;
220
227
  }
221
228
  decode(cellValue) {
222
229
  const value = cellValue.value;
223
230
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
224
231
  return this.serializer.decode(value);
225
232
  }
233
+ createLocalOpMetadata(op, previousValue) {
234
+ const pendingMessageId = ++this.messageId;
235
+ this.pendingMessageIds.push(pendingMessageId);
236
+ const localMetadata = {
237
+ pendingMessageId, previousValue,
238
+ };
239
+ return localMetadata;
240
+ }
226
241
  /**
227
242
  * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}
228
243
  * @internal
229
244
  */
230
245
  applyStashedOp(content) {
231
246
  const cellContent = content;
232
- this.applyInnerOp(cellContent);
233
- ++this.messageId;
234
- return this.messageId;
247
+ const previousValue = this.applyInnerOp(cellContent);
248
+ return this.createLocalOpMetadata(cellContent, previousValue);
249
+ }
250
+ /**
251
+ * Rollback a local op
252
+ * @param content - The operation to rollback
253
+ * @param localOpMetadata - The local metadata associated with the op.
254
+ */
255
+ rollback(content, localOpMetadata) {
256
+ const cellOpMetadata = localOpMetadata;
257
+ if (content.type === "setCell" || content.type === "deleteCell") {
258
+ if (cellOpMetadata.previousValue === undefined) {
259
+ this.deleteCore();
260
+ }
261
+ else {
262
+ this.setCore(cellOpMetadata.previousValue);
263
+ }
264
+ const lastPendingMessageId = this.pendingMessageIds.pop();
265
+ if (lastPendingMessageId !== cellOpMetadata.pendingMessageId) {
266
+ throw new Error("Rollback op does not match last pending");
267
+ }
268
+ }
269
+ else {
270
+ throw new Error("Unsupported op for rollback");
271
+ }
272
+ }
273
+ /**
274
+ * Submit a cell message to remote clients.
275
+ * @param op - The cell message
276
+ * @param previousValue - The value of the cell before this op
277
+ */
278
+ submitCellMessage(op, previousValue) {
279
+ const localMetadata = this.createLocalOpMetadata(op, previousValue);
280
+ this.submitLocalMessage(op, localMetadata);
235
281
  }
236
282
  }
237
283
  exports.SharedCell = SharedCell;
package/dist/cell.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cell.js","sourceRoot":"","sources":["../src/cell.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/common-utils\";\nimport { ISequencedDocumentMessage, MessageType } from \"@fluidframework/protocol-definitions\";\nimport {\n IChannelAttributes,\n IFluidDataStoreRuntime,\n IChannelStorageService,\n IChannelFactory,\n Serializable,\n} from \"@fluidframework/datastore-definitions\";\nimport { ISummaryTreeWithStats } from \"@fluidframework/runtime-definitions\";\nimport { readAndParse } from \"@fluidframework/driver-utils\";\nimport { createSingleBlobSummary, IFluidSerializer, SharedObject } from \"@fluidframework/shared-object-base\";\nimport { CellFactory } from \"./cellFactory\";\nimport { ISharedCell, ISharedCellEvents } from \"./interfaces\";\n\n/**\n * Description of a cell delta operation\n */\ntype ICellOperation = ISetCellOperation | IDeleteCellOperation;\n\ninterface ISetCellOperation {\n type: \"setCell\";\n value: ICellValue;\n}\n\ninterface IDeleteCellOperation {\n type: \"deleteCell\";\n}\n\ninterface ICellValue {\n // The actual value contained in the cell which needs to be wrapped to handle undefined\n value: any;\n}\n\nconst snapshotFileName = \"header\";\n\n/**\n * The SharedCell distributed data structure can be used to store a single serializable value.\n *\n * @remarks\n * ### Creation\n *\n * To create a `SharedCell`, call the static create method:\n *\n * ```typescript\n * const myCell = SharedCell.create(this.runtime, id);\n * ```\n *\n * ### Usage\n *\n * The value stored in the cell can be set with the `.set()` method and retrieved with the `.get()` method:\n *\n * ```typescript\n * myCell.set(3);\n * console.log(myCell.get()); // 3\n * ```\n *\n * The value must only be plain JS objects or `SharedObject` handles (e.g. to another DDS or Fluid object).\n * In collaborative scenarios, the value is settled with a policy of _last write wins_.\n *\n * The `.delete()` method will delete the stored value from the cell:\n *\n * ```typescript\n * myCell.delete();\n * console.log(myCell.get()); // undefined\n * ```\n *\n * The `.empty()` method will check if the value is undefined.\n *\n * ```typescript\n * if (myCell.empty()) {\n * // myCell.get() will return undefined\n * } else {\n * // myCell.get() will return a non-undefined value\n * }\n * ```\n *\n * ### Eventing\n *\n * `SharedCell` is an `EventEmitter`, and will emit events when other clients make modifications. You should\n * register for these events and respond appropriately as the data is modified. `valueChanged` will be emitted\n * in response to a `set`, and `delete` will be emitted in response to a `delete`.\n */\nexport class SharedCell<T = any> extends SharedObject<ISharedCellEvents<T>>\n implements ISharedCell<T> {\n /**\n * Create a new shared cell\n *\n * @param runtime - data store runtime the new shared map belongs to\n * @param id - optional name of the shared map\n * @returns newly create shared map (but not attached yet)\n */\n public static create(runtime: IFluidDataStoreRuntime, id?: string) {\n return runtime.createChannel(id, CellFactory.Type) as SharedCell;\n }\n\n /**\n * Get a factory for SharedCell to register with the data store.\n *\n * @returns a factory that creates and load SharedCell\n */\n public static getFactory(): IChannelFactory {\n return new CellFactory();\n }\n /**\n * The data held by this cell.\n */\n private data: Serializable<T> | undefined;\n\n /**\n * This is used to assign a unique id to outgoing messages. It is used to track messages until\n * they are ack'd.\n */\n private messageId: number = -1;\n\n /**\n * This keeps track of the messageId of messages that have been ack'd. It is updated every time\n * we a message is ack'd with it's messageId.\n */\n private messageIdObserved: number = -1;\n\n /**\n * Constructs a new shared cell. If the object is non-local an id and service interfaces will\n * be provided\n *\n * @param runtime - data store runtime the shared map belongs to\n * @param id - optional name of the shared map\n */\n constructor(id: string, runtime: IFluidDataStoreRuntime, attributes: IChannelAttributes) {\n super(id, runtime, attributes, \"fluid_cell_\");\n }\n\n /**\n * {@inheritDoc ISharedCell.get}\n */\n public get(): Serializable<T> | undefined {\n return this.data;\n }\n\n /**\n * {@inheritDoc ISharedCell.set}\n */\n public set(value: Serializable<T>) {\n // Serialize the value if required.\n const operationValue: ICellValue = {\n value: this.serializer.encode(value, this.handle),\n };\n\n // Set the value locally.\n this.setCore(value);\n\n // If we are not attached, don't submit the op.\n if (!this.isAttached()) {\n return;\n }\n\n const op: ISetCellOperation = {\n type: \"setCell\",\n value: operationValue,\n };\n this.submitLocalMessage(op, ++this.messageId);\n }\n\n /**\n * {@inheritDoc ISharedCell.delete}\n */\n public delete() {\n // Delete the value locally.\n this.deleteCore();\n\n // If we are not attached, don't submit the op.\n if (!this.isAttached()) {\n return;\n }\n\n const op: IDeleteCellOperation = {\n type: \"deleteCell\",\n };\n this.submitLocalMessage(op, ++this.messageId);\n }\n\n /**\n * {@inheritDoc ISharedCell.empty}\n */\n public empty() {\n return this.data === undefined;\n }\n\n /**\n * Create a summary for the cell\n *\n * @returns the summary of the current state of the cell\n */\n protected summarizeCore(serializer: IFluidSerializer): ISummaryTreeWithStats {\n const content: ICellValue = { value: this.data };\n return createSingleBlobSummary(snapshotFileName, serializer.stringify(content, this.handle));\n }\n\n /**\n * {@inheritDoc @fluidframework/shared-object-base#SharedObject.loadCore}\n */\n protected async loadCore(storage: IChannelStorageService): Promise<void> {\n const content = await readAndParse<ICellValue>(storage, snapshotFileName);\n\n this.data = this.decode(content);\n }\n\n /**\n * Initialize a local instance of cell\n */\n protected initializeLocalCore() {\n this.data = undefined;\n }\n\n /**\n * Call back on disconnect\n */\n protected onDisconnect() { }\n\n /**\n * Apply inner op\n * @param content - ICellOperation content\n */\n private applyInnerOp(content: ICellOperation) {\n switch (content.type) {\n case \"setCell\":\n this.setCore(this.decode(content.value));\n break;\n\n case \"deleteCell\":\n this.deleteCore();\n break;\n\n default:\n throw new Error(\"Unknown operation\");\n }\n }\n\n /**\n * Process a cell operation\n *\n * @param message - the message to prepare\n * @param local - whether the message was sent by the local client\n * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.\n * For messages from a remote client, this will be undefined.\n */\n protected processCore(message: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown) {\n if (this.messageId !== this.messageIdObserved) {\n // We are waiting for an ACK on our change to this cell - we will ignore all messages until we get it.\n if (local) {\n const messageIdReceived = localOpMetadata as number;\n assert(messageIdReceived !== undefined && messageIdReceived <= this.messageId,\n 0x00c /* \"messageId is incorrect from from the local client's ACK\" */);\n\n // We got an ACK. Update messageIdObserved.\n this.messageIdObserved = localOpMetadata as number;\n }\n return;\n }\n\n if (message.type === MessageType.Operation && !local) {\n const op = message.contents as ICellOperation;\n this.applyInnerOp(op);\n }\n }\n\n private setCore(value: Serializable<T>) {\n this.data = value;\n this.emit(\"valueChanged\", value);\n }\n\n private deleteCore() {\n this.data = undefined;\n this.emit(\"delete\");\n }\n\n private decode(cellValue: ICellValue) {\n const value = cellValue.value;\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return this.serializer.decode(value);\n }\n\n /**\n * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}\n * @internal\n */\n protected applyStashedOp(content: unknown): unknown {\n const cellContent = content as ICellOperation;\n this.applyInnerOp(cellContent);\n ++this.messageId;\n return this.messageId;\n }\n}\n"]}
1
+ {"version":3,"file":"cell.js","sourceRoot":"","sources":["../src/cell.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,+DAAsD;AACtD,+EAA8F;AAS9F,+DAA4D;AAC5D,2EAA6G;AAC7G,+CAA4C;AA0B5C,MAAM,gBAAgB,GAAG,QAAQ,CAAC;AAElC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAa,UAAoB,SAAQ,iCAAkC;IAwCvE;;;;;;OAMG;IACH,YAAY,EAAU,EAAE,OAA+B,EAAE,UAA8B;QACnF,KAAK,CAAC,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;QAtBlD;;;WAGG;QACK,cAAS,GAAW,CAAC,CAAC,CAAC;QAE/B;;;WAGG;QACK,sBAAiB,GAAW,CAAC,CAAC,CAAC;QAEtB,sBAAiB,GAAa,EAAE,CAAC;IAWlD,CAAC;IA/CD;;;;;;OAMG;IACI,MAAM,CAAC,MAAM,CAAC,OAA+B,EAAE,EAAW;QAC7D,OAAO,OAAO,CAAC,aAAa,CAAC,EAAE,EAAE,yBAAW,CAAC,IAAI,CAAe,CAAC;IACrE,CAAC;IAED;;;;OAIG;IACI,MAAM,CAAC,UAAU;QACpB,OAAO,IAAI,yBAAW,EAAE,CAAC;IAC7B,CAAC;IA+BD;;OAEG;IACI,GAAG;QACN,OAAO,IAAI,CAAC,IAAI,CAAC;IACrB,CAAC;IAED;;OAEG;IACI,GAAG,CAAC,KAAsB;QAC7B,mCAAmC;QACnC,MAAM,cAAc,GAAe;YAC/B,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC;SACpD,CAAC;QAEF,yBAAyB;QACzB,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAE1C,+CAA+C;QAC/C,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE;YACpB,OAAO;SACV;QAED,MAAM,EAAE,GAAsB;YAC1B,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,cAAc;SACxB,CAAC;QACF,IAAI,CAAC,iBAAiB,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;IAC9C,CAAC;IAED;;OAEG;IACI,MAAM;QACT,4BAA4B;QAC5B,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAExC,+CAA+C;QAC/C,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE;YACpB,OAAO;SACV;QAED,MAAM,EAAE,GAAyB;YAC7B,IAAI,EAAE,YAAY;SACrB,CAAC;QACF,IAAI,CAAC,iBAAiB,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;IAC9C,CAAC;IAED;;OAEG;IACI,KAAK;QACR,OAAO,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC;IACnC,CAAC;IAED;;;;OAIG;IACO,aAAa,CAAC,UAA4B;QAChD,MAAM,OAAO,GAAe,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;QACjD,OAAO,IAAA,4CAAuB,EAAC,gBAAgB,EAAE,UAAU,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IACjG,CAAC;IAED;;OAEG;IACO,KAAK,CAAC,QAAQ,CAAC,OAA+B;QACpD,MAAM,OAAO,GAAG,MAAM,IAAA,2BAAY,EAAa,OAAO,EAAE,gBAAgB,CAAC,CAAC;QAE1E,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAED;;OAEG;IACO,mBAAmB;QACzB,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;IAC1B,CAAC;IAED;;OAEG;IACO,YAAY,KAAK,CAAC;IAE5B;;;OAGG;IACK,YAAY,CAAC,OAAuB;QACxC,QAAQ,OAAO,CAAC,IAAI,EAAE;YAClB,KAAK,SAAS;gBACV,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YAEpD,KAAK,YAAY;gBACb,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC;YAE7B;gBACI,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;SAC5C;IACL,CAAC;IAED;;;;;;;OAOG;IACO,WAAW,CAAC,OAAkC,EAAE,KAAc,EAAE,eAAwB;QAC9F,MAAM,cAAc,GAAG,eAAuC,CAAC;QAC/D,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,iBAAiB,EAAE;YAC3C,sGAAsG;YACtG,IAAI,KAAK,EAAE;gBACP,MAAM,iBAAiB,GAAG,cAAc,CAAC,gBAAgB,CAAC;gBAC1D,IAAA,qBAAM,EAAC,iBAAiB,KAAK,SAAS,IAAI,iBAAiB,IAAI,IAAI,CAAC,SAAS,EACzE,KAAK,CAAC,+DAA+D,CAAC,CAAC;gBAC3E,IAAA,qBAAM,EAAC,IAAI,CAAC,iBAAiB,KAAK,SAAS;oBACvC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,KAAK,cAAc,CAAC,gBAAgB,EAC7D,KAAK,CAAC,yCAAyC,CAAC,CAAC;gBACrD,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC;gBAC/B,2CAA2C;gBAC3C,IAAI,CAAC,iBAAiB,GAAG,cAAc,CAAC,gBAAgB,CAAC;aAC5D;YACD,OAAO;SACV;QAED,IAAI,OAAO,CAAC,IAAI,KAAK,kCAAW,CAAC,SAAS,IAAI,CAAC,KAAK,EAAE;YAClD,MAAM,EAAE,GAAG,OAAO,CAAC,QAA0B,CAAC;YAC9C,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;SACzB;IACL,CAAC;IAEO,OAAO,CAAC,KAAsB;QAClC,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;QAClB,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QACjC,OAAO,kBAAkB,CAAC;IAC9B,CAAC;IAEO,UAAU;QACd,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtC,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;QACtB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACpB,OAAO,kBAAkB,CAAC;IAC9B,CAAC;IAEO,MAAM,CAAC,SAAqB;QAChC,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC;QAC9B,+DAA+D;QAC/D,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACzC,CAAC;IAEO,qBAAqB,CAAC,EAAkB,EAAE,aAA+B;QAC7E,MAAM,gBAAgB,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC;QAC1C,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC9C,MAAM,aAAa,GAAyB;YACxC,gBAAgB,EAAE,aAAa;SAClC,CAAC;QACF,OAAO,aAAa,CAAC;IACzB,CAAC;IAED;;;OAGG;IACO,cAAc,CAAC,OAAgB;QACrC,MAAM,WAAW,GAAG,OAAyB,CAAC;QAC9C,MAAM,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC,qBAAqB,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAClE,CAAC;IAED;;;;OAIG;IACO,QAAQ,CAAC,OAAY,EAAE,eAAwB;QACrD,MAAM,cAAc,GAAG,eAAuC,CAAC;QAC/D,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,YAAY,EAAE;YAC7D,IAAI,cAAc,CAAC,aAAa,KAAK,SAAS,EAAE;gBAC5C,IAAI,CAAC,UAAU,EAAE,CAAC;aACrB;iBAAM;gBACH,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;aAC9C;YAED,MAAM,oBAAoB,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,CAAC;YAC1D,IAAI,oBAAoB,KAAK,cAAc,CAAC,gBAAgB,EAAE;gBAC1D,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;aAC9D;SACJ;aAAM;YACH,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;SAClD;IACL,CAAC;IAEA;;;;MAIE;IACK,iBAAiB,CAAC,EAAkB,EAAE,aAAmB;QAC7D,MAAM,aAAa,GAAG,IAAI,CAAC,qBAAqB,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;QACpE,IAAI,CAAC,kBAAkB,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;IAC/C,CAAC;CACJ;AAlQD,gCAkQC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/common-utils\";\nimport { ISequencedDocumentMessage, MessageType } from \"@fluidframework/protocol-definitions\";\nimport {\n IChannelAttributes,\n IFluidDataStoreRuntime,\n IChannelStorageService,\n IChannelFactory,\n Serializable,\n} from \"@fluidframework/datastore-definitions\";\nimport { ISummaryTreeWithStats } from \"@fluidframework/runtime-definitions\";\nimport { readAndParse } from \"@fluidframework/driver-utils\";\nimport { createSingleBlobSummary, IFluidSerializer, SharedObject } from \"@fluidframework/shared-object-base\";\nimport { CellFactory } from \"./cellFactory\";\nimport {\n ISharedCell,\n ISharedCellEvents,\n ICellLocalOpMetadata,\n} from \"./interfaces\";\n\n/**\n * Description of a cell delta operation\n */\ntype ICellOperation = ISetCellOperation | IDeleteCellOperation;\n\ninterface ISetCellOperation {\n type: \"setCell\";\n value: ICellValue;\n}\n\ninterface IDeleteCellOperation {\n type: \"deleteCell\";\n}\n\ninterface ICellValue {\n // The actual value contained in the cell which needs to be wrapped to handle undefined\n value: any;\n}\n\nconst snapshotFileName = \"header\";\n\n/**\n * The SharedCell distributed data structure can be used to store a single serializable value.\n *\n * @remarks\n * ### Creation\n *\n * To create a `SharedCell`, call the static create method:\n *\n * ```typescript\n * const myCell = SharedCell.create(this.runtime, id);\n * ```\n *\n * ### Usage\n *\n * The value stored in the cell can be set with the `.set()` method and retrieved with the `.get()` method:\n *\n * ```typescript\n * myCell.set(3);\n * console.log(myCell.get()); // 3\n * ```\n *\n * The value must only be plain JS objects or `SharedObject` handles (e.g. to another DDS or Fluid object).\n * In collaborative scenarios, the value is settled with a policy of _last write wins_.\n *\n * The `.delete()` method will delete the stored value from the cell:\n *\n * ```typescript\n * myCell.delete();\n * console.log(myCell.get()); // undefined\n * ```\n *\n * The `.empty()` method will check if the value is undefined.\n *\n * ```typescript\n * if (myCell.empty()) {\n * // myCell.get() will return undefined\n * } else {\n * // myCell.get() will return a non-undefined value\n * }\n * ```\n *\n * ### Eventing\n *\n * `SharedCell` is an `EventEmitter`, and will emit events when other clients make modifications. You should\n * register for these events and respond appropriately as the data is modified. `valueChanged` will be emitted\n * in response to a `set`, and `delete` will be emitted in response to a `delete`.\n */\nexport class SharedCell<T = any> extends SharedObject<ISharedCellEvents<T>>\n implements ISharedCell<T> {\n /**\n * Create a new shared cell\n *\n * @param runtime - data store runtime the new shared map belongs to\n * @param id - optional name of the shared map\n * @returns newly create shared map (but not attached yet)\n */\n public static create(runtime: IFluidDataStoreRuntime, id?: string) {\n return runtime.createChannel(id, CellFactory.Type) as SharedCell;\n }\n\n /**\n * Get a factory for SharedCell to register with the data store.\n *\n * @returns a factory that creates and load SharedCell\n */\n public static getFactory(): IChannelFactory {\n return new CellFactory();\n }\n /**\n * The data held by this cell.\n */\n private data: Serializable<T> | undefined;\n\n /**\n * This is used to assign a unique id to outgoing messages. It is used to track messages until\n * they are ack'd.\n */\n private messageId: number = -1;\n\n /**\n * This keeps track of the messageId of messages that have been ack'd. It is updated every time\n * we a message is ack'd with it's messageId.\n */\n private messageIdObserved: number = -1;\n\n private readonly pendingMessageIds: number[] = [];\n\n /**\n * Constructs a new shared cell. If the object is non-local an id and service interfaces will\n * be provided\n *\n * @param runtime - data store runtime the shared map belongs to\n * @param id - optional name of the shared map\n */\n constructor(id: string, runtime: IFluidDataStoreRuntime, attributes: IChannelAttributes) {\n super(id, runtime, attributes, \"fluid_cell_\");\n }\n\n /**\n * {@inheritDoc ISharedCell.get}\n */\n public get(): Serializable<T> | undefined {\n return this.data;\n }\n\n /**\n * {@inheritDoc ISharedCell.set}\n */\n public set(value: Serializable<T>) {\n // Serialize the value if required.\n const operationValue: ICellValue = {\n value: this.serializer.encode(value, this.handle),\n };\n\n // Set the value locally.\n const previousValue = this.setCore(value);\n\n // If we are not attached, don't submit the op.\n if (!this.isAttached()) {\n return;\n }\n\n const op: ISetCellOperation = {\n type: \"setCell\",\n value: operationValue,\n };\n this.submitCellMessage(op, previousValue);\n }\n\n /**\n * {@inheritDoc ISharedCell.delete}\n */\n public delete() {\n // Delete the value locally.\n const previousValue = this.deleteCore();\n\n // If we are not attached, don't submit the op.\n if (!this.isAttached()) {\n return;\n }\n\n const op: IDeleteCellOperation = {\n type: \"deleteCell\",\n };\n this.submitCellMessage(op, previousValue);\n }\n\n /**\n * {@inheritDoc ISharedCell.empty}\n */\n public empty() {\n return this.data === undefined;\n }\n\n /**\n * Create a summary for the cell\n *\n * @returns the summary of the current state of the cell\n */\n protected summarizeCore(serializer: IFluidSerializer): ISummaryTreeWithStats {\n const content: ICellValue = { value: this.data };\n return createSingleBlobSummary(snapshotFileName, serializer.stringify(content, this.handle));\n }\n\n /**\n * {@inheritDoc @fluidframework/shared-object-base#SharedObject.loadCore}\n */\n protected async loadCore(storage: IChannelStorageService): Promise<void> {\n const content = await readAndParse<ICellValue>(storage, snapshotFileName);\n\n this.data = this.decode(content);\n }\n\n /**\n * Initialize a local instance of cell\n */\n protected initializeLocalCore() {\n this.data = undefined;\n }\n\n /**\n * Call back on disconnect\n */\n protected onDisconnect() { }\n\n /**\n * Apply inner op\n * @param content - ICellOperation content\n */\n private applyInnerOp(content: ICellOperation) {\n switch (content.type) {\n case \"setCell\":\n return this.setCore(this.decode(content.value));\n\n case \"deleteCell\":\n return this.deleteCore();\n\n default:\n throw new Error(\"Unknown operation\");\n }\n }\n\n /**\n * Process a cell operation\n *\n * @param message - the message to prepare\n * @param local - whether the message was sent by the local client\n * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.\n * For messages from a remote client, this will be undefined.\n */\n protected processCore(message: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown) {\n const cellOpMetadata = localOpMetadata as ICellLocalOpMetadata;\n if (this.messageId !== this.messageIdObserved) {\n // We are waiting for an ACK on our change to this cell - we will ignore all messages until we get it.\n if (local) {\n const messageIdReceived = cellOpMetadata.pendingMessageId;\n assert(messageIdReceived !== undefined && messageIdReceived <= this.messageId,\n 0x00c /* \"messageId is incorrect from from the local client's ACK\" */);\n assert(this.pendingMessageIds !== undefined &&\n this.pendingMessageIds[0] === cellOpMetadata.pendingMessageId,\n 0x471 /* Unexpected pending message received */);\n this.pendingMessageIds.shift();\n // We got an ACK. Update messageIdObserved.\n this.messageIdObserved = cellOpMetadata.pendingMessageId;\n }\n return;\n }\n\n if (message.type === MessageType.Operation && !local) {\n const op = message.contents as ICellOperation;\n this.applyInnerOp(op);\n }\n }\n\n private setCore(value: Serializable<T>): Serializable<T> | undefined {\n const previousLocalValue = this.get();\n this.data = value;\n this.emit(\"valueChanged\", value);\n return previousLocalValue;\n }\n\n private deleteCore(): Serializable<T> | undefined {\n const previousLocalValue = this.get();\n this.data = undefined;\n this.emit(\"delete\");\n return previousLocalValue;\n }\n\n private decode(cellValue: ICellValue) {\n const value = cellValue.value;\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return this.serializer.decode(value);\n }\n\n private createLocalOpMetadata(op: ICellOperation, previousValue?: Serializable<T>): ICellLocalOpMetadata {\n const pendingMessageId = ++this.messageId;\n this.pendingMessageIds.push(pendingMessageId);\n const localMetadata: ICellLocalOpMetadata = {\n pendingMessageId, previousValue,\n };\n return localMetadata;\n }\n\n /**\n * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}\n * @internal\n */\n protected applyStashedOp(content: unknown): unknown {\n const cellContent = content as ICellOperation;\n const previousValue = this.applyInnerOp(cellContent);\n return this.createLocalOpMetadata(cellContent, previousValue);\n }\n\n /**\n * Rollback a local op\n * @param content - The operation to rollback\n * @param localOpMetadata - The local metadata associated with the op.\n */\n protected rollback(content: any, localOpMetadata: unknown) {\n const cellOpMetadata = localOpMetadata as ICellLocalOpMetadata;\n if (content.type === \"setCell\" || content.type === \"deleteCell\") {\n if (cellOpMetadata.previousValue === undefined) {\n this.deleteCore();\n } else {\n this.setCore(cellOpMetadata.previousValue);\n }\n\n const lastPendingMessageId = this.pendingMessageIds.pop();\n if (lastPendingMessageId !== cellOpMetadata.pendingMessageId) {\n throw new Error(\"Rollback op does not match last pending\");\n }\n } else {\n throw new Error(\"Unsupported op for rollback\");\n }\n }\n\n /**\n * Submit a cell message to remote clients.\n * @param op - The cell message\n * @param previousValue - The value of the cell before this op\n */\n private submitCellMessage(op: ICellOperation, previousValue?: any): void {\n const localMetadata = this.createLocalOpMetadata(op, previousValue);\n this.submitLocalMessage(op, localMetadata);\n }\n}\n"]}
@@ -35,4 +35,8 @@ export interface ISharedCell<T = any> extends ISharedObject<ISharedCellEvents<T>
35
35
  */
36
36
  delete(): void;
37
37
  }
38
+ export interface ICellLocalOpMetadata {
39
+ pendingMessageId: number;
40
+ previousValue?: any;
41
+ }
38
42
  //# sourceMappingURL=interfaces.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAErE,MAAM,WAAW,iBAAiB,CAAC,CAAC,CAAE,SAAQ,mBAAmB;IAC7D,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,OAAE;IACpE,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;CAC3C;AAED;;GAEG;AAEH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,GAAG,CAAE,SAAQ,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAC7E;;;;OAIG;IACH,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAEnC;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAElC;;;;OAIG;IACH,KAAK,IAAI,OAAO,CAAC;IAEjB;;OAEG;IACH,MAAM,IAAI,IAAI,CAAC;CAClB"}
1
+ {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAErE,MAAM,WAAW,iBAAiB,CAAC,CAAC,CAAE,SAAQ,mBAAmB;IAC7D,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,OAAE;IACpE,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;CAC3C;AAED;;GAEG;AAEH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,GAAG,CAAE,SAAQ,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAC7E;;;;OAIG;IACH,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAEnC;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAElC;;;;OAIG;IACH,KAAK,IAAI,OAAO,CAAC;IAEjB;;OAEG;IACH,MAAM,IAAI,IAAI,CAAC;CAClB;AACD,MAAM,WAAW,oBAAoB;IACjC,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,GAAG,CAAC;CACvB"}
@@ -1 +1 @@
1
- {"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ISharedObject, ISharedObjectEvents } from \"@fluidframework/shared-object-base\";\nimport { Serializable } from \"@fluidframework/datastore-definitions\";\n\nexport interface ISharedCellEvents<T> extends ISharedObjectEvents {\n (event: \"valueChanged\", listener: (value: Serializable<T>) => void);\n (event: \"delete\", listener: () => void);\n}\n\n/**\n * Shared cell interface\n */\n\nexport interface ISharedCell<T = any> extends ISharedObject<ISharedCellEvents<T>> {\n /**\n * Retrieves the cell value.\n *\n * @returns - the value of the cell\n */\n get(): Serializable<T> | undefined;\n\n /**\n * Sets the cell value.\n *\n * @param value - a JSON-able or SharedObject value to set the cell to\n */\n set(value: Serializable<T>): void;\n\n /**\n * Checks whether cell is empty or not.\n *\n * @returns - `true` if the value of cell is `undefined`, `false` otherwise\n */\n empty(): boolean;\n\n /**\n * Delete the value from the cell.\n */\n delete(): void;\n}\n"]}
1
+ {"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ISharedObject, ISharedObjectEvents } from \"@fluidframework/shared-object-base\";\nimport { Serializable } from \"@fluidframework/datastore-definitions\";\n\nexport interface ISharedCellEvents<T> extends ISharedObjectEvents {\n (event: \"valueChanged\", listener: (value: Serializable<T>) => void);\n (event: \"delete\", listener: () => void);\n}\n\n/**\n * Shared cell interface\n */\n\nexport interface ISharedCell<T = any> extends ISharedObject<ISharedCellEvents<T>> {\n /**\n * Retrieves the cell value.\n *\n * @returns - the value of the cell\n */\n get(): Serializable<T> | undefined;\n\n /**\n * Sets the cell value.\n *\n * @param value - a JSON-able or SharedObject value to set the cell to\n */\n set(value: Serializable<T>): void;\n\n /**\n * Checks whether cell is empty or not.\n *\n * @returns - `true` if the value of cell is `undefined`, `false` otherwise\n */\n empty(): boolean;\n\n /**\n * Delete the value from the cell.\n */\n delete(): void;\n}\nexport interface ICellLocalOpMetadata {\n pendingMessageId: number;\n previousValue?: any;\n}\n"]}
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/cell";
8
- export declare const pkgVersion = "2.0.0-internal.2.1.1";
8
+ export declare const pkgVersion = "2.0.0-internal.2.2.0";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -8,5 +8,5 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.pkgVersion = exports.pkgName = void 0;
10
10
  exports.pkgName = "@fluidframework/cell";
11
- exports.pkgVersion = "2.0.0-internal.2.1.1";
11
+ exports.pkgVersion = "2.0.0-internal.2.2.0";
12
12
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,sBAAsB,CAAC;AACjC,QAAA,UAAU,GAAG,sBAAsB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/cell\";\nexport const pkgVersion = \"2.0.0-internal.2.1.1\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,sBAAsB,CAAC;AACjC,QAAA,UAAU,GAAG,sBAAsB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/cell\";\nexport const pkgVersion = \"2.0.0-internal.2.2.0\";\n"]}
package/lib/cell.d.ts CHANGED
@@ -83,6 +83,7 @@ export declare class SharedCell<T = any> extends SharedObject<ISharedCellEvents<
83
83
  * we a message is ack'd with it's messageId.
84
84
  */
85
85
  private messageIdObserved;
86
+ private readonly pendingMessageIds;
86
87
  /**
87
88
  * Constructs a new shared cell. If the object is non-local an id and service interfaces will
88
89
  * be provided
@@ -142,10 +143,23 @@ export declare class SharedCell<T = any> extends SharedObject<ISharedCellEvents<
142
143
  private setCore;
143
144
  private deleteCore;
144
145
  private decode;
146
+ private createLocalOpMetadata;
145
147
  /**
146
148
  * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}
147
149
  * @internal
148
150
  */
149
151
  protected applyStashedOp(content: unknown): unknown;
152
+ /**
153
+ * Rollback a local op
154
+ * @param content - The operation to rollback
155
+ * @param localOpMetadata - The local metadata associated with the op.
156
+ */
157
+ protected rollback(content: any, localOpMetadata: unknown): void;
158
+ /**
159
+ * Submit a cell message to remote clients.
160
+ * @param op - The cell message
161
+ * @param previousValue - The value of the cell before this op
162
+ */
163
+ private submitCellMessage;
150
164
  }
151
165
  //# sourceMappingURL=cell.d.ts.map
package/lib/cell.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cell.d.ts","sourceRoot":"","sources":["../src/cell.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,yBAAyB,EAAe,MAAM,sCAAsC,CAAC;AAC9F,OAAO,EACH,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,eAAe,EACf,YAAY,EACf,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAE5E,OAAO,EAA2B,gBAAgB,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAE7G,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAuB9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,qBAAa,UAAU,CAAC,CAAC,GAAG,GAAG,CAAE,SAAQ,YAAY,CAAC,iBAAiB,CAAC,CAAC,CAAC,CACtE,YAAW,WAAW,CAAC,CAAC,CAAC;IACzB;;;;;;OAMG;WACW,MAAM,CAAC,OAAO,EAAE,sBAAsB,EAAE,EAAE,CAAC,EAAE,MAAM;IAIjE;;;;OAIG;WACW,UAAU,IAAI,eAAe;IAG3C;;OAEG;IACH,OAAO,CAAC,IAAI,CAA8B;IAE1C;;;OAGG;IACH,OAAO,CAAC,SAAS,CAAc;IAE/B;;;OAGG;IACH,OAAO,CAAC,iBAAiB,CAAc;IAEvC;;;;;;OAMG;gBACS,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,kBAAkB;IAIvF;;OAEG;IACI,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS;IAIzC;;OAEG;IACI,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;IAqBjC;;OAEG;IACI,MAAM;IAeb;;OAEG;IACI,KAAK;IAIZ;;;;OAIG;IACH,SAAS,CAAC,aAAa,CAAC,UAAU,EAAE,gBAAgB,GAAG,qBAAqB;IAK5E;;OAEG;cACa,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxE;;OAEG;IACH,SAAS,CAAC,mBAAmB;IAI7B;;OAEG;IACH,SAAS,CAAC,YAAY;IAEtB;;;OAGG;IACH,OAAO,CAAC,YAAY;IAepB;;;;;;;OAOG;IACH,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,yBAAyB,EAAE,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO;IAoBlG,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,MAAM;IAMd;;;OAGG;IACH,SAAS,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO;CAMtD"}
1
+ {"version":3,"file":"cell.d.ts","sourceRoot":"","sources":["../src/cell.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,yBAAyB,EAAe,MAAM,sCAAsC,CAAC;AAC9F,OAAO,EACH,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,eAAe,EACf,YAAY,EACf,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAE5E,OAAO,EAA2B,gBAAgB,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAE7G,OAAO,EACH,WAAW,EACX,iBAAiB,EAEpB,MAAM,cAAc,CAAC;AAuBtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,qBAAa,UAAU,CAAC,CAAC,GAAG,GAAG,CAAE,SAAQ,YAAY,CAAC,iBAAiB,CAAC,CAAC,CAAC,CACtE,YAAW,WAAW,CAAC,CAAC,CAAC;IACzB;;;;;;OAMG;WACW,MAAM,CAAC,OAAO,EAAE,sBAAsB,EAAE,EAAE,CAAC,EAAE,MAAM;IAIjE;;;;OAIG;WACW,UAAU,IAAI,eAAe;IAG3C;;OAEG;IACH,OAAO,CAAC,IAAI,CAA8B;IAE1C;;;OAGG;IACH,OAAO,CAAC,SAAS,CAAc;IAE/B;;;OAGG;IACH,OAAO,CAAC,iBAAiB,CAAc;IAEvC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAgB;IAElD;;;;;;OAMG;gBACS,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,kBAAkB;IAIvF;;OAEG;IACI,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS;IAIzC;;OAEG;IACI,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;IAqBjC;;OAEG;IACI,MAAM;IAeb;;OAEG;IACI,KAAK;IAIZ;;;;OAIG;IACH,SAAS,CAAC,aAAa,CAAC,UAAU,EAAE,gBAAgB,GAAG,qBAAqB;IAK5E;;OAEG;cACa,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxE;;OAEG;IACH,SAAS,CAAC,mBAAmB;IAI7B;;OAEG;IACH,SAAS,CAAC,YAAY;IAEtB;;;OAGG;IACH,OAAO,CAAC,YAAY;IAapB;;;;;;;OAOG;IACH,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,yBAAyB,EAAE,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO;IAwBlG,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,qBAAqB;IAS7B;;;OAGG;IACH,SAAS,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO;IAMnD;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,eAAe,EAAE,OAAO;IAkBxD;;;;MAIE;IACH,OAAO,CAAC,iBAAiB;CAI5B"}
package/lib/cell.js CHANGED
@@ -75,6 +75,7 @@ export class SharedCell extends SharedObject {
75
75
  * we a message is ack'd with it's messageId.
76
76
  */
77
77
  this.messageIdObserved = -1;
78
+ this.pendingMessageIds = [];
78
79
  }
79
80
  /**
80
81
  * Create a new shared cell
@@ -109,7 +110,7 @@ export class SharedCell extends SharedObject {
109
110
  value: this.serializer.encode(value, this.handle),
110
111
  };
111
112
  // Set the value locally.
112
- this.setCore(value);
113
+ const previousValue = this.setCore(value);
113
114
  // If we are not attached, don't submit the op.
114
115
  if (!this.isAttached()) {
115
116
  return;
@@ -118,14 +119,14 @@ export class SharedCell extends SharedObject {
118
119
  type: "setCell",
119
120
  value: operationValue,
120
121
  };
121
- this.submitLocalMessage(op, ++this.messageId);
122
+ this.submitCellMessage(op, previousValue);
122
123
  }
123
124
  /**
124
125
  * {@inheritDoc ISharedCell.delete}
125
126
  */
126
127
  delete() {
127
128
  // Delete the value locally.
128
- this.deleteCore();
129
+ const previousValue = this.deleteCore();
129
130
  // If we are not attached, don't submit the op.
130
131
  if (!this.isAttached()) {
131
132
  return;
@@ -133,7 +134,7 @@ export class SharedCell extends SharedObject {
133
134
  const op = {
134
135
  type: "deleteCell",
135
136
  };
136
- this.submitLocalMessage(op, ++this.messageId);
137
+ this.submitCellMessage(op, previousValue);
137
138
  }
138
139
  /**
139
140
  * {@inheritDoc ISharedCell.empty}
@@ -174,11 +175,9 @@ export class SharedCell extends SharedObject {
174
175
  applyInnerOp(content) {
175
176
  switch (content.type) {
176
177
  case "setCell":
177
- this.setCore(this.decode(content.value));
178
- break;
178
+ return this.setCore(this.decode(content.value));
179
179
  case "deleteCell":
180
- this.deleteCore();
181
- break;
180
+ return this.deleteCore();
182
181
  default:
183
182
  throw new Error("Unknown operation");
184
183
  }
@@ -192,13 +191,17 @@ export class SharedCell extends SharedObject {
192
191
  * For messages from a remote client, this will be undefined.
193
192
  */
194
193
  processCore(message, local, localOpMetadata) {
194
+ const cellOpMetadata = localOpMetadata;
195
195
  if (this.messageId !== this.messageIdObserved) {
196
196
  // We are waiting for an ACK on our change to this cell - we will ignore all messages until we get it.
197
197
  if (local) {
198
- const messageIdReceived = localOpMetadata;
198
+ const messageIdReceived = cellOpMetadata.pendingMessageId;
199
199
  assert(messageIdReceived !== undefined && messageIdReceived <= this.messageId, 0x00c /* "messageId is incorrect from from the local client's ACK" */);
200
+ assert(this.pendingMessageIds !== undefined &&
201
+ this.pendingMessageIds[0] === cellOpMetadata.pendingMessageId, 0x471 /* Unexpected pending message received */);
202
+ this.pendingMessageIds.shift();
200
203
  // We got an ACK. Update messageIdObserved.
201
- this.messageIdObserved = localOpMetadata;
204
+ this.messageIdObserved = cellOpMetadata.pendingMessageId;
202
205
  }
203
206
  return;
204
207
  }
@@ -208,27 +211,70 @@ export class SharedCell extends SharedObject {
208
211
  }
209
212
  }
210
213
  setCore(value) {
214
+ const previousLocalValue = this.get();
211
215
  this.data = value;
212
216
  this.emit("valueChanged", value);
217
+ return previousLocalValue;
213
218
  }
214
219
  deleteCore() {
220
+ const previousLocalValue = this.get();
215
221
  this.data = undefined;
216
222
  this.emit("delete");
223
+ return previousLocalValue;
217
224
  }
218
225
  decode(cellValue) {
219
226
  const value = cellValue.value;
220
227
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
221
228
  return this.serializer.decode(value);
222
229
  }
230
+ createLocalOpMetadata(op, previousValue) {
231
+ const pendingMessageId = ++this.messageId;
232
+ this.pendingMessageIds.push(pendingMessageId);
233
+ const localMetadata = {
234
+ pendingMessageId, previousValue,
235
+ };
236
+ return localMetadata;
237
+ }
223
238
  /**
224
239
  * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}
225
240
  * @internal
226
241
  */
227
242
  applyStashedOp(content) {
228
243
  const cellContent = content;
229
- this.applyInnerOp(cellContent);
230
- ++this.messageId;
231
- return this.messageId;
244
+ const previousValue = this.applyInnerOp(cellContent);
245
+ return this.createLocalOpMetadata(cellContent, previousValue);
246
+ }
247
+ /**
248
+ * Rollback a local op
249
+ * @param content - The operation to rollback
250
+ * @param localOpMetadata - The local metadata associated with the op.
251
+ */
252
+ rollback(content, localOpMetadata) {
253
+ const cellOpMetadata = localOpMetadata;
254
+ if (content.type === "setCell" || content.type === "deleteCell") {
255
+ if (cellOpMetadata.previousValue === undefined) {
256
+ this.deleteCore();
257
+ }
258
+ else {
259
+ this.setCore(cellOpMetadata.previousValue);
260
+ }
261
+ const lastPendingMessageId = this.pendingMessageIds.pop();
262
+ if (lastPendingMessageId !== cellOpMetadata.pendingMessageId) {
263
+ throw new Error("Rollback op does not match last pending");
264
+ }
265
+ }
266
+ else {
267
+ throw new Error("Unsupported op for rollback");
268
+ }
269
+ }
270
+ /**
271
+ * Submit a cell message to remote clients.
272
+ * @param op - The cell message
273
+ * @param previousValue - The value of the cell before this op
274
+ */
275
+ submitCellMessage(op, previousValue) {
276
+ const localMetadata = this.createLocalOpMetadata(op, previousValue);
277
+ this.submitLocalMessage(op, localMetadata);
232
278
  }
233
279
  }
234
280
  //# sourceMappingURL=cell.js.map
package/lib/cell.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cell.js","sourceRoot":"","sources":["../src/cell.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/common-utils\";\nimport { ISequencedDocumentMessage, MessageType } from \"@fluidframework/protocol-definitions\";\nimport {\n IChannelAttributes,\n IFluidDataStoreRuntime,\n IChannelStorageService,\n IChannelFactory,\n Serializable,\n} from \"@fluidframework/datastore-definitions\";\nimport { ISummaryTreeWithStats } from \"@fluidframework/runtime-definitions\";\nimport { readAndParse } from \"@fluidframework/driver-utils\";\nimport { createSingleBlobSummary, IFluidSerializer, SharedObject } from \"@fluidframework/shared-object-base\";\nimport { CellFactory } from \"./cellFactory\";\nimport { ISharedCell, ISharedCellEvents } from \"./interfaces\";\n\n/**\n * Description of a cell delta operation\n */\ntype ICellOperation = ISetCellOperation | IDeleteCellOperation;\n\ninterface ISetCellOperation {\n type: \"setCell\";\n value: ICellValue;\n}\n\ninterface IDeleteCellOperation {\n type: \"deleteCell\";\n}\n\ninterface ICellValue {\n // The actual value contained in the cell which needs to be wrapped to handle undefined\n value: any;\n}\n\nconst snapshotFileName = \"header\";\n\n/**\n * The SharedCell distributed data structure can be used to store a single serializable value.\n *\n * @remarks\n * ### Creation\n *\n * To create a `SharedCell`, call the static create method:\n *\n * ```typescript\n * const myCell = SharedCell.create(this.runtime, id);\n * ```\n *\n * ### Usage\n *\n * The value stored in the cell can be set with the `.set()` method and retrieved with the `.get()` method:\n *\n * ```typescript\n * myCell.set(3);\n * console.log(myCell.get()); // 3\n * ```\n *\n * The value must only be plain JS objects or `SharedObject` handles (e.g. to another DDS or Fluid object).\n * In collaborative scenarios, the value is settled with a policy of _last write wins_.\n *\n * The `.delete()` method will delete the stored value from the cell:\n *\n * ```typescript\n * myCell.delete();\n * console.log(myCell.get()); // undefined\n * ```\n *\n * The `.empty()` method will check if the value is undefined.\n *\n * ```typescript\n * if (myCell.empty()) {\n * // myCell.get() will return undefined\n * } else {\n * // myCell.get() will return a non-undefined value\n * }\n * ```\n *\n * ### Eventing\n *\n * `SharedCell` is an `EventEmitter`, and will emit events when other clients make modifications. You should\n * register for these events and respond appropriately as the data is modified. `valueChanged` will be emitted\n * in response to a `set`, and `delete` will be emitted in response to a `delete`.\n */\nexport class SharedCell<T = any> extends SharedObject<ISharedCellEvents<T>>\n implements ISharedCell<T> {\n /**\n * Create a new shared cell\n *\n * @param runtime - data store runtime the new shared map belongs to\n * @param id - optional name of the shared map\n * @returns newly create shared map (but not attached yet)\n */\n public static create(runtime: IFluidDataStoreRuntime, id?: string) {\n return runtime.createChannel(id, CellFactory.Type) as SharedCell;\n }\n\n /**\n * Get a factory for SharedCell to register with the data store.\n *\n * @returns a factory that creates and load SharedCell\n */\n public static getFactory(): IChannelFactory {\n return new CellFactory();\n }\n /**\n * The data held by this cell.\n */\n private data: Serializable<T> | undefined;\n\n /**\n * This is used to assign a unique id to outgoing messages. It is used to track messages until\n * they are ack'd.\n */\n private messageId: number = -1;\n\n /**\n * This keeps track of the messageId of messages that have been ack'd. It is updated every time\n * we a message is ack'd with it's messageId.\n */\n private messageIdObserved: number = -1;\n\n /**\n * Constructs a new shared cell. If the object is non-local an id and service interfaces will\n * be provided\n *\n * @param runtime - data store runtime the shared map belongs to\n * @param id - optional name of the shared map\n */\n constructor(id: string, runtime: IFluidDataStoreRuntime, attributes: IChannelAttributes) {\n super(id, runtime, attributes, \"fluid_cell_\");\n }\n\n /**\n * {@inheritDoc ISharedCell.get}\n */\n public get(): Serializable<T> | undefined {\n return this.data;\n }\n\n /**\n * {@inheritDoc ISharedCell.set}\n */\n public set(value: Serializable<T>) {\n // Serialize the value if required.\n const operationValue: ICellValue = {\n value: this.serializer.encode(value, this.handle),\n };\n\n // Set the value locally.\n this.setCore(value);\n\n // If we are not attached, don't submit the op.\n if (!this.isAttached()) {\n return;\n }\n\n const op: ISetCellOperation = {\n type: \"setCell\",\n value: operationValue,\n };\n this.submitLocalMessage(op, ++this.messageId);\n }\n\n /**\n * {@inheritDoc ISharedCell.delete}\n */\n public delete() {\n // Delete the value locally.\n this.deleteCore();\n\n // If we are not attached, don't submit the op.\n if (!this.isAttached()) {\n return;\n }\n\n const op: IDeleteCellOperation = {\n type: \"deleteCell\",\n };\n this.submitLocalMessage(op, ++this.messageId);\n }\n\n /**\n * {@inheritDoc ISharedCell.empty}\n */\n public empty() {\n return this.data === undefined;\n }\n\n /**\n * Create a summary for the cell\n *\n * @returns the summary of the current state of the cell\n */\n protected summarizeCore(serializer: IFluidSerializer): ISummaryTreeWithStats {\n const content: ICellValue = { value: this.data };\n return createSingleBlobSummary(snapshotFileName, serializer.stringify(content, this.handle));\n }\n\n /**\n * {@inheritDoc @fluidframework/shared-object-base#SharedObject.loadCore}\n */\n protected async loadCore(storage: IChannelStorageService): Promise<void> {\n const content = await readAndParse<ICellValue>(storage, snapshotFileName);\n\n this.data = this.decode(content);\n }\n\n /**\n * Initialize a local instance of cell\n */\n protected initializeLocalCore() {\n this.data = undefined;\n }\n\n /**\n * Call back on disconnect\n */\n protected onDisconnect() { }\n\n /**\n * Apply inner op\n * @param content - ICellOperation content\n */\n private applyInnerOp(content: ICellOperation) {\n switch (content.type) {\n case \"setCell\":\n this.setCore(this.decode(content.value));\n break;\n\n case \"deleteCell\":\n this.deleteCore();\n break;\n\n default:\n throw new Error(\"Unknown operation\");\n }\n }\n\n /**\n * Process a cell operation\n *\n * @param message - the message to prepare\n * @param local - whether the message was sent by the local client\n * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.\n * For messages from a remote client, this will be undefined.\n */\n protected processCore(message: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown) {\n if (this.messageId !== this.messageIdObserved) {\n // We are waiting for an ACK on our change to this cell - we will ignore all messages until we get it.\n if (local) {\n const messageIdReceived = localOpMetadata as number;\n assert(messageIdReceived !== undefined && messageIdReceived <= this.messageId,\n 0x00c /* \"messageId is incorrect from from the local client's ACK\" */);\n\n // We got an ACK. Update messageIdObserved.\n this.messageIdObserved = localOpMetadata as number;\n }\n return;\n }\n\n if (message.type === MessageType.Operation && !local) {\n const op = message.contents as ICellOperation;\n this.applyInnerOp(op);\n }\n }\n\n private setCore(value: Serializable<T>) {\n this.data = value;\n this.emit(\"valueChanged\", value);\n }\n\n private deleteCore() {\n this.data = undefined;\n this.emit(\"delete\");\n }\n\n private decode(cellValue: ICellValue) {\n const value = cellValue.value;\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return this.serializer.decode(value);\n }\n\n /**\n * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}\n * @internal\n */\n protected applyStashedOp(content: unknown): unknown {\n const cellContent = content as ICellOperation;\n this.applyInnerOp(cellContent);\n ++this.messageId;\n return this.messageId;\n }\n}\n"]}
1
+ {"version":3,"file":"cell.js","sourceRoot":"","sources":["../src/cell.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/common-utils\";\nimport { ISequencedDocumentMessage, MessageType } from \"@fluidframework/protocol-definitions\";\nimport {\n IChannelAttributes,\n IFluidDataStoreRuntime,\n IChannelStorageService,\n IChannelFactory,\n Serializable,\n} from \"@fluidframework/datastore-definitions\";\nimport { ISummaryTreeWithStats } from \"@fluidframework/runtime-definitions\";\nimport { readAndParse } from \"@fluidframework/driver-utils\";\nimport { createSingleBlobSummary, IFluidSerializer, SharedObject } from \"@fluidframework/shared-object-base\";\nimport { CellFactory } from \"./cellFactory\";\nimport {\n ISharedCell,\n ISharedCellEvents,\n ICellLocalOpMetadata,\n} from \"./interfaces\";\n\n/**\n * Description of a cell delta operation\n */\ntype ICellOperation = ISetCellOperation | IDeleteCellOperation;\n\ninterface ISetCellOperation {\n type: \"setCell\";\n value: ICellValue;\n}\n\ninterface IDeleteCellOperation {\n type: \"deleteCell\";\n}\n\ninterface ICellValue {\n // The actual value contained in the cell which needs to be wrapped to handle undefined\n value: any;\n}\n\nconst snapshotFileName = \"header\";\n\n/**\n * The SharedCell distributed data structure can be used to store a single serializable value.\n *\n * @remarks\n * ### Creation\n *\n * To create a `SharedCell`, call the static create method:\n *\n * ```typescript\n * const myCell = SharedCell.create(this.runtime, id);\n * ```\n *\n * ### Usage\n *\n * The value stored in the cell can be set with the `.set()` method and retrieved with the `.get()` method:\n *\n * ```typescript\n * myCell.set(3);\n * console.log(myCell.get()); // 3\n * ```\n *\n * The value must only be plain JS objects or `SharedObject` handles (e.g. to another DDS or Fluid object).\n * In collaborative scenarios, the value is settled with a policy of _last write wins_.\n *\n * The `.delete()` method will delete the stored value from the cell:\n *\n * ```typescript\n * myCell.delete();\n * console.log(myCell.get()); // undefined\n * ```\n *\n * The `.empty()` method will check if the value is undefined.\n *\n * ```typescript\n * if (myCell.empty()) {\n * // myCell.get() will return undefined\n * } else {\n * // myCell.get() will return a non-undefined value\n * }\n * ```\n *\n * ### Eventing\n *\n * `SharedCell` is an `EventEmitter`, and will emit events when other clients make modifications. You should\n * register for these events and respond appropriately as the data is modified. `valueChanged` will be emitted\n * in response to a `set`, and `delete` will be emitted in response to a `delete`.\n */\nexport class SharedCell<T = any> extends SharedObject<ISharedCellEvents<T>>\n implements ISharedCell<T> {\n /**\n * Create a new shared cell\n *\n * @param runtime - data store runtime the new shared map belongs to\n * @param id - optional name of the shared map\n * @returns newly create shared map (but not attached yet)\n */\n public static create(runtime: IFluidDataStoreRuntime, id?: string) {\n return runtime.createChannel(id, CellFactory.Type) as SharedCell;\n }\n\n /**\n * Get a factory for SharedCell to register with the data store.\n *\n * @returns a factory that creates and load SharedCell\n */\n public static getFactory(): IChannelFactory {\n return new CellFactory();\n }\n /**\n * The data held by this cell.\n */\n private data: Serializable<T> | undefined;\n\n /**\n * This is used to assign a unique id to outgoing messages. It is used to track messages until\n * they are ack'd.\n */\n private messageId: number = -1;\n\n /**\n * This keeps track of the messageId of messages that have been ack'd. It is updated every time\n * we a message is ack'd with it's messageId.\n */\n private messageIdObserved: number = -1;\n\n private readonly pendingMessageIds: number[] = [];\n\n /**\n * Constructs a new shared cell. If the object is non-local an id and service interfaces will\n * be provided\n *\n * @param runtime - data store runtime the shared map belongs to\n * @param id - optional name of the shared map\n */\n constructor(id: string, runtime: IFluidDataStoreRuntime, attributes: IChannelAttributes) {\n super(id, runtime, attributes, \"fluid_cell_\");\n }\n\n /**\n * {@inheritDoc ISharedCell.get}\n */\n public get(): Serializable<T> | undefined {\n return this.data;\n }\n\n /**\n * {@inheritDoc ISharedCell.set}\n */\n public set(value: Serializable<T>) {\n // Serialize the value if required.\n const operationValue: ICellValue = {\n value: this.serializer.encode(value, this.handle),\n };\n\n // Set the value locally.\n const previousValue = this.setCore(value);\n\n // If we are not attached, don't submit the op.\n if (!this.isAttached()) {\n return;\n }\n\n const op: ISetCellOperation = {\n type: \"setCell\",\n value: operationValue,\n };\n this.submitCellMessage(op, previousValue);\n }\n\n /**\n * {@inheritDoc ISharedCell.delete}\n */\n public delete() {\n // Delete the value locally.\n const previousValue = this.deleteCore();\n\n // If we are not attached, don't submit the op.\n if (!this.isAttached()) {\n return;\n }\n\n const op: IDeleteCellOperation = {\n type: \"deleteCell\",\n };\n this.submitCellMessage(op, previousValue);\n }\n\n /**\n * {@inheritDoc ISharedCell.empty}\n */\n public empty() {\n return this.data === undefined;\n }\n\n /**\n * Create a summary for the cell\n *\n * @returns the summary of the current state of the cell\n */\n protected summarizeCore(serializer: IFluidSerializer): ISummaryTreeWithStats {\n const content: ICellValue = { value: this.data };\n return createSingleBlobSummary(snapshotFileName, serializer.stringify(content, this.handle));\n }\n\n /**\n * {@inheritDoc @fluidframework/shared-object-base#SharedObject.loadCore}\n */\n protected async loadCore(storage: IChannelStorageService): Promise<void> {\n const content = await readAndParse<ICellValue>(storage, snapshotFileName);\n\n this.data = this.decode(content);\n }\n\n /**\n * Initialize a local instance of cell\n */\n protected initializeLocalCore() {\n this.data = undefined;\n }\n\n /**\n * Call back on disconnect\n */\n protected onDisconnect() { }\n\n /**\n * Apply inner op\n * @param content - ICellOperation content\n */\n private applyInnerOp(content: ICellOperation) {\n switch (content.type) {\n case \"setCell\":\n return this.setCore(this.decode(content.value));\n\n case \"deleteCell\":\n return this.deleteCore();\n\n default:\n throw new Error(\"Unknown operation\");\n }\n }\n\n /**\n * Process a cell operation\n *\n * @param message - the message to prepare\n * @param local - whether the message was sent by the local client\n * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.\n * For messages from a remote client, this will be undefined.\n */\n protected processCore(message: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown) {\n const cellOpMetadata = localOpMetadata as ICellLocalOpMetadata;\n if (this.messageId !== this.messageIdObserved) {\n // We are waiting for an ACK on our change to this cell - we will ignore all messages until we get it.\n if (local) {\n const messageIdReceived = cellOpMetadata.pendingMessageId;\n assert(messageIdReceived !== undefined && messageIdReceived <= this.messageId,\n 0x00c /* \"messageId is incorrect from from the local client's ACK\" */);\n assert(this.pendingMessageIds !== undefined &&\n this.pendingMessageIds[0] === cellOpMetadata.pendingMessageId,\n 0x471 /* Unexpected pending message received */);\n this.pendingMessageIds.shift();\n // We got an ACK. Update messageIdObserved.\n this.messageIdObserved = cellOpMetadata.pendingMessageId;\n }\n return;\n }\n\n if (message.type === MessageType.Operation && !local) {\n const op = message.contents as ICellOperation;\n this.applyInnerOp(op);\n }\n }\n\n private setCore(value: Serializable<T>): Serializable<T> | undefined {\n const previousLocalValue = this.get();\n this.data = value;\n this.emit(\"valueChanged\", value);\n return previousLocalValue;\n }\n\n private deleteCore(): Serializable<T> | undefined {\n const previousLocalValue = this.get();\n this.data = undefined;\n this.emit(\"delete\");\n return previousLocalValue;\n }\n\n private decode(cellValue: ICellValue) {\n const value = cellValue.value;\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return this.serializer.decode(value);\n }\n\n private createLocalOpMetadata(op: ICellOperation, previousValue?: Serializable<T>): ICellLocalOpMetadata {\n const pendingMessageId = ++this.messageId;\n this.pendingMessageIds.push(pendingMessageId);\n const localMetadata: ICellLocalOpMetadata = {\n pendingMessageId, previousValue,\n };\n return localMetadata;\n }\n\n /**\n * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}\n * @internal\n */\n protected applyStashedOp(content: unknown): unknown {\n const cellContent = content as ICellOperation;\n const previousValue = this.applyInnerOp(cellContent);\n return this.createLocalOpMetadata(cellContent, previousValue);\n }\n\n /**\n * Rollback a local op\n * @param content - The operation to rollback\n * @param localOpMetadata - The local metadata associated with the op.\n */\n protected rollback(content: any, localOpMetadata: unknown) {\n const cellOpMetadata = localOpMetadata as ICellLocalOpMetadata;\n if (content.type === \"setCell\" || content.type === \"deleteCell\") {\n if (cellOpMetadata.previousValue === undefined) {\n this.deleteCore();\n } else {\n this.setCore(cellOpMetadata.previousValue);\n }\n\n const lastPendingMessageId = this.pendingMessageIds.pop();\n if (lastPendingMessageId !== cellOpMetadata.pendingMessageId) {\n throw new Error(\"Rollback op does not match last pending\");\n }\n } else {\n throw new Error(\"Unsupported op for rollback\");\n }\n }\n\n /**\n * Submit a cell message to remote clients.\n * @param op - The cell message\n * @param previousValue - The value of the cell before this op\n */\n private submitCellMessage(op: ICellOperation, previousValue?: any): void {\n const localMetadata = this.createLocalOpMetadata(op, previousValue);\n this.submitLocalMessage(op, localMetadata);\n }\n}\n"]}
@@ -35,4 +35,8 @@ export interface ISharedCell<T = any> extends ISharedObject<ISharedCellEvents<T>
35
35
  */
36
36
  delete(): void;
37
37
  }
38
+ export interface ICellLocalOpMetadata {
39
+ pendingMessageId: number;
40
+ previousValue?: any;
41
+ }
38
42
  //# sourceMappingURL=interfaces.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAErE,MAAM,WAAW,iBAAiB,CAAC,CAAC,CAAE,SAAQ,mBAAmB;IAC7D,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,OAAE;IACpE,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;CAC3C;AAED;;GAEG;AAEH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,GAAG,CAAE,SAAQ,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAC7E;;;;OAIG;IACH,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAEnC;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAElC;;;;OAIG;IACH,KAAK,IAAI,OAAO,CAAC;IAEjB;;OAEG;IACH,MAAM,IAAI,IAAI,CAAC;CAClB"}
1
+ {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAErE,MAAM,WAAW,iBAAiB,CAAC,CAAC,CAAE,SAAQ,mBAAmB;IAC7D,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,OAAE;IACpE,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;CAC3C;AAED;;GAEG;AAEH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,GAAG,CAAE,SAAQ,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAC7E;;;;OAIG;IACH,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAEnC;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAElC;;;;OAIG;IACH,KAAK,IAAI,OAAO,CAAC;IAEjB;;OAEG;IACH,MAAM,IAAI,IAAI,CAAC;CAClB;AACD,MAAM,WAAW,oBAAoB;IACjC,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,GAAG,CAAC;CACvB"}
@@ -1 +1 @@
1
- {"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ISharedObject, ISharedObjectEvents } from \"@fluidframework/shared-object-base\";\nimport { Serializable } from \"@fluidframework/datastore-definitions\";\n\nexport interface ISharedCellEvents<T> extends ISharedObjectEvents {\n (event: \"valueChanged\", listener: (value: Serializable<T>) => void);\n (event: \"delete\", listener: () => void);\n}\n\n/**\n * Shared cell interface\n */\n\nexport interface ISharedCell<T = any> extends ISharedObject<ISharedCellEvents<T>> {\n /**\n * Retrieves the cell value.\n *\n * @returns - the value of the cell\n */\n get(): Serializable<T> | undefined;\n\n /**\n * Sets the cell value.\n *\n * @param value - a JSON-able or SharedObject value to set the cell to\n */\n set(value: Serializable<T>): void;\n\n /**\n * Checks whether cell is empty or not.\n *\n * @returns - `true` if the value of cell is `undefined`, `false` otherwise\n */\n empty(): boolean;\n\n /**\n * Delete the value from the cell.\n */\n delete(): void;\n}\n"]}
1
+ {"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ISharedObject, ISharedObjectEvents } from \"@fluidframework/shared-object-base\";\nimport { Serializable } from \"@fluidframework/datastore-definitions\";\n\nexport interface ISharedCellEvents<T> extends ISharedObjectEvents {\n (event: \"valueChanged\", listener: (value: Serializable<T>) => void);\n (event: \"delete\", listener: () => void);\n}\n\n/**\n * Shared cell interface\n */\n\nexport interface ISharedCell<T = any> extends ISharedObject<ISharedCellEvents<T>> {\n /**\n * Retrieves the cell value.\n *\n * @returns - the value of the cell\n */\n get(): Serializable<T> | undefined;\n\n /**\n * Sets the cell value.\n *\n * @param value - a JSON-able or SharedObject value to set the cell to\n */\n set(value: Serializable<T>): void;\n\n /**\n * Checks whether cell is empty or not.\n *\n * @returns - `true` if the value of cell is `undefined`, `false` otherwise\n */\n empty(): boolean;\n\n /**\n * Delete the value from the cell.\n */\n delete(): void;\n}\nexport interface ICellLocalOpMetadata {\n pendingMessageId: number;\n previousValue?: any;\n}\n"]}
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/cell";
8
- export declare const pkgVersion = "2.0.0-internal.2.1.1";
8
+ export declare const pkgVersion = "2.0.0-internal.2.2.0";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export const pkgName = "@fluidframework/cell";
8
- export const pkgVersion = "2.0.0-internal.2.1.1";
8
+ export const pkgVersion = "2.0.0-internal.2.2.0";
9
9
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,sBAAsB,CAAC;AAC9C,MAAM,CAAC,MAAM,UAAU,GAAG,sBAAsB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/cell\";\nexport const pkgVersion = \"2.0.0-internal.2.1.1\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,sBAAsB,CAAC;AAC9C,MAAM,CAAC,MAAM,UAAU,GAAG,sBAAsB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/cell\";\nexport const pkgVersion = \"2.0.0-internal.2.2.0\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/cell",
3
- "version": "2.0.0-internal.2.1.1",
3
+ "version": "2.0.0-internal.2.2.0",
4
4
  "description": "Distributed data structure for a single value",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -28,14 +28,18 @@
28
28
  "clean": "rimraf dist lib *.tsbuildinfo *.build.log",
29
29
  "eslint": "eslint --format stylish src",
30
30
  "eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
31
+ "format": "npm run prettier:fix",
31
32
  "lint": "npm run eslint",
32
33
  "lint:fix": "npm run eslint:fix",
34
+ "prettier": "prettier --check . --ignore-path ../../../.prettierignore",
35
+ "prettier:fix": "prettier --write . --ignore-path ../../../.prettierignore",
33
36
  "test": "npm run test:mocha",
34
37
  "test:coverage": "nyc npm test -- --reporter xunit --reporter-option output=nyc/junit-report.xml",
35
38
  "test:mocha": "mocha --ignore 'dist/test/types/*' --recursive dist/test -r node_modules/@fluidframework/mocha-test-setup --unhandled-rejections=strict",
36
39
  "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
37
40
  "tsc": "tsc",
38
- "typetests:gen": "fluid-type-validator -g -d ."
41
+ "typetests:gen": "flub generate typetests --generate --dir .",
42
+ "typetests:prepare": "flub generate typetests --prepare --dir . --pin"
39
43
  },
40
44
  "nyc": {
41
45
  "all": true,
@@ -59,22 +63,22 @@
59
63
  },
60
64
  "dependencies": {
61
65
  "@fluidframework/common-utils": "^1.0.0",
62
- "@fluidframework/core-interfaces": ">=2.0.0-internal.2.1.1 <2.0.0-internal.3.0.0",
63
- "@fluidframework/datastore-definitions": ">=2.0.0-internal.2.1.1 <2.0.0-internal.3.0.0",
64
- "@fluidframework/driver-utils": ">=2.0.0-internal.2.1.1 <2.0.0-internal.3.0.0",
66
+ "@fluidframework/core-interfaces": ">=2.0.0-internal.2.2.0 <2.0.0-internal.3.0.0",
67
+ "@fluidframework/datastore-definitions": ">=2.0.0-internal.2.2.0 <2.0.0-internal.3.0.0",
68
+ "@fluidframework/driver-utils": ">=2.0.0-internal.2.2.0 <2.0.0-internal.3.0.0",
65
69
  "@fluidframework/protocol-definitions": "^1.1.0",
66
- "@fluidframework/runtime-definitions": ">=2.0.0-internal.2.1.1 <2.0.0-internal.3.0.0",
67
- "@fluidframework/shared-object-base": ">=2.0.0-internal.2.1.1 <2.0.0-internal.3.0.0"
70
+ "@fluidframework/runtime-definitions": ">=2.0.0-internal.2.2.0 <2.0.0-internal.3.0.0",
71
+ "@fluidframework/shared-object-base": ">=2.0.0-internal.2.2.0 <2.0.0-internal.3.0.0"
68
72
  },
69
73
  "devDependencies": {
70
- "@fluid-internal/test-dds-utils": ">=2.0.0-internal.2.1.1 <2.0.0-internal.3.0.0",
71
- "@fluid-tools/build-cli": "^0.5.0",
74
+ "@fluid-internal/test-dds-utils": ">=2.0.0-internal.2.2.0 <2.0.0-internal.3.0.0",
75
+ "@fluid-tools/build-cli": "^0.7.0",
72
76
  "@fluidframework/build-common": "^1.1.0",
73
- "@fluidframework/build-tools": "^0.5.0",
74
- "@fluidframework/cell-previous": "npm:@fluidframework/cell@2.0.0-internal.2.0.0",
75
- "@fluidframework/eslint-config-fluid": "^1.1.0",
76
- "@fluidframework/mocha-test-setup": ">=2.0.0-internal.2.1.1 <2.0.0-internal.3.0.0",
77
- "@fluidframework/test-runtime-utils": ">=2.0.0-internal.2.1.1 <2.0.0-internal.3.0.0",
77
+ "@fluidframework/build-tools": "^0.7.0",
78
+ "@fluidframework/cell-previous": "npm:@fluidframework/cell@2.0.0-internal.2.1.0",
79
+ "@fluidframework/eslint-config-fluid": "^1.2.0",
80
+ "@fluidframework/mocha-test-setup": ">=2.0.0-internal.2.2.0 <2.0.0-internal.3.0.0",
81
+ "@fluidframework/test-runtime-utils": ">=2.0.0-internal.2.2.0 <2.0.0-internal.3.0.0",
78
82
  "@microsoft/api-extractor": "^7.22.2",
79
83
  "@rushstack/eslint-config": "^2.5.1",
80
84
  "@types/mocha": "^9.1.1",
@@ -85,11 +89,14 @@
85
89
  "eslint": "~8.6.0",
86
90
  "mocha": "^10.0.0",
87
91
  "nyc": "^15.0.0",
92
+ "prettier": "~2.6.2",
88
93
  "rimraf": "^2.6.2",
89
94
  "typescript": "~4.5.5"
90
95
  },
91
96
  "typeValidation": {
92
- "version": "2.0.0-internal.2.1.0",
97
+ "version": "2.0.0-internal.2.2.0",
98
+ "baselineRange": ">=2.0.0-internal.2.1.0 <2.0.0-internal.2.2.0",
99
+ "baselineVersion": "2.0.0-internal.2.1.0",
93
100
  "broken": {}
94
101
  }
95
102
  }
@@ -0,0 +1,8 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ module.exports = {
7
+ ...require("@fluidframework/build-common/prettier.config.cjs"),
8
+ };
package/src/cell.ts CHANGED
@@ -16,7 +16,11 @@ import { ISummaryTreeWithStats } from "@fluidframework/runtime-definitions";
16
16
  import { readAndParse } from "@fluidframework/driver-utils";
17
17
  import { createSingleBlobSummary, IFluidSerializer, SharedObject } from "@fluidframework/shared-object-base";
18
18
  import { CellFactory } from "./cellFactory";
19
- import { ISharedCell, ISharedCellEvents } from "./interfaces";
19
+ import {
20
+ ISharedCell,
21
+ ISharedCellEvents,
22
+ ICellLocalOpMetadata,
23
+ } from "./interfaces";
20
24
 
21
25
  /**
22
26
  * Description of a cell delta operation
@@ -124,6 +128,8 @@ export class SharedCell<T = any> extends SharedObject<ISharedCellEvents<T>>
124
128
  */
125
129
  private messageIdObserved: number = -1;
126
130
 
131
+ private readonly pendingMessageIds: number[] = [];
132
+
127
133
  /**
128
134
  * Constructs a new shared cell. If the object is non-local an id and service interfaces will
129
135
  * be provided
@@ -152,7 +158,7 @@ export class SharedCell<T = any> extends SharedObject<ISharedCellEvents<T>>
152
158
  };
153
159
 
154
160
  // Set the value locally.
155
- this.setCore(value);
161
+ const previousValue = this.setCore(value);
156
162
 
157
163
  // If we are not attached, don't submit the op.
158
164
  if (!this.isAttached()) {
@@ -163,7 +169,7 @@ export class SharedCell<T = any> extends SharedObject<ISharedCellEvents<T>>
163
169
  type: "setCell",
164
170
  value: operationValue,
165
171
  };
166
- this.submitLocalMessage(op, ++this.messageId);
172
+ this.submitCellMessage(op, previousValue);
167
173
  }
168
174
 
169
175
  /**
@@ -171,7 +177,7 @@ export class SharedCell<T = any> extends SharedObject<ISharedCellEvents<T>>
171
177
  */
172
178
  public delete() {
173
179
  // Delete the value locally.
174
- this.deleteCore();
180
+ const previousValue = this.deleteCore();
175
181
 
176
182
  // If we are not attached, don't submit the op.
177
183
  if (!this.isAttached()) {
@@ -181,7 +187,7 @@ export class SharedCell<T = any> extends SharedObject<ISharedCellEvents<T>>
181
187
  const op: IDeleteCellOperation = {
182
188
  type: "deleteCell",
183
189
  };
184
- this.submitLocalMessage(op, ++this.messageId);
190
+ this.submitCellMessage(op, previousValue);
185
191
  }
186
192
 
187
193
  /**
@@ -229,12 +235,10 @@ export class SharedCell<T = any> extends SharedObject<ISharedCellEvents<T>>
229
235
  private applyInnerOp(content: ICellOperation) {
230
236
  switch (content.type) {
231
237
  case "setCell":
232
- this.setCore(this.decode(content.value));
233
- break;
238
+ return this.setCore(this.decode(content.value));
234
239
 
235
240
  case "deleteCell":
236
- this.deleteCore();
237
- break;
241
+ return this.deleteCore();
238
242
 
239
243
  default:
240
244
  throw new Error("Unknown operation");
@@ -250,15 +254,19 @@ export class SharedCell<T = any> extends SharedObject<ISharedCellEvents<T>>
250
254
  * For messages from a remote client, this will be undefined.
251
255
  */
252
256
  protected processCore(message: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown) {
257
+ const cellOpMetadata = localOpMetadata as ICellLocalOpMetadata;
253
258
  if (this.messageId !== this.messageIdObserved) {
254
259
  // We are waiting for an ACK on our change to this cell - we will ignore all messages until we get it.
255
260
  if (local) {
256
- const messageIdReceived = localOpMetadata as number;
261
+ const messageIdReceived = cellOpMetadata.pendingMessageId;
257
262
  assert(messageIdReceived !== undefined && messageIdReceived <= this.messageId,
258
263
  0x00c /* "messageId is incorrect from from the local client's ACK" */);
259
-
264
+ assert(this.pendingMessageIds !== undefined &&
265
+ this.pendingMessageIds[0] === cellOpMetadata.pendingMessageId,
266
+ 0x471 /* Unexpected pending message received */);
267
+ this.pendingMessageIds.shift();
260
268
  // We got an ACK. Update messageIdObserved.
261
- this.messageIdObserved = localOpMetadata as number;
269
+ this.messageIdObserved = cellOpMetadata.pendingMessageId;
262
270
  }
263
271
  return;
264
272
  }
@@ -269,14 +277,18 @@ export class SharedCell<T = any> extends SharedObject<ISharedCellEvents<T>>
269
277
  }
270
278
  }
271
279
 
272
- private setCore(value: Serializable<T>) {
280
+ private setCore(value: Serializable<T>): Serializable<T> | undefined {
281
+ const previousLocalValue = this.get();
273
282
  this.data = value;
274
283
  this.emit("valueChanged", value);
284
+ return previousLocalValue;
275
285
  }
276
286
 
277
- private deleteCore() {
287
+ private deleteCore(): Serializable<T> | undefined {
288
+ const previousLocalValue = this.get();
278
289
  this.data = undefined;
279
290
  this.emit("delete");
291
+ return previousLocalValue;
280
292
  }
281
293
 
282
294
  private decode(cellValue: ICellValue) {
@@ -285,14 +297,55 @@ export class SharedCell<T = any> extends SharedObject<ISharedCellEvents<T>>
285
297
  return this.serializer.decode(value);
286
298
  }
287
299
 
300
+ private createLocalOpMetadata(op: ICellOperation, previousValue?: Serializable<T>): ICellLocalOpMetadata {
301
+ const pendingMessageId = ++this.messageId;
302
+ this.pendingMessageIds.push(pendingMessageId);
303
+ const localMetadata: ICellLocalOpMetadata = {
304
+ pendingMessageId, previousValue,
305
+ };
306
+ return localMetadata;
307
+ }
308
+
288
309
  /**
289
310
  * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}
290
311
  * @internal
291
312
  */
292
313
  protected applyStashedOp(content: unknown): unknown {
293
314
  const cellContent = content as ICellOperation;
294
- this.applyInnerOp(cellContent);
295
- ++this.messageId;
296
- return this.messageId;
315
+ const previousValue = this.applyInnerOp(cellContent);
316
+ return this.createLocalOpMetadata(cellContent, previousValue);
317
+ }
318
+
319
+ /**
320
+ * Rollback a local op
321
+ * @param content - The operation to rollback
322
+ * @param localOpMetadata - The local metadata associated with the op.
323
+ */
324
+ protected rollback(content: any, localOpMetadata: unknown) {
325
+ const cellOpMetadata = localOpMetadata as ICellLocalOpMetadata;
326
+ if (content.type === "setCell" || content.type === "deleteCell") {
327
+ if (cellOpMetadata.previousValue === undefined) {
328
+ this.deleteCore();
329
+ } else {
330
+ this.setCore(cellOpMetadata.previousValue);
331
+ }
332
+
333
+ const lastPendingMessageId = this.pendingMessageIds.pop();
334
+ if (lastPendingMessageId !== cellOpMetadata.pendingMessageId) {
335
+ throw new Error("Rollback op does not match last pending");
336
+ }
337
+ } else {
338
+ throw new Error("Unsupported op for rollback");
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Submit a cell message to remote clients.
344
+ * @param op - The cell message
345
+ * @param previousValue - The value of the cell before this op
346
+ */
347
+ private submitCellMessage(op: ICellOperation, previousValue?: any): void {
348
+ const localMetadata = this.createLocalOpMetadata(op, previousValue);
349
+ this.submitLocalMessage(op, localMetadata);
297
350
  }
298
351
  }
package/src/interfaces.ts CHANGED
@@ -42,3 +42,7 @@ export interface ISharedCell<T = any> extends ISharedObject<ISharedCellEvents<T>
42
42
  */
43
43
  delete(): void;
44
44
  }
45
+ export interface ICellLocalOpMetadata {
46
+ pendingMessageId: number;
47
+ previousValue?: any;
48
+ }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/cell";
9
- export const pkgVersion = "2.0.0-internal.2.1.1";
9
+ export const pkgVersion = "2.0.0-internal.2.2.0";