@fluidframework/container-runtime 2.51.0-347100 → 2.52.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 (96) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/api-report/container-runtime.legacy.alpha.api.md +1 -2
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/blobManager/blobManager.d.ts +15 -7
  5. package/dist/blobManager/blobManager.d.ts.map +1 -1
  6. package/dist/blobManager/blobManager.js +72 -186
  7. package/dist/blobManager/blobManager.js.map +1 -1
  8. package/dist/containerCompatibility.d.ts +34 -0
  9. package/dist/containerCompatibility.d.ts.map +1 -0
  10. package/dist/containerCompatibility.js +125 -0
  11. package/dist/containerCompatibility.js.map +1 -0
  12. package/dist/containerRuntime.d.ts +27 -15
  13. package/dist/containerRuntime.d.ts.map +1 -1
  14. package/dist/containerRuntime.js +175 -136
  15. package/dist/containerRuntime.js.map +1 -1
  16. package/dist/dataStoreContext.d.ts +6 -6
  17. package/dist/dataStoreContext.d.ts.map +1 -1
  18. package/dist/dataStoreContext.js.map +1 -1
  19. package/dist/index.d.ts +5 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/metadata.d.ts +3 -2
  23. package/dist/metadata.d.ts.map +1 -1
  24. package/dist/metadata.js +7 -1
  25. package/dist/metadata.js.map +1 -1
  26. package/dist/packageVersion.d.ts +1 -1
  27. package/dist/packageVersion.d.ts.map +1 -1
  28. package/dist/packageVersion.js +1 -1
  29. package/dist/packageVersion.js.map +1 -1
  30. package/dist/storageServiceWithAttachBlobs.d.ts +40 -5
  31. package/dist/storageServiceWithAttachBlobs.d.ts.map +1 -1
  32. package/dist/storageServiceWithAttachBlobs.js +56 -5
  33. package/dist/storageServiceWithAttachBlobs.js.map +1 -1
  34. package/dist/summary/documentSchema.d.ts +1 -1
  35. package/dist/summary/documentSchema.d.ts.map +1 -1
  36. package/dist/summary/documentSchema.js.map +1 -1
  37. package/dist/summary/summaryFormat.d.ts +3 -3
  38. package/dist/summary/summaryFormat.d.ts.map +1 -1
  39. package/dist/summary/summaryFormat.js.map +1 -1
  40. package/lib/blobManager/blobManager.d.ts +15 -7
  41. package/lib/blobManager/blobManager.d.ts.map +1 -1
  42. package/lib/blobManager/blobManager.js +39 -153
  43. package/lib/blobManager/blobManager.js.map +1 -1
  44. package/lib/containerCompatibility.d.ts +34 -0
  45. package/lib/containerCompatibility.d.ts.map +1 -0
  46. package/lib/containerCompatibility.js +120 -0
  47. package/lib/containerCompatibility.js.map +1 -0
  48. package/lib/containerRuntime.d.ts +27 -15
  49. package/lib/containerRuntime.d.ts.map +1 -1
  50. package/lib/containerRuntime.js +103 -64
  51. package/lib/containerRuntime.js.map +1 -1
  52. package/lib/dataStoreContext.d.ts +6 -6
  53. package/lib/dataStoreContext.d.ts.map +1 -1
  54. package/lib/dataStoreContext.js +1 -1
  55. package/lib/dataStoreContext.js.map +1 -1
  56. package/lib/index.d.ts +5 -1
  57. package/lib/index.d.ts.map +1 -1
  58. package/lib/index.js.map +1 -1
  59. package/lib/metadata.d.ts +3 -2
  60. package/lib/metadata.d.ts.map +1 -1
  61. package/lib/metadata.js +5 -0
  62. package/lib/metadata.js.map +1 -1
  63. package/lib/packageVersion.d.ts +1 -1
  64. package/lib/packageVersion.d.ts.map +1 -1
  65. package/lib/packageVersion.js +1 -1
  66. package/lib/packageVersion.js.map +1 -1
  67. package/lib/storageServiceWithAttachBlobs.d.ts +40 -5
  68. package/lib/storageServiceWithAttachBlobs.d.ts.map +1 -1
  69. package/lib/storageServiceWithAttachBlobs.js +56 -5
  70. package/lib/storageServiceWithAttachBlobs.js.map +1 -1
  71. package/lib/summary/documentSchema.d.ts +1 -1
  72. package/lib/summary/documentSchema.d.ts.map +1 -1
  73. package/lib/summary/documentSchema.js.map +1 -1
  74. package/lib/summary/summaryFormat.d.ts +3 -3
  75. package/lib/summary/summaryFormat.d.ts.map +1 -1
  76. package/lib/summary/summaryFormat.js.map +1 -1
  77. package/package.json +20 -20
  78. package/src/blobManager/blobManager.ts +53 -195
  79. package/src/containerCompatibility.ts +176 -0
  80. package/src/containerRuntime.ts +157 -122
  81. package/src/dataStoreContext.ts +13 -5
  82. package/src/index.ts +6 -1
  83. package/src/metadata.ts +10 -2
  84. package/src/packageVersion.ts +1 -1
  85. package/src/storageServiceWithAttachBlobs.ts +92 -10
  86. package/src/summary/documentSchema.ts +1 -1
  87. package/src/summary/summaryFormat.ts +2 -2
  88. package/dist/compatUtils.d.ts +0 -106
  89. package/dist/compatUtils.d.ts.map +0 -1
  90. package/dist/compatUtils.js +0 -251
  91. package/dist/compatUtils.js.map +0 -1
  92. package/lib/compatUtils.d.ts +0 -106
  93. package/lib/compatUtils.d.ts.map +0 -1
  94. package/lib/compatUtils.js +0 -242
  95. package/lib/compatUtils.js.map +0 -1
  96. package/src/compatUtils.ts +0 -365
@@ -15,15 +15,16 @@ const internal_4 = require("@fluidframework/driver-utils/internal");
15
15
  const internal_5 = require("@fluidframework/id-compressor/internal");
16
16
  const internal_6 = require("@fluidframework/runtime-definitions/internal");
17
17
  const internal_7 = require("@fluidframework/runtime-utils/internal");
18
- const internal_8 = require("@fluidframework/telemetry-utils/internal");
18
+ const internal_8 = require("@fluidframework/runtime-utils/internal");
19
+ const internal_9 = require("@fluidframework/telemetry-utils/internal");
19
20
  const semver_ts_1 = require("semver-ts");
20
21
  const uuid_1 = require("uuid");
21
22
  const batchTracker_js_1 = require("./batchTracker.js");
22
23
  const index_js_1 = require("./blobManager/index.js");
23
24
  const channelCollection_js_1 = require("./channelCollection.js");
24
- const compatUtils_js_1 = require("./compatUtils.js");
25
25
  const compressionDefinitions_js_1 = require("./compressionDefinitions.js");
26
26
  const connectionTelemetry_js_1 = require("./connectionTelemetry.js");
27
+ const containerCompatibility_js_1 = require("./containerCompatibility.js");
27
28
  const containerHandleContext_js_1 = require("./containerHandleContext.js");
28
29
  const dataStore_js_1 = require("./dataStore.js");
29
30
  const dataStoreRegistry_js_1 = require("./dataStoreRegistry.js");
@@ -53,7 +54,7 @@ const throttler_js_1 = require("./throttler.js");
53
54
  *
54
55
  */
55
56
  function getUnknownMessageTypeError(unknownContainerRuntimeMessageType, codePath, sequencedMessage) {
56
- return internal_8.DataProcessingError.create("Runtime message of unknown type", codePath, sequencedMessage, {
57
+ return internal_9.DataProcessingError.create("Runtime message of unknown type", codePath, sequencedMessage, {
57
58
  messageDetails: {
58
59
  type: unknownContainerRuntimeMessageType,
59
60
  },
@@ -186,6 +187,12 @@ let getSingleUseLegacyLogCallback = (logger, type) => {
186
187
  };
187
188
  };
188
189
  exports.getSingleUseLegacyLogCallback = getSingleUseLegacyLogCallback;
190
+ /**
191
+ * Does nothing helper to apply unverified branding to a value.
192
+ */
193
+ function markUnverified(value) {
194
+ return value;
195
+ }
189
196
  /**
190
197
  * This is meant to be used by a {@link @fluidframework/container-definitions#IRuntimeFactory} to instantiate a container runtime.
191
198
  * @param params - An object which specifies all required and optional params necessary to instantiate a runtime.
@@ -236,14 +243,14 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
236
243
  * This object should provide all the functionality that the Container is expected to provide to the loader layer.
237
244
  */
238
245
  static async loadRuntime(params) {
239
- const { context, registryEntries, existing, requestHandler, provideEntryPoint, runtimeOptions = {}, containerScope = {}, containerRuntimeCtor = ContainerRuntime, minVersionForCollab = compatUtils_js_1.defaultMinVersionForCollab, } = params;
246
+ const { context, registryEntries, existing, requestHandler, provideEntryPoint, runtimeOptions = {}, containerScope = {}, containerRuntimeCtor = ContainerRuntime, minVersionForCollab = internal_7.defaultMinVersionForCollab, } = params;
240
247
  // If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
241
248
  // back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
242
249
  const backCompatContext = context;
243
250
  const passLogger = backCompatContext.taggedLogger ??
244
251
  // eslint-disable-next-line import/no-deprecated
245
- new internal_8.TaggedLoggerAdapter(backCompatContext.logger);
246
- const logger = (0, internal_8.createChildLogger)({
252
+ new internal_9.TaggedLoggerAdapter(backCompatContext.logger);
253
+ const logger = (0, internal_9.createChildLogger)({
247
254
  logger: passLogger,
248
255
  properties: {
249
256
  all: {
@@ -251,19 +258,19 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
251
258
  },
252
259
  },
253
260
  });
254
- const mc = (0, internal_8.loggerToMonitoringContext)(logger);
261
+ const mc = (0, internal_9.loggerToMonitoringContext)(logger);
255
262
  // Some options require a minimum version of the FF runtime to operate, so the default configs will be generated
256
263
  // based on the minVersionForCollab.
257
264
  // For example, if minVersionForCollab is set to "1.0.0", the default configs will ensure compatibility with FF runtime
258
265
  // 1.0.0 or later. If the minVersionForCollab is set to "2.10.0", the default values will be generated to ensure compatibility
259
266
  // with FF runtime 2.10.0 or later.
260
- if (!(0, compatUtils_js_1.isValidMinVersionForCollab)(minVersionForCollab)) {
261
- throw new internal_8.UsageError(`Invalid minVersionForCollab: ${minVersionForCollab}. It must be an existing FF version (i.e. 2.22.1).`);
267
+ if (!(0, internal_7.isValidMinVersionForCollab)(minVersionForCollab)) {
268
+ throw new internal_9.UsageError(`Invalid minVersionForCollab: ${minVersionForCollab}. It must be an existing FF version (i.e. 2.22.1).`);
262
269
  }
263
270
  // We also validate that there is not a mismatch between `minVersionForCollab` and runtime options that
264
271
  // were manually set.
265
- (0, compatUtils_js_1.validateRuntimeOptions)(minVersionForCollab, runtimeOptions);
266
- const defaultsAffectingDocSchema = (0, compatUtils_js_1.getMinVersionForCollabDefaults)(minVersionForCollab);
272
+ (0, containerCompatibility_js_1.validateRuntimeOptions)(minVersionForCollab, runtimeOptions);
273
+ const defaultsAffectingDocSchema = (0, containerCompatibility_js_1.getMinVersionForCollabDefaults)(minVersionForCollab);
267
274
  // The following are the default values for the options that do not affect the DocumentSchema.
268
275
  const defaultsNotAffectingDocSchema = {
269
276
  summaryOptions: {},
@@ -326,7 +333,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
326
333
  // Older runtimes do not understand new schema, and thus could corrupt document if they proceed, thus we are using
327
334
  // this poison pill to prevent them from proceeding.
328
335
  // "Load from summary, runtime metadata sequenceNumber !== initialSequenceNumber"
329
- const error = new internal_8.DataCorruptionError(
336
+ const error = new internal_9.DataCorruptionError(
330
337
  // pre-0.58 error message: SummaryMetadataMismatch
331
338
  "Summary metadata mismatch", { runtimeVersion: packageVersion_js_1.pkgVersion, runtimeSequenceNumber, protocolSequenceNumber });
332
339
  if (loadSequenceNumberVerification === "log") {
@@ -397,7 +404,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
397
404
  },
398
405
  };
399
406
  })();
400
- const compressorLogger = (0, internal_8.createSampledLogger)(logger, idCompressorEventSampler);
407
+ const compressorLogger = (0, internal_9.createSampledLogger)(logger, idCompressorEventSampler);
401
408
  const pendingLocalState = context.pendingLocalState;
402
409
  if (pendingLocalState?.pendingIdCompressorState !== undefined) {
403
410
  return (0, internal_5.deserializeIdCompressor)(pendingLocalState.pendingIdCompressorState, compressorLogger);
@@ -428,7 +435,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
428
435
  ? minVersionForCollab
429
436
  : existingMinVersionForCollab;
430
437
  if (compressionLz4 && !enableGroupedBatching) {
431
- throw new internal_8.UsageError("If compression is enabled, op grouping must be enabled too");
438
+ throw new internal_9.UsageError("If compression is enabled, op grouping must be enabled too");
432
439
  }
433
440
  const featureGatesForTelemetry = {};
434
441
  // Make sure we've got all the options including internal ones
@@ -447,13 +454,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
447
454
  };
448
455
  const runtime = new containerRuntimeCtor(context, registry, metadata, electedSummarizerData, chunks ?? [], aliases ?? [], internalRuntimeOptions, containerScope, logger, existing, blobManagerLoadInfo, context.storage, createIdCompressorFn, documentSchemaController, featureGatesForTelemetry, provideEntryPoint, updatedMinVersionForCollab, requestHandler, undefined, // summaryConfiguration
449
456
  recentBatchInfo);
450
- runtime.blobManager.stashedBlobsUploadP.then(() => {
451
- // make sure we didn't reconnect before the promise resolved
452
- if (runtime.delayConnectClientId !== undefined && !runtime.disposed) {
453
- runtime.delayConnectClientId = undefined;
454
- runtime.setConnectionStateCore(true, runtime.delayConnectClientId);
455
- }
456
- }, (error) => runtime.closeFn(error));
457
457
  // Initialize the base state of the runtime before it's returned.
458
458
  await runtime.initializeBaseState(context.loader);
459
459
  // Apply stashed ops with a reference sequence number equal to the sequence number of the snapshot,
@@ -597,7 +597,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
597
597
  this.minVersionForCollab = minVersionForCollab;
598
598
  this.requestHandler = requestHandler;
599
599
  this.summaryConfiguration = summaryConfiguration;
600
- this.imminentClosure = false;
601
600
  this.isReadOnly = () => this.deltaManager.readOnlyInfo.readonly === true;
602
601
  // We accumulate Id compressor Ops while Id compressor is not loaded yet (only for "delayed" mode)
603
602
  // Once it loads, it will process all such ops and we will stop accumulating further ops - ops will be processes as they come in.
@@ -627,10 +626,10 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
627
626
  // eslint-disable-next-line import/no-deprecated
628
627
  this.enterStagingMode = () => {
629
628
  if (this.stageControls !== undefined) {
630
- throw new internal_8.UsageError("Already in staging mode");
629
+ throw new internal_9.UsageError("Already in staging mode");
631
630
  }
632
631
  if (this.attachState === container_definitions_1.AttachState.Detached) {
633
- throw new internal_8.UsageError("Cannot enter staging mode while Detached");
632
+ throw new internal_9.UsageError("Cannot enter staging mode while Detached");
634
633
  }
635
634
  // Make sure Outbox is empty before entering staging mode,
636
635
  // since we mark whole batches as "staged" or not to indicate whether to submit them.
@@ -648,7 +647,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
648
647
  this.channelCollection.notifyStagingMode(false);
649
648
  }
650
649
  catch (error) {
651
- const normalizedError = (0, internal_8.normalizeError)(error);
650
+ const normalizedError = (0, internal_9.normalizeError)(error);
652
651
  this.closeFn(normalizedError);
653
652
  throw normalizedError;
654
653
  }
@@ -684,17 +683,29 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
684
683
  // It is lazily create to avoid listeners (old events) that ultimately go nowhere.
685
684
  this.lazyEventsForExtensions = new internal_2.Lazy(() => {
686
685
  const eventEmitter = (0, client_utils_1.createEmitter)();
687
- this.on("connected", (clientId) => eventEmitter.emit("connected", clientId));
688
- this.on("disconnected", () => eventEmitter.emit("disconnected"));
686
+ if (this.getConnectionState) {
687
+ this.on("connectedToService", (clientId, canWrite) => {
688
+ eventEmitter.emit("joined", { clientId, canWrite });
689
+ });
690
+ this.on("disconnectedFromService", () => eventEmitter.emit("disconnected"));
691
+ this.on("connectionTypeChanged", (canWrite) => eventEmitter.emit("connectionTypeChanged", canWrite));
692
+ }
693
+ else {
694
+ this.on("connected", (clientId) => {
695
+ eventEmitter.emit("joined", { clientId, canWrite: true });
696
+ });
697
+ this.on("disconnected", () => eventEmitter.emit("disconnected"));
698
+ }
689
699
  return eventEmitter;
690
700
  });
691
- const { options, clientDetails, connected, baseSnapshot, submitFn, submitBatchFn, submitSummaryFn, submitSignalFn, disposeFn, closeFn, deltaManager, quorum, audience, pendingLocalState, supportedFeatures, snapshotWithContents, } = context;
701
+ const { options, clientDetails, connected, baseSnapshot, submitFn, submitBatchFn, submitSummaryFn, submitSignalFn, disposeFn, closeFn, deltaManager, quorum, audience, pendingLocalState, supportedFeatures, snapshotWithContents, getConnectionState, } = context;
702
+ this.getConnectionState = getConnectionState;
692
703
  // In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
693
704
  this.disposeFn = disposeFn ?? closeFn;
694
705
  // Validate that the Loader is compatible with this Runtime.
695
- const maybeloaderCompatDetailsForRuntime = context;
696
- (0, runtimeLayerCompatState_js_1.validateLoaderCompatibility)(maybeloaderCompatDetailsForRuntime.ILayerCompatDetails, this.disposeFn);
697
- this.mc = (0, internal_8.createChildMonitoringContext)({
706
+ const maybeLoaderCompatDetailsForRuntime = context;
707
+ (0, runtimeLayerCompatState_js_1.validateLoaderCompatibility)(maybeLoaderCompatDetailsForRuntime.ILayerCompatDetails, this.disposeFn);
708
+ this.mc = (0, internal_9.createChildMonitoringContext)({
698
709
  logger: this.baseLogger,
699
710
  namespace: "ContainerRuntime",
700
711
  properties: {
@@ -804,6 +815,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
804
815
  // Note that we only need to pull the *initial* connected state from the context.
805
816
  // Later updates come through calls to setConnectionState.
806
817
  this.canSendOps = connected;
818
+ this.canSendSignals = this.getConnectionState
819
+ ? this.getConnectionState() === internal_1.ConnectionState.Connected
820
+ : undefined;
807
821
  this.mc.logger.sendTelemetryEvent({
808
822
  eventName: "GCFeatureMatrix",
809
823
  metadataValue: JSON.stringify(metadata?.gcFeatureMatrix),
@@ -852,7 +866,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
852
866
  this.mc.config.getNumber(maxConsecutiveReconnectsKey) ?? defaultMaxConsecutiveReconnects;
853
867
  // If the context has ILayerCompatDetails, it supports referenceSequenceNumbers since that features
854
868
  // predates ILayerCompatDetails.
855
- const referenceSequenceNumbersSupported = maybeloaderCompatDetailsForRuntime.ILayerCompatDetails === undefined
869
+ const referenceSequenceNumbersSupported = maybeLoaderCompatDetailsForRuntime.ILayerCompatDetails === undefined
856
870
  ? supportedFeatures?.get("referenceSequenceNumbers") === true
857
871
  : true;
858
872
  if (runtimeOptions.flushMode === internal_6.FlushModeExperimental.Async &&
@@ -867,7 +881,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
867
881
  this.offlineEnabled =
868
882
  this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ?? false;
869
883
  if (this.offlineEnabled && this._flushMode !== internal_6.FlushMode.TurnBased) {
870
- const error = new internal_8.UsageError("Offline mode is only supported in turn-based mode");
884
+ const error = new internal_9.UsageError("Offline mode is only supported in turn-based mode");
871
885
  this.closeFn(error);
872
886
  throw error;
873
887
  }
@@ -885,7 +899,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
885
899
  // This is a runtime enforcement of what's already explicit in the policy's type itself,
886
900
  // which dictates the value is either undefined or exactly 5 days in ms.
887
901
  // As long as the actual value is less than 5 days, the assumptions GC makes here are valid.
888
- throw new internal_8.UsageError("Driver's maximumCacheDurationMs policy cannot exceed 5 days");
902
+ throw new internal_9.UsageError("Driver's maximumCacheDurationMs policy cannot exceed 5 days");
889
903
  }
890
904
  }
891
905
  this.garbageCollector = index_js_2.GarbageCollector.create({
@@ -910,7 +924,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
910
924
  const summaryReferenceSequenceNumber = baseSnapshot === undefined || metadata?.disableIsolatedChannels === true
911
925
  ? undefined
912
926
  : loadedFromSequenceNumber;
913
- this.summarizerNode = (0, index_js_4.createRootSummarizerNodeWithGC)((0, internal_8.createChildLogger)({ logger: this.baseLogger, namespace: "SummarizerNode" }),
927
+ this.summarizerNode = (0, index_js_4.createRootSummarizerNodeWithGC)((0, internal_9.createChildLogger)({ logger: this.baseLogger, namespace: "SummarizerNode" }),
914
928
  // Summarize function to call when summarize is called. Summarizer node always tracks summary state.
915
929
  async (fullTree, trackState, telemetryContext) => this.summarizeInternal(fullTree, trackState, telemetryContext),
916
930
  // Latest change sequence number, no changes since summary applied yet
@@ -973,8 +987,8 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
973
987
  stashedBlobs: pendingRuntimeState?.pendingAttachmentBlobs,
974
988
  createBlobPayloadPending: this.sessionSchema.createBlobPayloadPending === true,
975
989
  });
976
- this.deltaScheduler = new deltaScheduler_js_1.DeltaScheduler(this.innerDeltaManager, this, (0, internal_8.createChildLogger)({ logger: this.baseLogger, namespace: "DeltaScheduler" }));
977
- this.inboundBatchAggregator = new inboundBatchAggregator_js_1.InboundBatchAggregator(this.innerDeltaManager, () => this.clientId, (0, internal_8.createChildLogger)({ logger: this.baseLogger, namespace: "InboundBatchAggregator" }));
990
+ this.deltaScheduler = new deltaScheduler_js_1.DeltaScheduler(this.innerDeltaManager, this, (0, internal_9.createChildLogger)({ logger: this.baseLogger, namespace: "DeltaScheduler" }));
991
+ this.inboundBatchAggregator = new inboundBatchAggregator_js_1.InboundBatchAggregator(this.innerDeltaManager, () => this.clientId, (0, internal_9.createChildLogger)({ logger: this.baseLogger, namespace: "InboundBatchAggregator" }));
978
992
  const legacySendBatchFn = (0, exports.makeLegacySendBatchFn)(submitFn, this.innerDeltaManager);
979
993
  this.skipSafetyFlushDuringProcessStack =
980
994
  // Keep the old flag name even though we renamed the class member (it shipped in 2.31.0)
@@ -1118,7 +1132,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1118
1132
  async initializeBaseState(loader) {
1119
1133
  if (this.sessionSchema.idCompressorMode === "on" ||
1120
1134
  (this.sessionSchema.idCompressorMode === "delayed" && this.connected)) {
1121
- internal_8.PerformanceEvent.timedExec(this.mc.logger, { eventName: "CreateIdCompressorOnBoot" }, (event) => {
1135
+ internal_9.PerformanceEvent.timedExec(this.mc.logger, { eventName: "CreateIdCompressorOnBoot" }, (event) => {
1122
1136
  this._idCompressor = this.createIdCompressorFn();
1123
1137
  event.end({
1124
1138
  details: {
@@ -1148,7 +1162,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1148
1162
  this.summaryConfiguration.initialSummarizerDelayMs,
1149
1163
  };
1150
1164
  const summaryCollection = new index_js_4.SummaryCollection(this.deltaManager, this.baseLogger);
1151
- const orderedClientLogger = (0, internal_8.createChildLogger)({
1165
+ const orderedClientLogger = (0, internal_9.createChildLogger)({
1152
1166
  logger: this.baseLogger,
1153
1167
  namespace: "OrderedClientElection",
1154
1168
  });
@@ -1272,7 +1286,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1272
1286
  // Snapshots should only move forward. If we observe an older snapshot than the one we loaded from, then likely
1273
1287
  // the file has been overwritten or service lost data.
1274
1288
  if (snapshotSeqNumber < this.deltaManager.initialSequenceNumber) {
1275
- throw internal_8.DataProcessingError.create("Downloaded snapshot older than snapshot we loaded from", "getSnapshotForLoadingGroupId", undefined, {
1289
+ throw internal_9.DataProcessingError.create("Downloaded snapshot older than snapshot we loaded from", "getSnapshotForLoadingGroupId", undefined, {
1276
1290
  loadingGroupIds: sortedLoadingGroupIds.join(","),
1277
1291
  snapshotSeqNumber,
1278
1292
  initialSequenceNumber: this.deltaManager.initialSequenceNumber,
@@ -1295,7 +1309,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1295
1309
  targetSequenceNumber: snapshotSeqNumber, // This is so we reuse some columns in telemetry
1296
1310
  sequenceNumber: this.deltaManager.lastSequenceNumber, // This is so we reuse some columns in telemetry
1297
1311
  };
1298
- const event = internal_8.PerformanceEvent.start(this.mc.logger, {
1312
+ const event = internal_9.PerformanceEvent.start(this.mc.logger, {
1299
1313
  ...props,
1300
1314
  });
1301
1315
  // If the inbound deltas queue is paused or disconnected, we expect a reconnect and unpause
@@ -1340,7 +1354,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1340
1354
  // @ts-expect-error expected to be used by LTS Loaders and Containers
1341
1355
  async request(request) {
1342
1356
  try {
1343
- const parser = internal_7.RequestParser.create(request);
1357
+ const parser = internal_8.RequestParser.create(request);
1344
1358
  const id = parser.pathParts[0];
1345
1359
  if (id === index_js_4.summarizerRequestUrl && parser.pathParts.length === 1) {
1346
1360
  if (this._summarizer !== undefined) {
@@ -1350,16 +1364,16 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1350
1364
  value: this._summarizer,
1351
1365
  };
1352
1366
  }
1353
- return (0, internal_7.create404Response)(request);
1367
+ return (0, internal_8.create404Response)(request);
1354
1368
  }
1355
1369
  if (this.requestHandler !== undefined) {
1356
1370
  // eslint-disable-next-line @typescript-eslint/return-await -- Adding an await here causes test failures
1357
1371
  return this.requestHandler(parser, this);
1358
1372
  }
1359
- return (0, internal_7.create404Response)(request);
1373
+ return (0, internal_8.create404Response)(request);
1360
1374
  }
1361
1375
  catch (error) {
1362
- return (0, internal_7.exceptionToResponse)(error);
1376
+ return (0, internal_8.exceptionToResponse)(error);
1363
1377
  }
1364
1378
  }
1365
1379
  /**
@@ -1368,7 +1382,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1368
1382
  */
1369
1383
  async resolveHandle(request) {
1370
1384
  try {
1371
- const requestParser = internal_7.RequestParser.create(request);
1385
+ const requestParser = internal_8.RequestParser.create(request);
1372
1386
  const id = requestParser.pathParts[0];
1373
1387
  if (id === "_channels") {
1374
1388
  // eslint-disable-next-line @typescript-eslint/return-await -- Adding an await here causes test failures
@@ -1376,10 +1390,10 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1376
1390
  }
1377
1391
  if (id === index_js_1.blobManagerBasePath && requestParser.isLeaf(2)) {
1378
1392
  const localId = requestParser.pathParts[1];
1379
- const payloadPending = requestParser.headers?.[internal_7.RuntimeHeaders.payloadPending] === true;
1393
+ const payloadPending = requestParser.headers?.[internal_8.RuntimeHeaders.payloadPending] === true;
1380
1394
  if (!this.blobManager.hasBlob(localId) &&
1381
- requestParser.headers?.[internal_7.RuntimeHeaders.wait] === false) {
1382
- return (0, internal_7.create404Response)(request);
1395
+ requestParser.headers?.[internal_8.RuntimeHeaders.wait] === false) {
1396
+ return (0, internal_8.create404Response)(request);
1383
1397
  }
1384
1398
  const blob = await this.blobManager.getBlob(localId, payloadPending);
1385
1399
  return {
@@ -1391,10 +1405,10 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1391
1405
  else if (requestParser.pathParts.length > 0) {
1392
1406
  return await this.channelCollection.request(request);
1393
1407
  }
1394
- return (0, internal_7.create404Response)(request);
1408
+ return (0, internal_8.create404Response)(request);
1395
1409
  }
1396
1410
  catch (error) {
1397
- return (0, internal_7.exceptionToResponse)(error);
1411
+ return (0, internal_8.exceptionToResponse)(error);
1398
1412
  }
1399
1413
  }
1400
1414
  /**
@@ -1435,39 +1449,39 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1435
1449
  lastMessage: explicitSchemaControl ? message : undefined,
1436
1450
  documentSchema,
1437
1451
  };
1438
- (0, internal_7.addBlobToSummary)(summaryTree, index_js_4.metadataBlobName, JSON.stringify(metadata));
1452
+ (0, internal_8.addBlobToSummary)(summaryTree, index_js_4.metadataBlobName, JSON.stringify(metadata));
1439
1453
  }
1440
1454
  addContainerStateToSummary(summaryTree, fullTree, trackState, telemetryContext) {
1441
1455
  this.addMetadataToSummary(summaryTree);
1442
1456
  if (this._idCompressor) {
1443
1457
  const idCompressorState = JSON.stringify(this._idCompressor.serialize(false));
1444
- (0, internal_7.addBlobToSummary)(summaryTree, index_js_4.idCompressorBlobName, idCompressorState);
1458
+ (0, internal_8.addBlobToSummary)(summaryTree, index_js_4.idCompressorBlobName, idCompressorState);
1445
1459
  }
1446
1460
  if (this.remoteMessageProcessor.partialMessages.size > 0) {
1447
1461
  const content = JSON.stringify([...this.remoteMessageProcessor.partialMessages]);
1448
- (0, internal_7.addBlobToSummary)(summaryTree, index_js_4.chunksBlobName, content);
1462
+ (0, internal_8.addBlobToSummary)(summaryTree, index_js_4.chunksBlobName, content);
1449
1463
  }
1450
1464
  const recentBatchInfo = this.duplicateBatchDetector?.getRecentBatchInfoForSummary(telemetryContext);
1451
1465
  if (recentBatchInfo !== undefined) {
1452
- (0, internal_7.addBlobToSummary)(summaryTree, index_js_4.recentBatchInfoBlobName, JSON.stringify(recentBatchInfo));
1466
+ (0, internal_8.addBlobToSummary)(summaryTree, index_js_4.recentBatchInfoBlobName, JSON.stringify(recentBatchInfo));
1453
1467
  }
1454
1468
  const dataStoreAliases = this.channelCollection.aliases;
1455
1469
  if (dataStoreAliases.size > 0) {
1456
- (0, internal_7.addBlobToSummary)(summaryTree, index_js_4.aliasBlobName, JSON.stringify([...dataStoreAliases]));
1470
+ (0, internal_8.addBlobToSummary)(summaryTree, index_js_4.aliasBlobName, JSON.stringify([...dataStoreAliases]));
1457
1471
  }
1458
1472
  if (this.summarizerClientElection) {
1459
1473
  const electedSummarizerContent = JSON.stringify(this.summarizerClientElection?.serialize());
1460
- (0, internal_7.addBlobToSummary)(summaryTree, index_js_4.electedSummarizerBlobName, electedSummarizerContent);
1474
+ (0, internal_8.addBlobToSummary)(summaryTree, index_js_4.electedSummarizerBlobName, electedSummarizerContent);
1461
1475
  }
1462
1476
  const blobManagerSummary = this.blobManager.summarize();
1463
1477
  // Some storage (like git) doesn't allow empty tree, so we can omit it.
1464
1478
  // and the blob manager can handle the tree not existing when loading
1465
1479
  if (Object.keys(blobManagerSummary.summary.tree).length > 0) {
1466
- (0, internal_7.addSummarizeResultToSummary)(summaryTree, index_js_1.blobsTreeName, blobManagerSummary);
1480
+ (0, internal_8.addSummarizeResultToSummary)(summaryTree, index_js_1.blobsTreeName, blobManagerSummary);
1467
1481
  }
1468
1482
  const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
1469
1483
  if (gcSummary !== undefined) {
1470
- (0, internal_7.addSummarizeResultToSummary)(summaryTree, internal_6.gcTreeKey, gcSummary);
1484
+ (0, internal_8.addSummarizeResultToSummary)(summaryTree, internal_6.gcTreeKey, gcSummary);
1471
1485
  }
1472
1486
  }
1473
1487
  // Track how many times the container tries to reconnect with pending messages.
@@ -1573,7 +1587,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1573
1587
  }
1574
1588
  case messageTypes_js_1.ContainerMessageType.GC: {
1575
1589
  // GC op is only sent in summarizer which should never have stashed ops.
1576
- throw new internal_8.LoggingError("GC op not expected to be stashed in summarizer");
1590
+ throw new internal_9.LoggingError("GC op not expected to be stashed in summarizer");
1577
1591
  }
1578
1592
  default: {
1579
1593
  const error = getUnknownMessageTypeError(opContents.type, "applyStashedOp" /* codePath */);
@@ -1585,7 +1599,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1585
1599
  loadIdCompressor() {
1586
1600
  if (this._idCompressor === undefined &&
1587
1601
  this.sessionSchema.idCompressorMode !== undefined) {
1588
- internal_8.PerformanceEvent.timedExec(this.mc.logger, { eventName: "CreateIdCompressorOnDelayedLoad" }, (event) => {
1602
+ internal_9.PerformanceEvent.timedExec(this.mc.logger, { eventName: "CreateIdCompressorOnDelayedLoad" }, (event) => {
1589
1603
  this._idCompressor = this.createIdCompressorFn();
1590
1604
  // Finalize any ranges we received while the compressor was turned off.
1591
1605
  const ops = this.pendingIdCompressorOps;
@@ -1613,24 +1627,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1613
1627
  if (canSendOps && this.sessionSchema.idCompressorMode === "delayed") {
1614
1628
  this.loadIdCompressor();
1615
1629
  }
1616
- if (canSendOps === false && this.delayConnectClientId !== undefined) {
1617
- this.delayConnectClientId = undefined;
1618
- this.mc.logger.sendTelemetryEvent({
1619
- eventName: "UnsuccessfulConnectedTransition",
1620
- });
1621
- // Don't propagate "disconnected" event because we didn't propagate the previous "connected" event
1622
- return;
1623
- }
1624
- // If there are stashed blobs in the pending state, we need to delay
1625
- // propagation of the "connected" event until we have uploaded them to
1626
- // ensure we don't submit ops referencing a blob that has not been uploaded
1627
- const connecting = canSendOps && !this.canSendOps;
1628
- if (connecting && this.blobManager.hasPendingStashedUploads()) {
1629
- (0, internal_2.assert)(!this.delayConnectClientId, 0x791 /* Connect event delay must be canceled before subsequent connect event */);
1630
- (0, internal_2.assert)(!!clientId, 0x792 /* Must have clientId when connecting */);
1631
- this.delayConnectClientId = clientId;
1632
- return;
1633
- }
1634
1630
  this.setConnectionStateCore(canSendOps, clientId);
1635
1631
  }
1636
1632
  /**
@@ -1639,7 +1635,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1639
1635
  * @remarks The connection state from container context used here when raising connected events.
1640
1636
  */
1641
1637
  setConnectionStateCore(canSendOps, clientId) {
1642
- (0, internal_2.assert)(!this.delayConnectClientId, 0x394 /* connect event delay must be cleared before propagating connect event */);
1643
1638
  this.verifyNotClosed();
1644
1639
  // There might be no change of state due to Container calling this API after loading runtime.
1645
1640
  const canSendOpsChanged = this.canSendOps !== canSendOps;
@@ -1661,7 +1656,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1661
1656
  if (reconnection) {
1662
1657
  this.consecutiveReconnects++;
1663
1658
  if (!this.shouldContinueReconnecting()) {
1664
- this.closeFn(internal_8.DataProcessingError.create("Runtime detected too many reconnects with no progress syncing local ops.", "setConnectionState", undefined, {
1659
+ this.closeFn(internal_9.DataProcessingError.create("Runtime detected too many reconnects with no progress syncing local ops.", "setConnectionState", undefined, {
1665
1660
  dataLoss: 1,
1666
1661
  attempts: this.consecutiveReconnects,
1667
1662
  pendingMessages: this.pendingMessagesCount,
@@ -1674,7 +1669,40 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1674
1669
  }
1675
1670
  this.channelCollection.setConnectionState(canSendOps, clientId);
1676
1671
  this.garbageCollector.setConnectionState(canSendOps, clientId);
1677
- (0, internal_8.raiseConnectedEvent)(this.mc.logger, this, this.connected /* canSendOps */, clientId);
1672
+ // Emit "connected" and "disconnected" events based on ability to send ops
1673
+ (0, internal_9.raiseConnectedEvent)(this.mc.logger, this, this.connected /* canSendOps */, clientId);
1674
+ // Emit "connectedToService" and "disconnectedFromService" events based on service connection status
1675
+ this.emitServiceConnectionEvents(canSendOpsChanged, canSendOps, clientId);
1676
+ }
1677
+ /**
1678
+ * Emits service connection events based on connection state changes.
1679
+ *
1680
+ * @remarks
1681
+ * "connectedToService" is emitted when container connection state transitions to 'Connected' regardless of connection mode.
1682
+ * "disconnectedFromService" excludes false "disconnected" events that happen when readonly client transitions to 'Connected'.
1683
+ */
1684
+ emitServiceConnectionEvents(canSendOpsChanged, canSendOps, clientId) {
1685
+ if (!this.getConnectionState) {
1686
+ return;
1687
+ }
1688
+ const canSendSignals = this.getConnectionState() === internal_1.ConnectionState.Connected;
1689
+ const canSendSignalsChanged = this.canSendSignals !== canSendSignals;
1690
+ this.canSendSignals = canSendSignals;
1691
+ if (canSendSignalsChanged) {
1692
+ // If canSendSignals changed, we either transitioned from Connected to Disconnected or CatchingUp to Connected
1693
+ if (canSendSignals) {
1694
+ // Emit for CatchingUp to Connected transition
1695
+ this.emit("connectedToService", clientId, canSendOps);
1696
+ }
1697
+ else {
1698
+ // Emit for Connected to Disconnected transition
1699
+ this.emit("disconnectedFromService");
1700
+ }
1701
+ }
1702
+ else if (canSendOpsChanged) {
1703
+ // If canSendSignals did not change but canSendOps did, then connection type has changed.
1704
+ this.emit("connectionTypeChanged", canSendOps);
1705
+ }
1678
1706
  }
1679
1707
  async notifyOpReplay(message) {
1680
1708
  await this.pendingStateManager.applyStashedOpsAt(message.sequenceNumber);
@@ -1727,7 +1755,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1727
1755
  const batchStart = inboundResult.batchStart;
1728
1756
  const result = this.duplicateBatchDetector?.processInboundBatch(batchStart);
1729
1757
  if (result?.duplicate) {
1730
- const error = new internal_8.DataCorruptionError("Duplicate batch - The same batch was sequenced twice", { batchId: batchStart.batchId });
1758
+ const error = new internal_9.DataCorruptionError("Duplicate batch - The same batch was sequenced twice", { batchId: batchStart.batchId });
1731
1759
  this.mc.logger.sendTelemetryEvent({
1732
1760
  eventName: "DuplicateBatch",
1733
1761
  details: {
@@ -1736,7 +1764,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1736
1764
  batchStartCsn: batchStart.batchStartCsn,
1737
1765
  size: inboundResult.length,
1738
1766
  duplicateBatchSequenceNumber: result.otherSequenceNumber,
1739
- ...(0, internal_8.extractSafePropertiesFromMessage)(batchStart.keyMessage),
1767
+ ...(0, internal_9.extractSafePropertiesFromMessage)(batchStart.keyMessage),
1740
1768
  },
1741
1769
  }, error);
1742
1770
  throw error;
@@ -1984,12 +2012,12 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1984
2012
  }
1985
2013
  processSignal(message, local) {
1986
2014
  const envelope = message.content;
1987
- const transformed = {
2015
+ const transformed = markUnverified({
1988
2016
  clientId: message.clientId,
1989
2017
  content: envelope.contents.content,
1990
2018
  type: envelope.contents.type,
1991
2019
  targetClientId: message.targetClientId,
1992
- };
2020
+ });
1993
2021
  // Only collect signal telemetry for broadcast messages sent by the current client.
1994
2022
  if (message.clientId === this.clientId) {
1995
2023
  this.signalTelemetryManager.trackReceivedSignal(envelope, this.mc.logger, this.consecutiveReconnects);
@@ -2008,12 +2036,14 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2008
2036
  // Due to a mismatch between different layers in terms of
2009
2037
  // what is the interface of passing signals, we need to adjust
2010
2038
  // the signal envelope before sending it to the datastores to be processed
2011
- const envelope = {
2012
- address,
2013
- contents: signalMessage.content,
2039
+ const channelSignalMessage = {
2040
+ ...signalMessage,
2041
+ content: {
2042
+ address,
2043
+ contents: signalMessage.content,
2044
+ },
2014
2045
  };
2015
- signalMessage.content = envelope;
2016
- this.channelCollection.processSignal(signalMessage, local);
2046
+ this.channelCollection.processSignal(channelSignalMessage, local);
2017
2047
  return;
2018
2048
  }
2019
2049
  const addresses = address.split("/");
@@ -2028,7 +2058,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2028
2058
  (0, internal_2.assert)(!local, 0xba0 /* No recipient found for local signal */);
2029
2059
  this.mc.logger.sendTelemetryEvent({
2030
2060
  eventName: "SignalAddressNotFound",
2031
- ...(0, internal_8.tagCodeArtifacts)({
2061
+ ...(0, internal_9.tagCodeArtifacts)({
2032
2062
  address,
2033
2063
  }),
2034
2064
  });
@@ -2048,7 +2078,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2048
2078
  (0, internal_2.assert)(this.outbox.isEmpty, 0x3cf /* reentrancy */);
2049
2079
  }
2050
2080
  catch (error) {
2051
- const error2 = (0, internal_8.normalizeError)(error, {
2081
+ const error2 = (0, internal_9.normalizeError)(error, {
2052
2082
  props: {
2053
2083
  orderSequentiallyCalls: this.batchRunner.runs,
2054
2084
  },
@@ -2089,15 +2119,15 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2089
2119
  stageControls = undefined;
2090
2120
  }
2091
2121
  catch (error_) {
2092
- const error2 = (0, internal_8.wrapError)(error_, (message) => {
2093
- return internal_8.DataProcessingError.create(`RollbackError: ${message}`, "checkpointRollback", undefined);
2122
+ const error2 = (0, internal_9.wrapError)(error_, (message) => {
2123
+ return internal_9.DataProcessingError.create(`RollbackError: ${message}`, "checkpointRollback", undefined);
2094
2124
  });
2095
2125
  this.closeFn(error2);
2096
2126
  throw error2;
2097
2127
  }
2098
2128
  }
2099
2129
  else {
2100
- this.closeFn((0, internal_8.wrapError)(error, (errorMessage) => new internal_8.GenericError(`orderSequentially callback exception: ${errorMessage}`, error, {
2130
+ this.closeFn((0, internal_9.wrapError)(error, (errorMessage) => new internal_9.GenericError(`orderSequentially callback exception: ${errorMessage}`, error, {
2101
2131
  orderSequentiallyCalls: this.batchRunner.runs,
2102
2132
  })));
2103
2133
  }
@@ -2143,7 +2173,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2143
2173
  }
2144
2174
  const channel = await context.realize();
2145
2175
  if (channel.entryPoint === undefined) {
2146
- throw new internal_8.UsageError("entryPoint must be defined on data store runtime for using getAliasedDataStoreEntryPoint");
2176
+ throw new internal_9.UsageError("entryPoint must be defined on data store runtime for using getAliasedDataStoreEntryPoint");
2147
2177
  }
2148
2178
  this.garbageCollector.nodeUpdated({
2149
2179
  node: { type: "DataStore", path: `/${internalId}` },
@@ -2163,7 +2193,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2163
2193
  shouldSendOps() {
2164
2194
  // Note that the real (non-proxy) delta manager is needed here to get the readonly info. This is because
2165
2195
  // container runtime's ability to send ops depend on the actual readonly state of the delta manager.
2166
- return (this.connected && !this.innerDeltaManager.readOnlyInfo.readonly && !this.imminentClosure);
2196
+ return this.connected && !this.innerDeltaManager.readOnlyInfo.readonly;
2167
2197
  }
2168
2198
  getQuorum() {
2169
2199
  return this._quorum;
@@ -2265,7 +2295,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2265
2295
  async summarize(options) {
2266
2296
  this.verifyNotClosed();
2267
2297
  const { fullTree = false, trackState = true, summaryLogger = this.mc.logger, runGC = this.garbageCollector.shouldRunGC, runSweep, fullGC, } = options;
2268
- const telemetryContext = new internal_7.TelemetryContext();
2298
+ const telemetryContext = new internal_8.TelemetryContext();
2269
2299
  // Add the options that are used to generate this summary to the telemetry context.
2270
2300
  telemetryContext.setMultiple("fluid_Summarize", "Options", {
2271
2301
  fullTree,
@@ -2298,7 +2328,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2298
2328
  * @see IGarbageCollectionRuntime.getGCData
2299
2329
  */
2300
2330
  async getGCData(fullGC) {
2301
- const builder = new internal_7.GCDataBuilder();
2331
+ const builder = new internal_8.GCDataBuilder();
2302
2332
  const dsGCData = await this.summarizerNode.getGCData(fullGC);
2303
2333
  builder.addNodes(dsGCData.gcNodes);
2304
2334
  const blobsGCData = this.blobManager.getGCData(fullGC);
@@ -2425,7 +2455,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2425
2455
  if (timestampMs === undefined) {
2426
2456
  this.mc.logger.sendTelemetryEvent({
2427
2457
  eventName: "NoTimestampInGCOutboundRoute",
2428
- ...(0, internal_8.tagCodeArtifacts)({
2458
+ ...(0, internal_9.tagCodeArtifacts)({
2429
2459
  id: toPath,
2430
2460
  fromId: fromPath,
2431
2461
  }),
@@ -2448,7 +2478,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2448
2478
  // use it for all events logged during this summary.
2449
2479
  const summaryNumber = this.nextSummaryNumber;
2450
2480
  let summaryRefSeqNum;
2451
- const summaryNumberLogger = (0, internal_8.createChildLogger)({
2481
+ const summaryNumberLogger = (0, internal_9.createChildLogger)({
2452
2482
  logger: summaryLogger,
2453
2483
  properties: {
2454
2484
  all: {
@@ -2459,7 +2489,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2459
2489
  });
2460
2490
  // legacy: assert 0x3d1
2461
2491
  if (!this.outbox.isEmpty) {
2462
- throw internal_8.DataProcessingError.create("Can't trigger summary in the middle of a batch", "submitSummary", undefined, {
2492
+ throw internal_9.DataProcessingError.create("Can't trigger summary in the middle of a batch", "submitSummary", undefined, {
2463
2493
  summaryNumber,
2464
2494
  pendingMessages: this.pendingMessagesCount,
2465
2495
  outboxLength: this.outbox.messageCount,
@@ -2596,7 +2626,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2596
2626
  stage: "base",
2597
2627
  referenceSequenceNumber: summaryRefSeqNum,
2598
2628
  minimumSequenceNumber,
2599
- error: (0, internal_8.wrapError)(error, (msg) => new index_js_4.RetriableSummaryError(msg)),
2629
+ error: (0, internal_9.wrapError)(error, (msg) => new index_js_4.RetriableSummaryError(msg)),
2600
2630
  };
2601
2631
  }
2602
2632
  // Validate that the summary generated by summarizer nodes is correct before uploading.
@@ -2627,7 +2657,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2627
2657
  (0, internal_2.assert)(dataStoreTree.type === driver_definitions_1.SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
2628
2658
  const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === driver_definitions_1.SummaryType.Handle).length;
2629
2659
  const gcSummaryTreeStats = summaryTree.tree[internal_6.gcTreeKey]
2630
- ? (0, internal_7.calculateStats)(summaryTree.tree[internal_6.gcTreeKey])
2660
+ ? (0, internal_8.calculateStats)(summaryTree.tree[internal_6.gcTreeKey])
2631
2661
  : undefined;
2632
2662
  const summaryStats = {
2633
2663
  dataStoreCount: this.channelCollection.size,
@@ -2666,7 +2696,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2666
2696
  return {
2667
2697
  stage: "generate",
2668
2698
  ...generateSummaryData,
2669
- error: (0, internal_8.wrapError)(error, (msg) => new index_js_4.RetriableSummaryError(msg)),
2699
+ error: (0, internal_9.wrapError)(error, (msg) => new index_js_4.RetriableSummaryError(msg)),
2670
2700
  };
2671
2701
  }
2672
2702
  const parent = summaryContext.ackHandle;
@@ -2698,7 +2728,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2698
2728
  return {
2699
2729
  stage: "upload",
2700
2730
  ...uploadData,
2701
- error: (0, internal_8.wrapError)(error, (msg) => new index_js_4.RetriableSummaryError(msg)),
2731
+ error: (0, internal_9.wrapError)(error, (msg) => new index_js_4.RetriableSummaryError(msg)),
2702
2732
  };
2703
2733
  }
2704
2734
  const submitData = {
@@ -2714,7 +2744,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2714
2744
  return {
2715
2745
  stage: "upload",
2716
2746
  ...uploadData,
2717
- error: (0, internal_8.wrapError)(error, (msg) => new index_js_4.RetriableSummaryError(msg)),
2747
+ error: (0, internal_9.wrapError)(error, (msg) => new index_js_4.RetriableSummaryError(msg)),
2718
2748
  };
2719
2749
  }
2720
2750
  return submitData;
@@ -2750,7 +2780,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2750
2780
  // the summarizer.
2751
2781
  if (finalAttempt &&
2752
2782
  this.mc.config.getBoolean("Fluid.Summarizer.SkipFailingIncorrectSummary")) {
2753
- const error = internal_8.DataProcessingError.create("Pending ops during summarization", "submitSummary", undefined, { pendingMessages: this.pendingMessagesCount });
2783
+ const error = internal_9.DataProcessingError.create("Pending ops during summarization", "submitSummary", undefined, { pendingMessages: this.pendingMessagesCount });
2754
2784
  logger.sendErrorEvent({
2755
2785
  eventName: "SkipFailingIncorrectSummary",
2756
2786
  referenceSequenceNumber,
@@ -2897,7 +2927,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2897
2927
  this.scheduleFlush();
2898
2928
  }
2899
2929
  catch (error) {
2900
- const dpe = internal_8.DataProcessingError.wrapIfUnrecognized(error, "ContainerRuntime.submit", {
2930
+ const dpe = internal_9.DataProcessingError.wrapIfUnrecognized(error, "ContainerRuntime.submit", {
2901
2931
  referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
2902
2932
  });
2903
2933
  this.closeFn(dpe);
@@ -3142,7 +3172,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
3142
3172
  * happen in scenarios where the snapshot for the ack was lost in storage in scenarios like DB rollback, etc.
3143
3173
  */
3144
3174
  async fetchLatestSnapshotAndMaybeClose(targetRefSeq, targetAckHandle, logger) {
3145
- const fetchedSnapshotRefSeq = await internal_8.PerformanceEvent.timedExecAsync(logger, { eventName: "RefreshLatestSummaryAckFetch" }, async (perfEvent) => {
3175
+ const fetchedSnapshotRefSeq = await internal_9.PerformanceEvent.timedExecAsync(logger, { eventName: "RefreshLatestSummaryAckFetch" }, async (perfEvent) => {
3146
3176
  const props = { targetRefSeq, targetAckHandle };
3147
3177
  const trace = client_utils_1.Trace.start();
3148
3178
  let snapshotTree;
@@ -3173,7 +3203,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
3173
3203
  props.snapshotVersion = versions[0].id;
3174
3204
  }
3175
3205
  props.getSnapshotDuration = trace.trace().duration;
3176
- const snapshotRefSeq = await (0, internal_7.seqFromTree)(snapshotTree, this.readAndParseBlob);
3206
+ const snapshotRefSeq = await (0, internal_8.seqFromTree)(snapshotTree, this.readAndParseBlob);
3177
3207
  props.snapshotRefSeq = snapshotRefSeq;
3178
3208
  props.newerSnapshotPresent = snapshotRefSeq >= targetRefSeq;
3179
3209
  perfEvent.end({ details: props });
@@ -3191,39 +3221,35 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
3191
3221
  }
3192
3222
  getPendingLocalState(props) {
3193
3223
  this.verifyNotClosed();
3224
+ if (props?.notifyImminentClosure) {
3225
+ throw new internal_9.UsageError("notifyImminentClosure is no longer supported in ContainerRuntime");
3226
+ }
3194
3227
  if (this.batchRunner.running) {
3195
- throw new internal_8.UsageError("can't get state while manually accumulating a batch");
3228
+ throw new internal_9.UsageError("can't get state while manually accumulating a batch");
3196
3229
  }
3197
- this.imminentClosure ||= props?.notifyImminentClosure ?? false;
3198
- const getSyncState = (pendingAttachmentBlobs) => {
3230
+ // Flush pending batch.
3231
+ // getPendingLocalState() is only exposed through Container.getPendingLocalState(), so it's safe
3232
+ // to close current batch.
3233
+ this.flush();
3234
+ return internal_9.PerformanceEvent.timedExec(this.mc.logger, {
3235
+ eventName: "getPendingLocalState",
3236
+ }, (event) => {
3199
3237
  const pending = this.pendingStateManager.getLocalState(props?.snapshotSequenceNumber);
3200
3238
  const sessionExpiryTimerStarted = props?.sessionExpiryTimerStarted ?? this.garbageCollector.sessionExpiryTimerStarted;
3201
3239
  const pendingIdCompressorState = this._idCompressor?.serialize(true);
3202
- return {
3240
+ const pendingAttachmentBlobs = this.blobManager.getPendingBlobs();
3241
+ const pendingRuntimeState = {
3203
3242
  pending,
3204
3243
  pendingIdCompressorState,
3205
3244
  pendingAttachmentBlobs,
3206
3245
  sessionExpiryTimerStarted,
3207
3246
  };
3208
- };
3209
- const perfEvent = {
3210
- eventName: "getPendingLocalState",
3211
- notifyImminentClosure: props?.notifyImminentClosure,
3212
- };
3213
- const logAndReturnPendingState = (event, pendingState) => {
3214
3247
  event.end({
3215
- attachmentBlobsSize: Object.keys(pendingState?.pendingAttachmentBlobs ?? {}).length,
3216
- pendingOpsSize: pendingState?.pending?.pendingStates.length,
3248
+ attachmentBlobsSize: Object.keys(pendingAttachmentBlobs ?? {}).length,
3249
+ pendingOpsSize: pendingRuntimeState?.pending?.pendingStates.length,
3217
3250
  });
3218
- return pendingState;
3219
- };
3220
- // Flush pending batch.
3221
- // getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
3222
- // to close current batch.
3223
- this.flush();
3224
- return props?.notifyImminentClosure === true
3225
- ? internal_8.PerformanceEvent.timedExecAsync(this.mc.logger, perfEvent, async (event) => logAndReturnPendingState(event, getSyncState(await this.blobManager.attachAndGetPendingBlobs(props?.stopBlobAttachingSignal))))
3226
- : internal_8.PerformanceEvent.timedExec(this.mc.logger, perfEvent, (event) => logAndReturnPendingState(event, getSyncState()));
3251
+ return pendingRuntimeState;
3252
+ });
3227
3253
  }
3228
3254
  summarizeOnDemand(options) {
3229
3255
  if (this._summarizer !== undefined) {
@@ -3233,7 +3259,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
3233
3259
  // If we're not the summarizer, and we don't have a summaryManager, we expect that
3234
3260
  // disableSummaries is turned on. We are throwing instead of returning a failure here,
3235
3261
  // because it is a misuse of the API rather than an expected failure.
3236
- throw new internal_8.UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
3262
+ throw new internal_9.UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
3237
3263
  }
3238
3264
  else {
3239
3265
  return this.summaryManager.summarizeOnDemand(options);
@@ -3247,17 +3273,30 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
3247
3273
  // If we're not the summarizer, and we don't have a summaryManager, we expect that
3248
3274
  // generateSummaries is turned off. We are throwing instead of returning a failure here,
3249
3275
  // because it is a misuse of the API rather than an expected failure.
3250
- throw new internal_8.UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
3276
+ throw new internal_9.UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
3251
3277
  }
3252
3278
  else {
3253
3279
  return this.summaryManager.enqueueSummarize(options);
3254
3280
  }
3255
3281
  }
3282
+ getJoinedStatus() {
3283
+ const getConnectionState = this.getConnectionState;
3284
+ if (getConnectionState) {
3285
+ const connectionState = getConnectionState();
3286
+ if (connectionState === internal_1.ConnectionState.Connected) {
3287
+ return this.canSendOps ? "joinedForWriting" : "joinedForReading";
3288
+ }
3289
+ }
3290
+ else if (this.canSendOps) {
3291
+ return "joinedForWriting";
3292
+ }
3293
+ return "disconnected";
3294
+ }
3256
3295
  acquireExtension(id, factory, ...useContext) {
3257
3296
  let entry = this.extensions.get(id);
3258
3297
  if (entry === undefined) {
3259
3298
  const runtime = {
3260
- isConnected: () => this.connected,
3299
+ getJoinedStatus: this.getJoinedStatus.bind(this),
3261
3300
  getClientId: () => this.clientId,
3262
3301
  events: this.lazyEventsForExtensions.value,
3263
3302
  logger: this.baseLogger,