@fluid-experimental/property-dds 1.1.0 → 1.2.0-77818

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.
@@ -69,7 +69,7 @@ interface ISnapshot {
69
69
  useMH: boolean;
70
70
  numChunks: number;
71
71
  }
72
- interface ISnapshotSummary {
72
+ export interface ISnapshotSummary {
73
73
  remoteTipView?: SerializedChangeSet;
74
74
  remoteChanges?: IPropertyTreeMessage[];
75
75
  unrebasedRemoteChanges?: Record<string, IRemotePropertyTreeMessage>;
@@ -81,6 +81,33 @@ export interface SharedPropertyTreeOptions {
81
81
  useMH?: boolean;
82
82
  }
83
83
 
84
+ export interface ISharedPropertyTreeEncDec {
85
+ messageEncoder: { encode: (IPropertyTreeMessage) => IPropertyTreeMessage;
86
+ decode: (IPropertyTreeMessage) => IPropertyTreeMessage; };
87
+ summaryEncoder: { encode: (ISnapshotSummary) => Buffer; decode: (Buffer) => ISnapshotSummary; };
88
+ }
89
+
90
+ export interface IPropertyTreeConfig {
91
+ encDec: ISharedPropertyTreeEncDec;
92
+ }
93
+
94
+ const defaultEncDec: ISharedPropertyTreeEncDec = {
95
+ messageEncoder: { encode: (msg: IPropertyTreeMessage) => msg,
96
+ decode: (msg: IPropertyTreeMessage) => msg },
97
+ summaryEncoder: {
98
+ encode: (summary: ISnapshotSummary) => {
99
+ const packr = new Packr();
100
+ const serializedSummary = packr.pack(summary);
101
+ return serializedSummary;
102
+ },
103
+ decode: (serializedSummary) => {
104
+ const packr = new Packr();
105
+ const snapshotSummary = packr.unpack(serializedSummary);
106
+ return snapshotSummary as ISnapshotSummary;
107
+ },
108
+ },
109
+ };
110
+
84
111
  /**
85
112
  * Silly DDS example that models a six sided die.
86
113
  *
@@ -107,12 +134,14 @@ export class SharedPropertyTree extends SharedObject {
107
134
  skipSequenceNumber: number = -1;
108
135
  headCommitGuid: string = "";
109
136
  useMH: boolean = false;
137
+ propertyTreeConfig: IPropertyTreeConfig;
110
138
 
111
139
  public constructor(
112
140
  id: string,
113
141
  runtime: IFluidDataStoreRuntime,
114
142
  attributes: IChannelAttributes,
115
143
  options: SharedPropertyTreeOptions,
144
+ propertyTreeConfig: IPropertyTreeConfig = { encDec: defaultEncDec },
116
145
  ) {
117
146
  super(id, runtime, attributes, "fluid_propertyTree_");
118
147
 
@@ -122,6 +151,7 @@ export class SharedPropertyTree extends SharedObject {
122
151
 
123
152
  // By default, we currently don't use the MH
124
153
  this.useMH = options.useMH ?? false;
154
+ this.propertyTreeConfig = propertyTreeConfig;
125
155
  }
126
156
 
127
157
  /**
@@ -203,6 +233,22 @@ export class SharedPropertyTree extends SharedObject {
203
233
  }
204
234
  }
205
235
 
236
+ /**
237
+ * This method encodes the given message to the transfer form
238
+ * @param change - The message to be encoded.
239
+ */
240
+ private encodeMessage(change: IPropertyTreeMessage): IPropertyTreeMessage {
241
+ return this.propertyTreeConfig.encDec.messageEncoder.encode(change);
242
+ }
243
+
244
+ /**
245
+ * This method decodes message from the transfer form.
246
+ * @param transferChange - The message to be decoded.
247
+ */
248
+ private decodeMessage(transferChange: IPropertyTreeMessage): IPropertyTreeMessage {
249
+ return this.propertyTreeConfig.encDec.messageEncoder.decode(transferChange);
250
+ }
251
+
206
252
  private applyChangeSet(changeSet: SerializedChangeSet, metadata: Metadata) {
207
253
  const _changeSet = new ChangeSet(changeSet);
208
254
  _changeSet._toReversibleChangeSet(this.tipView);
@@ -223,12 +269,12 @@ export class SharedPropertyTree extends SharedObject {
223
269
  useMH: this.useMH,
224
270
  };
225
271
  this._applyLocalChangeSet(change);
226
-
227
272
  // Queue the op for transmission to the Fluid service.
273
+ const transferChange = this.encodeMessage(cloneDeep(change));
228
274
  if (this.transmissionsHaveBeenStopped) {
229
- this.enqueuedMessages.push(cloneDeep(change));
275
+ this.enqueuedMessages.push(transferChange);
230
276
  } else {
231
- this.submitLocalMessage(cloneDeep(change));
277
+ this.submitLocalMessage(transferChange);
232
278
  }
233
279
  }
234
280
 
@@ -277,7 +323,8 @@ export class SharedPropertyTree extends SharedObject {
277
323
  */
278
324
  protected processCore(message: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown) {
279
325
  if (message.type === MessageType.Operation && message.sequenceNumber > this.skipSequenceNumber) {
280
- const content: IRemotePropertyTreeMessage = { ...message.contents, sequenceNumber: message.sequenceNumber };
326
+ const change: IPropertyTreeMessage = this.decodeMessage(cloneDeep(message.contents));
327
+ const content: IRemotePropertyTreeMessage = { ...change, sequenceNumber: message.sequenceNumber };
281
328
  switch (content.op) {
282
329
  case OpKind.ChangeSet:
283
330
  // If the op originated locally from this client, we've already accounted for it
@@ -384,6 +431,53 @@ export class SharedPropertyTree extends SharedObject {
384
431
  this.remoteChanges = remoteChanges;
385
432
  this.unrebasedRemoteChanges = unrebasedRemoteChanges;
386
433
  }
434
+
435
+ /**
436
+ * This method encodes the local summary (snapshot) object into the serialized form.
437
+ * @param summary - The local summary (snapshot)representation.
438
+ * @returns The serialized summary representation.
439
+ */
440
+ private encodeSummary(summary: ISnapshotSummary) {
441
+ return this.propertyTreeConfig.encDec.summaryEncoder.encode(summary);
442
+ }
443
+
444
+ /**
445
+ * This method decodes the serialized form of the summary into the local summary (snapshot) object.
446
+ * @param serializedSummary - The serialized summary representation.
447
+ * @returns The local summary (snapshot)representation.
448
+ */
449
+ private decodeSummary(serializedSummary): ISnapshotSummary {
450
+ return this.propertyTreeConfig.encDec.summaryEncoder.decode(serializedSummary);
451
+ }
452
+
453
+ /**
454
+ * This method writes the log message if the logging is enabled in the extended DDS.
455
+ * The logging is not enabled in the default Property DDS
456
+ * @param message - The message to be logged.
457
+ */
458
+ protected logIfEnabled(message) {}
459
+
460
+ /**
461
+ * This method encodes the binary representation of the
462
+ * blob.
463
+ * @param blob - The binary representation of the blob.
464
+ * @returns The encoded representation of the blob.
465
+ */
466
+ private encodeSummaryBlob(blob: ArrayBuffer): any {
467
+ return bufferToString(blob, "base64");
468
+ }
469
+
470
+ /**
471
+ * This method decodes the encoded representation of the
472
+ * blob.
473
+ * @param blob - The encoded representation of the blob.
474
+ * @returns The binary representation of the blob.
475
+ */
476
+ private decodeSummaryBlob(encoded: any): ArrayBuffer {
477
+ const buffer = bufferToString(encoded, "utf8");
478
+ return stringToBuffer(buffer, "base64");
479
+ }
480
+
387
481
  public summarizeCore(serializer: IFluidSerializer): ISummaryTreeWithStats {
388
482
  this.pruneHistory();
389
483
  const snapshot: ISnapshot = {
@@ -403,14 +497,16 @@ export class SharedPropertyTree extends SharedObject {
403
497
  unrebasedRemoteChanges: this.unrebasedRemoteChanges,
404
498
  };
405
499
  const chunkSize = 5000 * 1024; // Default limit seems to be 5MB
406
- const packr = new Packr();
407
- const serializedSummary = packr.pack(summary);
408
-
500
+ let totalBlobsSize = 0;
501
+ const serializedSummary = this.encodeSummary(summary);
409
502
  for (let pos = 0, i = 0; pos < serializedSummary.length; pos += chunkSize, i++) {
410
- builder.addBlob(`summaryChunk_${i}`,
411
- bufferToString(serializedSummary.slice(pos, pos + chunkSize), "base64"));
503
+ const summaryBlob = this.encodeSummaryBlob(serializedSummary.slice(pos, pos + chunkSize));
504
+ // eslint-disable-next-line @typescript-eslint/dot-notation
505
+ totalBlobsSize += summaryBlob["length"];
506
+ builder.addBlob(`summaryChunk_${i}`, summaryBlob);
412
507
  snapshot.numChunks++;
413
508
  }
509
+ this.logIfEnabled(`Total blobs transfer size: ${totalBlobsSize}`);
414
510
  }
415
511
 
416
512
  builder.addBlob("properties", serializer !== undefined
@@ -432,8 +528,7 @@ export class SharedPropertyTree extends SharedObject {
432
528
  // We load all chunks
433
529
  const chunks: ArrayBufferLike[] = await Promise.all(
434
530
  range(snapshot.numChunks).map(async (i) => {
435
- const buffer = bufferToString(await storage.readBlob(`summaryChunk_${i}`), "utf8");
436
- return stringToBuffer(buffer, "base64");
531
+ return this.decodeSummaryBlob(await storage.readBlob(`summaryChunk_${i}`));
437
532
  }),
438
533
  );
439
534
 
@@ -443,9 +538,7 @@ export class SharedPropertyTree extends SharedObject {
443
538
  serializedSummary.set(new Uint8Array(chunk), offset);
444
539
  return offset + chunk.byteLength;
445
540
  }, 0);
446
-
447
- const packr = new Packr();
448
- const snapshotSummary = packr.unpack(serializedSummary);
541
+ const snapshotSummary = this.decodeSummary(serializedSummary);
449
542
  if (
450
543
  snapshotSummary.remoteChanges === undefined ||
451
544
  snapshotSummary.remoteTipView === undefined ||
@@ -0,0 +1,22 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { IChannelFactory, IFluidDataStoreRuntime } from "@fluidframework/datastore-definitions";
7
+ import { SharedPropertyTree } from "./propertyTree";
8
+ import { DeflatedPropertyTreeFactory } from "./propertyTreeExtFactories";
9
+
10
+ /**
11
+ * This class is the extension of SharedPropertyTree which compresses
12
+ * the deltas and summaries communicated to the server by Deflate.
13
+ */
14
+ export class DeflatedPropertyTree extends SharedPropertyTree {
15
+ public static create(runtime: IFluidDataStoreRuntime, id?: string, queryString?: string) {
16
+ return runtime.createChannel(id, DeflatedPropertyTreeFactory.Type) as DeflatedPropertyTree;
17
+ }
18
+
19
+ public static getFactory(): IChannelFactory {
20
+ return new DeflatedPropertyTreeFactory();
21
+ }
22
+ }
@@ -0,0 +1,104 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { deflate, inflate } from "pako";
6
+ import { bufferToString, stringToBuffer } from "@fluidframework/common-utils";
7
+ import {
8
+ IChannelAttributes,
9
+ IFluidDataStoreRuntime,
10
+ IChannelServices,
11
+ IChannelFactory,
12
+ } from "@fluidframework/datastore-definitions";
13
+ import { IPropertyTreeMessage, ISharedPropertyTreeEncDec, ISnapshotSummary, SharedPropertyTreeOptions }
14
+ from "./propertyTree";
15
+ import { DeflatedPropertyTree } from "./propertyTreeExt";
16
+
17
+ function encodeSummary(snapshotSummary: ISnapshotSummary) {
18
+ const summaryStr = JSON.stringify(snapshotSummary);
19
+ const unzipped = new TextEncoder().encode(summaryStr);
20
+ const serializedSummary: Buffer = deflate(unzipped);
21
+ return serializedSummary;
22
+ }
23
+
24
+ function decodeSummary(serializedSummary): ISnapshotSummary {
25
+ const unzipped = inflate(serializedSummary);
26
+ const summaryStr = new TextDecoder().decode(unzipped);
27
+ const snapshotSummary: ISnapshotSummary = JSON.parse(summaryStr);
28
+ return snapshotSummary;
29
+ }
30
+
31
+ function encodeMessage(change: IPropertyTreeMessage) {
32
+ const changeSetStr = JSON.stringify(change.changeSet);
33
+ const unzipped = new TextEncoder().encode(changeSetStr);
34
+ const zipped: Buffer = deflate(unzipped);
35
+ const zippedStr = bufferToString(zipped, "base64");
36
+ if (zippedStr.length < changeSetStr.length) {
37
+ // eslint-disable-next-line @typescript-eslint/dot-notation
38
+ change["isZipped"] = "1";
39
+ change.changeSet = zippedStr;
40
+ }
41
+ return change;
42
+ }
43
+
44
+ function decodeMessage(transferChange: IPropertyTreeMessage) {
45
+ // eslint-disable-next-line @typescript-eslint/dot-notation
46
+ if (transferChange["isZipped"]) {
47
+ const zipped = stringToBuffer(transferChange.changeSet, "base64");
48
+ const unzipped = inflate(zipped);
49
+ const changeSetStr = new TextDecoder().decode(unzipped);
50
+ transferChange.changeSet = JSON.parse(changeSetStr);
51
+ }
52
+ return transferChange;
53
+ }
54
+
55
+ const encDec: ISharedPropertyTreeEncDec = {
56
+ messageEncoder: {
57
+ encode: encodeMessage,
58
+ decode: decodeMessage,
59
+ },
60
+ summaryEncoder: {
61
+ encode: encodeSummary,
62
+ decode: decodeSummary,
63
+ },
64
+ };
65
+
66
+ export class DeflatedPropertyTreeFactory implements IChannelFactory {
67
+ public static readonly Type = "DeflatedPropertyTree:84534a0fe613522101f6";
68
+
69
+ public static readonly Attributes: IChannelAttributes = {
70
+ type: DeflatedPropertyTreeFactory.Type,
71
+ snapshotFormatVersion: "0.1",
72
+ packageVersion: "0.0.1",
73
+ };
74
+
75
+ public get type() {
76
+ return DeflatedPropertyTreeFactory.Type;
77
+ }
78
+
79
+ public get attributes() {
80
+ return DeflatedPropertyTreeFactory.Attributes;
81
+ }
82
+
83
+ public async load(
84
+ runtime: IFluidDataStoreRuntime,
85
+ id: string,
86
+ services: IChannelServices,
87
+ attributes: IChannelAttributes,
88
+ url?: string,
89
+ ): Promise<DeflatedPropertyTree> {
90
+ const options = {};
91
+ const instance = new DeflatedPropertyTree(id, runtime, attributes, options as SharedPropertyTreeOptions
92
+ , { encDec });
93
+ await instance.load(services);
94
+ return instance;
95
+ }
96
+
97
+ public create(document: IFluidDataStoreRuntime, id: string, requestUrl?: string): DeflatedPropertyTree {
98
+ const options = {};
99
+ const cell = new DeflatedPropertyTree(id, document,
100
+ this.attributes, options as SharedPropertyTreeOptions, { encDec });
101
+ cell.initializeLocal();
102
+ return cell;
103
+ }
104
+ }