@fluidframework/container-runtime 2.70.0-361788 → 2.71.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 (78) hide show
  1. package/.eslintrc.cjs +5 -1
  2. package/CHANGELOG.md +14 -0
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/channelCollection.d.ts +66 -17
  5. package/dist/channelCollection.d.ts.map +1 -1
  6. package/dist/channelCollection.js +118 -84
  7. package/dist/channelCollection.js.map +1 -1
  8. package/dist/containerRuntime.d.ts +19 -11
  9. package/dist/containerRuntime.d.ts.map +1 -1
  10. package/dist/containerRuntime.js +146 -52
  11. package/dist/containerRuntime.js.map +1 -1
  12. package/dist/dataStore.d.ts +3 -1
  13. package/dist/dataStore.d.ts.map +1 -1
  14. package/dist/dataStore.js +8 -9
  15. package/dist/dataStore.js.map +1 -1
  16. package/dist/dataStoreContext.d.ts +6 -5
  17. package/dist/dataStoreContext.d.ts.map +1 -1
  18. package/dist/dataStoreContext.js +7 -4
  19. package/dist/dataStoreContext.js.map +1 -1
  20. package/dist/index.d.ts +3 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/messageTypes.d.ts +17 -4
  24. package/dist/messageTypes.d.ts.map +1 -1
  25. package/dist/messageTypes.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/runtimeLayerCompatState.d.ts +2 -2
  31. package/dist/runtimeLayerCompatState.d.ts.map +1 -1
  32. package/dist/runtimeLayerCompatState.js.map +1 -1
  33. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  34. package/dist/summary/summarizerNode/summarizerNode.js +3 -1
  35. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  36. package/lib/channelCollection.d.ts +66 -17
  37. package/lib/channelCollection.d.ts.map +1 -1
  38. package/lib/channelCollection.js +115 -82
  39. package/lib/channelCollection.js.map +1 -1
  40. package/lib/containerRuntime.d.ts +19 -11
  41. package/lib/containerRuntime.d.ts.map +1 -1
  42. package/lib/containerRuntime.js +148 -53
  43. package/lib/containerRuntime.js.map +1 -1
  44. package/lib/dataStore.d.ts +3 -1
  45. package/lib/dataStore.d.ts.map +1 -1
  46. package/lib/dataStore.js +3 -4
  47. package/lib/dataStore.js.map +1 -1
  48. package/lib/dataStoreContext.d.ts +6 -5
  49. package/lib/dataStoreContext.d.ts.map +1 -1
  50. package/lib/dataStoreContext.js +8 -5
  51. package/lib/dataStoreContext.js.map +1 -1
  52. package/lib/index.d.ts +3 -1
  53. package/lib/index.d.ts.map +1 -1
  54. package/lib/index.js +1 -1
  55. package/lib/index.js.map +1 -1
  56. package/lib/messageTypes.d.ts +17 -4
  57. package/lib/messageTypes.d.ts.map +1 -1
  58. package/lib/messageTypes.js.map +1 -1
  59. package/lib/packageVersion.d.ts +1 -1
  60. package/lib/packageVersion.d.ts.map +1 -1
  61. package/lib/packageVersion.js +1 -1
  62. package/lib/packageVersion.js.map +1 -1
  63. package/lib/runtimeLayerCompatState.d.ts +2 -2
  64. package/lib/runtimeLayerCompatState.d.ts.map +1 -1
  65. package/lib/runtimeLayerCompatState.js.map +1 -1
  66. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  67. package/lib/summary/summarizerNode/summarizerNode.js +3 -1
  68. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  69. package/package.json +22 -30
  70. package/src/channelCollection.ts +255 -109
  71. package/src/containerRuntime.ts +262 -92
  72. package/src/dataStore.ts +12 -10
  73. package/src/dataStoreContext.ts +45 -39
  74. package/src/index.ts +8 -3
  75. package/src/messageTypes.ts +17 -2
  76. package/src/packageVersion.ts +1 -1
  77. package/src/runtimeLayerCompatState.ts +1 -1
  78. package/src/summary/summarizerNode/summarizerNode.ts +1 -0
@@ -11,7 +11,10 @@ import type {
11
11
  IResponse,
12
12
  ITelemetryBaseLogger,
13
13
  } from "@fluidframework/core-interfaces";
14
- import type { IFluidHandleInternal } from "@fluidframework/core-interfaces/internal";
14
+ import type {
15
+ IFluidHandleInternal,
16
+ ISignalEnvelope,
17
+ } from "@fluidframework/core-interfaces/internal";
15
18
  import { assert, Lazy, LazyPromise } from "@fluidframework/core-utils/internal";
16
19
  import { FluidObjectHandle } from "@fluidframework/datastore/internal";
17
20
  import type {
@@ -24,29 +27,33 @@ import {
24
27
  getSnapshotTree,
25
28
  isInstanceOfISnapshot,
26
29
  } from "@fluidframework/driver-utils/internal";
30
+ import type {
31
+ AliasResult,
32
+ ContainerExtensionProvider,
33
+ FluidDataStoreMessage,
34
+ IAttachMessage,
35
+ IEnvelope,
36
+ IFluidDataStoreChannel,
37
+ IFluidDataStoreContext,
38
+ IFluidDataStoreContextDetached,
39
+ IFluidDataStoreFactory,
40
+ IFluidDataStoreRegistry,
41
+ IFluidParentContext,
42
+ IGarbageCollectionData,
43
+ IInboundSignalMessage,
44
+ InboundAttachMessage,
45
+ IRuntimeMessageCollection,
46
+ IRuntimeMessagesContent,
47
+ ISummarizeResult,
48
+ ISummaryTreeWithStats,
49
+ ITelemetryContext,
50
+ MinimumVersionForCollab,
51
+ NamedFluidDataStoreRegistryEntries,
52
+ } from "@fluidframework/runtime-definitions/internal";
27
53
  import {
28
- type ISummaryTreeWithStats,
29
- type ITelemetryContext,
30
- type IGarbageCollectionData,
31
- type AliasResult,
32
54
  CreateSummarizerNodeSource,
33
- type IAttachMessage,
34
- type IEnvelope,
35
- type IFluidDataStoreChannel,
36
- type IFluidDataStoreContext,
37
- type IFluidDataStoreContextDetached,
38
- type IFluidDataStoreFactory,
39
- type IFluidDataStoreRegistry,
40
- type IFluidParentContext,
41
- type ISummarizeResult,
42
- type NamedFluidDataStoreRegistryEntries,
43
55
  channelsTreeName,
44
- type IInboundSignalMessage,
45
56
  gcDataBlobKey,
46
- type IRuntimeMessagesContent,
47
- type InboundAttachMessage,
48
- type IRuntimeMessageCollection,
49
- type MinimumVersionForCollab,
50
57
  } from "@fluidframework/runtime-definitions/internal";
51
58
  import {
52
59
  GCDataBuilder,
@@ -89,7 +96,7 @@ import {
89
96
  } from "./dataStore.js";
90
97
  import {
91
98
  FluidDataStoreContext,
92
- type IFluidDataStoreContextInternal,
99
+ type IFluidDataStoreContextPrivate,
93
100
  type ILocalDetachedFluidDataStoreContextProps,
94
101
  LocalDetachedFluidDataStoreContext,
95
102
  LocalFluidDataStoreContext,
@@ -99,6 +106,11 @@ import {
99
106
  import { DataStoreContexts } from "./dataStoreContexts.js";
100
107
  import { FluidDataStoreRegistry } from "./dataStoreRegistry.js";
101
108
  import { GCNodeType, type IGCNodeUpdatedProps, urlToGCNodePath } from "./gc/index.js";
109
+ import type {
110
+ ContainerRuntimeAliasMessage,
111
+ ContainerRuntimeDataStoreOpMessage,
112
+ OutboundContainerRuntimeAttachMessage,
113
+ } from "./messageTypes.js";
102
114
  import { ContainerMessageType, type LocalContainerRuntimeMessage } from "./messageTypes.js";
103
115
  import { StorageServiceWithAttachBlobs } from "./storageServiceWithAttachBlobs.js";
104
116
  import {
@@ -115,10 +127,16 @@ export const AllowTombstoneRequestHeaderKey = "allowTombstone"; // Belongs in th
115
127
 
116
128
  type PendingAliasResolve = (success: boolean) => void;
117
129
 
118
- interface FluidDataStoreMessage {
119
- content: unknown;
120
- type: string;
121
- }
130
+ /**
131
+ * Envelope for signals not intended for the container.
132
+ *
133
+ * @privateRemarks
134
+ * `clientBroadcastSignalSequenceNumber` might be added to the envelope by the container runtime.
135
+ * But it should not be provided to start with.
136
+ *
137
+ * Equivalent to `Required<Omit<ISignalEnvelope, "clientBroadcastSignalSequenceNumber">>`.
138
+ */
139
+ export type AddressedUnsequencedSignalEnvelope = IEnvelope<ISignalEnvelope["contents"]>;
122
140
 
123
141
  /**
124
142
  * This version of the interface is private to this the package. It should never be exported under any tag.
@@ -129,19 +147,64 @@ interface FluidDataStoreMessage {
129
147
  * to ease interactions within this package.
130
148
  */
131
149
  export interface IFluidParentContextPrivate
132
- extends Omit<IFluidParentContext, "isReadOnly" | "minVersionForCollab"> {
150
+ extends IFluidParentContext,
151
+ ContainerExtensionProvider {
133
152
  readonly isReadOnly: () => boolean;
153
+ readonly minVersionForCollab: MinimumVersionForCollab;
154
+ }
134
155
 
156
+ /**
157
+ * Kin of {@link @fluidframework/runtime-definitions#IFluidParentContext} with alternate
158
+ * `submitMessage` and `submitSignal` methods that are typed specifically for the
159
+ * root context (aka {@link ContainerRuntime} provided context).
160
+ *
161
+ * @privateRemarks
162
+ * These replacements might be able to get cleaned up if the future suggestions
163
+ * found in {@link @fluidframework/runtime-definitions#FluidDataStoreMessage}
164
+ * `@privateRemarks` section are implemented.
165
+ */
166
+ export interface IFluidRootParentContextPrivate
167
+ extends Omit<IFluidParentContextPrivate, "submitMessage" | "submitSignal"> {
135
168
  /**
136
- * {@inheritdoc IFluidParentContext.minVersionForCollab}
169
+ * Submits the message to be sent to other clients.
170
+ * @param containerRuntimeMessage - The message.
171
+ * @param localOpMetadata - The local metadata associated with the message.
172
+ * This is kept locally and not sent to the server. This will be sent back
173
+ * when this message is received back from the server. This is also sent if
174
+ * we are asked to resubmit the message.
137
175
  */
138
- readonly minVersionForCollab: MinimumVersionForCollab;
176
+ readonly submitMessage: (
177
+ containerRuntimeMessage:
178
+ | ContainerRuntimeDataStoreOpMessage
179
+ | OutboundContainerRuntimeAttachMessage
180
+ | ContainerRuntimeAliasMessage,
181
+ localOpMetadata: unknown,
182
+ ) => void;
183
+ /**
184
+ * Submits the signal to be sent to other clients.
185
+ * @param envelope - {@link IEnvelope} containing the signal address and contents.
186
+ * @param targetClientId - When specified, the signal is only sent to the provided client id.
187
+ */
188
+ readonly submitSignal: (
189
+ envelope: AddressedUnsequencedSignalEnvelope,
190
+ targetClientId?: string,
191
+ ) => void;
139
192
  }
140
193
 
194
+ type SubmitKeys = "submitMessage" | "submitSignal";
195
+
141
196
  /**
142
- * Creates a shallow wrapper of {@link IFluidParentContext}. The wrapper can then have its methods overwritten as needed
197
+ * Creates a shallow wrapper of {@link IFluidParentContextPrivate} or
198
+ * {@link IFluidRootParentContextPrivate} with `submitMessage` and `submitSignal`
199
+ * methods replaced with the provided overrides.
143
200
  */
144
- export function wrapContext(context: IFluidParentContextPrivate): IFluidParentContextPrivate {
201
+ export function formParentContext<
202
+ T extends IFluidParentContextPrivate | IFluidRootParentContextPrivate,
203
+ >(
204
+ context: Omit<IFluidParentContextPrivate & IFluidRootParentContextPrivate, SubmitKeys>,
205
+ overrides: Pick<T, SubmitKeys>,
206
+ ): Omit<IFluidParentContextPrivate & IFluidRootParentContextPrivate, SubmitKeys> &
207
+ Pick<T, SubmitKeys> {
145
208
  return {
146
209
  get IFluidDataStoreRegistry() {
147
210
  return context.IFluidDataStoreRegistry;
@@ -181,12 +244,8 @@ export function wrapContext(context: IFluidParentContextPrivate): IFluidParentCo
181
244
  getAudience: (...args) => {
182
245
  return context.getAudience(...args);
183
246
  },
184
- submitMessage: (...args) => {
185
- return context.submitMessage(...args);
186
- },
187
- submitSignal: (...args) => {
188
- return context.submitSignal(...args);
189
- },
247
+ submitMessage: overrides.submitMessage.bind(overrides),
248
+ submitSignal: overrides.submitSignal,
190
249
  makeLocallyVisible: (...args) => {
191
250
  return context.makeLocallyVisible(...args);
192
251
  },
@@ -206,46 +265,41 @@ export function wrapContext(context: IFluidParentContextPrivate): IFluidParentCo
206
265
  return context.setChannelDirty(address);
207
266
  },
208
267
  minVersionForCollab: context.minVersionForCollab,
268
+ getExtension: context.getExtension.bind(context),
209
269
  };
210
270
  }
211
271
 
212
272
  /**
213
- * Creates a wrapper of a {@link IFluidParentContext} to be provided to the inner datastore channels.
273
+ * Creates a wrapper of a {@link IFluidRootParentContextPrivate} to be provided to the inner datastore channels.
214
274
  * The wrapper will have the submit methods overwritten with the appropriate id as the destination address.
215
275
  *
216
276
  * @param id - the id of the channel
217
- * @param parentContext - the {@link IFluidParentContext} to wrap
277
+ * @param parentContext - the {@link IFluidRootParentContextPrivate} to wrap
218
278
  * @returns A wrapped {@link IFluidParentContext}
219
279
  */
220
280
  function wrapContextForInnerChannel(
221
281
  id: string,
222
- parentContext: IFluidParentContextPrivate,
282
+ parentContext: IFluidRootParentContextPrivate,
223
283
  ): IFluidParentContextPrivate {
224
- const context = wrapContext(parentContext);
225
-
226
- context.submitMessage = (type: string, content: unknown, localOpMetadata: unknown) => {
227
- const fluidDataStoreContent: FluidDataStoreMessage = {
228
- content,
229
- type,
230
- };
231
- const envelope: IEnvelope = {
232
- address: id,
233
- contents: fluidDataStoreContent,
234
- };
235
- parentContext.submitMessage(
236
- ContainerMessageType.FluidDataStoreOp,
237
- envelope,
238
- localOpMetadata,
239
- );
240
- };
241
-
242
- context.submitSignal = (type: string, contents: unknown, targetClientId?: string) => {
243
- const envelope: IEnvelope = {
244
- address: id,
245
- contents,
246
- };
247
- parentContext.submitSignal(type, envelope, targetClientId);
248
- };
284
+ const context = formParentContext<IFluidParentContextPrivate>(parentContext, {
285
+ submitMessage: (type: string, content: unknown, localOpMetadata: unknown) => {
286
+ const fluidDataStoreContent: FluidDataStoreMessage = {
287
+ content,
288
+ type,
289
+ };
290
+ const envelope = {
291
+ address: id,
292
+ contents: fluidDataStoreContent,
293
+ };
294
+ parentContext.submitMessage(
295
+ { type: ContainerMessageType.FluidDataStoreOp, contents: envelope },
296
+ localOpMetadata,
297
+ );
298
+ },
299
+ submitSignal: (type: string, content: unknown, targetClientId?: string) => {
300
+ parentContext.submitSignal({ address: id, contents: { type, content } }, targetClientId);
301
+ },
302
+ });
249
303
 
250
304
  return context;
251
305
  }
@@ -262,7 +316,9 @@ export function getLocalDataStoreType(localDataStore: LocalFluidDataStoreContext
262
316
  * but eventually could be hosted on any channel once we formalize the channel api boundary.
263
317
  * @internal
264
318
  */
265
- export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
319
+ export class ChannelCollection
320
+ implements Omit<IFluidDataStoreChannel, "entryPoint" | "reSubmit" | "rollback">, IDisposable
321
+ {
266
322
  // Stores tracked by the Domain
267
323
  private readonly pendingAttach = new Map<string, IAttachMessage>();
268
324
  // 0.24 back-compat attachingBeforeSummary
@@ -273,8 +329,6 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
273
329
  // eslint-disable-next-line unicorn/consistent-function-scoping -- Property is defined once; no need to extract inner lambda
274
330
  private readonly disposeOnce = new Lazy<void>(() => this.contexts.dispose());
275
331
 
276
- public readonly entryPoint: IFluidHandleInternal<FluidObject>;
277
-
278
332
  public readonly containerLoadStats: {
279
333
  // number of dataStores during loadContainer
280
334
  readonly containerLoadDataStoreCount: number;
@@ -292,20 +346,14 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
292
346
 
293
347
  constructor(
294
348
  protected readonly baseSnapshot: ISnapshotTree | ISnapshot | undefined,
295
- public readonly parentContext: IFluidParentContextPrivate,
349
+ public readonly parentContext: IFluidRootParentContextPrivate,
296
350
  baseLogger: ITelemetryBaseLogger,
297
351
  private readonly gcNodeUpdated: (props: IGCNodeUpdatedProps) => void,
298
352
  private readonly isDataStoreDeleted: (nodePath: string) => boolean,
299
353
  private readonly aliasMap: Map<string, string>,
300
- provideEntryPoint: (runtime: ChannelCollection) => Promise<FluidObject>,
301
354
  ) {
302
355
  this.mc = createChildMonitoringContext({ logger: baseLogger });
303
356
  this.contexts = new DataStoreContexts(baseLogger);
304
- this.entryPoint = new FluidObjectHandle<FluidObject>(
305
- new LazyPromise(async () => provideEntryPoint(this)),
306
- "",
307
- this.parentContext.IFluidHandleContext,
308
- );
309
357
  this.aliasedDataStores = new Set(aliasMap.values());
310
358
 
311
359
  // Extract stores stored inside the snapshot
@@ -605,7 +653,10 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
605
653
  protected submitAttachChannelOp(localContext: LocalFluidDataStoreContext): void {
606
654
  const message = this.generateAttachMessage(localContext);
607
655
  this.pendingAttach.set(localContext.id, message);
608
- this.parentContext.submitMessage(ContainerMessageType.Attach, message, undefined);
656
+ this.parentContext.submitMessage(
657
+ { type: ContainerMessageType.Attach, contents: message },
658
+ undefined,
659
+ );
609
660
  this.attachOpFiredForDataStore.add(localContext.id);
610
661
  }
611
662
 
@@ -664,7 +715,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
664
715
  public createDataStoreContext(
665
716
  pkg: readonly string[],
666
717
  loadingGroupId?: string,
667
- ): IFluidDataStoreContextInternal {
718
+ ): IFluidDataStoreContextPrivate {
668
719
  return this.createContext(
669
720
  this.createDataStoreId(),
670
721
  pkg,
@@ -712,34 +763,34 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
712
763
  return this.disposeOnce.value;
713
764
  }
714
765
 
715
- public reSubmit(
716
- type: string,
717
- content: unknown,
766
+ public readonly reSubmitContainerMessage = (
767
+ message:
768
+ | ContainerRuntimeDataStoreOpMessage
769
+ | OutboundContainerRuntimeAttachMessage
770
+ | ContainerRuntimeAliasMessage,
718
771
  localOpMetadata: unknown,
719
- squash: boolean,
720
- ): void {
721
- switch (type) {
772
+ squash: boolean | undefined,
773
+ ): void => {
774
+ switch (message.type) {
722
775
  case ContainerMessageType.Attach:
723
776
  case ContainerMessageType.Alias: {
724
- this.parentContext.submitMessage(type, content, localOpMetadata);
777
+ this.parentContext.submitMessage(message, localOpMetadata);
725
778
  return;
726
779
  }
727
780
  case ContainerMessageType.FluidDataStoreOp: {
728
- return this.reSubmitChannelOp(type, content, localOpMetadata, squash);
781
+ return this.resubmitDataStoreOp(message.contents, localOpMetadata, squash);
729
782
  }
730
783
  default: {
731
784
  assert(false, 0x907 /* unknown op type */);
732
785
  }
733
786
  }
734
- }
787
+ };
735
788
 
736
- protected reSubmitChannelOp(
737
- type: string,
738
- content: unknown,
789
+ protected readonly resubmitDataStoreOp = (
790
+ envelope: IEnvelope<FluidDataStoreMessage>,
739
791
  localOpMetadata: unknown,
740
- squash: boolean,
741
- ): void {
742
- const envelope = content as IEnvelope;
792
+ squash: boolean | undefined,
793
+ ): void => {
743
794
  const context = this.contexts.get(envelope.address);
744
795
  // If the data store has been deleted, log an error and throw an error. If there are local changes for a
745
796
  // deleted data store, it can otherwise lead to inconsistent state when compared to other clients.
@@ -752,13 +803,13 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
752
803
  });
753
804
  }
754
805
  assert(!!context, 0x160 /* "There should be a store context for the op" */);
755
- const innerContents = envelope.contents as FluidDataStoreMessage;
756
- context.reSubmit(innerContents.type, innerContents.content, localOpMetadata, squash);
757
- }
806
+ context.reSubmit(envelope.contents, localOpMetadata, squash);
807
+ };
758
808
 
759
- public rollback(type: string, content: unknown, localOpMetadata: unknown): void {
760
- assert(type === ContainerMessageType.FluidDataStoreOp, 0x8e8 /* type */);
761
- const envelope = content as IEnvelope;
809
+ public readonly rollbackDataStoreOp = (
810
+ envelope: IEnvelope<FluidDataStoreMessage>,
811
+ localOpMetadata: unknown,
812
+ ): void => {
762
813
  const context = this.contexts.get(envelope.address);
763
814
  // If the data store has been deleted, log an error and throw an error. If there are local changes for a
764
815
  // deleted data store, it can otherwise lead to inconsistent state when compared to other clients.
@@ -771,9 +822,8 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
771
822
  });
772
823
  }
773
824
  assert(!!context, 0x2e8 /* "There should be a store context for the op" */);
774
- const innerContents = envelope.contents as FluidDataStoreMessage;
775
- context.rollback(innerContents.type, innerContents.content, localOpMetadata);
776
- }
825
+ context.rollback(envelope.contents, localOpMetadata);
826
+ };
777
827
 
778
828
  public async applyStashedOp(content: unknown): Promise<unknown> {
779
829
  const opContents = content as LocalContainerRuntimeMessage;
@@ -907,7 +957,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
907
957
  * like merge tree or shared tree can process ops more efficiently when they are bunched together.
908
958
  */
909
959
  for (const { contents, ...restOfMessagesContent } of messagesContent) {
910
- const contentsEnvelope = contents as IEnvelope;
960
+ const contentsEnvelope = contents as IEnvelope<FluidDataStoreMessage>;
911
961
  const address = contentsEnvelope.address;
912
962
  const context = this.contexts.get(address);
913
963
 
@@ -934,8 +984,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
934
984
  );
935
985
  }
936
986
 
937
- const { type: contextType, content: contextContents } =
938
- contentsEnvelope.contents as FluidDataStoreMessage;
987
+ const { type: contextType, content: contextContents } = contentsEnvelope.contents;
939
988
  // If the address or type of the message changes while processing the message, send the current bunch.
940
989
  if (
941
990
  currentMessageState?.address !== address ||
@@ -972,7 +1021,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
972
1021
  id: string,
973
1022
  requestHeaderData: RuntimeHeaderData,
974
1023
  originalRequest: IRequest,
975
- ): Promise<IFluidDataStoreContextInternal> {
1024
+ ): Promise<IFluidDataStoreContextPrivate> {
976
1025
  const headerData = { ...defaultRuntimeHeaderData, ...requestHeaderData };
977
1026
  if (
978
1027
  this.checkAndLogIfDeleted(
@@ -1008,7 +1057,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
1008
1057
  public async getDataStoreIfAvailable(
1009
1058
  id: string,
1010
1059
  requestHeaderData: RuntimeHeaderData,
1011
- ): Promise<IFluidDataStoreContextInternal | undefined> {
1060
+ ): Promise<IFluidDataStoreContextPrivate | undefined> {
1012
1061
  // If the data store has been deleted, log an error and return undefined.
1013
1062
  if (
1014
1063
  this.checkAndLogIfDeleted(
@@ -1675,7 +1724,102 @@ export function detectOutboundReferences(
1675
1724
  }
1676
1725
  }
1677
1726
 
1727
+ // #region Experimentation
1728
+ // The code below here is for experimentation (and one test) only.
1729
+
1730
+ /**
1731
+ * @privateRemarks This class is only used for experimentation/testing.
1732
+ */
1733
+ export class ComposableChannelCollection
1734
+ extends ChannelCollection
1735
+ implements IFluidDataStoreChannel
1736
+ {
1737
+ public readonly entryPoint: IFluidHandleInternal<FluidObject>;
1738
+
1739
+ public constructor(
1740
+ baseSnapshot: ISnapshotTree | ISnapshot | undefined,
1741
+ parentContext: IFluidParentContextPrivate,
1742
+ baseLogger: ITelemetryBaseLogger,
1743
+ gcNodeUpdated: (props: IGCNodeUpdatedProps) => void,
1744
+ isDataStoreDeleted: (nodePath: string) => boolean,
1745
+ aliasMap: Map<string, string>,
1746
+ provideEntryPoint: (runtime: ComposableChannelCollection) => Promise<FluidObject>,
1747
+ ) {
1748
+ super(
1749
+ baseSnapshot,
1750
+ /* [root] parentContext */
1751
+ formParentContext<IFluidRootParentContextPrivate>(parentContext, {
1752
+ submitMessage: (
1753
+ containerRuntimeMessage:
1754
+ | ContainerRuntimeDataStoreOpMessage
1755
+ | OutboundContainerRuntimeAttachMessage
1756
+ | ContainerRuntimeAliasMessage,
1757
+ localOpMetadata: unknown,
1758
+ ): void => {
1759
+ // Note that here our message format is reconfigured.
1760
+ // While `ContainerRuntime*Message`s use `contents`
1761
+ // as `FluidDataStoreMessage`s, the content is
1762
+ // stored in `content`.
1763
+ parentContext.submitMessage(
1764
+ containerRuntimeMessage.type,
1765
+ containerRuntimeMessage.contents,
1766
+ localOpMetadata,
1767
+ );
1768
+ },
1769
+ submitSignal: (
1770
+ envelope: AddressedUnsequencedSignalEnvelope,
1771
+ targetClientId?: string,
1772
+ ): void => {
1773
+ parentContext.submitSignal(
1774
+ envelope.contents.type,
1775
+ {
1776
+ address: envelope.address,
1777
+ contents: envelope.contents.content,
1778
+ } satisfies IEnvelope<unknown>,
1779
+ targetClientId,
1780
+ );
1781
+ },
1782
+ }),
1783
+ baseLogger,
1784
+ gcNodeUpdated,
1785
+ isDataStoreDeleted,
1786
+ aliasMap,
1787
+ );
1788
+ this.entryPoint = new FluidObjectHandle<FluidObject>(
1789
+ new LazyPromise(async () => provideEntryPoint(this)),
1790
+ "",
1791
+ this.parentContext.IFluidHandleContext,
1792
+ );
1793
+ }
1794
+
1795
+ public reSubmit(
1796
+ type: string,
1797
+ content: unknown,
1798
+ localOpMetadata: unknown,
1799
+ squash?: boolean,
1800
+ ): void {
1801
+ // If the cast is incorrect and type is not one of the three supported,
1802
+ // reSubmitContainerMessage will assert.
1803
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Need to force conversion
1804
+ const message = {
1805
+ type,
1806
+ contents: content,
1807
+ } as
1808
+ | ContainerRuntimeDataStoreOpMessage
1809
+ | OutboundContainerRuntimeAttachMessage
1810
+ | ContainerRuntimeAliasMessage;
1811
+ this.reSubmitContainerMessage(message, localOpMetadata, squash);
1812
+ }
1813
+
1814
+ public rollback(type: string, content: unknown, localOpMetadata: unknown): void {
1815
+ assert(type === ContainerMessageType.FluidDataStoreOp, 0x8e8 /* type */);
1816
+ this.rollbackDataStoreOp(content as IEnvelope<FluidDataStoreMessage>, localOpMetadata);
1817
+ }
1818
+ }
1819
+
1678
1820
  /**
1821
+ * @privateRemarks This factory is only used for experimentation/testing.
1822
+ *
1679
1823
  * @internal
1680
1824
  */
1681
1825
  export class ChannelCollectionFactory implements IFluidDataStoreFactory {
@@ -1715,16 +1859,18 @@ export class ChannelCollectionFactory implements IFluidDataStoreFactory {
1715
1859
  0xb8f /* we don't support the layer boundary here today */,
1716
1860
  );
1717
1861
 
1718
- const runtime = new ChannelCollection(
1862
+ const runtime = new ComposableChannelCollection(
1719
1863
  context.baseSnapshot,
1720
- context, // parentContext
1864
+ /* parentContext */ context,
1721
1865
  context.baseLogger,
1722
- () => {}, // gcNodeUpdated
1723
- (_nodePath: string) => false, // isDataStoreDeleted
1866
+ /* gcNodeUpdated */ () => {},
1867
+ /* isDataStoreDeleted */ (_nodePath: string) => false,
1724
1868
  new Map(), // aliasMap
1725
1869
  this.provideEntryPoint,
1726
1870
  );
1727
1871
 
1728
1872
  return runtime;
1729
1873
  }
1874
+
1875
+ // #endregion Experimentation
1730
1876
  }