@fluidframework/datastore 2.0.0-internal.4.2.1 → 2.0.0-internal.4.4.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 (54) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/channelContext.d.ts +12 -5
  3. package/dist/channelContext.d.ts.map +1 -1
  4. package/dist/channelContext.js +74 -5
  5. package/dist/channelContext.js.map +1 -1
  6. package/dist/channelDeltaConnection.d.ts +4 -5
  7. package/dist/channelDeltaConnection.d.ts.map +1 -1
  8. package/dist/channelDeltaConnection.js +3 -4
  9. package/dist/channelDeltaConnection.js.map +1 -1
  10. package/dist/dataStoreRuntime.d.ts.map +1 -1
  11. package/dist/dataStoreRuntime.js +1 -2
  12. package/dist/dataStoreRuntime.js.map +1 -1
  13. package/dist/localChannelContext.d.ts +7 -16
  14. package/dist/localChannelContext.d.ts.map +1 -1
  15. package/dist/localChannelContext.js +52 -65
  16. package/dist/localChannelContext.js.map +1 -1
  17. package/dist/remoteChannelContext.d.ts +3 -8
  18. package/dist/remoteChannelContext.d.ts.map +1 -1
  19. package/dist/remoteChannelContext.js +22 -89
  20. package/dist/remoteChannelContext.js.map +1 -1
  21. package/lib/channelContext.d.ts +12 -5
  22. package/lib/channelContext.d.ts.map +1 -1
  23. package/lib/channelContext.js +70 -3
  24. package/lib/channelContext.js.map +1 -1
  25. package/lib/channelDeltaConnection.d.ts +4 -5
  26. package/lib/channelDeltaConnection.d.ts.map +1 -1
  27. package/lib/channelDeltaConnection.js +3 -4
  28. package/lib/channelDeltaConnection.js.map +1 -1
  29. package/lib/dataStoreRuntime.d.ts.map +1 -1
  30. package/lib/dataStoreRuntime.js +1 -2
  31. package/lib/dataStoreRuntime.js.map +1 -1
  32. package/lib/localChannelContext.d.ts +7 -16
  33. package/lib/localChannelContext.d.ts.map +1 -1
  34. package/lib/localChannelContext.js +54 -67
  35. package/lib/localChannelContext.js.map +1 -1
  36. package/lib/remoteChannelContext.d.ts +3 -8
  37. package/lib/remoteChannelContext.d.ts.map +1 -1
  38. package/lib/remoteChannelContext.js +25 -92
  39. package/lib/remoteChannelContext.js.map +1 -1
  40. package/package.json +14 -15
  41. package/src/channelContext.ts +105 -6
  42. package/src/channelDeltaConnection.ts +4 -5
  43. package/src/dataStoreRuntime.ts +4 -8
  44. package/src/localChannelContext.ts +105 -140
  45. package/src/remoteChannelContext.ts +58 -125
  46. package/dist/packageVersion.d.ts +0 -9
  47. package/dist/packageVersion.d.ts.map +0 -1
  48. package/dist/packageVersion.js +0 -12
  49. package/dist/packageVersion.js.map +0 -1
  50. package/lib/packageVersion.d.ts +0 -9
  51. package/lib/packageVersion.d.ts.map +0 -1
  52. package/lib/packageVersion.js +0 -9
  53. package/lib/packageVersion.js.map +0 -1
  54. package/src/packageVersion.ts +0 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/datastore",
3
- "version": "2.0.0-internal.4.2.1",
3
+ "version": "2.0.0-internal.4.4.0",
4
4
  "description": "Fluid data store implementation",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -37,18 +37,18 @@
37
37
  "dependencies": {
38
38
  "@fluidframework/common-definitions": "^0.20.1",
39
39
  "@fluidframework/common-utils": "^1.1.1",
40
- "@fluidframework/container-definitions": ">=2.0.0-internal.4.2.1 <2.0.0-internal.4.3.0",
41
- "@fluidframework/container-utils": ">=2.0.0-internal.4.2.1 <2.0.0-internal.4.3.0",
42
- "@fluidframework/core-interfaces": ">=2.0.0-internal.4.2.1 <2.0.0-internal.4.3.0",
43
- "@fluidframework/datastore-definitions": ">=2.0.0-internal.4.2.1 <2.0.0-internal.4.3.0",
44
- "@fluidframework/driver-definitions": ">=2.0.0-internal.4.2.1 <2.0.0-internal.4.3.0",
45
- "@fluidframework/driver-utils": ">=2.0.0-internal.4.2.1 <2.0.0-internal.4.3.0",
46
- "@fluidframework/garbage-collector": ">=2.0.0-internal.4.2.1 <2.0.0-internal.4.3.0",
40
+ "@fluidframework/container-definitions": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
41
+ "@fluidframework/container-utils": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
42
+ "@fluidframework/core-interfaces": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
43
+ "@fluidframework/datastore-definitions": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
44
+ "@fluidframework/driver-definitions": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
45
+ "@fluidframework/driver-utils": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
46
+ "@fluidframework/garbage-collector": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
47
47
  "@fluidframework/protocol-base": "^0.1039.1000",
48
48
  "@fluidframework/protocol-definitions": "^1.1.0",
49
- "@fluidframework/runtime-definitions": ">=2.0.0-internal.4.2.1 <2.0.0-internal.4.3.0",
50
- "@fluidframework/runtime-utils": ">=2.0.0-internal.4.2.1 <2.0.0-internal.4.3.0",
51
- "@fluidframework/telemetry-utils": ">=2.0.0-internal.4.2.1 <2.0.0-internal.4.3.0",
49
+ "@fluidframework/runtime-definitions": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
50
+ "@fluidframework/runtime-utils": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
51
+ "@fluidframework/telemetry-utils": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
52
52
  "lodash": "^4.17.21",
53
53
  "uuid": "^8.3.1"
54
54
  },
@@ -58,8 +58,8 @@
58
58
  "@fluidframework/build-tools": "^0.17.0",
59
59
  "@fluidframework/datastore-previous": "npm:@fluidframework/datastore@2.0.0-internal.4.1.0",
60
60
  "@fluidframework/eslint-config-fluid": "^2.0.0",
61
- "@fluidframework/mocha-test-setup": ">=2.0.0-internal.4.2.1 <2.0.0-internal.4.3.0",
62
- "@fluidframework/test-runtime-utils": ">=2.0.0-internal.4.2.1 <2.0.0-internal.4.3.0",
61
+ "@fluidframework/mocha-test-setup": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
62
+ "@fluidframework/test-runtime-utils": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
63
63
  "@microsoft/api-extractor": "^7.34.4",
64
64
  "@types/mocha": "^9.1.1",
65
65
  "@types/node": "^14.18.38",
@@ -85,14 +85,13 @@
85
85
  }
86
86
  },
87
87
  "scripts": {
88
- "build": "npm run build:genver && concurrently npm:build:compile npm:lint && npm run build:docs",
88
+ "build": "concurrently npm:build:compile npm:lint && npm run build:docs",
89
89
  "build:commonjs": "npm run tsc && npm run typetests:gen && npm run build:test",
90
90
  "build:compile": "concurrently npm:build:commonjs npm:build:esnext",
91
91
  "build:docs": "api-extractor run --local --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/doc-models/* ../../../_api-extractor-temp/",
92
92
  "build:esnext": "tsc --project ./tsconfig.esnext.json",
93
93
  "build:full": "npm run build",
94
94
  "build:full:compile": "npm run build:compile",
95
- "build:genver": "gen-version",
96
95
  "build:test": "tsc --project ./src/test/tsconfig.json",
97
96
  "ci:build:docs": "api-extractor run --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/* ../../../_api-extractor-temp/",
98
97
  "clean": "rimraf dist lib *.tsbuildinfo *.build.log",
@@ -5,7 +5,12 @@
5
5
 
6
6
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
7
7
  import { IFluidHandle } from "@fluidframework/core-interfaces";
8
- import { IChannel } from "@fluidframework/datastore-definitions";
8
+ import {
9
+ IChannel,
10
+ IChannelAttributes,
11
+ IChannelFactory,
12
+ IFluidDataStoreRuntime,
13
+ } from "@fluidframework/datastore-definitions";
9
14
  import { IDocumentStorageService } from "@fluidframework/driver-definitions";
10
15
  import { ISequencedDocumentMessage, ISnapshotTree } from "@fluidframework/protocol-definitions";
11
16
  import {
@@ -14,10 +19,15 @@ import {
14
19
  ISummarizeResult,
15
20
  ISummaryTreeWithStats,
16
21
  ITelemetryContext,
22
+ IFluidDataStoreContext,
17
23
  } from "@fluidframework/runtime-definitions";
18
24
  import { addBlobToSummary } from "@fluidframework/runtime-utils";
19
- import { ChannelDeltaConnection } from "./channelDeltaConnection";
25
+ import { DataCorruptionError } from "@fluidframework/container-utils";
26
+ import { readAndParse } from "@fluidframework/driver-utils";
27
+ import { TelemetryDataTag } from "@fluidframework/telemetry-utils";
20
28
  import { ChannelStorageService } from "./channelStorageService";
29
+ import { ChannelDeltaConnection } from "./channelDeltaConnection";
30
+ import { ISharedObjectRegistry } from "./dataStoreRuntime";
21
31
 
22
32
  export const attributesBlobKey = ".attributes";
23
33
 
@@ -56,8 +66,12 @@ export interface IChannelContext {
56
66
  updateUsedRoutes(usedRoutes: string[]): void;
57
67
  }
58
68
 
59
- export function createServiceEndpoints(
60
- id: string,
69
+ export interface ChannelServiceEndpoints {
70
+ deltaConnection: ChannelDeltaConnection;
71
+ objectStorage: ChannelStorageService;
72
+ }
73
+
74
+ export function createChannelServiceEndpoints(
61
75
  connected: boolean,
62
76
  submitFn: (content: any, localOpMetadata: unknown) => void,
63
77
  dirtyFn: () => void,
@@ -66,9 +80,8 @@ export function createServiceEndpoints(
66
80
  logger: ITelemetryLogger,
67
81
  tree?: ISnapshotTree,
68
82
  extraBlobs?: Map<string, ArrayBufferLike>,
69
- ) {
83
+ ): ChannelServiceEndpoints {
70
84
  const deltaConnection = new ChannelDeltaConnection(
71
- id,
72
85
  connected,
73
86
  (message, localOpMetadata) => submitFn(message, localOpMetadata),
74
87
  dirtyFn,
@@ -113,3 +126,89 @@ export async function summarizeChannelAsync(
113
126
  addBlobToSummary(summarizeResult, attributesBlobKey, JSON.stringify(channel.attributes));
114
127
  return summarizeResult;
115
128
  }
129
+
130
+ export async function loadChannelFactoryAndAttributes(
131
+ dataStoreContext: IFluidDataStoreContext,
132
+ services: ChannelServiceEndpoints,
133
+ channelId: string,
134
+ registry: ISharedObjectRegistry,
135
+ attachMessageType?: string,
136
+ ): Promise<{ factory: IChannelFactory; attributes: IChannelAttributes }> {
137
+ let attributes: IChannelAttributes | undefined;
138
+ if (await services.objectStorage.contains(attributesBlobKey)) {
139
+ attributes = await readAndParse<IChannelAttributes | undefined>(
140
+ services.objectStorage,
141
+ attributesBlobKey,
142
+ );
143
+ }
144
+
145
+ // This is a backward compatibility case where the attach message doesn't include attributes. They must
146
+ // include attach message type.
147
+ // Since old attach messages will not have attributes, we need to keep this as long as we support old attach
148
+ // messages.
149
+ const channelFactoryType = attributes ? attributes.type : attachMessageType;
150
+ if (channelFactoryType === undefined) {
151
+ throw new DataCorruptionError("channelTypeNotAvailable", {
152
+ channelId: {
153
+ value: channelId,
154
+ tag: TelemetryDataTag.CodeArtifact,
155
+ },
156
+ dataStoreId: {
157
+ value: dataStoreContext.id,
158
+ tag: TelemetryDataTag.CodeArtifact,
159
+ },
160
+ dataStorePackagePath: dataStoreContext.packagePath.join("/"),
161
+ channelFactoryType: attachMessageType,
162
+ });
163
+ }
164
+ const factory = registry.get(channelFactoryType);
165
+ if (factory === undefined) {
166
+ // TODO: dataStoreId may require a different tag from PackageData #7488
167
+ throw new DataCorruptionError("channelFactoryNotRegisteredForGivenType", {
168
+ channelId: {
169
+ value: channelId,
170
+ tag: TelemetryDataTag.CodeArtifact,
171
+ },
172
+ dataStoreId: {
173
+ value: dataStoreContext.id,
174
+ tag: TelemetryDataTag.CodeArtifact,
175
+ },
176
+ dataStorePackagePath: dataStoreContext.packagePath.join("/"),
177
+ channelFactoryType,
178
+ });
179
+ }
180
+ // This is a backward compatibility case where the attach message doesn't include attributes. Get the attributes
181
+ // from the factory.
182
+ attributes = attributes ?? factory.attributes;
183
+ return { factory, attributes };
184
+ }
185
+
186
+ export async function loadChannel(
187
+ dataStoreRuntime: IFluidDataStoreRuntime,
188
+ attributes: IChannelAttributes,
189
+ factory: IChannelFactory,
190
+ services: ChannelServiceEndpoints,
191
+ logger: ITelemetryLogger,
192
+ channelId: string,
193
+ ): Promise<IChannel> {
194
+ // Compare snapshot version to collaborative object version
195
+ if (
196
+ attributes.snapshotFormatVersion !== undefined &&
197
+ attributes.snapshotFormatVersion !== factory.attributes.snapshotFormatVersion
198
+ ) {
199
+ logger.sendTelemetryEvent({
200
+ eventName: "ChannelAttributesVersionMismatch",
201
+ channelType: { value: attributes.type, tag: TelemetryDataTag.CodeArtifact },
202
+ channelSnapshotVersion: {
203
+ value: `${attributes.snapshotFormatVersion}@${attributes.packageVersion}`,
204
+ tag: TelemetryDataTag.CodeArtifact,
205
+ },
206
+ channelCodeVersion: {
207
+ value: `${factory.attributes.snapshotFormatVersion}@${factory.attributes.packageVersion}`,
208
+ tag: TelemetryDataTag.CodeArtifact,
209
+ },
210
+ });
211
+ }
212
+
213
+ return factory.load(dataStoreRuntime, channelId, services, attributes);
214
+ }
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { assert } from "@fluidframework/common-utils";
7
- import { IDocumentMessage, ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
7
+ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
8
8
  import { IDeltaConnection, IDeltaHandler } from "@fluidframework/datastore-definitions";
9
9
  import { DataProcessingError } from "@fluidframework/container-utils";
10
10
  import { IFluidHandle } from "@fluidframework/core-interfaces";
@@ -21,9 +21,8 @@ export class ChannelDeltaConnection implements IDeltaConnection {
21
21
  }
22
22
 
23
23
  constructor(
24
- public objectId: string,
25
24
  private _connected: boolean,
26
- public readonly submit: (message: IDocumentMessage, localOpMetadata: unknown) => void,
25
+ public readonly submit: (content: any, localOpMetadata: unknown) => void,
27
26
  public readonly dirty: () => void,
28
27
  public readonly addedGCOutboundReference: (
29
28
  srcHandle: IFluidHandle,
@@ -65,7 +64,7 @@ export class ChannelDeltaConnection implements IDeltaConnection {
65
64
  this.handler.rollback(content, localOpMetadata);
66
65
  }
67
66
 
68
- public applyStashedOp(message: ISequencedDocumentMessage): unknown {
69
- return this.handler.applyStashedOp(message);
67
+ public applyStashedOp(content: any): unknown {
68
+ return this.handler.applyStashedOp(content);
70
69
  }
71
70
  }
@@ -278,7 +278,7 @@ export class FluidDataStoreRuntime
278
278
  return;
279
279
  }
280
280
 
281
- let channelContext: IChannelContext;
281
+ let channelContext: RemoteChannelContext | RehydratedLocalChannelContext;
282
282
  // If already exists on storage, then create a remote channel. However, if it is case of rehydrating a
283
283
  // container from snapshot where we load detached container from a snapshot, isLocalDataStore would be
284
284
  // true. In this case create a RehydratedLocalChannelContext.
@@ -302,12 +302,9 @@ export class FluidDataStoreRuntime
302
302
  // the channel visible. So do it now. Otherwise, add it to local channel context queue, so
303
303
  // that it can be make it visible later with the data store.
304
304
  if (dataStoreContext.attachState !== AttachState.Detached) {
305
- (channelContext as LocalChannelContextBase).makeVisible();
305
+ channelContext.makeVisible();
306
306
  } else {
307
- this.localChannelContextQueue.set(
308
- path,
309
- channelContext as LocalChannelContextBase,
310
- );
307
+ this.localChannelContextQueue.set(path, channelContext);
311
308
  }
312
309
  } else {
313
310
  channelContext = new RemoteChannelContext(
@@ -478,7 +475,6 @@ export class FluidDataStoreRuntime
478
475
  // Channels (DDS) should not be created in summarizer client.
479
476
  this.identifyLocalChangeInSummarizer("DDSCreatedInSummarizer", id, type);
480
477
 
481
- assert(!!context.channel, 0x17a /* "Channel should be loaded when created!!" */);
482
478
  return context.channel;
483
479
  }
484
480
 
@@ -490,7 +486,7 @@ export class FluidDataStoreRuntime
490
486
  public bindChannel(channel: IChannel): void {
491
487
  assert(
492
488
  this.notBoundedChannelContextSet.has(channel.id),
493
- 0x17b /* "Channel to be binded should be in not bounded set" */,
489
+ 0x17b /* "Channel to be bound should be in not bounded set" */,
494
490
  );
495
491
  this.notBoundedChannelContextSet.delete(channel.id);
496
492
  // If our data store is attached, then attach the channel.
@@ -8,65 +8,58 @@ import cloneDeep from "lodash/cloneDeep";
8
8
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
9
9
  import { IDocumentStorageService } from "@fluidframework/driver-definitions";
10
10
  import { ISequencedDocumentMessage, ISnapshotTree } from "@fluidframework/protocol-definitions";
11
- import {
12
- IChannel,
13
- IFluidDataStoreRuntime,
14
- IChannelFactory,
15
- IChannelAttributes,
16
- } from "@fluidframework/datastore-definitions";
11
+ import { IChannel, IFluidDataStoreRuntime } from "@fluidframework/datastore-definitions";
17
12
  import {
18
13
  IFluidDataStoreContext,
19
14
  IGarbageCollectionData,
20
15
  ISummarizeResult,
21
16
  ITelemetryContext,
22
17
  } from "@fluidframework/runtime-definitions";
23
- import { readAndParse } from "@fluidframework/driver-utils";
24
18
  import { DataProcessingError } from "@fluidframework/container-utils";
25
- import { assert, Lazy } from "@fluidframework/common-utils";
19
+ import { assert, Lazy, LazyPromise } from "@fluidframework/common-utils";
26
20
  import { IFluidHandle } from "@fluidframework/core-interfaces";
27
21
  import {
28
- createServiceEndpoints,
22
+ ChannelServiceEndpoints,
23
+ createChannelServiceEndpoints,
29
24
  IChannelContext,
25
+ loadChannel,
26
+ loadChannelFactoryAndAttributes,
30
27
  summarizeChannel,
31
28
  summarizeChannelAsync,
32
29
  } from "./channelContext";
33
- import { ChannelDeltaConnection } from "./channelDeltaConnection";
34
30
  import { ISharedObjectRegistry } from "./dataStoreRuntime";
35
- import { ChannelStorageService } from "./channelStorageService";
36
31
 
37
32
  /**
38
33
  * Channel context for a locally created channel
39
34
  */
40
35
  export abstract class LocalChannelContextBase implements IChannelContext {
41
- public channel: IChannel | undefined;
42
36
  private globallyVisible = false;
43
37
  protected readonly pending: ISequencedDocumentMessage[] = [];
44
- protected factory: IChannelFactory | undefined;
45
38
  constructor(
46
39
  protected readonly id: string,
47
- protected readonly registry: ISharedObjectRegistry,
48
40
  protected readonly runtime: IFluidDataStoreRuntime,
49
- private readonly servicesGetter: () => Lazy<{
50
- readonly deltaConnection: ChannelDeltaConnection;
51
- readonly objectStorage: ChannelStorageService;
52
- }>,
41
+ protected readonly services: Lazy<ChannelServiceEndpoints>,
42
+ private readonly channelP: Promise<IChannel>,
43
+ private _channel?: IChannel,
53
44
  ) {
54
45
  assert(!this.id.includes("/"), 0x30f /* Channel context ID cannot contain slashes */);
55
46
  }
56
47
 
57
48
  public async getChannel(): Promise<IChannel> {
58
- assert(this.channel !== undefined, 0x207 /* "Channel should be defined" */);
59
- return this.channel;
49
+ if (this._channel === undefined) {
50
+ return this.channelP.then((c) => (this._channel = c));
51
+ }
52
+ return this.channelP;
60
53
  }
61
54
 
62
55
  public get isLoaded(): boolean {
63
- return this.channel !== undefined;
56
+ return this._channel !== undefined;
64
57
  }
65
58
 
66
59
  public setConnectionState(connected: boolean, clientId?: string) {
67
60
  // Connection events are ignored if the data store is not yet globallyVisible or loaded
68
61
  if (this.globallyVisible && this.isLoaded) {
69
- this.servicesGetter().value.deltaConnection.setConnectionState(connected);
62
+ this.services.value.deltaConnection.setConnectionState(connected);
70
63
  }
71
64
  }
72
65
 
@@ -84,7 +77,7 @@ export abstract class LocalChannelContextBase implements IChannelContext {
84
77
  // delay loading. So after the container is attached and some other client joins which start generating
85
78
  // ops for this channel. So not loaded local channel can still receive ops and we store them to process later.
86
79
  if (this.isLoaded) {
87
- this.servicesGetter().value.deltaConnection.process(message, local, localOpMetadata);
80
+ this.services.value.deltaConnection.process(message, local, localOpMetadata);
88
81
  } else {
89
82
  assert(
90
83
  local === false,
@@ -100,7 +93,7 @@ export abstract class LocalChannelContextBase implements IChannelContext {
100
93
  this.globallyVisible,
101
94
  0x2d4 /* "Local channel must be globally visible when resubmitting op" */,
102
95
  );
103
- this.servicesGetter().value.deltaConnection.reSubmit(content, localOpMetadata);
96
+ this.services.value.deltaConnection.reSubmit(content, localOpMetadata);
104
97
  }
105
98
  public rollback(content: any, localOpMetadata: unknown) {
106
99
  assert(this.isLoaded, 0x2ee /* "Channel should be loaded to rollback ops" */);
@@ -108,7 +101,7 @@ export abstract class LocalChannelContextBase implements IChannelContext {
108
101
  this.globallyVisible,
109
102
  0x2ef /* "Local channel must be globally visible when rolling back op" */,
110
103
  );
111
- this.servicesGetter().value.deltaConnection.rollback(content, localOpMetadata);
104
+ this.services.value.deltaConnection.rollback(content, localOpMetadata);
112
105
  }
113
106
 
114
107
  public applyStashedOp() {
@@ -126,20 +119,17 @@ export abstract class LocalChannelContextBase implements IChannelContext {
126
119
  trackState: boolean = false,
127
120
  telemetryContext?: ITelemetryContext,
128
121
  ): Promise<ISummarizeResult> {
129
- assert(
130
- this.isLoaded && this.channel !== undefined,
131
- 0x18c /* "Channel should be loaded to summarize" */,
132
- );
133
- return summarizeChannelAsync(this.channel, fullTree, trackState, telemetryContext);
122
+ const channel = await this.getChannel();
123
+ return summarizeChannelAsync(channel, fullTree, trackState, telemetryContext);
134
124
  }
135
125
 
136
126
  public getAttachSummary(telemetryContext?: ITelemetryContext): ISummarizeResult {
137
127
  assert(
138
- this.isLoaded && this.channel !== undefined,
128
+ this._channel !== undefined,
139
129
  0x18d /* "Channel should be loaded to take snapshot" */,
140
130
  );
141
131
  return summarizeChannel(
142
- this.channel,
132
+ this._channel,
143
133
  true /* fullTree */,
144
134
  false /* trackState */,
145
135
  telemetryContext,
@@ -152,8 +142,8 @@ export abstract class LocalChannelContextBase implements IChannelContext {
152
142
  }
153
143
 
154
144
  if (this.isLoaded) {
155
- assert(!!this.channel, 0x192 /* "Channel should be there if loaded!!" */);
156
- this.channel.connect(this.servicesGetter().value);
145
+ assert(!!this._channel, 0x192 /* "Channel should be there if loaded!!" */);
146
+ this._channel.connect(this.services.value);
157
147
  }
158
148
  this.globallyVisible = true;
159
149
  }
@@ -165,11 +155,8 @@ export abstract class LocalChannelContextBase implements IChannelContext {
165
155
  * @param fullGC - true to bypass optimizations and force full generation of GC data.
166
156
  */
167
157
  public async getGCData(fullGC: boolean = false): Promise<IGarbageCollectionData> {
168
- assert(
169
- this.isLoaded && this.channel !== undefined,
170
- 0x193 /* "Channel should be loaded to run GC" */,
171
- );
172
- return this.channel.getGCData(fullGC);
158
+ const channel = await this.getChannel();
159
+ return channel.getGCData(fullGC);
173
160
  }
174
161
 
175
162
  public updateUsedRoutes(usedRoutes: string[]) {
@@ -182,13 +169,7 @@ export abstract class LocalChannelContextBase implements IChannelContext {
182
169
  }
183
170
 
184
171
  export class RehydratedLocalChannelContext extends LocalChannelContextBase {
185
- private readonly services: Lazy<{
186
- readonly deltaConnection: ChannelDeltaConnection;
187
- readonly objectStorage: ChannelStorageService;
188
- }>;
189
-
190
172
  private readonly dirtyFn: () => void;
191
-
192
173
  constructor(
193
174
  id: string,
194
175
  registry: ISharedObjectRegistry,
@@ -201,88 +182,69 @@ export class RehydratedLocalChannelContext extends LocalChannelContextBase {
201
182
  addedGCOutboundReferenceFn: (srcHandle: IFluidHandle, outboundHandle: IFluidHandle) => void,
202
183
  private readonly snapshotTree: ISnapshotTree,
203
184
  ) {
204
- super(id, registry, runtime, () => this.services);
205
- const blobMap: Map<string, ArrayBufferLike> = new Map<string, ArrayBufferLike>();
206
- const clonedSnapshotTree = cloneDeep(this.snapshotTree);
207
- // 0.47 back-compat Need to sanitize if snapshotTree.blobs still contains blob contents too.
208
- // This is for older snapshot which is generated by loader <=0.47 version which still contains
209
- // the contents within blobs. After a couple of revisions we can remove it.
210
- if (this.isSnapshotInOldFormatAndCollectBlobs(clonedSnapshotTree, blobMap)) {
211
- this.sanitizeSnapshot(clonedSnapshotTree);
212
- }
185
+ super(
186
+ id,
187
+ runtime,
188
+ new Lazy(() => {
189
+ const blobMap: Map<string, ArrayBufferLike> = new Map<string, ArrayBufferLike>();
190
+ const clonedSnapshotTree = cloneDeep(this.snapshotTree);
191
+ // 0.47 back-compat Need to sanitize if snapshotTree.blobs still contains blob contents too.
192
+ // This is for older snapshot which is generated by loader <=0.47 version which still contains
193
+ // the contents within blobs. After a couple of revisions we can remove it.
194
+ if (this.isSnapshotInOldFormatAndCollectBlobs(clonedSnapshotTree, blobMap)) {
195
+ this.sanitizeSnapshot(clonedSnapshotTree);
196
+ }
197
+ return createChannelServiceEndpoints(
198
+ dataStoreContext.connected,
199
+ submitFn,
200
+ this.dirtyFn,
201
+ addedGCOutboundReferenceFn,
202
+ storageService,
203
+ logger,
204
+ clonedSnapshotTree,
205
+ blobMap,
206
+ );
207
+ }),
208
+ new LazyPromise<IChannel>(async () => {
209
+ try {
210
+ const { attributes, factory } = await loadChannelFactoryAndAttributes(
211
+ dataStoreContext,
212
+ this.services.value,
213
+ this.id,
214
+ registry,
215
+ );
216
+ const channel = await loadChannel(
217
+ runtime,
218
+ attributes,
219
+ factory,
220
+ this.services.value,
221
+ logger,
222
+ this.id,
223
+ );
224
+ // Send all pending messages to the channel
225
+ for (const message of this.pending) {
226
+ this.services.value.deltaConnection.process(
227
+ message,
228
+ false,
229
+ undefined /* localOpMetadata */,
230
+ );
231
+ }
232
+ return channel;
233
+ } catch (err) {
234
+ throw DataProcessingError.wrapIfUnrecognized(
235
+ err,
236
+ "rehydratedLocalChannelContextFailedToLoadChannel",
237
+ undefined,
238
+ );
239
+ }
240
+ }),
241
+ );
213
242
 
214
- this.services = new Lazy(() => {
215
- return createServiceEndpoints(
216
- this.id,
217
- dataStoreContext.connected,
218
- submitFn,
219
- this.dirtyFn,
220
- addedGCOutboundReferenceFn,
221
- storageService,
222
- logger,
223
- clonedSnapshotTree,
224
- blobMap,
225
- );
226
- });
227
243
  this.dirtyFn = () => {
228
244
  dirtyFn(id);
229
245
  };
230
246
  }
231
247
 
232
- public async getChannel(): Promise<IChannel> {
233
- if (this.channel === undefined) {
234
- this.channel = await this.loadChannel().catch((err) => {
235
- throw DataProcessingError.wrapIfUnrecognized(
236
- err,
237
- "rehydratedLocalChannelContextFailedToLoadChannel",
238
- undefined,
239
- );
240
- });
241
- }
242
- return this.channel;
243
- }
244
-
245
- private async loadChannel(): Promise<IChannel> {
246
- assert(!this.isLoaded, 0x18e /* "Channel must not already be loaded when loading" */);
247
- assert(
248
- await this.services.value.objectStorage.contains(".attributes"),
249
- 0x190 /* ".attributes blob should be present" */,
250
- );
251
- const attributes = await readAndParse<IChannelAttributes>(
252
- this.services.value.objectStorage,
253
- ".attributes",
254
- );
255
-
256
- assert(
257
- this.factory === undefined,
258
- 0x208 /* "Factory should be undefined before loading" */,
259
- );
260
- this.factory = this.registry.get(attributes.type);
261
- if (this.factory === undefined) {
262
- throw new Error(`Channel Factory ${attributes.type} not registered`);
263
- }
264
- // Services will be assigned during this load.
265
- const channel = await this.factory.load(
266
- this.runtime,
267
- this.id,
268
- this.services.value,
269
- attributes,
270
- );
271
-
272
- // Commit changes.
273
- this.channel = channel;
274
-
275
- // Send all pending messages to the channel
276
- for (const message of this.pending) {
277
- this.services.value.deltaConnection.process(
278
- message,
279
- false,
280
- undefined /* localOpMetadata */,
281
- );
282
- }
283
- return this.channel;
284
- }
285
-
286
248
  private isSnapshotInOldFormatAndCollectBlobs(
287
249
  snapshotTree: ISnapshotTree,
288
250
  blobMap: Map<string, ArrayBufferLike>,
@@ -318,11 +280,8 @@ export class RehydratedLocalChannelContext extends LocalChannelContextBase {
318
280
  }
319
281
 
320
282
  export class LocalChannelContext extends LocalChannelContextBase {
321
- private readonly services: Lazy<{
322
- readonly deltaConnection: ChannelDeltaConnection;
323
- readonly objectStorage: ChannelStorageService;
324
- }>;
325
283
  private readonly dirtyFn: () => void;
284
+ public readonly channel: IChannel;
326
285
  constructor(
327
286
  id: string,
328
287
  registry: ISharedObjectRegistry,
@@ -335,24 +294,30 @@ export class LocalChannelContext extends LocalChannelContextBase {
335
294
  dirtyFn: (address: string) => void,
336
295
  addedGCOutboundReferenceFn: (srcHandle: IFluidHandle, outboundHandle: IFluidHandle) => void,
337
296
  ) {
338
- super(id, registry, runtime, () => this.services);
339
297
  assert(type !== undefined, 0x209 /* "Factory Type should be defined" */);
340
- this.factory = registry.get(type);
341
- if (this.factory === undefined) {
298
+ const factory = registry.get(type);
299
+ if (factory === undefined) {
342
300
  throw new Error(`Channel Factory ${type} not registered`);
343
301
  }
344
- this.channel = this.factory.create(runtime, id);
345
- this.services = new Lazy(() => {
346
- return createServiceEndpoints(
347
- this.id,
348
- dataStoreContext.connected,
349
- submitFn,
350
- this.dirtyFn,
351
- addedGCOutboundReferenceFn,
352
- storageService,
353
- logger,
354
- );
355
- });
302
+ const channel = factory.create(runtime, id);
303
+ super(
304
+ id,
305
+ runtime,
306
+ new Lazy(() => {
307
+ return createChannelServiceEndpoints(
308
+ dataStoreContext.connected,
309
+ submitFn,
310
+ this.dirtyFn,
311
+ addedGCOutboundReferenceFn,
312
+ storageService,
313
+ logger,
314
+ );
315
+ }),
316
+ Promise.resolve(channel),
317
+ channel,
318
+ );
319
+ this.channel = channel;
320
+
356
321
  this.dirtyFn = () => {
357
322
  dirtyFn(id);
358
323
  };