@fluidframework/container-runtime 2.33.0-333010 → 2.33.1

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 (180) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/api-report/container-runtime.legacy.alpha.api.md +71 -67
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/blobManager/blobManager.d.ts +7 -4
  5. package/dist/blobManager/blobManager.d.ts.map +1 -1
  6. package/dist/blobManager/blobManager.js +38 -12
  7. package/dist/blobManager/blobManager.js.map +1 -1
  8. package/dist/channelCollection.d.ts +4 -0
  9. package/dist/channelCollection.d.ts.map +1 -1
  10. package/dist/channelCollection.js +24 -0
  11. package/dist/channelCollection.js.map +1 -1
  12. package/dist/compatUtils.d.ts +74 -0
  13. package/dist/compatUtils.d.ts.map +1 -0
  14. package/dist/compatUtils.js +151 -0
  15. package/dist/compatUtils.js.map +1 -0
  16. package/dist/compressionDefinitions.d.ts +39 -0
  17. package/dist/compressionDefinitions.d.ts.map +1 -0
  18. package/dist/compressionDefinitions.js +30 -0
  19. package/dist/compressionDefinitions.js.map +1 -0
  20. package/dist/containerRuntime.d.ts +78 -52
  21. package/dist/containerRuntime.d.ts.map +1 -1
  22. package/dist/containerRuntime.js +141 -54
  23. package/dist/containerRuntime.js.map +1 -1
  24. package/dist/dataStoreContext.d.ts +3 -0
  25. package/dist/dataStoreContext.d.ts.map +1 -1
  26. package/dist/dataStoreContext.js +122 -66
  27. package/dist/dataStoreContext.js.map +1 -1
  28. package/dist/deltaManagerProxies.d.ts +55 -12
  29. package/dist/deltaManagerProxies.d.ts.map +1 -1
  30. package/dist/deltaManagerProxies.js +63 -55
  31. package/dist/deltaManagerProxies.js.map +1 -1
  32. package/dist/gc/gcDefinitions.d.ts +2 -0
  33. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  34. package/dist/gc/gcDefinitions.js.map +1 -1
  35. package/dist/index.d.ts +4 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +3 -2
  38. package/dist/index.js.map +1 -1
  39. package/dist/legacy.d.ts +1 -0
  40. package/dist/opLifecycle/batchManager.d.ts +1 -1
  41. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  42. package/dist/opLifecycle/batchManager.js +4 -1
  43. package/dist/opLifecycle/batchManager.js.map +1 -1
  44. package/dist/opLifecycle/definitions.d.ts +35 -4
  45. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  46. package/dist/opLifecycle/definitions.js.map +1 -1
  47. package/dist/opLifecycle/index.d.ts +1 -1
  48. package/dist/opLifecycle/index.d.ts.map +1 -1
  49. package/dist/opLifecycle/index.js.map +1 -1
  50. package/dist/opLifecycle/opCompressor.js +2 -2
  51. package/dist/opLifecycle/opCompressor.js.map +1 -1
  52. package/dist/opLifecycle/opDecompressor.js +3 -3
  53. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  54. package/dist/opLifecycle/opGroupingManager.d.ts +2 -2
  55. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  56. package/dist/opLifecycle/opGroupingManager.js +1 -2
  57. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  58. package/dist/opLifecycle/opSerialization.d.ts +3 -1
  59. package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
  60. package/dist/opLifecycle/opSerialization.js +4 -2
  61. package/dist/opLifecycle/opSerialization.js.map +1 -1
  62. package/dist/opLifecycle/outbox.d.ts +6 -3
  63. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  64. package/dist/opLifecycle/outbox.js +46 -20
  65. package/dist/opLifecycle/outbox.js.map +1 -1
  66. package/dist/packageVersion.d.ts +1 -1
  67. package/dist/packageVersion.d.ts.map +1 -1
  68. package/dist/packageVersion.js +1 -1
  69. package/dist/packageVersion.js.map +1 -1
  70. package/dist/pendingStateManager.d.ts +36 -7
  71. package/dist/pendingStateManager.d.ts.map +1 -1
  72. package/dist/pendingStateManager.js +83 -16
  73. package/dist/pendingStateManager.js.map +1 -1
  74. package/dist/runtimeLayerCompatState.d.ts.map +1 -1
  75. package/dist/runtimeLayerCompatState.js +1 -1
  76. package/dist/runtimeLayerCompatState.js.map +1 -1
  77. package/dist/summary/documentSchema.d.ts +1 -0
  78. package/dist/summary/documentSchema.d.ts.map +1 -1
  79. package/dist/summary/documentSchema.js +2 -0
  80. package/dist/summary/documentSchema.js.map +1 -1
  81. package/lib/blobManager/blobManager.d.ts +7 -4
  82. package/lib/blobManager/blobManager.d.ts.map +1 -1
  83. package/lib/blobManager/blobManager.js +38 -12
  84. package/lib/blobManager/blobManager.js.map +1 -1
  85. package/lib/channelCollection.d.ts +4 -0
  86. package/lib/channelCollection.d.ts.map +1 -1
  87. package/lib/channelCollection.js +24 -0
  88. package/lib/channelCollection.js.map +1 -1
  89. package/lib/compatUtils.d.ts +74 -0
  90. package/lib/compatUtils.d.ts.map +1 -0
  91. package/lib/compatUtils.js +142 -0
  92. package/lib/compatUtils.js.map +1 -0
  93. package/lib/compressionDefinitions.d.ts +39 -0
  94. package/lib/compressionDefinitions.d.ts.map +1 -0
  95. package/lib/compressionDefinitions.js +27 -0
  96. package/lib/compressionDefinitions.js.map +1 -0
  97. package/lib/containerRuntime.d.ts +78 -52
  98. package/lib/containerRuntime.d.ts.map +1 -1
  99. package/lib/containerRuntime.js +143 -56
  100. package/lib/containerRuntime.js.map +1 -1
  101. package/lib/dataStoreContext.d.ts +3 -0
  102. package/lib/dataStoreContext.d.ts.map +1 -1
  103. package/lib/dataStoreContext.js +57 -1
  104. package/lib/dataStoreContext.js.map +1 -1
  105. package/lib/deltaManagerProxies.d.ts +55 -12
  106. package/lib/deltaManagerProxies.d.ts.map +1 -1
  107. package/lib/deltaManagerProxies.js +63 -55
  108. package/lib/deltaManagerProxies.js.map +1 -1
  109. package/lib/gc/gcDefinitions.d.ts +2 -0
  110. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  111. package/lib/gc/gcDefinitions.js.map +1 -1
  112. package/lib/index.d.ts +4 -2
  113. package/lib/index.d.ts.map +1 -1
  114. package/lib/index.js +2 -1
  115. package/lib/index.js.map +1 -1
  116. package/lib/legacy.d.ts +1 -0
  117. package/lib/opLifecycle/batchManager.d.ts +1 -1
  118. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  119. package/lib/opLifecycle/batchManager.js +4 -1
  120. package/lib/opLifecycle/batchManager.js.map +1 -1
  121. package/lib/opLifecycle/definitions.d.ts +35 -4
  122. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  123. package/lib/opLifecycle/definitions.js.map +1 -1
  124. package/lib/opLifecycle/index.d.ts +1 -1
  125. package/lib/opLifecycle/index.d.ts.map +1 -1
  126. package/lib/opLifecycle/index.js.map +1 -1
  127. package/lib/opLifecycle/opCompressor.js +1 -1
  128. package/lib/opLifecycle/opCompressor.js.map +1 -1
  129. package/lib/opLifecycle/opDecompressor.js +1 -1
  130. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  131. package/lib/opLifecycle/opGroupingManager.d.ts +2 -2
  132. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  133. package/lib/opLifecycle/opGroupingManager.js +1 -2
  134. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  135. package/lib/opLifecycle/opSerialization.d.ts +3 -1
  136. package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
  137. package/lib/opLifecycle/opSerialization.js +4 -2
  138. package/lib/opLifecycle/opSerialization.js.map +1 -1
  139. package/lib/opLifecycle/outbox.d.ts +6 -3
  140. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  141. package/lib/opLifecycle/outbox.js +46 -20
  142. package/lib/opLifecycle/outbox.js.map +1 -1
  143. package/lib/packageVersion.d.ts +1 -1
  144. package/lib/packageVersion.d.ts.map +1 -1
  145. package/lib/packageVersion.js +1 -1
  146. package/lib/packageVersion.js.map +1 -1
  147. package/lib/pendingStateManager.d.ts +36 -7
  148. package/lib/pendingStateManager.d.ts.map +1 -1
  149. package/lib/pendingStateManager.js +84 -17
  150. package/lib/pendingStateManager.js.map +1 -1
  151. package/lib/runtimeLayerCompatState.d.ts.map +1 -1
  152. package/lib/runtimeLayerCompatState.js +2 -2
  153. package/lib/runtimeLayerCompatState.js.map +1 -1
  154. package/lib/summary/documentSchema.d.ts +1 -0
  155. package/lib/summary/documentSchema.d.ts.map +1 -1
  156. package/lib/summary/documentSchema.js +2 -0
  157. package/lib/summary/documentSchema.js.map +1 -1
  158. package/lib/tsdoc-metadata.json +1 -1
  159. package/package.json +21 -20
  160. package/src/blobManager/blobManager.ts +48 -15
  161. package/src/channelCollection.ts +27 -0
  162. package/src/compatUtils.ts +211 -0
  163. package/src/compressionDefinitions.ts +47 -0
  164. package/src/containerRuntime.ts +259 -108
  165. package/src/dataStoreContext.ts +82 -2
  166. package/src/deltaManagerProxies.ts +132 -70
  167. package/src/gc/gcDefinitions.ts +2 -0
  168. package/src/index.ts +5 -3
  169. package/src/opLifecycle/batchManager.ts +5 -4
  170. package/src/opLifecycle/definitions.ts +34 -4
  171. package/src/opLifecycle/index.ts +1 -0
  172. package/src/opLifecycle/opCompressor.ts +1 -1
  173. package/src/opLifecycle/opDecompressor.ts +1 -1
  174. package/src/opLifecycle/opGroupingManager.ts +7 -5
  175. package/src/opLifecycle/opSerialization.ts +6 -2
  176. package/src/opLifecycle/outbox.ts +65 -30
  177. package/src/packageVersion.ts +1 -1
  178. package/src/pendingStateManager.ts +135 -21
  179. package/src/runtimeLayerCompatState.ts +5 -2
  180. package/src/summary/documentSchema.ts +3 -0
@@ -90,6 +90,11 @@ import type {
90
90
  IInboundSignalMessage,
91
91
  IRuntimeMessagesContent,
92
92
  ISummarizerNodeWithGC,
93
+ // eslint-disable-next-line import/no-deprecated
94
+ StageControlsExperimental,
95
+ // eslint-disable-next-line import/no-deprecated
96
+ IContainerRuntimeBaseExperimental,
97
+ IFluidParentContext,
93
98
  } from "@fluidframework/runtime-definitions/internal";
94
99
  import {
95
100
  FlushMode,
@@ -100,6 +105,7 @@ import {
100
105
  import {
101
106
  GCDataBuilder,
102
107
  RequestParser,
108
+ RuntimeHeaders,
103
109
  TelemetryContext,
104
110
  addBlobToSummary,
105
111
  addSummarizeResultToSummary,
@@ -151,11 +157,20 @@ import {
151
157
  getSummaryForDatastores,
152
158
  wrapContext,
153
159
  } from "./channelCollection.js";
160
+ import {
161
+ defaultCompatibilityVersion,
162
+ getCompatibilityVersionDefaults,
163
+ isValidCompatVersion,
164
+ type RuntimeOptionsAffectingDocSchema,
165
+ } from "./compatUtils.js";
166
+ import type { ICompressionRuntimeOptions } from "./compressionDefinitions.js";
167
+ import { CompressionAlgorithms, disabledCompressionConfig } from "./compressionDefinitions.js";
154
168
  import { ReportOpPerfTelemetry } from "./connectionTelemetry.js";
155
169
  import { ContainerFluidHandleContext } from "./containerHandleContext.js";
156
170
  import { channelToDataStore } from "./dataStore.js";
157
171
  import { FluidDataStoreRegistry } from "./dataStoreRegistry.js";
158
172
  import {
173
+ BaseDeltaManagerProxy,
159
174
  DeltaManagerPendingOpsProxy,
160
175
  DeltaManagerSummarizerProxy,
161
176
  } from "./deltaManagerProxies.js";
@@ -168,6 +183,7 @@ import {
168
183
  IGarbageCollector,
169
184
  gcGenerationOptionName,
170
185
  type GarbageCollectionMessage,
186
+ type IGarbageCollectionRuntime,
171
187
  } from "./gc/index.js";
172
188
  import { InboundBatchAggregator } from "./inboundBatchAggregator.js";
173
189
  import {
@@ -193,7 +209,6 @@ import {
193
209
  OpSplitter,
194
210
  Outbox,
195
211
  RemoteMessageProcessor,
196
- serializeOp,
197
212
  type OutboundBatch,
198
213
  } from "./opLifecycle/index.js";
199
214
  import { pkgVersion } from "./packageVersion.js";
@@ -247,7 +262,6 @@ import {
247
262
  idCompressorBlobName,
248
263
  metadataBlobName,
249
264
  rootHasIsolatedChannels,
250
- summarizerClientType,
251
265
  wrapSummaryInChannelsTree,
252
266
  formCreateSummarizerFn,
253
267
  summarizerRequestUrl,
@@ -259,6 +273,7 @@ import {
259
273
  ISummaryConfiguration,
260
274
  DefaultSummaryConfiguration,
261
275
  isSummariesDisabled,
276
+ summarizerClientType,
262
277
  } from "./summary/index.js";
263
278
  import { Throttler, formExponentialFn } from "./throttler.js";
264
279
 
@@ -311,32 +326,27 @@ export interface ISummaryRuntimeOptions {
311
326
  }
312
327
 
313
328
  /**
314
- * Options for op compression.
315
- * @legacy
316
- * @alpha
317
- */
318
- export interface ICompressionRuntimeOptions {
319
- /**
320
- * The value the batch's content size must exceed for the batch to be compressed.
321
- * By default the value is 600 * 1024 = 614400 bytes. If the value is set to `Infinity`, compression will be disabled.
322
- */
323
- readonly minimumBatchSizeInBytes: number;
324
-
325
- /**
326
- * The compression algorithm that will be used to compress the op.
327
- * By default the value is `lz4` which is the only compression algorithm currently supported.
328
- */
329
- readonly compressionAlgorithm: CompressionAlgorithms;
330
- }
331
-
332
- /**
333
- * Options for container runtime.
329
+ * Full set of options for container runtime as "required".
330
+ *
331
+ * @remarks
332
+ * {@link IContainerRuntimeOptions} is expected to be used by consumers.
333
+ *
334
+ * @privateRemarks If any new properties are added to this interface (or
335
+ * {@link IContainerRuntimeOptionsInternal}), then we will also need to make
336
+ * changes in {@link file://./compatUtils.ts}.
337
+ * If the new property does not change the DocumentSchema, then it must be
338
+ * explicity omitted from {@link RuntimeOptionsAffectingDocSchema}.
339
+ * If it does change the DocumentSchema, then a corresponding entry must be
340
+ * added to `runtimeOptionsAffectingDocSchemaConfigMap` with the appropriate
341
+ * compat configuration info.
342
+ * If neither of the above is done, then the build will fail to compile.
343
+ *
334
344
  * @legacy
335
345
  * @alpha
336
346
  */
337
- export interface IContainerRuntimeOptions {
338
- readonly summaryOptions?: ISummaryRuntimeOptions;
339
- readonly gcOptions?: IGCRuntimeOptions;
347
+ export interface ContainerRuntimeOptions {
348
+ readonly summaryOptions: ISummaryRuntimeOptions;
349
+ readonly gcOptions: IGCRuntimeOptions;
340
350
  /**
341
351
  * Affects the behavior while loading the runtime when the data verification check which
342
352
  * compares the DeltaManager sequence number (obtained from protocol in summary) to the
@@ -345,12 +355,12 @@ export interface IContainerRuntimeOptions {
345
355
  * 2. "log" will log an error event to telemetry, but still continue to load.
346
356
  * 3. "bypass" will skip the check entirely. This is not recommended.
347
357
  */
348
- readonly loadSequenceNumberVerification?: "close" | "log" | "bypass";
358
+ readonly loadSequenceNumberVerification: "close" | "log" | "bypass";
349
359
 
350
360
  /**
351
361
  * Enables the runtime to compress ops. See {@link ICompressionRuntimeOptions}.
352
362
  */
353
- readonly compressionOptions?: ICompressionRuntimeOptions;
363
+ readonly compressionOptions: ICompressionRuntimeOptions;
354
364
  /**
355
365
  * If specified, when in FlushMode.TurnBased, if the size of the ops between JS turns exceeds this value,
356
366
  * an error will be thrown and the container will close.
@@ -361,27 +371,27 @@ export interface IContainerRuntimeOptions {
361
371
  *
362
372
  * @experimental This config should be driven by the connection with the service and will be moved in the future.
363
373
  */
364
- readonly maxBatchSizeInBytes?: number;
374
+ readonly maxBatchSizeInBytes: number;
365
375
  /**
366
376
  * If the op payload needs to be chunked in order to work around the maximum size of the batch, this value represents
367
377
  * how large the individual chunks will be. This is only supported when compression is enabled. If after compression, the
368
378
  * batch content size exceeds this value, it will be chunked into smaller ops of this exact size.
369
379
  *
370
380
  * This value is a trade-off between having many small chunks vs fewer larger chunks and by default, the runtime is configured to use
371
- * 200 * 1024 = 204800 bytes. This default value ensures that no compressed payload's content is able to exceed {@link IContainerRuntimeOptions.maxBatchSizeInBytes}
381
+ * 200 * 1024 = 204800 bytes. This default value ensures that no compressed payload's content is able to exceed {@link ContainerRuntimeOptions.maxBatchSizeInBytes}
372
382
  * regardless of the overhead of an individual op.
373
383
  *
374
- * Any value of `chunkSizeInBytes` exceeding {@link IContainerRuntimeOptions.maxBatchSizeInBytes} will disable this feature, therefore if a compressed batch's content
375
- * size exceeds {@link IContainerRuntimeOptions.maxBatchSizeInBytes} after compression, the container will close with an instance of `DataProcessingError` with
384
+ * Any value of `chunkSizeInBytes` exceeding {@link ContainerRuntimeOptions.maxBatchSizeInBytes} will disable this feature, therefore if a compressed batch's content
385
+ * size exceeds {@link ContainerRuntimeOptions.maxBatchSizeInBytes} after compression, the container will close with an instance of `DataProcessingError` with
376
386
  * the `BatchTooLarge` message.
377
387
  */
378
- readonly chunkSizeInBytes?: number;
388
+ readonly chunkSizeInBytes: number;
379
389
 
380
390
  /**
381
391
  * Enable the IdCompressor in the runtime.
382
392
  * @experimental Not ready for use.
383
393
  */
384
- readonly enableRuntimeIdCompressor?: IdCompressorMode;
394
+ readonly enableRuntimeIdCompressor: IdCompressorMode;
385
395
 
386
396
  /**
387
397
  * If enabled, the runtime will group messages within a batch into a single
@@ -391,7 +401,7 @@ export interface IContainerRuntimeOptions {
391
401
  * By default, the feature is enabled. This feature can only be disabled when compression is also disabled.
392
402
  * @deprecated The ability to disable Grouped Batching is deprecated and will be removed in a future release. This feature is required for the proper functioning of the Fluid Framework.
393
403
  */
394
- readonly enableGroupedBatching?: boolean;
404
+ readonly enableGroupedBatching: boolean;
395
405
 
396
406
  /**
397
407
  * When this property is set to true, it requires runtime to control is document schema properly through ops
@@ -400,34 +410,60 @@ export interface IContainerRuntimeOptions {
400
410
  * When this property is not set (or set to false), runtime operates in legacy mode, where new features (modifying document schema)
401
411
  * are engaged as they become available, without giving legacy clients any chance to fail predictably.
402
412
  */
403
- readonly explicitSchemaControl?: boolean;
413
+ readonly explicitSchemaControl: boolean;
414
+
415
+ /**
416
+ * Create blob handles with pending payloads when calling createBlob (default is `undefined` (disabled)).
417
+ * When enabled (`true`), createBlob will return a handle before the blob upload completes.
418
+ */
419
+ readonly createBlobPayloadPending: true | undefined;
404
420
  }
405
421
 
406
422
  /**
407
- * Internal extension of @see IContainerRuntimeOptions
423
+ * Options for container runtime.
424
+ *
425
+ * @legacy
426
+ * @alpha
427
+ */
428
+ export type IContainerRuntimeOptions = Partial<ContainerRuntimeOptions>;
429
+
430
+ /**
431
+ * Internal extension of {@link ContainerRuntimeOptions}
408
432
  *
433
+ * @privateRemarks
409
434
  * These options are not available to consumers when creating a new container runtime,
410
435
  * but we do need to expose them for internal use, e.g. when configuring the container runtime
411
436
  * to ensure compatibility with older versions.
412
437
  *
438
+ * This is defined as a fully required set of options as this package does not yet
439
+ * use `exactOptionalPropertyTypes` and `Required<>` applied to optional type allowing
440
+ * `undefined` like {@link IdCompressorMode} will exclude `undefined`.
441
+ *
413
442
  * @internal
414
443
  */
415
- export interface IContainerRuntimeOptionsInternal extends IContainerRuntimeOptions {
444
+ export interface ContainerRuntimeOptionsInternal extends ContainerRuntimeOptions {
416
445
  /**
417
446
  * Sets the flush mode for the runtime. In Immediate flush mode the runtime will immediately
418
447
  * send all operations to the driver layer, while in TurnBased the operations will be buffered
419
448
  * and then sent them as a single batch at the end of the turn.
420
449
  * By default, flush mode is TurnBased.
421
450
  */
422
- readonly flushMode?: FlushMode;
451
+ readonly flushMode: FlushMode;
423
452
 
424
453
  /**
425
454
  * Allows Grouped Batching to be disabled by setting to false (default is true).
426
455
  * In that case, batched messages will be sent individually (but still all at the same time).
427
456
  */
428
- readonly enableGroupedBatching?: boolean;
457
+ readonly enableGroupedBatching: boolean;
429
458
  }
430
459
 
460
+ /**
461
+ * Internal extension of {@link IContainerRuntimeOptions}
462
+ *
463
+ * @internal
464
+ */
465
+ export type IContainerRuntimeOptionsInternal = Partial<ContainerRuntimeOptionsInternal>;
466
+
431
467
  /**
432
468
  * Error responses when requesting a deleted object will have this header set to true
433
469
  * @internal
@@ -468,24 +504,6 @@ export const defaultRuntimeHeaderData: Required<RuntimeHeaderData> = {
468
504
  allowTombstone: false,
469
505
  };
470
506
 
471
- /**
472
- * Available compression algorithms for op compression.
473
- * @legacy
474
- * @alpha
475
- */
476
- export enum CompressionAlgorithms {
477
- lz4 = "lz4",
478
- }
479
-
480
- /**
481
- * @legacy
482
- * @alpha
483
- */
484
- export const disabledCompressionConfig: ICompressionRuntimeOptions = {
485
- minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
486
- compressionAlgorithm: CompressionAlgorithms.lz4,
487
- };
488
-
489
507
  /**
490
508
  * @deprecated
491
509
  * Untagged logger is unsupported going forward. There are old loaders with old ContainerContexts that only
@@ -524,20 +542,12 @@ export interface IPendingRuntimeState {
524
542
 
525
543
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
526
544
 
527
- const defaultFlushMode = FlushMode.TurnBased;
528
-
529
545
  // The actual limit is 1Mb (socket.io and Kafka limits)
530
546
  // We can't estimate it fully, as we
531
547
  // - do not know what properties relay service will add
532
548
  // - we do not stringify final op, thus we do not know how much escaping will be added.
533
549
  const defaultMaxBatchSizeInBytes = 700 * 1024;
534
550
 
535
- const defaultCompressionConfig = {
536
- // Batches with content size exceeding this value will be compressed
537
- minimumBatchSizeInBytes: 614400,
538
- compressionAlgorithm: CompressionAlgorithms.lz4,
539
- };
540
-
541
551
  const defaultChunkSizeInBytes = 204800;
542
552
 
543
553
  /**
@@ -718,9 +728,13 @@ export class ContainerRuntime
718
728
  extends TypedEventEmitter<IContainerRuntimeEvents>
719
729
  implements
720
730
  IContainerRuntime,
731
+ // eslint-disable-next-line import/no-deprecated
732
+ IContainerRuntimeBaseExperimental,
721
733
  IRuntime,
734
+ IGarbageCollectionRuntime,
722
735
  ISummarizerRuntime,
723
736
  ISummarizerInternalsProvider,
737
+ IFluidParentContext,
724
738
  IProvideFluidHandleContext,
725
739
  IProvideLayerCompatDetails
726
740
  {
@@ -781,21 +795,63 @@ export class ContainerRuntime
781
795
 
782
796
  const mc = loggerToMonitoringContext(logger);
783
797
 
798
+ // Some options require a minimum version of the FF runtime to operate, so the default configs will be generated
799
+ // based on the compatibility mode.
800
+ // For example, if compatibility mode is set to "1.0.0", the default configs will ensure compatibility with FF runtime
801
+ // 1.0.0 or later. If the compatibility mode is set to "2.10.0", the default values will be generated to ensure compatibility
802
+ // with FF runtime 2.10.0 or later.
803
+ // TODO: We will add in a way for users to pass in compatibilityVersion in a follow up PR.
804
+ const compatibilityVersion = defaultCompatibilityVersion;
805
+ if (!isValidCompatVersion(compatibilityVersion)) {
806
+ throw new UsageError(
807
+ `Invalid compatibility version: ${compatibilityVersion}. It must be an existing FF version (i.e. 2.22.1).`,
808
+ );
809
+ }
810
+ const defaultVersionDependentConfigs =
811
+ getCompatibilityVersionDefaults(compatibilityVersion);
812
+
813
+ // The following are the default values for the options that do not affect the DocumentSchema.
814
+ const defaultConfigsNonVersionDependent: Required<
815
+ Omit<IContainerRuntimeOptionsInternal, keyof RuntimeOptionsAffectingDocSchema>
816
+ > = {
817
+ summaryOptions: {},
818
+ loadSequenceNumberVerification: "close",
819
+ maxBatchSizeInBytes: defaultMaxBatchSizeInBytes,
820
+ chunkSizeInBytes: defaultChunkSizeInBytes,
821
+ };
822
+
823
+ const defaultConfigs = {
824
+ ...defaultVersionDependentConfigs,
825
+ ...defaultConfigsNonVersionDependent,
826
+ };
827
+
828
+ // Here we set each option to its corresponding default config value if it's not provided in runtimeOptions.
829
+ // Note: We cannot do a simple object merge of defaultConfigs/runtimeOptions because in most cases we don't want
830
+ // a option that is undefined in runtimeOptions to override the default value (except for idCompressor, see below).
784
831
  const {
785
- summaryOptions = {},
786
- gcOptions = {},
787
- loadSequenceNumberVerification = "close",
788
- flushMode = defaultFlushMode,
789
- compressionOptions = runtimeOptions.enableGroupedBatching === false
790
- ? disabledCompressionConfig // Compression must be disabled if Grouping is disabled
791
- : defaultCompressionConfig,
792
- maxBatchSizeInBytes = defaultMaxBatchSizeInBytes,
793
- enableRuntimeIdCompressor,
794
- chunkSizeInBytes = defaultChunkSizeInBytes,
795
- enableGroupedBatching = true,
796
- explicitSchemaControl = false,
832
+ summaryOptions = defaultConfigs.summaryOptions,
833
+ gcOptions = defaultConfigs.gcOptions,
834
+ loadSequenceNumberVerification = defaultConfigs.loadSequenceNumberVerification,
835
+ maxBatchSizeInBytes = defaultConfigs.maxBatchSizeInBytes,
836
+ chunkSizeInBytes = defaultConfigs.chunkSizeInBytes,
837
+ explicitSchemaControl = defaultConfigs.explicitSchemaControl,
838
+ enableGroupedBatching = defaultConfigs.enableGroupedBatching,
839
+ flushMode = defaultConfigs.flushMode,
840
+ // If batching is disabled then we should disable compression as well. If batching is disabled and compression
841
+ // is enabled via runtimeOptions, we will throw an error later.
842
+ compressionOptions = enableGroupedBatching === false
843
+ ? disabledCompressionConfig
844
+ : defaultConfigs.compressionOptions,
845
+ createBlobPayloadPending = defaultConfigs.createBlobPayloadPending,
797
846
  }: IContainerRuntimeOptionsInternal = runtimeOptions;
798
847
 
848
+ // The logic for enableRuntimeIdCompressor is a bit different. Since `undefined` represents a logical state (off)
849
+ // we need to check it's explicitly set in runtimeOptions. If so, we should use that value even if it's undefined.
850
+ const enableRuntimeIdCompressor =
851
+ "enableRuntimeIdCompressor" in runtimeOptions
852
+ ? runtimeOptions.enableRuntimeIdCompressor
853
+ : defaultConfigs.enableRuntimeIdCompressor;
854
+
799
855
  const registry = new FluidDataStoreRegistry(registryEntries);
800
856
 
801
857
  const tryFetchBlob = async <T>(blobName: string): Promise<T | undefined> => {
@@ -970,6 +1026,7 @@ export class ContainerRuntime
970
1026
  compressionLz4,
971
1027
  idCompressorMode,
972
1028
  opGroupingEnabled: enableGroupedBatching,
1029
+ createBlobPayloadPending,
973
1030
  disallowedVersions: [],
974
1031
  },
975
1032
  (schema) => {
@@ -984,7 +1041,7 @@ export class ContainerRuntime
984
1041
  const featureGatesForTelemetry: Record<string, boolean | number | undefined> = {};
985
1042
 
986
1043
  // Make sure we've got all the options including internal ones
987
- const internalRuntimeOptions: Readonly<Required<IContainerRuntimeOptionsInternal>> = {
1044
+ const internalRuntimeOptions: Readonly<ContainerRuntimeOptionsInternal> = {
988
1045
  summaryOptions,
989
1046
  gcOptions,
990
1047
  loadSequenceNumberVerification,
@@ -992,10 +1049,10 @@ export class ContainerRuntime
992
1049
  compressionOptions,
993
1050
  maxBatchSizeInBytes,
994
1051
  chunkSizeInBytes,
995
- // Requires<> drops undefined from IdCompressorType
996
- enableRuntimeIdCompressor: enableRuntimeIdCompressor as "on" | "delayed",
1052
+ enableRuntimeIdCompressor,
997
1053
  enableGroupedBatching,
998
1054
  explicitSchemaControl,
1055
+ createBlobPayloadPending,
999
1056
  };
1000
1057
 
1001
1058
  const runtime = new containerRuntimeCtor(
@@ -1087,6 +1144,8 @@ export class ContainerRuntime
1087
1144
  return this._getAttachState();
1088
1145
  }
1089
1146
 
1147
+ public readonly isReadOnly = (): boolean => this.deltaManager.readOnlyInfo.readonly === true;
1148
+
1090
1149
  /**
1091
1150
  * Current session schema - defines what options are on & off.
1092
1151
  * It's overlap of document schema (controlled by summary & ops) and options controlling this session.
@@ -1158,7 +1217,7 @@ export class ContainerRuntime
1158
1217
  return this._deltaManager;
1159
1218
  }
1160
1219
 
1161
- private readonly _deltaManager: IDeltaManagerFull;
1220
+ private readonly _deltaManager: BaseDeltaManagerProxy;
1162
1221
 
1163
1222
  /**
1164
1223
  * The delta manager provided by the container context. By default, using the default delta manager (proxy)
@@ -1334,7 +1393,7 @@ export class ContainerRuntime
1334
1393
  private readonly electedSummarizerData: ISerializedElection | undefined,
1335
1394
  chunks: [string, string[]][],
1336
1395
  dataStoreAliasMap: [string, string][],
1337
- private readonly runtimeOptions: Readonly<Required<IContainerRuntimeOptionsInternal>>,
1396
+ private readonly runtimeOptions: Readonly<ContainerRuntimeOptionsInternal>,
1338
1397
  private readonly containerScope: FluidObject,
1339
1398
  // Create a custom ITelemetryBaseLogger to output telemetry events.
1340
1399
  public readonly baseLogger: ITelemetryBaseLogger,
@@ -1537,26 +1596,27 @@ export class ContainerRuntime
1537
1596
  this.baseLogger,
1538
1597
  );
1539
1598
 
1540
- let outerDeltaManager: IDeltaManagerFull;
1599
+ let outerDeltaManager: IDeltaManagerFull = this.innerDeltaManager;
1541
1600
  this.useDeltaManagerOpsProxy =
1542
1601
  this.mc.config.getBoolean("Fluid.ContainerRuntime.DeltaManagerOpsProxy") === true;
1543
1602
  // The summarizerDeltaManager Proxy is used to lie to the summarizer to convince it is in the right state as a summarizer client.
1544
- const summarizerDeltaManagerProxy = new DeltaManagerSummarizerProxy(
1545
- this.innerDeltaManager,
1546
- );
1547
- outerDeltaManager = summarizerDeltaManagerProxy;
1603
+ outerDeltaManager = DeltaManagerSummarizerProxy.wrapIfSummarizer(outerDeltaManager);
1548
1604
 
1549
1605
  // The DeltaManagerPendingOpsProxy is used to control the minimum sequence number
1550
1606
  // It allows us to lie to the layers below so that they can maintain enough local state for rebasing ops.
1551
1607
  if (this.useDeltaManagerOpsProxy) {
1552
1608
  const pendingOpsDeltaManagerProxy = new DeltaManagerPendingOpsProxy(
1553
- summarizerDeltaManagerProxy,
1609
+ outerDeltaManager,
1554
1610
  this.pendingStateManager,
1555
1611
  );
1556
1612
  outerDeltaManager = pendingOpsDeltaManagerProxy;
1557
1613
  }
1558
1614
 
1559
- this._deltaManager = outerDeltaManager;
1615
+ // always wrap the exposed delta manager in at least on layer of proxying
1616
+ this._deltaManager =
1617
+ outerDeltaManager instanceof BaseDeltaManagerProxy
1618
+ ? outerDeltaManager
1619
+ : new BaseDeltaManagerProxy(outerDeltaManager);
1560
1620
 
1561
1621
  this.handleContext = new ContainerFluidHandleContext("", this);
1562
1622
 
@@ -1704,6 +1764,7 @@ export class ContainerRuntime
1704
1764
  new Map<string, string>(dataStoreAliasMap),
1705
1765
  async (runtime: ChannelCollection) => provideEntryPoint,
1706
1766
  );
1767
+ this._deltaManager.on("readonly", this.notifyReadOnlyState);
1707
1768
 
1708
1769
  this.blobManager = new BlobManager({
1709
1770
  routeContext: this.handleContext,
@@ -1730,6 +1791,7 @@ export class ContainerRuntime
1730
1791
  isBlobDeleted: (blobPath: string) => this.garbageCollector.isNodeDeleted(blobPath),
1731
1792
  runtime: this,
1732
1793
  stashedBlobs: pendingRuntimeState?.pendingAttachmentBlobs,
1794
+ createBlobPayloadPending: this.sessionSchema.createBlobPayloadPending === true,
1733
1795
  });
1734
1796
 
1735
1797
  this.deltaScheduler = new DeltaScheduler(
@@ -2093,6 +2155,7 @@ export class ContainerRuntime
2093
2155
  this.pendingStateManager.dispose();
2094
2156
  this.inboundBatchAggregator.dispose();
2095
2157
  this.deltaScheduler.dispose();
2158
+ this._deltaManager.dispose();
2096
2159
  this.emit("dispose");
2097
2160
  this.removeAllListeners();
2098
2161
  }
@@ -2275,7 +2338,16 @@ export class ContainerRuntime
2275
2338
  }
2276
2339
 
2277
2340
  if (id === blobManagerBasePath && requestParser.isLeaf(2)) {
2278
- const blob = await this.blobManager.getBlob(requestParser.pathParts[1]);
2341
+ const localId = requestParser.pathParts[1];
2342
+ const payloadPending = requestParser.headers?.[RuntimeHeaders.payloadPending] === true;
2343
+ if (
2344
+ !this.blobManager.hasBlob(localId) &&
2345
+ requestParser.headers?.[RuntimeHeaders.wait] === false
2346
+ ) {
2347
+ return create404Response(request);
2348
+ }
2349
+
2350
+ const blob = await this.blobManager.getBlob(localId, payloadPending);
2279
2351
  return {
2280
2352
  status: 200,
2281
2353
  mimeType: "fluid/object",
@@ -2545,6 +2617,9 @@ export class ContainerRuntime
2545
2617
  return this._loadIdCompressor;
2546
2618
  }
2547
2619
 
2620
+ private readonly notifyReadOnlyState = (readonly: boolean): void =>
2621
+ this.channelCollection.notifyReadOnlyState(readonly);
2622
+
2548
2623
  public setConnectionState(connected: boolean, clientId?: string): void {
2549
2624
  // Validate we have consistent state
2550
2625
  const currentClientId = this._audience.getSelf()?.clientId;
@@ -3121,15 +3196,17 @@ export class ContainerRuntime
3121
3196
  *
3122
3197
  * @param resubmittingBatchId - If defined, indicates this is a resubmission of a batch
3123
3198
  * with the given Batch ID, which must be preserved
3199
+ * @param resubmittingStagedBatch - If defined, indicates this is a resubmission of a batch that is staged,
3200
+ * meaning it should not be sent to the ordering service yet.
3124
3201
  */
3125
- private flush(resubmittingBatchId?: BatchId): void {
3202
+ private flush(resubmittingBatchId?: BatchId, resubmittingStagedBatch?: boolean): void {
3126
3203
  try {
3127
3204
  assert(
3128
3205
  !this.batchRunner.running,
3129
3206
  0x24c /* "Cannot call `flush()` while manually accumulating a batch (e.g. under orderSequentially) */,
3130
3207
  );
3131
3208
 
3132
- this.outbox.flush(resubmittingBatchId);
3209
+ this.outbox.flush(resubmittingBatchId, resubmittingStagedBatch);
3133
3210
  assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
3134
3211
  } catch (error) {
3135
3212
  const error2 = normalizeError(error, {
@@ -3148,7 +3225,12 @@ export class ContainerRuntime
3148
3225
  public orderSequentially<T>(callback: () => T): T {
3149
3226
  let checkpoint: IBatchCheckpoint | undefined;
3150
3227
  const checkpointDirtyState = this.dirtyContainer;
3228
+ // eslint-disable-next-line import/no-deprecated
3229
+ let stageControls: StageControlsExperimental | undefined;
3151
3230
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
3231
+ if (!this.batchRunner.running && !this.inStagingMode) {
3232
+ stageControls = this.enterStagingMode();
3233
+ }
3152
3234
  // Note: we are not touching any batches other than mainBatch here, for two reasons:
3153
3235
  // 1. It would not help, as other batches are flushed independently from main batch.
3154
3236
  // 2. There is no way to undo process of data store creation, blob creation, ID compressor ops, or other things tracked by other batches.
@@ -3162,12 +3244,14 @@ export class ContainerRuntime
3162
3244
  // This will throw and close the container if rollback fails
3163
3245
  try {
3164
3246
  checkpoint.rollback((message: LocalBatchMessage) =>
3165
- this.rollback(message.serializedOp, message.localOpMetadata),
3247
+ this.rollback(message.runtimeOp, message.localOpMetadata),
3166
3248
  );
3167
3249
  // reset the dirty state after rollback to what it was before to keep it consistent
3168
3250
  if (this.dirtyContainer !== checkpointDirtyState) {
3169
3251
  this.updateDocumentDirtyState(checkpointDirtyState);
3170
3252
  }
3253
+ stageControls?.discardChanges();
3254
+ stageControls = undefined;
3171
3255
  } catch (error_) {
3172
3256
  const error2 = wrapError(error_, (message) => {
3173
3257
  return DataProcessingError.create(
@@ -3198,6 +3282,7 @@ export class ContainerRuntime
3198
3282
  throw error; // throw the original error for the consumer of the runtime
3199
3283
  }
3200
3284
  });
3285
+ stageControls?.commitChanges();
3201
3286
 
3202
3287
  // We don't flush on TurnBased since we expect all messages in the same JS turn to be part of the same batch
3203
3288
  if (this.flushMode !== FlushMode.TurnBased && !this.batchRunner.running) {
@@ -3206,6 +3291,70 @@ export class ContainerRuntime
3206
3291
  return result;
3207
3292
  }
3208
3293
 
3294
+ // eslint-disable-next-line import/no-deprecated
3295
+ private stageControls: StageControlsExperimental | undefined;
3296
+
3297
+ /**
3298
+ * If true, the ContainerRuntime is not submitting any new ops to the ordering service.
3299
+ * Ops submitted to the ContainerRuntime while in Staging Mode will be queued in the PendingStateManager,
3300
+ * either to be discarded or committed later (via the Stage Controls returned from enterStagingMode).
3301
+ */
3302
+ public get inStagingMode(): boolean {
3303
+ return this.stageControls !== undefined;
3304
+ }
3305
+
3306
+ /**
3307
+ * Enter Staging Mode, such that ops submitted to the ContainerRuntime will not be sent to the ordering service.
3308
+ * To exit Staging Mode, call either discardChanges or commitChanges on the Stage Controls returned from this method.
3309
+ *
3310
+ * @returns StageControlsExperimental - Controls for exiting Staging Mode.
3311
+ */
3312
+ // eslint-disable-next-line import/no-deprecated
3313
+ public enterStagingMode = (): StageControlsExperimental => {
3314
+ if (this.stageControls !== undefined) {
3315
+ throw new Error("already in staging mode");
3316
+ }
3317
+
3318
+ // Make sure all BatchManagers are empty before entering staging mode,
3319
+ // since we mark whole batches as "staged" or not to indicate whether to submit them.
3320
+ this.outbox.flush();
3321
+ const exitStagingMode = (discardOrCommit: () => void) => (): void => {
3322
+ // Final flush of any last staged changes
3323
+ this.outbox.flush(undefined, true /* staged */);
3324
+
3325
+ this.stageControls = undefined;
3326
+
3327
+ discardOrCommit();
3328
+ };
3329
+
3330
+ const stageControls = {
3331
+ discardChanges: exitStagingMode(() => {
3332
+ // Pop all staged batches from the PSM and roll them back in LIFO order
3333
+ this.pendingStateManager.popStagedBatches(({ runtimeOp, localOpMetadata }) => {
3334
+ assert(
3335
+ runtimeOp !== undefined,
3336
+ 0xb82 /* Staged batches expected to have runtimeOp defined */,
3337
+ );
3338
+ this.rollback(runtimeOp, localOpMetadata);
3339
+ });
3340
+ if (this.attachState === AttachState.Attached) {
3341
+ this.updateDocumentDirtyState(this.pendingMessagesCount !== 0);
3342
+ }
3343
+ }),
3344
+ commitChanges: exitStagingMode(() => {
3345
+ // All staged changes are in the PSM, so just replay them (ignore pre-staging batches)
3346
+ // FUTURE: Have this do squash-rebase instead of resubmitting all intermediate changes
3347
+ if (this.connected) {
3348
+ this.pendingStateManager.replayPendingStates(true /* onlyStagedBatched */);
3349
+ } else {
3350
+ this.pendingStateManager.clearStagingFlags();
3351
+ }
3352
+ }),
3353
+ };
3354
+
3355
+ return (this.stageControls = stageControls);
3356
+ };
3357
+
3209
3358
  /**
3210
3359
  * Returns the aliased data store's entryPoint, given the alias.
3211
3360
  * @param alias - The alias for the data store.
@@ -4214,8 +4363,11 @@ export class ContainerRuntime
4214
4363
  contents: idRange,
4215
4364
  };
4216
4365
  const idAllocationBatchMessage: LocalBatchMessage = {
4217
- serializedOp: serializeOp(idAllocationMessage),
4366
+ runtimeOp: idAllocationMessage,
4218
4367
  referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
4368
+ // Note: For now, we will never stage ID Allocation messages.
4369
+ // They won't contain personal info and no harm in extra allocations in case of discarding the staged changes
4370
+ staged: false,
4219
4371
  };
4220
4372
  this.outbox.submitIdAllocation(idAllocationBatchMessage);
4221
4373
  }
@@ -4277,18 +4429,20 @@ export class ContainerRuntime
4277
4429
  contents: schemaChangeMessage,
4278
4430
  };
4279
4431
  this.outbox.submit({
4280
- serializedOp: serializeOp(msg),
4432
+ runtimeOp: msg,
4281
4433
  referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
4434
+ staged: this.inStagingMode,
4282
4435
  });
4283
4436
  }
4284
4437
 
4285
4438
  const message: LocalBatchMessage = {
4286
4439
  // This will encode any handles present in this op before serializing to string
4287
4440
  // Note: handles may already have been encoded by the DDS layer, but encoding handles is idempotent so there's no problem.
4288
- serializedOp: serializeOp(containerRuntimeMessage),
4441
+ runtimeOp: containerRuntimeMessage,
4289
4442
  metadata,
4290
4443
  localOpMetadata,
4291
4444
  referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
4445
+ staged: this.inStagingMode,
4292
4446
  };
4293
4447
  if (type === ContainerMessageType.BlobAttach) {
4294
4448
  // BlobAttach ops must have their metadata visible and cannot be grouped (see opGroupingManager.ts)
@@ -4391,7 +4545,11 @@ export class ContainerRuntime
4391
4545
  * @remarks - If the "Offline Load" feature is enabled, the batchId is included in the resubmitted messages,
4392
4546
  * for correlation to detect container forking.
4393
4547
  */
4394
- private reSubmitBatch(batch: PendingMessageResubmitData[], batchId: BatchId): void {
4548
+ private reSubmitBatch(
4549
+ batch: PendingMessageResubmitData[],
4550
+ batchId: BatchId,
4551
+ staged: boolean,
4552
+ ): void {
4395
4553
  this.batchRunner.run(() => {
4396
4554
  for (const message of batch) {
4397
4555
  this.reSubmit(message);
@@ -4400,15 +4558,11 @@ export class ContainerRuntime
4400
4558
 
4401
4559
  // Only include Batch ID if "Offline Load" feature is enabled
4402
4560
  // It's only needed to identify batches across container forks arising from misuse of offline load.
4403
- this.flush(this.offlineEnabled ? batchId : undefined);
4561
+ this.flush(this.offlineEnabled ? batchId : undefined, staged);
4404
4562
  }
4405
4563
 
4406
4564
  private reSubmit(message: PendingMessageResubmitData): void {
4407
- // Messages in the PendingStateManager and Outbox (both call resubmit) have serialized contents, so parse it here.
4408
- // Note that roundtripping handles through string like this means this parsed contents
4409
- // contains RemoteFluidObjectHandles, not the original handle.
4410
- const containerRuntimeMessage = this.parseLocalOpContent(message.content);
4411
- this.reSubmitCore(containerRuntimeMessage, message.localOpMetadata, message.opMetadata);
4565
+ this.reSubmitCore(message.runtimeOp, message.localOpMetadata, message.opMetadata);
4412
4566
  }
4413
4567
 
4414
4568
  /**
@@ -4472,11 +4626,8 @@ export class ContainerRuntime
4472
4626
  }
4473
4627
  }
4474
4628
 
4475
- private rollback(content: string | undefined, localOpMetadata: unknown): void {
4476
- // Messages in the Outbox (which calls rollback) have serialized contents, so parse it here.
4477
- // Note that roundtripping handles through string like this means this parsed contents
4478
- // contains RemoteFluidObjectHandles, not the original handle.
4479
- const { type, contents } = this.parseLocalOpContent(content);
4629
+ private rollback(runtimeOp: LocalContainerRuntimeMessage, localOpMetadata: unknown): void {
4630
+ const { type, contents } = runtimeOp;
4480
4631
  switch (type) {
4481
4632
  case ContainerMessageType.FluidDataStoreOp: {
4482
4633
  // For operations, call rollbackDataStoreOp which will find the right store