@fluidframework/shared-object-base 0.54.0 → 0.55.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.
Files changed (73) hide show
  1. package/.eslintrc.js +5 -2
  2. package/bench/src/index.ts +68 -0
  3. package/bench/src/util.ts +56 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/packageVersion.d.ts +1 -1
  9. package/dist/packageVersion.js +1 -1
  10. package/dist/packageVersion.js.map +1 -1
  11. package/dist/remoteObjectHandle.d.ts +32 -0
  12. package/dist/remoteObjectHandle.d.ts.map +1 -0
  13. package/dist/remoteObjectHandle.js +66 -0
  14. package/dist/remoteObjectHandle.js.map +1 -0
  15. package/dist/serializer.d.ts +82 -0
  16. package/dist/serializer.d.ts.map +1 -0
  17. package/dist/serializer.js +148 -0
  18. package/dist/serializer.js.map +1 -0
  19. package/dist/sharedObject.d.ts +54 -26
  20. package/dist/sharedObject.d.ts.map +1 -1
  21. package/dist/sharedObject.js +77 -75
  22. package/dist/sharedObject.js.map +1 -1
  23. package/dist/summarySerializer.d.ts +1 -1
  24. package/dist/summarySerializer.d.ts.map +1 -1
  25. package/dist/summarySerializer.js +2 -2
  26. package/dist/summarySerializer.js.map +1 -1
  27. package/dist/types.d.ts +8 -2
  28. package/dist/types.d.ts.map +1 -1
  29. package/dist/types.js.map +1 -1
  30. package/dist/utils.d.ts +10 -1
  31. package/dist/utils.d.ts.map +1 -1
  32. package/dist/utils.js +15 -5
  33. package/dist/utils.js.map +1 -1
  34. package/lib/index.d.ts +1 -0
  35. package/lib/index.d.ts.map +1 -1
  36. package/lib/index.js +1 -0
  37. package/lib/index.js.map +1 -1
  38. package/lib/packageVersion.d.ts +1 -1
  39. package/lib/packageVersion.js +1 -1
  40. package/lib/packageVersion.js.map +1 -1
  41. package/lib/remoteObjectHandle.d.ts +32 -0
  42. package/lib/remoteObjectHandle.d.ts.map +1 -0
  43. package/lib/remoteObjectHandle.js +62 -0
  44. package/lib/remoteObjectHandle.js.map +1 -0
  45. package/lib/serializer.d.ts +82 -0
  46. package/lib/serializer.d.ts.map +1 -0
  47. package/lib/serializer.js +143 -0
  48. package/lib/serializer.js.map +1 -0
  49. package/lib/sharedObject.d.ts +54 -26
  50. package/lib/sharedObject.d.ts.map +1 -1
  51. package/lib/sharedObject.js +75 -74
  52. package/lib/sharedObject.js.map +1 -1
  53. package/lib/summarySerializer.d.ts +1 -1
  54. package/lib/summarySerializer.d.ts.map +1 -1
  55. package/lib/summarySerializer.js +1 -1
  56. package/lib/summarySerializer.js.map +1 -1
  57. package/lib/types.d.ts +8 -2
  58. package/lib/types.d.ts.map +1 -1
  59. package/lib/types.js.map +1 -1
  60. package/lib/utils.d.ts +10 -1
  61. package/lib/utils.d.ts.map +1 -1
  62. package/lib/utils.js +13 -4
  63. package/lib/utils.js.map +1 -1
  64. package/package.json +53 -17
  65. package/src/index.ts +1 -0
  66. package/src/packageVersion.ts +1 -1
  67. package/src/remoteObjectHandle.ts +79 -0
  68. package/src/serializer.ts +216 -0
  69. package/src/sharedObject.ts +116 -97
  70. package/src/summarySerializer.ts +1 -1
  71. package/src/types.ts +9 -2
  72. package/src/utils.ts +17 -8
  73. package/tsconfig.json +5 -3
@@ -7,21 +7,21 @@ import { v4 as uuid } from "uuid";
7
7
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
8
8
  import { assert, EventEmitterEventType } from "@fluidframework/common-utils";
9
9
  import { AttachState } from "@fluidframework/container-definitions";
10
- import { IFluidHandle, IFluidSerializer } from "@fluidframework/core-interfaces";
10
+ import { IFluidHandle } from "@fluidframework/core-interfaces";
11
11
  import {
12
12
  IChannelAttributes,
13
13
  IFluidDataStoreRuntime,
14
14
  IChannelStorageService,
15
15
  IChannelServices,
16
16
  } from "@fluidframework/datastore-definitions";
17
- import { ISequencedDocumentMessage, ITree } from "@fluidframework/protocol-definitions";
17
+ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
18
18
  import {
19
19
  IGarbageCollectionData,
20
20
  ISummaryTreeWithStats,
21
21
  } from "@fluidframework/runtime-definitions";
22
- import { convertToSummaryTreeWithStats, FluidSerializer } from "@fluidframework/runtime-utils";
23
22
  import { ChildLogger, EventEmitterWithErrorHandling } from "@fluidframework/telemetry-utils";
24
23
  import { DataProcessingError } from "@fluidframework/container-utils";
24
+ import { FluidSerializer, IFluidSerializer } from "./serializer";
25
25
  import { SharedObjectHandle } from "./handle";
26
26
  import { SummarySerializer } from "./summarySerializer";
27
27
  import { ISharedObject, ISharedObjectEvents } from "./types";
@@ -29,13 +29,13 @@ import { ISharedObject, ISharedObjectEvents } from "./types";
29
29
  /**
30
30
  * Base class from which all shared objects derive
31
31
  */
32
- export abstract class SharedObject<TEvent extends ISharedObjectEvents = ISharedObjectEvents>
32
+ export abstract class SharedObjectCore<TEvent extends ISharedObjectEvents = ISharedObjectEvents>
33
33
  extends EventEmitterWithErrorHandling<TEvent> implements ISharedObject<TEvent> {
34
34
  /**
35
35
  * @param obj - The thing to check if it is a SharedObject
36
36
  * @returns Returns true if the thing is a SharedObject
37
37
  */
38
- public static is(obj: any): obj is SharedObject {
38
+ public static is(obj: any): obj is SharedObjectCore {
39
39
  return obj?.ISharedObject !== undefined;
40
40
  }
41
41
 
@@ -68,11 +68,6 @@ export abstract class SharedObject<TEvent extends ISharedObjectEvents = ISharedO
68
68
  */
69
69
  private _isBoundToContext: boolean = false;
70
70
 
71
- /**
72
- * True while we are summarizing this object's data.
73
- */
74
- private _isSummarizing: boolean = false;
75
-
76
71
  /**
77
72
  * Tracks error that closed this object.
78
73
  */
@@ -86,24 +81,6 @@ export abstract class SharedObject<TEvent extends ISharedObjectEvents = ISharedO
86
81
  return this._connected;
87
82
  }
88
83
 
89
- protected get serializer(): IFluidSerializer {
90
- /**
91
- * During summarize, the SummarySerializer keeps track of IFluidHandles that are serialized. These handles
92
- * represent references to other Fluid objects and are used for garbage collection.
93
- *
94
- * This is fine for now. However, if we implement delay loading in DDss, they may load and de-serialize content
95
- * in summarize. When that happens, they may incorrectly hit this assert and we will have to change this.
96
- */
97
- assert(!this._isSummarizing,
98
- 0x075 /* "SummarySerializer should be used for serializing data during summary." */);
99
- return this._serializer;
100
- }
101
-
102
- /**
103
- * The serializer to use to serialize / parse handles, if any.
104
- */
105
- private readonly _serializer: IFluidSerializer;
106
-
107
84
  /**
108
85
  * @param id - The id of the shared object
109
86
  * @param runtime - The IFluidDataStoreRuntime which contains the shared object
@@ -127,11 +104,6 @@ export abstract class SharedObject<TEvent extends ISharedObjectEvents = ISharedO
127
104
  { all: { sharedObjectId: uuid() } },
128
105
  );
129
106
 
130
- this._serializer = new FluidSerializer(
131
- this.runtime.channelsRoutingContext,
132
- (handle: IFluidHandle) => this.handleDecoded(handle),
133
- );
134
-
135
107
  this.attachListeners();
136
108
  }
137
109
 
@@ -241,71 +213,19 @@ export abstract class SharedObject<TEvent extends ISharedObjectEvents = ISharedO
241
213
  }
242
214
 
243
215
  /**
244
- * {@inheritDoc (ISharedObject:interface).summarize}
216
+ * {@inheritDoc (ISharedObject:interface).getAttachSummary}
245
217
  */
246
- public summarize(fullTree: boolean = false, trackState: boolean = false): ISummaryTreeWithStats {
247
- // Set _isSummarizing to true. This flag is used to ensure that we only use SummarySerializer (created below)
248
- // to serialize handles in this object's data. The routes of these serialized handles are outbound routes
249
- // to other Fluid objects.
250
- assert(!this._isSummarizing, 0x076 /* "Possible re-entrancy! Summary should not already be in progress." */);
251
- this._isSummarizing = true;
252
-
253
- let summaryTree: ISummaryTreeWithStats;
254
- try {
255
- const serializer = new SummarySerializer(
256
- this.runtime.channelsRoutingContext,
257
- (handle: IFluidHandle) => this.handleDecoded(handle),
258
- );
259
- const snapshot: ITree = this.snapshotCore(serializer);
260
- summaryTree = convertToSummaryTreeWithStats(snapshot, fullTree);
261
- assert(this._isSummarizing, 0x077 /* "Possible re-entrancy! Summary should have been in progress." */);
262
- } finally {
263
- this._isSummarizing = false;
264
- }
265
- return summaryTree;
266
- }
218
+ public abstract getAttachSummary(fullTree?: boolean, trackState?: boolean): ISummaryTreeWithStats;
267
219
 
268
220
  /**
269
- * {@inheritDoc (ISharedObject:interface).getGCData}
221
+ * {@inheritDoc (ISharedObject:interface).summarize}
270
222
  */
271
- public getGCData(fullGC: boolean = false): IGarbageCollectionData {
272
- // Set _isSummarizing to true. This flag is used to ensure that we only use SummarySerializer (created in
273
- // getGCDataCore) to serialize handles in this object's data.
274
- assert(!this._isSummarizing, 0x078 /* "Possible re-entrancy! Summary should not already be in progress." */);
275
- this._isSummarizing = true;
276
-
277
- let gcData: IGarbageCollectionData;
278
- try {
279
- gcData = this.getGCDataCore();
280
- assert(this._isSummarizing, 0x079 /* "Possible re-entrancy! Summary should have been in progress." */);
281
- } finally {
282
- this._isSummarizing = false;
283
- }
284
-
285
- return gcData;
286
- }
223
+ public abstract summarize(fullTree?: boolean, trackState?: boolean): Promise<ISummaryTreeWithStats>;
287
224
 
288
225
  /**
289
- * Returns the GC data for this shared object. It contains a list of GC nodes that contains references to
290
- * other GC nodes.
291
- * Derived classes must override this to provide custom list of references to other GC nodes.
226
+ * {@inheritDoc (ISharedObject:interface).getGCData}
292
227
  */
293
- protected getGCDataCore(): IGarbageCollectionData {
294
- // We run the full summarize logic to get the list of outbound routes from this object. This is a little
295
- // expensive but its okay for now. It will be updated to not use full summarize and make it more efficient.
296
- // See: https://github.com/microsoft/FluidFramework/issues/4547
297
- const serializer = new SummarySerializer(
298
- this.runtime.channelsRoutingContext,
299
- (handle: IFluidHandle) => this.handleDecoded(handle),
300
- );
301
- this.snapshotCore(serializer);
302
-
303
- // The GC data for this shared object contains a single GC node. The outbound routes of this node are the
304
- // routes of handles serialized during snapshot.
305
- return {
306
- gcNodes: { "/": serializer.getSerializedRoutes() },
307
- };
308
- }
228
+ public abstract getGCData(fullGC?: boolean): IGarbageCollectionData;
309
229
 
310
230
  /**
311
231
  * Called when a handle is decoded by this object. A handle in the object's data represents an outbound reference
@@ -319,12 +239,6 @@ export abstract class SharedObject<TEvent extends ISharedObjectEvents = ISharedO
319
239
  }
320
240
  }
321
241
 
322
- /**
323
- * Gets a form of the object that can be serialized.
324
- * @returns A tree representing the snapshot of the shared object.
325
- */
326
- protected abstract snapshotCore(serializer: IFluidSerializer): ITree;
327
-
328
242
  /**
329
243
  * Allows the distributed data type to perform custom loading
330
244
  * @param services - Storage used by the shared object
@@ -519,3 +433,108 @@ export abstract class SharedObject<TEvent extends ISharedObjectEvents = ISharedO
519
433
 
520
434
  protected abstract applyStashedOp(content: any): unknown;
521
435
  }
436
+
437
+ /**
438
+ * SharedObject with simplified, synchronous summarization and GC
439
+ */
440
+ export abstract class SharedObject<TEvent extends ISharedObjectEvents = ISharedObjectEvents>
441
+ extends SharedObjectCore<TEvent> {
442
+ /**
443
+ * True while we are garbage collecting this object's data.
444
+ */
445
+ private _isGCing: boolean = false;
446
+
447
+ /**
448
+ * The serializer to use to serialize / parse handles, if any.
449
+ */
450
+ private readonly _serializer: IFluidSerializer;
451
+
452
+ protected get serializer(): IFluidSerializer {
453
+ /**
454
+ * During garbage collection, the SummarySerializer keeps track of IFluidHandles that are serialized. These
455
+ * handles represent references to other Fluid objects.
456
+ *
457
+ * This is fine for now. However, if we implement delay loading in DDss, they may load and de-serialize content
458
+ * in summarize. When that happens, they may incorrectly hit this assert and we will have to change this.
459
+ */
460
+ assert(!this._isGCing,
461
+ 0x075 /* "SummarySerializer should be used for serializing data during summary." */);
462
+ return this._serializer;
463
+ }
464
+
465
+ /**
466
+ * @param id - The id of the shared object
467
+ * @param runtime - The IFluidDataStoreRuntime which contains the shared object
468
+ * @param attributes - Attributes of the shared object
469
+ */
470
+ constructor(
471
+ id: string,
472
+ runtime: IFluidDataStoreRuntime,
473
+ attributes: IChannelAttributes)
474
+ {
475
+ super(id, runtime, attributes);
476
+
477
+ this._serializer = new FluidSerializer(
478
+ this.runtime.channelsRoutingContext,
479
+ (handle: IFluidHandle) => this.handleDecoded(handle),
480
+ );
481
+ }
482
+
483
+ /**
484
+ * {@inheritDoc (ISharedObject:interface).getAttachSummary}
485
+ */
486
+ public getAttachSummary(fullTree: boolean = false, trackState: boolean = false): ISummaryTreeWithStats {
487
+ return this.summarizeCore(this.serializer, fullTree);
488
+ }
489
+
490
+ /**
491
+ * {@inheritDoc (ISharedObject:interface).summarize}
492
+ */
493
+ public async summarize(fullTree: boolean = false, trackState: boolean = false): Promise<ISummaryTreeWithStats> {
494
+ return this.summarizeCore(this.serializer, fullTree);
495
+ }
496
+
497
+ /**
498
+ * {@inheritDoc (ISharedObject:interface).getGCData}
499
+ */
500
+ public getGCData(fullGC: boolean = false): IGarbageCollectionData {
501
+ // Set _isGCing to true. This flag is used to ensure that we only use SummarySerializer to serialize handles
502
+ // in this object's data.
503
+ assert(!this._isGCing, 0x078 /* "Possible re-entrancy! Summary should not already be in progress." */);
504
+ this._isGCing = true;
505
+
506
+ let gcData: IGarbageCollectionData;
507
+ try {
508
+ const serializer = new SummarySerializer(
509
+ this.runtime.channelsRoutingContext,
510
+ (handle: IFluidHandle) => this.handleDecoded(handle),
511
+ );
512
+ this.processGCDataCore(serializer);
513
+ // The GC data for this shared object contains a single GC node. The outbound routes of this node are the
514
+ // routes of handles serialized during summarization.
515
+ gcData = { gcNodes: { "/": serializer.getSerializedRoutes() } };
516
+ assert(this._isGCing, 0x079 /* "Possible re-entrancy! Summary should have been in progress." */);
517
+ } finally {
518
+ this._isGCing = false;
519
+ }
520
+
521
+ return gcData;
522
+ }
523
+
524
+ /**
525
+ * Calls the serializer over all data in this object that reference other GC nodes.
526
+ * Derived classes must override this to provide custom list of references to other GC nodes.
527
+ */
528
+ protected processGCDataCore(serializer: SummarySerializer) {
529
+ // We run the full summarize logic to get the list of outbound routes from this object. This is a little
530
+ // expensive but its okay for now. It will be updated to not use full summarize and make it more efficient.
531
+ // See: https://github.com/microsoft/FluidFramework/issues/4547
532
+ this.summarizeCore(serializer, false);
533
+ }
534
+
535
+ /**
536
+ * Gets a form of the object that can be serialized.
537
+ * @returns A tree representing the snapshot of the shared object.
538
+ */
539
+ protected abstract summarizeCore(serializer: IFluidSerializer, fullTree: boolean): ISummaryTreeWithStats;
540
+ }
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { IFluidHandle } from "@fluidframework/core-interfaces";
7
- import { FluidSerializer } from "@fluidframework/runtime-utils";
7
+ import { FluidSerializer } from "./serializer";
8
8
 
9
9
  /**
10
10
  * Serializer implementation for serializing handles during summary.
package/src/types.ts CHANGED
@@ -31,10 +31,17 @@ export interface ISharedObject<TEvent extends ISharedObjectEvents = ISharedObjec
31
31
  isAttached(): boolean;
32
32
 
33
33
  /**
34
- * Generates summary of the shared object.
34
+ * Generates summary of the channel synchronously.
35
35
  * @returns A tree representing the summary of the shared object.
36
36
  */
37
- summarize(fullTree?: boolean, trackState?: boolean): ISummaryTreeWithStats;
37
+ getAttachSummary(fullTree?: boolean, trackState?: boolean): ISummaryTreeWithStats;
38
+
39
+ /**
40
+ * Generates summary of the shared object asynchronously.
41
+ * This should not be called where the object can be modified while summarization is in progress.
42
+ * @returns A tree representing the summary of the channel.
43
+ */
44
+ summarize(fullTree?: boolean, trackState?: boolean): Promise<ISummaryTreeWithStats>;
38
45
 
39
46
  /**
40
47
  * Enables the channel to send and receive ops.
package/src/utils.ts CHANGED
@@ -3,10 +3,10 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import {
7
- IFluidHandle,
8
- IFluidSerializer,
9
- } from "@fluidframework/core-interfaces";
6
+ import { IFluidHandle } from "@fluidframework/core-interfaces";
7
+ import { ISummaryTreeWithStats } from "@fluidframework/runtime-definitions";
8
+ import { SummaryTreeBuilder } from "@fluidframework/runtime-utils";
9
+ import { IFluidSerializer } from "./serializer";
10
10
 
11
11
  /**
12
12
  * Given a mostly-plain object that may have handle objects embedded within, return a string representation of an object
@@ -17,7 +17,6 @@ import {
17
17
  * @param bind - Bind any other handles we find in the object against this given handle.
18
18
  * @returns Result of strigifying an object
19
19
  */
20
- // eslint-disable-next-line prefer-arrow/prefer-arrow-functions
21
20
  export function serializeHandles(
22
21
  value: any,
23
22
  serializer: IFluidSerializer,
@@ -43,14 +42,13 @@ export function serializeHandles(
43
42
  * @param bind - Bind any other handles we find in the object against this given handle.
44
43
  * @returns The fully-plain object
45
44
  */
46
- // eslint-disable-next-line prefer-arrow/prefer-arrow-functions
47
45
  export function makeHandlesSerializable(
48
46
  value: any,
49
47
  serializer: IFluidSerializer,
50
48
  bind: IFluidHandle,
51
49
  ) {
52
50
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
53
- return serializer.replaceHandles(
51
+ return serializer.encode(
54
52
  value,
55
53
  bind);
56
54
  }
@@ -63,7 +61,6 @@ export function makeHandlesSerializable(
63
61
  * @param context - The handle context for the container
64
62
  * @returns The mostly-plain object with handle objects within
65
63
  */
66
- // eslint-disable-next-line prefer-arrow/prefer-arrow-functions
67
64
  export function parseHandles(
68
65
  value: any,
69
66
  serializer: IFluidSerializer,
@@ -71,3 +68,15 @@ export function parseHandles(
71
68
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
72
69
  return value !== undefined ? serializer.parse(JSON.stringify(value)) : value;
73
70
  }
71
+
72
+ /**
73
+ * Create a new summary containing one blob
74
+ * @param key - the key for the blob in the summary
75
+ * @param content - blob content
76
+ * @returns The summary containing the blob
77
+ */
78
+ export function createSingleBlobSummary(key: string, content: string | Uint8Array): ISummaryTreeWithStats {
79
+ const builder = new SummaryTreeBuilder();
80
+ builder.addBlob(key, content);
81
+ return builder.getSummaryTree();
82
+ }
package/tsconfig.json CHANGED
@@ -2,13 +2,15 @@
2
2
  "extends": "@fluidframework/build-common/ts-common-config.json",
3
3
  "exclude": [
4
4
  "dist",
5
- "node_modules"
5
+ "node_modules",
6
+ "src/test/**/*"
6
7
  ],
7
8
  "compilerOptions": {
8
9
  "rootDir": "./src",
9
- "outDir": "./dist"
10
+ "outDir": "./dist",
11
+ "composite": true
10
12
  },
11
13
  "include": [
12
14
  "src/**/*"
13
15
  ]
14
- }
16
+ }