@fluidframework/datastore 2.0.0-dev-rc.1.0.0.228517 → 2.0.0-dev-rc.2.0.0.245554

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 (117) hide show
  1. package/{.eslintrc.js → .eslintrc.cjs} +4 -1
  2. package/{.mocharc.js → .mocharc.cjs} +1 -1
  3. package/CHANGELOG.md +42 -0
  4. package/{api-extractor-esm.json → api-extractor-cjs.json} +5 -1
  5. package/api-extractor-lint.json +1 -1
  6. package/api-extractor.json +1 -1
  7. package/api-report/datastore.api.md +7 -11
  8. package/dist/channelContext.d.ts +5 -4
  9. package/dist/channelContext.d.ts.map +1 -1
  10. package/dist/channelContext.js +6 -5
  11. package/dist/channelContext.js.map +1 -1
  12. package/dist/channelDeltaConnection.d.ts +6 -3
  13. package/dist/channelDeltaConnection.d.ts.map +1 -1
  14. package/dist/channelDeltaConnection.js +46 -7
  15. package/dist/channelDeltaConnection.js.map +1 -1
  16. package/dist/dataStoreRuntime.d.ts +25 -6
  17. package/dist/dataStoreRuntime.d.ts.map +1 -1
  18. package/dist/dataStoreRuntime.js +145 -62
  19. package/dist/dataStoreRuntime.js.map +1 -1
  20. package/dist/datastore-alpha.d.ts +23 -6
  21. package/dist/datastore-beta.d.ts +1 -2
  22. package/dist/datastore-public.d.ts +1 -2
  23. package/dist/datastore-untrimmed.d.ts +23 -6
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +7 -7
  27. package/dist/index.js.map +1 -1
  28. package/dist/localChannelContext.d.ts +18 -3
  29. package/dist/localChannelContext.d.ts.map +1 -1
  30. package/dist/localChannelContext.js +32 -10
  31. package/dist/localChannelContext.js.map +1 -1
  32. package/dist/package.json +3 -0
  33. package/dist/remoteChannelContext.d.ts +2 -2
  34. package/dist/remoteChannelContext.d.ts.map +1 -1
  35. package/dist/remoteChannelContext.js +6 -5
  36. package/dist/remoteChannelContext.js.map +1 -1
  37. package/dist/tsdoc-metadata.json +1 -1
  38. package/lib/{channelContext.d.mts → channelContext.d.ts} +6 -5
  39. package/lib/channelContext.d.ts.map +1 -0
  40. package/lib/{channelContext.mjs → channelContext.js} +6 -5
  41. package/lib/channelContext.js.map +1 -0
  42. package/lib/{channelDeltaConnection.d.mts → channelDeltaConnection.d.ts} +7 -4
  43. package/lib/channelDeltaConnection.d.ts.map +1 -0
  44. package/lib/channelDeltaConnection.js +92 -0
  45. package/lib/channelDeltaConnection.js.map +1 -0
  46. package/lib/{channelStorageService.d.mts → channelStorageService.d.ts} +1 -1
  47. package/lib/channelStorageService.d.ts.map +1 -0
  48. package/lib/{channelStorageService.mjs → channelStorageService.js} +1 -1
  49. package/lib/channelStorageService.js.map +1 -0
  50. package/lib/{dataStoreRuntime.d.mts → dataStoreRuntime.d.ts} +26 -7
  51. package/lib/dataStoreRuntime.d.ts.map +1 -0
  52. package/lib/{dataStoreRuntime.mjs → dataStoreRuntime.js} +142 -59
  53. package/lib/dataStoreRuntime.js.map +1 -0
  54. package/lib/{datastore-alpha.d.mts → datastore-alpha.d.ts} +23 -6
  55. package/lib/{datastore-public.d.mts → datastore-beta.d.ts} +1 -2
  56. package/lib/{datastore-beta.d.mts → datastore-public.d.ts} +1 -2
  57. package/lib/{datastore-untrimmed.d.mts → datastore-untrimmed.d.ts} +23 -6
  58. package/lib/{fluidHandle.d.mts → fluidHandle.d.ts} +1 -1
  59. package/lib/fluidHandle.d.ts.map +1 -0
  60. package/lib/{fluidHandle.mjs → fluidHandle.js} +1 -1
  61. package/lib/fluidHandle.js.map +1 -0
  62. package/lib/{index.d.mts → index.d.ts} +3 -3
  63. package/lib/index.d.ts.map +1 -0
  64. package/lib/{index.mjs → index.js} +3 -3
  65. package/lib/index.js.map +1 -0
  66. package/lib/{localChannelContext.d.mts → localChannelContext.d.ts} +19 -4
  67. package/lib/localChannelContext.d.ts.map +1 -0
  68. package/lib/{localChannelContext.mjs → localChannelContext.js} +29 -7
  69. package/lib/localChannelContext.js.map +1 -0
  70. package/lib/{localChannelStorageService.d.mts → localChannelStorageService.d.ts} +1 -1
  71. package/lib/localChannelStorageService.d.ts.map +1 -0
  72. package/lib/{localChannelStorageService.mjs → localChannelStorageService.js} +1 -1
  73. package/lib/localChannelStorageService.js.map +1 -0
  74. package/lib/{remoteChannelContext.d.mts → remoteChannelContext.d.ts} +3 -3
  75. package/lib/remoteChannelContext.d.ts.map +1 -0
  76. package/lib/{remoteChannelContext.mjs → remoteChannelContext.js} +4 -3
  77. package/lib/remoteChannelContext.js.map +1 -0
  78. package/lib/test/channelStorageService.spec.js +70 -0
  79. package/lib/test/channelStorageService.spec.js.map +1 -0
  80. package/lib/test/dataStoreRuntime.spec.js +121 -0
  81. package/lib/test/dataStoreRuntime.spec.js.map +1 -0
  82. package/lib/test/localChannelContext.spec.js +41 -0
  83. package/lib/test/localChannelContext.spec.js.map +1 -0
  84. package/lib/test/localChannelStorageService.spec.js +72 -0
  85. package/lib/test/localChannelStorageService.spec.js.map +1 -0
  86. package/lib/test/remoteChannelContext.spec.js +33 -0
  87. package/lib/test/remoteChannelContext.spec.js.map +1 -0
  88. package/lib/test/types/validateDatastorePrevious.generated.js +16 -0
  89. package/lib/test/types/validateDatastorePrevious.generated.js.map +1 -0
  90. package/package.json +48 -50
  91. package/src/channelContext.ts +6 -3
  92. package/src/channelDeltaConnection.ts +69 -5
  93. package/src/dataStoreRuntime.ts +181 -81
  94. package/src/index.ts +2 -2
  95. package/src/localChannelContext.ts +38 -5
  96. package/src/remoteChannelContext.ts +4 -2
  97. package/tsconfig.cjs.json +7 -0
  98. package/tsconfig.json +2 -5
  99. package/lib/channelContext.d.mts.map +0 -1
  100. package/lib/channelContext.mjs.map +0 -1
  101. package/lib/channelDeltaConnection.d.mts.map +0 -1
  102. package/lib/channelDeltaConnection.mjs +0 -53
  103. package/lib/channelDeltaConnection.mjs.map +0 -1
  104. package/lib/channelStorageService.d.mts.map +0 -1
  105. package/lib/channelStorageService.mjs.map +0 -1
  106. package/lib/dataStoreRuntime.d.mts.map +0 -1
  107. package/lib/dataStoreRuntime.mjs.map +0 -1
  108. package/lib/fluidHandle.d.mts.map +0 -1
  109. package/lib/fluidHandle.mjs.map +0 -1
  110. package/lib/index.d.mts.map +0 -1
  111. package/lib/index.mjs.map +0 -1
  112. package/lib/localChannelContext.d.mts.map +0 -1
  113. package/lib/localChannelContext.mjs.map +0 -1
  114. package/lib/localChannelStorageService.d.mts.map +0 -1
  115. package/lib/localChannelStorageService.mjs.map +0 -1
  116. package/lib/remoteChannelContext.d.mts.map +0 -1
  117. package/lib/remoteChannelContext.mjs.map +0 -1
@@ -14,10 +14,10 @@ const protocol_definitions_1 = require("@fluidframework/protocol-definitions");
14
14
  const runtime_definitions_1 = require("@fluidframework/runtime-definitions");
15
15
  const runtime_utils_1 = require("@fluidframework/runtime-utils");
16
16
  const uuid_1 = require("uuid");
17
- const channelContext_1 = require("./channelContext");
18
- const localChannelContext_1 = require("./localChannelContext");
19
- const remoteChannelContext_1 = require("./remoteChannelContext");
20
- const fluidHandle_1 = require("./fluidHandle");
17
+ const channelContext_js_1 = require("./channelContext.js");
18
+ const localChannelContext_js_1 = require("./localChannelContext.js");
19
+ const remoteChannelContext_js_1 = require("./remoteChannelContext.js");
20
+ const fluidHandle_js_1 = require("./fluidHandle.js");
21
21
  /**
22
22
  * @alpha
23
23
  */
@@ -138,7 +138,7 @@ class FluidDataStoreRuntime extends client_utils_1.TypedEventEmitter {
138
138
  // container from snapshot where we load detached container from a snapshot, isLocalDataStore would be
139
139
  // true. In this case create a RehydratedLocalChannelContext.
140
140
  if (dataStoreContext.isLocalDataStore) {
141
- channelContext = new localChannelContext_1.RehydratedLocalChannelContext(path, this.sharedObjectRegistry, this, this.dataStoreContext, this.dataStoreContext.storage, this.logger, (content, localOpMetadata) => this.submitChannelOp(path, content, localOpMetadata), (address) => this.setChannelDirty(address), (srcHandle, outboundHandle) => this.addedGCOutboundReference(srcHandle, outboundHandle), tree.trees[path]);
141
+ channelContext = new localChannelContext_js_1.RehydratedLocalChannelContext(path, this.sharedObjectRegistry, this, this.dataStoreContext, this.dataStoreContext.storage, this.logger, (content, localOpMetadata) => this.submitChannelOp(path, content, localOpMetadata), (address) => this.setChannelDirty(address), (srcHandle, outboundHandle) => this.addedGCOutboundReference(srcHandle, outboundHandle), tree.trees[path]);
142
142
  // This is the case of rehydrating a detached container from snapshot. Now due to delay loading of
143
143
  // data store, if the data store is loaded after the container is attached, then we missed making
144
144
  // the channel visible. So do it now. Otherwise, add it to local channel context queue, so
@@ -151,14 +151,14 @@ class FluidDataStoreRuntime extends client_utils_1.TypedEventEmitter {
151
151
  }
152
152
  }
153
153
  else {
154
- channelContext = new remoteChannelContext_1.RemoteChannelContext(this, dataStoreContext, dataStoreContext.storage, (content, localOpMetadata) => this.submitChannelOp(path, content, localOpMetadata), (address) => this.setChannelDirty(address), (srcHandle, outboundHandle) => this.addedGCOutboundReference(srcHandle, outboundHandle), path, tree.trees[path], this.sharedObjectRegistry, undefined /* extraBlobs */, this.dataStoreContext.getCreateChildSummarizerNodeFn(path, {
154
+ channelContext = new remoteChannelContext_js_1.RemoteChannelContext(this, dataStoreContext, dataStoreContext.storage, (content, localOpMetadata) => this.submitChannelOp(path, content, localOpMetadata), (address) => this.setChannelDirty(address), (srcHandle, outboundHandle) => this.addedGCOutboundReference(srcHandle, outboundHandle), path, tree.trees[path], this.sharedObjectRegistry, undefined /* extraBlobs */, this.dataStoreContext.getCreateChildSummarizerNodeFn(path, {
155
155
  type: runtime_definitions_1.CreateSummarizerNodeSource.FromSummary,
156
156
  }));
157
157
  }
158
158
  this.contexts.set(path, channelContext);
159
159
  });
160
160
  }
161
- this.entryPoint = new fluidHandle_1.FluidObjectHandle(new core_utils_1.LazyPromise(async () => provideEntryPoint(this)), "", this.objectsRoutingContext);
161
+ this.entryPoint = new fluidHandle_js_1.FluidObjectHandle(new core_utils_1.LazyPromise(async () => provideEntryPoint(this)), "", this.objectsRoutingContext);
162
162
  this.attachListener();
163
163
  this._attachState = dataStoreContext.attachState;
164
164
  /**
@@ -233,6 +233,20 @@ class FluidDataStoreRuntime extends client_utils_1.TypedEventEmitter {
233
233
  }
234
234
  return context.getChannel();
235
235
  }
236
+ /**
237
+ * Validate user provided channel ID
238
+ * Channel ID has limitations. "/" is not allowed as IDs in storage can not have slashes - we parse tree paths and use "/" as separator.
239
+ * IDs cannot start with "_" as it could result in collision of IDs with auto-assigned (by FF) short IDs.
240
+ * @param id - channel ID.
241
+ */
242
+ validateChannelId(id) {
243
+ if (id.includes("/")) {
244
+ throw new telemetry_utils_1.UsageError(`Id cannot contain slashes: ${id}`);
245
+ }
246
+ if (id.startsWith("_")) {
247
+ throw new telemetry_utils_1.UsageError(`Id cannot start with underscore: ${id}`);
248
+ }
249
+ }
236
250
  /**
237
251
  * Api which allows caller to create the channel first and then add it to the runtime.
238
252
  * The channel type should be present in the registry, otherwise the runtime would reject
@@ -242,9 +256,7 @@ class FluidDataStoreRuntime extends client_utils_1.TypedEventEmitter {
242
256
  */
243
257
  addChannel(channel) {
244
258
  const id = channel.id;
245
- if (id.includes("/")) {
246
- throw new telemetry_utils_1.UsageError(`Id cannot contain slashes: ${id}`);
247
- }
259
+ this.validateChannelId(id);
248
260
  this.verifyNotClosed();
249
261
  (0, core_utils_1.assert)(!this.contexts.has(id), 0x865 /* addChannel() with existing ID */);
250
262
  const type = channel.attributes.type;
@@ -256,9 +268,30 @@ class FluidDataStoreRuntime extends client_utils_1.TypedEventEmitter {
256
268
  // Channels (DDS) should not be created in summarizer client.
257
269
  this.identifyLocalChangeInSummarizer("DDSCreatedInSummarizer", id, type);
258
270
  }
259
- createChannel(id = (0, uuid_1.v4)(), type) {
260
- if (id.includes("/")) {
261
- throw new telemetry_utils_1.UsageError(`Id cannot contain slashes: ${id}`);
271
+ createChannel(idArg, type) {
272
+ let id;
273
+ if (idArg !== undefined) {
274
+ id = idArg;
275
+ this.validateChannelId(id);
276
+ }
277
+ else {
278
+ // We use three non-overlapping namespaces:
279
+ // - detached state: even numbers
280
+ // - attached state: odd numbers
281
+ // - uuids
282
+ // In first two cases we will encode result as strings in more compact form, with leading underscore,
283
+ // to ensure no overlap with user-provided DDS names (see validateChannelId())
284
+ if (this.visibilityState !== runtime_definitions_1.VisibilityState.GloballyVisible) {
285
+ // container is detached, only one client observes content, no way to hit collisions with other clients.
286
+ id = (0, runtime_utils_1.encodeCompactIdToString)(2 * this.contexts.size, "_");
287
+ }
288
+ else {
289
+ // Due to back-compat, we could not depend yet on generateDocumentUniqueId() being there.
290
+ // We can remove the need to leverage uuid() as fall-back in couple releases.
291
+ const res = this.dataStoreContext.containerRuntime.generateDocumentUniqueId?.() ?? (0, uuid_1.v4)();
292
+ id = typeof res === "number" ? (0, runtime_utils_1.encodeCompactIdToString)(2 * res + 1, "_") : res;
293
+ }
294
+ (0, core_utils_1.assert)(!id.includes("/"), 0x8fc /* slash */);
262
295
  }
263
296
  this.verifyNotClosed();
264
297
  (0, core_utils_1.assert)(!this.contexts.has(id), 0x179 /* "createChannel() with existing ID" */);
@@ -275,7 +308,7 @@ class FluidDataStoreRuntime extends client_utils_1.TypedEventEmitter {
275
308
  }
276
309
  createChannelContext(channel) {
277
310
  this.notBoundedChannelContextSet.add(channel.id);
278
- const context = new localChannelContext_1.LocalChannelContext(channel, this, this.dataStoreContext, this.dataStoreContext.storage, this.logger, (content, localOpMetadata) => this.submitChannelOp(channel.id, content, localOpMetadata), (address) => this.setChannelDirty(address), (srcHandle, outboundHandle) => this.addedGCOutboundReference(srcHandle, outboundHandle));
311
+ const context = new localChannelContext_js_1.LocalChannelContext(channel, this, this.dataStoreContext, this.dataStoreContext.storage, this.logger, (content, localOpMetadata) => this.submitChannelOp(channel.id, content, localOpMetadata), (address) => this.setChannelDirty(address), (srcHandle, outboundHandle) => this.addedGCOutboundReference(srcHandle, outboundHandle));
279
312
  this.contexts.set(channel.id, context);
280
313
  }
281
314
  /**
@@ -288,7 +321,7 @@ class FluidDataStoreRuntime extends client_utils_1.TypedEventEmitter {
288
321
  this.notBoundedChannelContextSet.delete(channel.id);
289
322
  // If our data store is attached, then attach the channel.
290
323
  if (this.isAttached) {
291
- this.attachChannel(channel);
324
+ this.makeChannelLocallyVisible(channel);
292
325
  return;
293
326
  }
294
327
  /**
@@ -360,7 +393,7 @@ class FluidDataStoreRuntime extends client_utils_1.TypedEventEmitter {
360
393
  createRemoteChannelContext(attachMessage, summarizerNodeParams) {
361
394
  const flatBlobs = new Map();
362
395
  const snapshotTree = (0, driver_utils_1.buildSnapshotTree)(attachMessage.snapshot.entries, flatBlobs);
363
- return new remoteChannelContext_1.RemoteChannelContext(this, this.dataStoreContext, this.dataStoreContext.storage, (content, localContentMetadata) => this.submitChannelOp(attachMessage.id, content, localContentMetadata), (address) => this.setChannelDirty(address), (srcHandle, outboundHandle) => this.addedGCOutboundReference(srcHandle, outboundHandle), attachMessage.id, snapshotTree, this.sharedObjectRegistry, flatBlobs, this.dataStoreContext.getCreateChildSummarizerNodeFn(attachMessage.id, summarizerNodeParams), attachMessage.type);
396
+ return new remoteChannelContext_js_1.RemoteChannelContext(this, this.dataStoreContext, this.dataStoreContext.storage, (content, localContentMetadata) => this.submitChannelOp(attachMessage.id, content, localContentMetadata), (address) => this.setChannelDirty(address), (srcHandle, outboundHandle) => this.addedGCOutboundReference(srcHandle, outboundHandle), attachMessage.id, snapshotTree, this.sharedObjectRegistry, flatBlobs, this.dataStoreContext.getCreateChildSummarizerNodeFn(attachMessage.id, summarizerNodeParams), attachMessage.type);
364
397
  }
365
398
  process(message, local, localOpMetadata) {
366
399
  this.verifyNotClosed();
@@ -370,6 +403,12 @@ class FluidDataStoreRuntime extends client_utils_1.TypedEventEmitter {
370
403
  case DataStoreMessageType.Attach: {
371
404
  const attachMessage = message.contents;
372
405
  const id = attachMessage.id;
406
+ // We need to process the GC Data for both local and remote attach messages
407
+ (0, runtime_utils_1.processAttachMessageGCData)(attachMessage.snapshot, (nodeId, toPath) => {
408
+ // Note: nodeId will be "/" unless and until we support sub-DDS GC Nodes
409
+ const fromPath = `/${this.id}/${id}${nodeId === "/" ? "" : nodeId}`;
410
+ this.dataStoreContext.addedGCOutboundRoute?.(fromPath, toPath);
411
+ });
373
412
  // If a non-local operation then go and create the object
374
413
  // Otherwise mark it as officially attached.
375
414
  if (local) {
@@ -526,6 +565,45 @@ class FluidDataStoreRuntime extends client_utils_1.TypedEventEmitter {
526
565
  return summaryBuilder.getSummaryTree();
527
566
  }
528
567
  getAttachSummary(telemetryContext) {
568
+ const summaryBuilder = new runtime_utils_1.SummaryTreeBuilder();
569
+ this.visitLocalBoundContextsDuringAttach((contextId, context) => {
570
+ let summaryTree;
571
+ if (context.isLoaded) {
572
+ const contextSummary = context.getAttachSummary(telemetryContext);
573
+ (0, core_utils_1.assert)(contextSummary.summary.type === protocol_definitions_1.SummaryType.Tree, 0x180 /* "getAttachSummary should always return a tree" */);
574
+ summaryTree = { stats: contextSummary.stats, summary: contextSummary.summary };
575
+ }
576
+ else {
577
+ // If this channel is not yet loaded, then there should be no changes in the snapshot from which
578
+ // it was created as it is detached container. So just use the previous snapshot.
579
+ (0, core_utils_1.assert)(!!this.dataStoreContext.baseSnapshot, 0x181 /* "BaseSnapshot should be there as detached container loaded from snapshot" */);
580
+ summaryTree = (0, runtime_utils_1.convertSnapshotTreeToSummaryTree)(this.dataStoreContext.baseSnapshot.trees[contextId]);
581
+ }
582
+ summaryBuilder.addWithStats(contextId, summaryTree);
583
+ });
584
+ return summaryBuilder.getSummaryTree();
585
+ }
586
+ /**
587
+ * Get the GC Data for the initial state being attached so remote clients can learn of this DataStore's outbound routes
588
+ */
589
+ getAttachGCData(telemetryContext) {
590
+ const gcDataBuilder = new runtime_utils_1.GCDataBuilder();
591
+ this.visitLocalBoundContextsDuringAttach((contextId, context) => {
592
+ if (context.isLoaded) {
593
+ const contextGCData = context.getAttachGCData(telemetryContext);
594
+ // Incorporate the GC Data for this context
595
+ gcDataBuilder.prefixAndAddNodes(contextId, contextGCData.gcNodes);
596
+ }
597
+ // else: Rehydrating detached container case. GC doesn't run until the container is attached, so nothing to do here.
598
+ });
599
+ this.updateGCNodes(gcDataBuilder);
600
+ return gcDataBuilder.getGCData();
601
+ }
602
+ /**
603
+ * Helper method for preparing to attach this dataStore.
604
+ * Runs the callback for each bound context to incorporate its data however the caller specifies
605
+ */
606
+ visitLocalBoundContextsDuringAttach(visitor) {
529
607
  /**
530
608
  * back-compat 0.59.1000 - getAttachSummary() is called when making a data store globally visible (previously
531
609
  * attaching state). Ideally, attachGraph() should have already be called making it locally visible. However,
@@ -544,29 +622,14 @@ class FluidDataStoreRuntime extends client_utils_1.TypedEventEmitter {
544
622
  // assert(this.visibilityState === VisibilityState.LocallyVisible,
545
623
  // "The data store should be locally visible when generating attach summary",
546
624
  // );
547
- const summaryBuilder = new runtime_utils_1.SummaryTreeBuilder();
548
- // Craft the .attributes file for each shared object
549
625
  for (const [contextId, context] of this.contexts) {
550
- if (!(context instanceof localChannelContext_1.LocalChannelContextBase)) {
626
+ if (!(context instanceof localChannelContext_js_1.LocalChannelContextBase)) {
551
627
  throw new telemetry_utils_1.LoggingError("Should only be called with local channel handles");
552
628
  }
553
629
  if (!this.notBoundedChannelContextSet.has(contextId)) {
554
- let summaryTree;
555
- if (context.isLoaded) {
556
- const contextSummary = context.getAttachSummary(telemetryContext);
557
- (0, core_utils_1.assert)(contextSummary.summary.type === protocol_definitions_1.SummaryType.Tree, 0x180 /* "getAttachSummary should always return a tree" */);
558
- summaryTree = { stats: contextSummary.stats, summary: contextSummary.summary };
559
- }
560
- else {
561
- // If this channel is not yet loaded, then there should be no changes in the snapshot from which
562
- // it was created as it is detached container. So just use the previous snapshot.
563
- (0, core_utils_1.assert)(!!this.dataStoreContext.baseSnapshot, 0x181 /* "BaseSnapshot should be there as detached container loaded from snapshot" */);
564
- summaryTree = (0, runtime_utils_1.convertSnapshotTreeToSummaryTree)(this.dataStoreContext.baseSnapshot.trees[contextId]);
565
- }
566
- summaryBuilder.addWithStats(contextId, summaryTree);
630
+ visitor(contextId, context);
567
631
  }
568
632
  }
569
- return summaryBuilder.getSummaryTree();
570
633
  }
571
634
  submitMessage(type, content, localOpMetadata) {
572
635
  this.submit(type, content, localOpMetadata);
@@ -588,9 +651,10 @@ class FluidDataStoreRuntime extends client_utils_1.TypedEventEmitter {
588
651
  return this.deferredAttached.promise;
589
652
  }
590
653
  /**
591
- * Attach channel should only be called after the data store has been attached
654
+ * Assuming this DataStore is already attached, this will make the given channel locally visible
655
+ * by submitting its attach op.
592
656
  */
593
- attachChannel(channel) {
657
+ makeChannelLocallyVisible(channel) {
594
658
  this.verifyNotClosed();
595
659
  // If this handle is already attached no need to attach again.
596
660
  if (channel.handle.isAttached) {
@@ -599,7 +663,10 @@ class FluidDataStoreRuntime extends client_utils_1.TypedEventEmitter {
599
663
  channel.handle.attachGraph();
600
664
  (0, core_utils_1.assert)(this.isAttached, 0x182 /* "Data store should be attached to attach the channel." */);
601
665
  (0, core_utils_1.assert)(this.visibilityState === runtime_definitions_1.VisibilityState.GloballyVisible, 0x2d0 /* "Data store should be globally visible to attach channels." */);
602
- const summarizeResult = (0, channelContext_1.summarizeChannel)(channel, true /* fullTree */, false /* trackState */);
666
+ const summarizeResult = (0, channelContext_js_1.summarizeChannel)(channel, true /* fullTree */, false /* trackState */);
667
+ // We need to include the channel's GC Data so remote clients can learn of this channel's outbound routes
668
+ const gcData = channel.getGCData(/* fullGC: */ true);
669
+ (0, runtime_utils_1.addBlobToSummary)(summarizeResult, runtime_definitions_1.gcDataBlobKey, JSON.stringify(gcData));
603
670
  // Attach message needs the summary in ITree format. Convert the ISummaryTree into an ITree.
604
671
  const snapshot = (0, runtime_utils_1.convertSummaryTreeToITree)(summarizeResult.summary);
605
672
  const message = {
@@ -709,33 +776,15 @@ class FluidDataStoreRuntime extends client_utils_1.TypedEventEmitter {
709
776
  }
710
777
  attachListener() {
711
778
  this.setMaxListeners(Number.MAX_SAFE_INTEGER);
712
- this.dataStoreContext.once("attaching", () => {
713
- /**
714
- * back-compat 0.59.1000 - Ideally, attachGraph() should have already been called making the data store
715
- * locally visible. However, before visibility state was added, this may not have been the case and data
716
- * store can move to "attaching" state in 2 scenarios:
717
- * 1) Before attachGraph() is called - When a data store is created and bound in an attached container.
718
- * 2) After attachGraph() is called - When a detached container is attached.
719
- *
720
- * The basic idea is that all local object should become locally visible before they are globally visible.
721
- */
722
- this.attachGraph();
723
- this._attachState = container_definitions_1.AttachState.Attaching;
724
- (0, core_utils_1.assert)(this.visibilityState === runtime_definitions_1.VisibilityState.LocallyVisible, 0x2d1 /* "Data store should be locally visible before it can become globally visible." */);
725
- // Mark the data store globally visible and make its child channels visible as well.
726
- this.visibilityState = runtime_definitions_1.VisibilityState.GloballyVisible;
727
- this.localChannelContextQueue.forEach((channel) => {
728
- channel.makeVisible();
729
- });
730
- this.localChannelContextQueue.clear();
731
- // This promise resolution will be moved to attached event once we fix the scheduler.
732
- this.deferredAttached.resolve();
733
- this.emit("attaching");
779
+ // back-compat, to be removed in the future.
780
+ // Added in "2.0.0-rc.2.0.0" timeframe.
781
+ this.dataStoreContext.once?.("attaching", () => {
782
+ this.setAttachState(container_definitions_1.AttachState.Attaching);
734
783
  });
735
- this.dataStoreContext.once("attached", () => {
736
- (0, core_utils_1.assert)(this.visibilityState === runtime_definitions_1.VisibilityState.GloballyVisible, 0x2d2 /* "Data store should be globally visible when its attached." */);
737
- this._attachState = container_definitions_1.AttachState.Attached;
738
- this.emit("attached");
784
+ // back-compat, to be removed in the future.
785
+ // Added in "2.0.0-rc.2.0.0" timeframe.
786
+ this.dataStoreContext.once?.("attached", () => {
787
+ this.setAttachState(container_definitions_1.AttachState.Attached);
739
788
  });
740
789
  }
741
790
  verifyNotClosed() {
@@ -767,6 +816,40 @@ class FluidDataStoreRuntime extends client_utils_1.TypedEventEmitter {
767
816
  });
768
817
  this.localChangesTelemetryCount--;
769
818
  }
819
+ setAttachState(attachState) {
820
+ switch (attachState) {
821
+ case container_definitions_1.AttachState.Attaching:
822
+ /**
823
+ * back-compat 0.59.1000 - Ideally, attachGraph() should have already been called making the data store
824
+ * locally visible. However, before visibility state was added, this may not have been the case and data
825
+ * store can move to "attaching" state in 2 scenarios:
826
+ * 1) Before attachGraph() is called - When a data store is created and bound in an attached container.
827
+ * 2) After attachGraph() is called - When a detached container is attached.
828
+ *
829
+ * The basic idea is that all local object should become locally visible before they are globally visible.
830
+ */
831
+ this.attachGraph();
832
+ this._attachState = container_definitions_1.AttachState.Attaching;
833
+ (0, core_utils_1.assert)(this.visibilityState === runtime_definitions_1.VisibilityState.LocallyVisible, 0x2d1 /* "Data store should be locally visible before it can become globally visible." */);
834
+ // Mark the data store globally visible and make its child channels visible as well.
835
+ this.visibilityState = runtime_definitions_1.VisibilityState.GloballyVisible;
836
+ this.localChannelContextQueue.forEach((channel) => {
837
+ channel.makeVisible();
838
+ });
839
+ this.localChannelContextQueue.clear();
840
+ // This promise resolution will be moved to attached event once we fix the scheduler.
841
+ this.deferredAttached.resolve();
842
+ this.emit("attaching");
843
+ break;
844
+ case container_definitions_1.AttachState.Attached:
845
+ (0, core_utils_1.assert)(this.visibilityState === runtime_definitions_1.VisibilityState.GloballyVisible, 0x2d2 /* "Data store should be globally visible when its attached." */);
846
+ this._attachState = container_definitions_1.AttachState.Attached;
847
+ this.emit("attached");
848
+ break;
849
+ default:
850
+ (0, core_utils_1.unreachableCase)(attachState, "unreached");
851
+ }
852
+ }
770
853
  }
771
854
  exports.FluidDataStoreRuntime = FluidDataStoreRuntime;
772
855
  /**