@fluidframework/container-loader 2.0.0-rc.3.0.2 → 2.0.0-rc.4.0.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 (188) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/api-report/container-loader.api.md +5 -1
  3. package/dist/attachment.d.ts +3 -2
  4. package/dist/attachment.d.ts.map +1 -1
  5. package/dist/attachment.js +5 -5
  6. package/dist/attachment.js.map +1 -1
  7. package/dist/audience.d.ts +6 -4
  8. package/dist/audience.d.ts.map +1 -1
  9. package/dist/audience.js +18 -3
  10. package/dist/audience.js.map +1 -1
  11. package/dist/catchUpMonitor.d.ts +1 -1
  12. package/dist/catchUpMonitor.d.ts.map +1 -1
  13. package/dist/catchUpMonitor.js.map +1 -1
  14. package/dist/connectionManager.d.ts +7 -3
  15. package/dist/connectionManager.d.ts.map +1 -1
  16. package/dist/connectionManager.js +57 -38
  17. package/dist/connectionManager.js.map +1 -1
  18. package/dist/connectionStateHandler.d.ts +31 -10
  19. package/dist/connectionStateHandler.d.ts.map +1 -1
  20. package/dist/connectionStateHandler.js +49 -36
  21. package/dist/connectionStateHandler.js.map +1 -1
  22. package/dist/container.d.ts +22 -13
  23. package/dist/container.d.ts.map +1 -1
  24. package/dist/container.js +145 -117
  25. package/dist/container.js.map +1 -1
  26. package/dist/containerContext.d.ts +3 -3
  27. package/dist/containerContext.d.ts.map +1 -1
  28. package/dist/containerContext.js.map +1 -1
  29. package/dist/containerStorageAdapter.d.ts +12 -3
  30. package/dist/containerStorageAdapter.d.ts.map +1 -1
  31. package/dist/containerStorageAdapter.js +42 -4
  32. package/dist/containerStorageAdapter.js.map +1 -1
  33. package/dist/contracts.d.ts +2 -2
  34. package/dist/contracts.d.ts.map +1 -1
  35. package/dist/contracts.js.map +1 -1
  36. package/dist/debugLogger.d.ts +1 -2
  37. package/dist/debugLogger.d.ts.map +1 -1
  38. package/dist/debugLogger.js.map +1 -1
  39. package/dist/deltaManager.d.ts +5 -6
  40. package/dist/deltaManager.d.ts.map +1 -1
  41. package/dist/deltaManager.js +29 -24
  42. package/dist/deltaManager.js.map +1 -1
  43. package/dist/deltaQueue.d.ts +1 -1
  44. package/dist/deltaQueue.d.ts.map +1 -1
  45. package/dist/deltaQueue.js.map +1 -1
  46. package/dist/error.d.ts +1 -2
  47. package/dist/error.d.ts.map +1 -1
  48. package/dist/error.js.map +1 -1
  49. package/dist/index.d.ts +1 -0
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +3 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/legacy.d.ts +2 -2
  54. package/dist/loadPaused.d.ts +35 -0
  55. package/dist/loadPaused.d.ts.map +1 -0
  56. package/dist/loadPaused.js +115 -0
  57. package/dist/loadPaused.js.map +1 -0
  58. package/dist/loader.d.ts +1 -1
  59. package/dist/loader.d.ts.map +1 -1
  60. package/dist/loader.js +1 -14
  61. package/dist/loader.js.map +1 -1
  62. package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -1
  63. package/dist/location-redirection-utilities/resolveWithLocationRedirection.js +4 -4
  64. package/dist/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -1
  65. package/dist/packageVersion.d.ts +1 -1
  66. package/dist/packageVersion.js +1 -1
  67. package/dist/packageVersion.js.map +1 -1
  68. package/dist/protocol.d.ts.map +1 -1
  69. package/dist/protocol.js +3 -0
  70. package/dist/protocol.js.map +1 -1
  71. package/dist/public.d.ts +1 -1
  72. package/dist/retriableDocumentStorageService.d.ts +1 -1
  73. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  74. package/dist/retriableDocumentStorageService.js.map +1 -1
  75. package/dist/serializedStateManager.d.ts +89 -9
  76. package/dist/serializedStateManager.d.ts.map +1 -1
  77. package/dist/serializedStateManager.js +150 -34
  78. package/dist/serializedStateManager.js.map +1 -1
  79. package/dist/utils.d.ts +11 -1
  80. package/dist/utils.d.ts.map +1 -1
  81. package/dist/utils.js +29 -14
  82. package/dist/utils.js.map +1 -1
  83. package/lib/attachment.d.ts +3 -2
  84. package/lib/attachment.d.ts.map +1 -1
  85. package/lib/attachment.js +5 -5
  86. package/lib/attachment.js.map +1 -1
  87. package/lib/audience.d.ts +6 -4
  88. package/lib/audience.d.ts.map +1 -1
  89. package/lib/audience.js +19 -4
  90. package/lib/audience.js.map +1 -1
  91. package/lib/catchUpMonitor.d.ts +1 -1
  92. package/lib/catchUpMonitor.d.ts.map +1 -1
  93. package/lib/catchUpMonitor.js.map +1 -1
  94. package/lib/connectionManager.d.ts +7 -3
  95. package/lib/connectionManager.d.ts.map +1 -1
  96. package/lib/connectionManager.js +36 -17
  97. package/lib/connectionManager.js.map +1 -1
  98. package/lib/connectionStateHandler.d.ts +31 -10
  99. package/lib/connectionStateHandler.d.ts.map +1 -1
  100. package/lib/connectionStateHandler.js +49 -36
  101. package/lib/connectionStateHandler.js.map +1 -1
  102. package/lib/container.d.ts +22 -13
  103. package/lib/container.d.ts.map +1 -1
  104. package/lib/container.js +146 -118
  105. package/lib/container.js.map +1 -1
  106. package/lib/containerContext.d.ts +3 -3
  107. package/lib/containerContext.d.ts.map +1 -1
  108. package/lib/containerContext.js.map +1 -1
  109. package/lib/containerStorageAdapter.d.ts +12 -3
  110. package/lib/containerStorageAdapter.d.ts.map +1 -1
  111. package/lib/containerStorageAdapter.js +42 -4
  112. package/lib/containerStorageAdapter.js.map +1 -1
  113. package/lib/contracts.d.ts +2 -2
  114. package/lib/contracts.d.ts.map +1 -1
  115. package/lib/contracts.js +1 -1
  116. package/lib/contracts.js.map +1 -1
  117. package/lib/debugLogger.d.ts +1 -2
  118. package/lib/debugLogger.d.ts.map +1 -1
  119. package/lib/debugLogger.js.map +1 -1
  120. package/lib/deltaManager.d.ts +5 -6
  121. package/lib/deltaManager.d.ts.map +1 -1
  122. package/lib/deltaManager.js +10 -5
  123. package/lib/deltaManager.js.map +1 -1
  124. package/lib/deltaQueue.d.ts +1 -1
  125. package/lib/deltaQueue.d.ts.map +1 -1
  126. package/lib/deltaQueue.js.map +1 -1
  127. package/lib/error.d.ts +1 -2
  128. package/lib/error.d.ts.map +1 -1
  129. package/lib/error.js.map +1 -1
  130. package/lib/index.d.ts +1 -0
  131. package/lib/index.d.ts.map +1 -1
  132. package/lib/index.js +1 -0
  133. package/lib/index.js.map +1 -1
  134. package/lib/legacy.d.ts +2 -2
  135. package/lib/loadPaused.d.ts +35 -0
  136. package/lib/loadPaused.d.ts.map +1 -0
  137. package/lib/loadPaused.js +111 -0
  138. package/lib/loadPaused.js.map +1 -0
  139. package/lib/loader.d.ts +1 -1
  140. package/lib/loader.d.ts.map +1 -1
  141. package/lib/loader.js +3 -16
  142. package/lib/loader.js.map +1 -1
  143. package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -1
  144. package/lib/location-redirection-utilities/resolveWithLocationRedirection.js +1 -1
  145. package/lib/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -1
  146. package/lib/packageVersion.d.ts +1 -1
  147. package/lib/packageVersion.js +1 -1
  148. package/lib/packageVersion.js.map +1 -1
  149. package/lib/protocol.d.ts.map +1 -1
  150. package/lib/protocol.js +3 -0
  151. package/lib/protocol.js.map +1 -1
  152. package/lib/public.d.ts +1 -1
  153. package/lib/retriableDocumentStorageService.d.ts +1 -1
  154. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  155. package/lib/retriableDocumentStorageService.js +1 -1
  156. package/lib/retriableDocumentStorageService.js.map +1 -1
  157. package/lib/serializedStateManager.d.ts +89 -9
  158. package/lib/serializedStateManager.d.ts.map +1 -1
  159. package/lib/serializedStateManager.js +146 -30
  160. package/lib/serializedStateManager.js.map +1 -1
  161. package/lib/tsdoc-metadata.json +1 -1
  162. package/lib/utils.d.ts +11 -1
  163. package/lib/utils.d.ts.map +1 -1
  164. package/lib/utils.js +15 -1
  165. package/lib/utils.js.map +1 -1
  166. package/package.json +24 -21
  167. package/src/attachment.ts +12 -13
  168. package/src/audience.ts +30 -9
  169. package/src/catchUpMonitor.ts +1 -1
  170. package/src/connectionManager.ts +45 -22
  171. package/src/connectionStateHandler.ts +78 -45
  172. package/src/container.ts +181 -160
  173. package/src/containerContext.ts +2 -2
  174. package/src/containerStorageAdapter.ts +61 -6
  175. package/src/contracts.ts +5 -4
  176. package/src/debugLogger.ts +1 -1
  177. package/src/deltaManager.ts +15 -8
  178. package/src/deltaQueue.ts +1 -1
  179. package/src/error.ts +1 -1
  180. package/src/index.ts +1 -0
  181. package/src/loadPaused.ts +140 -0
  182. package/src/loader.ts +6 -23
  183. package/src/location-redirection-utilities/resolveWithLocationRedirection.ts +1 -1
  184. package/src/packageVersion.ts +1 -1
  185. package/src/protocol.ts +4 -0
  186. package/src/retriableDocumentStorageService.ts +5 -2
  187. package/src/serializedStateManager.ts +215 -48
  188. package/src/utils.ts +19 -1
@@ -7,13 +7,13 @@ import {
7
7
  AttachState,
8
8
  IAudience,
9
9
  ICriticalContainerError,
10
- IDeltaManager,
11
10
  } from "@fluidframework/container-definitions";
12
11
  import {
13
12
  IBatchMessage,
14
13
  IContainerContext,
15
14
  ILoader,
16
15
  ILoaderOptions,
16
+ IDeltaManager,
17
17
  } from "@fluidframework/container-definitions/internal";
18
18
  import { type FluidObject } from "@fluidframework/core-interfaces";
19
19
  import { type ISignalEnvelope } from "@fluidframework/core-interfaces/internal";
@@ -28,7 +28,7 @@ import {
28
28
  IVersion,
29
29
  MessageType,
30
30
  } from "@fluidframework/protocol-definitions";
31
- import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
31
+ import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
32
32
 
33
33
  /**
34
34
  * {@inheritDoc @fluidframework/container-definitions#IContainerContext}
@@ -24,11 +24,16 @@ import {
24
24
  ISummaryTree,
25
25
  IVersion,
26
26
  } from "@fluidframework/protocol-definitions";
27
- import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
27
+ import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
28
28
 
29
29
  import { IDetachedBlobStorage } from "./loader.js";
30
30
  import { ProtocolTreeStorageService } from "./protocolTreeDocumentStorageService.js";
31
31
  import { RetriableDocumentStorageService } from "./retriableDocumentStorageService.js";
32
+ import type {
33
+ ISerializedStateManagerDocumentStorageService,
34
+ ISnapshotInfo,
35
+ } from "./serializedStateManager.js";
36
+ import { convertSnapshotInfoToSnapshot, getDocumentAttributes } from "./utils.js";
32
37
 
33
38
  /**
34
39
  * Stringified blobs from a summary/snapshot tree.
@@ -42,7 +47,9 @@ export interface ISerializableBlobContents {
42
47
  * This class wraps the actual storage and make sure no wrong apis are called according to
43
48
  * container attach state.
44
49
  */
45
- export class ContainerStorageAdapter implements IDocumentStorageService, IDisposable {
50
+ export class ContainerStorageAdapter
51
+ implements ISerializedStateManagerDocumentStorageService, IDocumentStorageService, IDisposable
52
+ {
46
53
  private _storageService: IDocumentStorageService & Partial<IDisposable>;
47
54
 
48
55
  private _summarizeProtocolTree: boolean | undefined;
@@ -53,11 +60,20 @@ export class ContainerStorageAdapter implements IDocumentStorageService, IDispos
53
60
  return this._summarizeProtocolTree === true;
54
61
  }
55
62
 
63
+ private _loadedGroupIdSnapshots: Record<string, ISnapshot> = {};
64
+ /**
65
+ * Any loading group id (virtualized) snapshot download from storage will be stored here.
66
+ */
67
+ public get loadedGroupIdSnapshots(): Record<string, ISnapshot> {
68
+ return this._loadedGroupIdSnapshots;
69
+ }
70
+
56
71
  /**
57
72
  * An adapter that ensures we're using detachedBlobStorage up until we connect to a real service, and then
58
73
  * after connecting to a real service augments it with retry and combined summary tree enforcement.
59
74
  * @param detachedBlobStorage - The detached blob storage to use up until we connect to a real service
60
75
  * @param logger - Telemetry logger
76
+ * @param loadingGroupIdSnapshotsFromPendingState - in offline mode, any loading group snapshots we've downloaded from the service that were stored in the pending state
61
77
  * @param addProtocolSummaryIfMissing - a callback to permit the container to inspect the summary we're about to
62
78
  * upload, and fix it up with a protocol tree if needed
63
79
  * @param forceEnableSummarizeProtocolTree - Enforce uploading a protocol summary regardless of the service's policy
@@ -69,6 +85,7 @@ export class ContainerStorageAdapter implements IDocumentStorageService, IDispos
69
85
  * ArrayBufferLikes or utf8 encoded strings, containing blobs from a snapshot
70
86
  */
71
87
  private readonly blobContents: { [id: string]: ArrayBufferLike | string } = {},
88
+ private loadingGroupIdSnapshotsFromPendingState: Record<string, ISnapshotInfo> | undefined,
72
89
  private readonly addProtocolSummaryIfMissing: (summaryTree: ISummaryTree) => ISummaryTree,
73
90
  forceEnableSummarizeProtocolTree: boolean | undefined,
74
91
  ) {
@@ -110,6 +127,10 @@ export class ContainerStorageAdapter implements IDocumentStorageService, IDispos
110
127
  }
111
128
  }
112
129
 
130
+ public clearPendingState() {
131
+ this.loadingGroupIdSnapshotsFromPendingState = undefined;
132
+ }
133
+
113
134
  public get policies(): IDocumentStorageServicePolicies | undefined {
114
135
  // back-compat 0.40 containerRuntime requests policies even in detached container if storage is present
115
136
  // and storage is always present in >=0.41.
@@ -127,12 +148,46 @@ export class ContainerStorageAdapter implements IDocumentStorageService, IDispos
127
148
  }
128
149
 
129
150
  public async getSnapshot(snapshotFetchOptions?: ISnapshotFetchOptions): Promise<ISnapshot> {
130
- if (this._storageService.getSnapshot !== undefined) {
131
- return this._storageService.getSnapshot(snapshotFetchOptions);
151
+ let snapshot: ISnapshot;
152
+ if (
153
+ this.loadingGroupIdSnapshotsFromPendingState !== undefined &&
154
+ snapshotFetchOptions?.loadingGroupIds !== undefined
155
+ ) {
156
+ const localSnapshot =
157
+ this.loadingGroupIdSnapshotsFromPendingState[
158
+ snapshotFetchOptions.loadingGroupIds[0]
159
+ ];
160
+ assert(localSnapshot !== undefined, 0x970 /* Local snapshot must be present */);
161
+ const attributes = await getDocumentAttributes(this, localSnapshot.baseSnapshot);
162
+ snapshot = convertSnapshotInfoToSnapshot(localSnapshot, attributes.sequenceNumber);
163
+ } else {
164
+ if (this._storageService.getSnapshot === undefined) {
165
+ throw new UsageError(
166
+ "getSnapshot api should exist in internal storage in ContainerStorageAdapter",
167
+ );
168
+ }
169
+ snapshot = await this._storageService.getSnapshot(snapshotFetchOptions);
132
170
  }
133
- throw new UsageError(
134
- "getSnapshot api should exist in internal storage in ContainerStorageAdapter",
171
+
172
+ // Track the latest snapshot for each loading group id
173
+ const loadingGroupIds = snapshotFetchOptions?.loadingGroupIds;
174
+ assert(
175
+ snapshot.sequenceNumber !== undefined,
176
+ 0x971 /* Snapshot must have sequence number */,
135
177
  );
178
+ if (loadingGroupIds !== undefined) {
179
+ for (const loadingGroupId of loadingGroupIds) {
180
+ // Do we actually want to update the stored snapshot?
181
+ // What if the incoming snapshot is way newer than the stored snapshot?
182
+ // We only want to update the stored snapshot if the incoming snapshot is newer (stored sequence number < incoming sequence number)
183
+ const storedSeqNum =
184
+ this._loadedGroupIdSnapshots[loadingGroupId]?.sequenceNumber ?? -1;
185
+ if (storedSeqNum < snapshot.sequenceNumber) {
186
+ this._loadedGroupIdSnapshots[loadingGroupId] = snapshot;
187
+ }
188
+ }
189
+ }
190
+ return snapshot;
136
191
  }
137
192
 
138
193
  public async readBlob(id: string): Promise<ArrayBufferLike> {
package/src/contracts.ts CHANGED
@@ -3,13 +3,14 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import { ICriticalContainerError } from "@fluidframework/container-definitions";
6
7
  import {
7
- IConnectionDetails,
8
- ICriticalContainerError,
9
8
  IDeltaQueue,
10
9
  ReadOnlyInfo,
11
- } from "@fluidframework/container-definitions";
12
- import { IFluidCodeDetails, isFluidPackage } from "@fluidframework/container-definitions/internal";
10
+ IFluidCodeDetails,
11
+ isFluidPackage,
12
+ IConnectionDetails,
13
+ } from "@fluidframework/container-definitions/internal";
13
14
  import { IErrorBase, ITelemetryBaseProperties } from "@fluidframework/core-interfaces";
14
15
  import { IContainerPackageInfo } from "@fluidframework/driver-definitions/internal";
15
16
  import {
@@ -9,8 +9,8 @@ import {
9
9
  ITelemetryBaseLogger,
10
10
  ITelemetryBaseProperties,
11
11
  } from "@fluidframework/core-interfaces";
12
- import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
13
12
  import {
13
+ ITelemetryLoggerExt,
14
14
  ITelemetryLoggerPropertyBags,
15
15
  createMultiSinkLogger,
16
16
  eventNamespaceSeparator,
@@ -3,13 +3,12 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { TypedEventEmitter } from "@fluid-internal/client-utils";
7
6
  import {
8
7
  ICriticalContainerError,
9
8
  IDeltaManager,
10
9
  IDeltaManagerEvents,
11
10
  IDeltaQueue,
12
- } from "@fluidframework/container-definitions";
11
+ } from "@fluidframework/container-definitions/internal";
13
12
  import {
14
13
  IEventProvider,
15
14
  type ITelemetryBaseEvent,
@@ -17,10 +16,10 @@ import {
17
16
  } from "@fluidframework/core-interfaces";
18
17
  import { IThrottlingWarning } from "@fluidframework/core-interfaces/internal";
19
18
  import { assert } from "@fluidframework/core-utils/internal";
20
- import { DriverErrorTypes } from "@fluidframework/driver-definitions";
21
19
  import {
22
20
  IDocumentDeltaStorageService,
23
21
  IDocumentService,
22
+ DriverErrorTypes,
24
23
  } from "@fluidframework/driver-definitions/internal";
25
24
  import {
26
25
  MessageType2,
@@ -38,8 +37,6 @@ import {
38
37
  type ITelemetryErrorEventExt,
39
38
  type ITelemetryGenericEventExt,
40
39
  ITelemetryLoggerExt,
41
- } from "@fluidframework/telemetry-utils";
42
- import {
43
40
  DataCorruptionError,
44
41
  DataProcessingError,
45
42
  UsageError,
@@ -47,6 +44,7 @@ import {
47
44
  isFluidError,
48
45
  normalizeError,
49
46
  safeRaiseEvent,
47
+ EventEmitterWithErrorHandling,
50
48
  } from "@fluidframework/telemetry-utils/internal";
51
49
  import { v4 as uuid } from "uuid";
52
50
 
@@ -149,7 +147,7 @@ function logIfFalse(
149
147
  * messages in order regardless of possible network conditions or timings causing out of order delivery.
150
148
  */
151
149
  export class DeltaManager<TConnectionManager extends IConnectionManager>
152
- extends TypedEventEmitter<IDeltaManagerInternalEvents>
150
+ extends EventEmitterWithErrorHandling<IDeltaManagerInternalEvents>
153
151
  implements
154
152
  IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
155
153
  IEventProvider<IDeltaManagerInternalEvents>
@@ -411,7 +409,16 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
411
409
  private readonly _active: () => boolean,
412
410
  createConnectionManager: (props: IConnectionManagerFactoryArgs) => TConnectionManager,
413
411
  ) {
414
- super();
412
+ super((name, error) => {
413
+ this.logger.sendErrorEvent(
414
+ {
415
+ eventName: "DeltaManagerEventHandlerException",
416
+ name: typeof name === "string" ? name : undefined,
417
+ },
418
+ error,
419
+ );
420
+ this.close(normalizeError(error));
421
+ });
415
422
  const props: IConnectionManagerFactoryArgs = {
416
423
  incomingOpHandler: (messages: ISequencedDocumentMessage[], reason: string) => {
417
424
  try {
@@ -550,7 +557,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
550
557
  minSequenceNumber: number,
551
558
  snapshotSequenceNumber: number,
552
559
  handler: IDeltaHandlerStrategy,
553
- prefetchType: "sequenceNumber" | "cached" | "all" | "none" = "none",
560
+ prefetchType: "cached" | "all" | "none" = "none",
554
561
  lastProcessedSequenceNumber: number = snapshotSequenceNumber,
555
562
  ) {
556
563
  this.initSequenceNumber = snapshotSequenceNumber;
package/src/deltaQueue.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { TypedEventEmitter, performance } from "@fluid-internal/client-utils";
7
- import { IDeltaQueue, IDeltaQueueEvents } from "@fluidframework/container-definitions";
7
+ import { IDeltaQueue, IDeltaQueueEvents } from "@fluidframework/container-definitions/internal";
8
8
  import { assert } from "@fluidframework/core-utils/internal";
9
9
  import Deque from "double-ended-queue";
10
10
 
package/src/error.ts CHANGED
@@ -6,9 +6,9 @@
6
6
  import { ContainerErrorTypes } from "@fluidframework/container-definitions/internal";
7
7
  import { ITelemetryBaseProperties } from "@fluidframework/core-interfaces";
8
8
  import { IThrottlingWarning } from "@fluidframework/core-interfaces/internal";
9
- import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
10
9
  import {
11
10
  IFluidErrorBase,
11
+ ITelemetryLoggerExt,
12
12
  LoggingError,
13
13
  wrapErrorAndLog,
14
14
  } from "@fluidframework/telemetry-utils/internal";
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export {
14
14
  ILoaderServices,
15
15
  Loader,
16
16
  } from "./loader.js";
17
+ export { loadContainerPaused } from "./loadPaused.js";
17
18
  export {
18
19
  isLocationRedirectionError,
19
20
  resolveWithLocationRedirectionHandling,
@@ -0,0 +1,140 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { ILoader, LoaderHeader } from "@fluidframework/container-definitions/internal";
7
+ import { IRequest } from "@fluidframework/core-interfaces";
8
+ import { GenericError } from "@fluidframework/telemetry-utils/internal";
9
+ import type { IErrorBase } from "@fluidframework/core-interfaces";
10
+
11
+ /* eslint-disable jsdoc/check-indentation */
12
+
13
+ /**
14
+ * Loads container and leaves it in a state where it does not process any ops.
15
+ * Container instance returned by this function is in special mode where some functionality that is available in normal use will not work correctly
16
+ * with instance of container returned by this function. Some examples:
17
+ * 1. calling IContainer.connect() will have very little impact on this container as it will not process ops.
18
+ * 2. functionality like waitContainerToCatchUp() or waiting for ops in any other way would hand infinitely, as this container is not processing ops
19
+ * 3. No changes can be made to this container - they will be lost.
20
+ *
21
+ * If sequence number is provided, loads up to this sequence number and stops there, otherwise stops immediately after loading snapshot.
22
+ * In all cases, container is returned disconnected & paused, or an exception is thrown
23
+ * Notes:
24
+ * 1. Ignores LoaderHeader.loadMode headers. Container is always returned with ops applied upt to provided sequence number,
25
+ * or no ops applied at all (if sequence number is not provided)
26
+ * 2. This call can hang infinitately if disconnected from internet (or hit some other conditions, like 429 storm).
27
+ * Compare to Container.load() experience (with default settings) - it either returns failure right away, or succeeds, with
28
+ * ops fetching / delta connection happening in parallel / after container load flow, and thus providing an object (Container instance) to observe
29
+ * network connectivity issues / ability to cancel (IContainer.disconnect) or close container (IContainer.close)
30
+ * This flow needs to fetch ops (potentially connecting to delta connection), and any retriable errors on this path result in infinite retry.
31
+ * If you need to cancel that process, consider supplying AbortSignal parameter.
32
+ * @param loader - loader instance to use to load container
33
+ * @param request - request identifying container instance / load parameters. LoaderHeader.loadMode headers are ignored (see above)
34
+ * @param loadToSequenceNumber - optional sequence number. If provided, ops are processed up to this sequence number.
35
+ * @param signal - optional abort signal that can be used to cancel waiting for the ops.
36
+ * @returns IContainer instance
37
+ *
38
+ * @internal
39
+ */
40
+ export async function loadContainerPaused(
41
+ loader: ILoader,
42
+ request: IRequest,
43
+ loadToSequenceNumber?: number,
44
+ signal?: AbortSignal,
45
+ ) {
46
+ const container = await loader.resolve({
47
+ url: request.url,
48
+ headers: {
49
+ ...request.headers,
50
+ // ensure we do not process any ops, such that we can examine container before ops starts to flow.
51
+ [LoaderHeader.loadMode]: { opsBeforeReturn: undefined, deltaConnection: "none" },
52
+ },
53
+ });
54
+
55
+ // Force readonly mode - this will ensure we don't receive an error for the lack of join op
56
+ container.forceReadonly?.(true);
57
+
58
+ const dm = container.deltaManager;
59
+ const lastProcessedSequenceNumber = dm.initialSequenceNumber;
60
+
61
+ const pauseContainer = () => {
62
+ void dm.inbound.pause();
63
+ void dm.outbound.pause();
64
+ };
65
+
66
+ // Happy path - we are already there.
67
+ if (
68
+ loadToSequenceNumber === undefined ||
69
+ lastProcessedSequenceNumber === loadToSequenceNumber
70
+ ) {
71
+ // If we have already reached the desired sequence number, call pauseContainer() to pause immediately.
72
+ pauseContainer();
73
+ return container;
74
+ }
75
+
76
+ // If we are trying to pause at a specific sequence number, ensure the latest snapshot is not newer than the desired sequence number.
77
+ if (lastProcessedSequenceNumber > loadToSequenceNumber) {
78
+ const error = new GenericError(
79
+ "Cannot satisfy request to pause the container at the specified sequence number. Most recent snapshot is newer than the specified sequence number.",
80
+ );
81
+ container.close(error);
82
+ throw error;
83
+ }
84
+
85
+ let opHandler: () => void;
86
+ let onAbort: () => void;
87
+ let onClose: (error?: IErrorBase) => void;
88
+
89
+ const promise = new Promise<void>((resolve, rejectArg) => {
90
+ onAbort = () => rejectArg(new GenericError("Canceled due to cancellation request."));
91
+ onClose = (error?: IErrorBase) => rejectArg(error);
92
+
93
+ // We need to setup a listener to stop op processing once we reach the desired sequence number (if specified).
94
+ opHandler = () => {
95
+ // If there is a specified sequence number, keep processing until we reach it.
96
+ if (
97
+ loadToSequenceNumber !== undefined &&
98
+ dm.lastSequenceNumber >= loadToSequenceNumber
99
+ ) {
100
+ // Pause op processing once we have processed the desired number of ops.
101
+ pauseContainer();
102
+ resolve();
103
+ }
104
+ };
105
+
106
+ // If we have not yet reached the desired sequence number, setup a listener to pause once we reach it.
107
+ signal?.addEventListener("abort", onAbort);
108
+ container.on("op", opHandler);
109
+ container.on("closed", onClose);
110
+ });
111
+
112
+ // There are no guarantees on when ops will land in storage.
113
+ // No guarantees that driver implements ops caching (i.e. ops observed in previous session can be served from cache)
114
+ // or that browser will provide caching capabilities / keep the data (localStorage).
115
+ // Thus, we have to ensure we connect to delta storage in order to make forward progress with ops.
116
+ // We also instructed not to fetch / apply any ops from storage above (to be able to install callback above before ops are processed),
117
+ // connect() call will fetch ops as needed.
118
+ container.connect();
119
+
120
+ // Wait for the ops to be processed.
121
+ await promise
122
+ .catch((error) => {
123
+ container.close(error);
124
+ throw error;
125
+ })
126
+ .finally(() => {
127
+ // There is not much value in leaving delta connection on. We are not processing ops, we also can't advance to "connected" state because of it.
128
+ // We are not sending ops (due to forceReadonly() call above). We are holding collab window and any consensus-based processes.
129
+ // It's better not to have connection in such case, as there are only nagatives, and no positives.
130
+ container.disconnect();
131
+
132
+ container.off("op", opHandler);
133
+ container.off("closed", onClose);
134
+ signal?.removeEventListener("abort", onAbort);
135
+ });
136
+
137
+ return container;
138
+ }
139
+
140
+ /* eslint-enable jsdoc/check-indentation */
package/src/loader.ts CHANGED
@@ -26,11 +26,10 @@ import {
26
26
  IUrlResolver,
27
27
  } from "@fluidframework/driver-definitions/internal";
28
28
  import { IClientDetails } from "@fluidframework/protocol-definitions";
29
- import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
30
29
  import {
30
+ ITelemetryLoggerExt,
31
31
  MonitoringContext,
32
32
  PerformanceEvent,
33
- UsageError,
34
33
  createChildMonitoringContext,
35
34
  mixinMonitoringContext,
36
35
  sessionStorageConfigProvider,
@@ -42,7 +41,10 @@ import { DebugLogger } from "./debugLogger.js";
42
41
  import { pkgVersion } from "./packageVersion.js";
43
42
  import { ProtocolHandlerBuilder } from "./protocol.js";
44
43
  import type { IPendingContainerState } from "./serializedStateManager.js";
45
- import { tryParseCompatibleResolvedUrl } from "./utils.js";
44
+ import {
45
+ getAttachedContainerStateFromSerializedContainer,
46
+ tryParseCompatibleResolvedUrl,
47
+ } from "./utils.js";
46
48
 
47
49
  function ensureResolvedUrlDefined(
48
50
  resolved: IResolvedUrl | undefined,
@@ -336,7 +338,7 @@ export class Loader implements IHostLoader {
336
338
  return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName }, async () => {
337
339
  return this.resolveCore(
338
340
  request,
339
- pendingLocalState !== undefined ? JSON.parse(pendingLocalState) : undefined,
341
+ getAttachedContainerStateFromSerializedContainer(pendingLocalState),
340
342
  );
341
343
  });
342
344
  }
@@ -369,24 +371,6 @@ export class Loader implements IHostLoader {
369
371
  // If set in both query string and headers, use query string. Also write the value from the query string into the header either way.
370
372
  request.headers[LoaderHeader.version] =
371
373
  parsed.version ?? request.headers[LoaderHeader.version];
372
- const fromSequenceNumber = request.headers[LoaderHeader.sequenceNumber] as
373
- | number
374
- | undefined;
375
- const opsBeforeReturn = request.headers[LoaderHeader.loadMode]?.opsBeforeReturn as
376
- | string
377
- | undefined;
378
-
379
- if (
380
- opsBeforeReturn === "sequenceNumber" &&
381
- (fromSequenceNumber === undefined || fromSequenceNumber < 0)
382
- ) {
383
- // If opsBeforeReturn is set to "sequenceNumber", then fromSequenceNumber should be set to a non-negative integer.
384
- throw new UsageError("sequenceNumber must be set to a non-negative integer");
385
- } else if (opsBeforeReturn !== "sequenceNumber" && fromSequenceNumber !== undefined) {
386
- // If opsBeforeReturn is not set to "sequenceNumber", then fromSequenceNumber should be undefined (default value).
387
- // In this case, we should throw an error since opsBeforeReturn is not explicitly set to "sequenceNumber".
388
- throw new UsageError('opsBeforeReturn must be set to "sequenceNumber"');
389
- }
390
374
 
391
375
  return this.loadContainer(request, resolvedAsFluid, pendingLocalState);
392
376
  }
@@ -402,7 +386,6 @@ export class Loader implements IHostLoader {
402
386
  version: request.headers?.[LoaderHeader.version] ?? undefined,
403
387
  loadMode: request.headers?.[LoaderHeader.loadMode],
404
388
  pendingLocalState,
405
- loadToSequenceNumber: request.headers?.[LoaderHeader.sequenceNumber],
406
389
  },
407
390
  {
408
391
  canReconnect: request.headers?.[LoaderHeader.reconnect],
@@ -4,10 +4,10 @@
4
4
  */
5
5
 
6
6
  import { IRequest, ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
7
- import { DriverErrorTypes } from "@fluidframework/driver-definitions";
8
7
  import {
9
8
  ILocationRedirectionError,
10
9
  IUrlResolver,
10
+ DriverErrorTypes,
11
11
  } from "@fluidframework/driver-definitions/internal";
12
12
  import { createChildLogger } from "@fluidframework/telemetry-utils/internal";
13
13
 
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "2.0.0-rc.3.0.2";
9
+ export const pkgVersion = "2.0.0-rc.4.0.0";
package/src/protocol.ts CHANGED
@@ -61,6 +61,10 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
61
61
  sendProposal,
62
62
  );
63
63
 
64
+ for (const [clientId, member] of this.quorum.getMembers()) {
65
+ audience.addMember(clientId, member.client);
66
+ }
67
+
64
68
  // Join / leave signals are ignored for "write" clients in favor of join / leave ops
65
69
  this.quorum.on("addMember", (clientId, details) =>
66
70
  audience.addMember(clientId, details.client),
@@ -21,8 +21,11 @@ import {
21
21
  ISummaryTree,
22
22
  IVersion,
23
23
  } from "@fluidframework/protocol-definitions";
24
- import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
25
- import { GenericError, UsageError } from "@fluidframework/telemetry-utils/internal";
24
+ import {
25
+ ITelemetryLoggerExt,
26
+ GenericError,
27
+ UsageError,
28
+ } from "@fluidframework/telemetry-utils/internal";
26
29
 
27
30
  export class RetriableDocumentStorageService implements IDocumentStorageService, IDisposable {
28
31
  private _disposed = false;