@fluidframework/container-loader 2.0.0-dev.3.1.0.125672 → 2.0.0-dev.4.1.0.148229
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.
- package/README.md +7 -4
- package/closeAndGetPendingLocalState.md +51 -0
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +43 -11
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +4 -4
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +7 -0
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +44 -4
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +152 -93
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +18 -8
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +47 -4
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +41 -2
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +87 -11
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +2 -2
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts +3 -2
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +4 -2
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaManagerProxy.d.ts +10 -22
- package/dist/deltaManagerProxy.d.ts.map +1 -1
- package/dist/deltaManagerProxy.js +14 -50
- package/dist/deltaManagerProxy.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +10 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +22 -16
- package/dist/loader.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts +6 -2
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js +7 -4
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -1
- package/dist/utils.js.map +1 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +44 -12
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +4 -4
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +7 -0
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +44 -4
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +155 -96
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +18 -8
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +48 -5
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +41 -2
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +85 -11
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +2 -2
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts +3 -2
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +4 -2
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaManagerProxy.d.ts +10 -22
- package/lib/deltaManagerProxy.d.ts.map +1 -1
- package/lib/deltaManagerProxy.js +14 -50
- package/lib/deltaManagerProxy.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts +10 -1
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +21 -16
- package/lib/loader.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts +6 -2
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.js +7 -4
- package/lib/protocolTreeDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +2 -1
- package/lib/utils.js.map +1 -1
- package/package.json +64 -56
- package/src/connectionManager.ts +48 -17
- package/src/connectionStateHandler.ts +17 -5
- package/src/container.ts +224 -116
- package/src/containerContext.ts +74 -11
- package/src/containerStorageAdapter.ts +113 -9
- package/src/contracts.ts +2 -2
- package/src/deltaManager.ts +9 -4
- package/src/deltaManagerProxy.ts +18 -73
- package/src/index.ts +2 -3
- package/src/loader.ts +28 -26
- package/src/packageVersion.ts +1 -1
- package/src/protocolTreeDocumentStorageService.ts +6 -3
- package/src/utils.ts +7 -4
package/src/containerContext.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
|
-
import { LazyPromise } from "@fluidframework/common-utils";
|
|
7
|
+
import { assert, LazyPromise, TypedEventEmitter } from "@fluidframework/common-utils";
|
|
8
8
|
import {
|
|
9
9
|
IAudience,
|
|
10
10
|
IContainerContext,
|
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
IProvideFluidCodeDetailsComparer,
|
|
22
22
|
ICodeDetailsLoader,
|
|
23
23
|
IFluidModuleWithDetails,
|
|
24
|
-
ISnapshotTreeWithBlobContents,
|
|
25
24
|
IBatchMessage,
|
|
26
25
|
} from "@fluidframework/container-definitions";
|
|
27
26
|
import { IRequest, IResponse, FluidObject } from "@fluidframework/core-interfaces";
|
|
@@ -42,10 +41,21 @@ import {
|
|
|
42
41
|
ISummaryContent,
|
|
43
42
|
} from "@fluidframework/protocol-definitions";
|
|
44
43
|
import { PerformanceEvent } from "@fluidframework/telemetry-utils";
|
|
44
|
+
import { UsageError } from "@fluidframework/container-utils";
|
|
45
45
|
import { Container } from "./container";
|
|
46
46
|
|
|
47
47
|
const PackageNotFactoryError = "Code package does not implement IRuntimeFactory";
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Events that {@link ContainerContext} can emit through its lifecycle.
|
|
51
|
+
*
|
|
52
|
+
* "runtimeInstantiated" - When an {@link @fluidframework/container-definitions#IRuntime} has been instantiated (by
|
|
53
|
+
* calling instantiateRuntime() on the runtime factory), and this._runtime is set.
|
|
54
|
+
*
|
|
55
|
+
* "disposed" - When its dispose() method is called. The {@link ContainerContext} is no longer usable at that point.
|
|
56
|
+
*/
|
|
57
|
+
type ContextLifecycleEvents = "runtimeInstantiated" | "disposed";
|
|
58
|
+
|
|
49
59
|
export class ContainerContext implements IContainerContext {
|
|
50
60
|
public static async createOrLoad(
|
|
51
61
|
container: Container,
|
|
@@ -57,8 +67,8 @@ export class ContainerContext implements IContainerContext {
|
|
|
57
67
|
quorum: IQuorum,
|
|
58
68
|
loader: ILoader,
|
|
59
69
|
submitFn: (type: MessageType, contents: any, batch: boolean, appData: any) => number,
|
|
60
|
-
submitSummaryFn: (summaryOp: ISummaryContent) => number,
|
|
61
|
-
submitBatchFn: (batch: IBatchMessage[]) => number,
|
|
70
|
+
submitSummaryFn: (summaryOp: ISummaryContent, referenceSequenceNumber?: number) => number,
|
|
71
|
+
submitBatchFn: (batch: IBatchMessage[], referenceSequenceNumber?: number) => number,
|
|
62
72
|
submitSignalFn: (contents: any) => void,
|
|
63
73
|
disposeFn: (error?: ICriticalContainerError) => void,
|
|
64
74
|
closeFn: (error?: ICriticalContainerError) => void,
|
|
@@ -92,6 +102,7 @@ export class ContainerContext implements IContainerContext {
|
|
|
92
102
|
}
|
|
93
103
|
|
|
94
104
|
public readonly taggedLogger: ITelemetryLogger;
|
|
105
|
+
public readonly supportedFeatures: ReadonlyMap<string, unknown>;
|
|
95
106
|
|
|
96
107
|
public get clientId(): string | undefined {
|
|
97
108
|
return this.container.clientId;
|
|
@@ -170,12 +181,46 @@ export class ContainerContext implements IContainerContext {
|
|
|
170
181
|
|
|
171
182
|
private readonly _fluidModuleP: Promise<IFluidModuleWithDetails>;
|
|
172
183
|
|
|
184
|
+
/**
|
|
185
|
+
* {@inheritDoc @fluidframework/container-definitions#IContainerContext.getEntryPoint}
|
|
186
|
+
*/
|
|
187
|
+
public async getEntryPoint?(): Promise<FluidObject | undefined> {
|
|
188
|
+
if (this._disposed) {
|
|
189
|
+
throw new UsageError("The context is already disposed");
|
|
190
|
+
}
|
|
191
|
+
if (this._runtime !== undefined) {
|
|
192
|
+
return this._runtime?.getEntryPoint?.();
|
|
193
|
+
}
|
|
194
|
+
return new Promise<FluidObject | undefined>((resolve, reject) => {
|
|
195
|
+
const runtimeInstantiatedHandler = () => {
|
|
196
|
+
assert(
|
|
197
|
+
this._runtime !== undefined,
|
|
198
|
+
0x5a3 /* runtimeInstantiated fired but runtime is still undefined */,
|
|
199
|
+
);
|
|
200
|
+
resolve(this._runtime.getEntryPoint?.());
|
|
201
|
+
this.lifecycleEvents.off("disposed", disposedHandler);
|
|
202
|
+
};
|
|
203
|
+
const disposedHandler = () => {
|
|
204
|
+
reject(new Error("ContainerContext was disposed"));
|
|
205
|
+
this.lifecycleEvents.off("runtimeInstantiated", runtimeInstantiatedHandler);
|
|
206
|
+
};
|
|
207
|
+
this.lifecycleEvents.once("runtimeInstantiated", runtimeInstantiatedHandler);
|
|
208
|
+
this.lifecycleEvents.once("disposed", disposedHandler);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Emits events about the container context's lifecycle.
|
|
214
|
+
* Use it to coordinate things inside the ContainerContext class.
|
|
215
|
+
*/
|
|
216
|
+
private readonly lifecycleEvents = new TypedEventEmitter<ContextLifecycleEvents>();
|
|
217
|
+
|
|
173
218
|
constructor(
|
|
174
219
|
private readonly container: Container,
|
|
175
220
|
public readonly scope: FluidObject,
|
|
176
221
|
private readonly codeLoader: ICodeDetailsLoader,
|
|
177
222
|
private readonly _codeDetails: IFluidCodeDetails,
|
|
178
|
-
private _baseSnapshot: ISnapshotTree | undefined,
|
|
223
|
+
private readonly _baseSnapshot: ISnapshotTree | undefined,
|
|
179
224
|
public readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
|
|
180
225
|
quorum: IQuorum,
|
|
181
226
|
public readonly loader: ILoader,
|
|
@@ -185,9 +230,15 @@ export class ContainerContext implements IContainerContext {
|
|
|
185
230
|
batch: boolean,
|
|
186
231
|
appData: any,
|
|
187
232
|
) => number,
|
|
188
|
-
public readonly submitSummaryFn: (
|
|
233
|
+
public readonly submitSummaryFn: (
|
|
234
|
+
summaryOp: ISummaryContent,
|
|
235
|
+
referenceSequenceNumber?: number,
|
|
236
|
+
) => number,
|
|
189
237
|
/** @returns clientSequenceNumber of last message in a batch */
|
|
190
|
-
public readonly submitBatchFn: (
|
|
238
|
+
public readonly submitBatchFn: (
|
|
239
|
+
batch: IBatchMessage[],
|
|
240
|
+
referenceSequenceNumber?: number,
|
|
241
|
+
) => number,
|
|
191
242
|
public readonly submitSignalFn: (contents: any) => void,
|
|
192
243
|
public readonly disposeFn: (error?: ICriticalContainerError) => void,
|
|
193
244
|
public readonly closeFn: (error?: ICriticalContainerError) => void,
|
|
@@ -202,6 +253,15 @@ export class ContainerContext implements IContainerContext {
|
|
|
202
253
|
this._fluidModuleP = new LazyPromise<IFluidModuleWithDetails>(async () =>
|
|
203
254
|
this.loadCodeModule(_codeDetails),
|
|
204
255
|
);
|
|
256
|
+
|
|
257
|
+
this.supportedFeatures = new Map([
|
|
258
|
+
/**
|
|
259
|
+
* This version of the loader accepts `referenceSequenceNumber`, provided by the container runtime,
|
|
260
|
+
* as a parameter to the `submitBatchFn` and `submitSummaryFn` functions.
|
|
261
|
+
* This is then used to set the reference sequence numbers of the submitted ops in the DeltaManager.
|
|
262
|
+
*/
|
|
263
|
+
["referenceSequenceNumbers", true],
|
|
264
|
+
]);
|
|
205
265
|
this.attachListener();
|
|
206
266
|
}
|
|
207
267
|
|
|
@@ -223,6 +283,7 @@ export class ContainerContext implements IContainerContext {
|
|
|
223
283
|
}
|
|
224
284
|
this._disposed = true;
|
|
225
285
|
|
|
286
|
+
this.lifecycleEvents.emit("disposed");
|
|
226
287
|
this.runtime.dispose(error);
|
|
227
288
|
this._quorum.dispose();
|
|
228
289
|
this.deltaManager.dispose();
|
|
@@ -313,10 +374,8 @@ export class ContainerContext implements IContainerContext {
|
|
|
313
374
|
return true;
|
|
314
375
|
}
|
|
315
376
|
|
|
316
|
-
public
|
|
317
|
-
this.
|
|
318
|
-
this.runtime.notifyAttaching?.(snapshot);
|
|
319
|
-
this.runtime.setAttachState(AttachState.Attaching);
|
|
377
|
+
public async notifyOpReplay(message: ISequencedDocumentMessage): Promise<void> {
|
|
378
|
+
return this.runtime.notifyOpReplay?.(message);
|
|
320
379
|
}
|
|
321
380
|
|
|
322
381
|
// #region private
|
|
@@ -340,9 +399,13 @@ export class ContainerContext implements IContainerContext {
|
|
|
340
399
|
{ eventName: "InstantiateRuntime" },
|
|
341
400
|
async () => runtimeFactory.instantiateRuntime(this, existing),
|
|
342
401
|
);
|
|
402
|
+
this.lifecycleEvents.emit("runtimeInstantiated");
|
|
343
403
|
}
|
|
344
404
|
|
|
345
405
|
private attachListener() {
|
|
406
|
+
this.container.once("attaching", () => {
|
|
407
|
+
this.runtime.setAttachState(AttachState.Attaching);
|
|
408
|
+
});
|
|
346
409
|
this.container.once("attached", () => {
|
|
347
410
|
this.runtime.setAttachState(AttachState.Attached);
|
|
348
411
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { IDisposable, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
|
-
import { assert } from "@fluidframework/common-utils";
|
|
7
|
+
import { assert, bufferToString, stringToBuffer } from "@fluidframework/common-utils";
|
|
8
8
|
import { ISnapshotTreeWithBlobContents } from "@fluidframework/container-definitions";
|
|
9
9
|
import {
|
|
10
10
|
FetchSource,
|
|
@@ -25,20 +25,51 @@ import { IDetachedBlobStorage } from "./loader";
|
|
|
25
25
|
import { ProtocolTreeStorageService } from "./protocolTreeDocumentStorageService";
|
|
26
26
|
import { RetriableDocumentStorageService } from "./retriableDocumentStorageService";
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Stringified blobs from a summary/snapshot tree.
|
|
30
|
+
* @deprecated this is an internal interface and will not longer be exported in future versions
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export interface ISerializableBlobContents {
|
|
34
|
+
[id: string]: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
28
37
|
/**
|
|
29
38
|
* This class wraps the actual storage and make sure no wrong apis are called according to
|
|
30
39
|
* container attach state.
|
|
31
40
|
*/
|
|
32
41
|
export class ContainerStorageAdapter implements IDocumentStorageService, IDisposable {
|
|
33
|
-
private readonly blobContents: { [id: string]: ArrayBufferLike } = {};
|
|
34
42
|
private _storageService: IDocumentStorageService & Partial<IDisposable>;
|
|
35
43
|
|
|
36
|
-
|
|
44
|
+
private _summarizeProtocolTree: boolean | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Whether the adapter will enforce sending combined summary trees.
|
|
47
|
+
*/
|
|
48
|
+
public get summarizeProtocolTree() {
|
|
49
|
+
return this._summarizeProtocolTree === true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* An adapter that ensures we're using detachedBlobStorage up until we connect to a real service, and then
|
|
54
|
+
* after connecting to a real service augments it with retry and combined summary tree enforcement.
|
|
55
|
+
* @param detachedBlobStorage - The detached blob storage to use up until we connect to a real service
|
|
56
|
+
* @param logger - Telemetry logger
|
|
57
|
+
* @param addProtocolSummaryIfMissing - a callback to permit the container to inspect the summary we're about to
|
|
58
|
+
* upload, and fix it up with a protocol tree if needed
|
|
59
|
+
* @param forceEnableSummarizeProtocolTree - Enforce uploading a protocol summary regardless of the service's policy
|
|
60
|
+
*/
|
|
61
|
+
public constructor(
|
|
37
62
|
detachedBlobStorage: IDetachedBlobStorage | undefined,
|
|
38
63
|
private readonly logger: ITelemetryLogger,
|
|
39
|
-
|
|
64
|
+
/**
|
|
65
|
+
* ArrayBufferLikes or utf8 encoded strings, containing blobs from a snapshot
|
|
66
|
+
*/
|
|
67
|
+
private readonly blobContents: { [id: string]: ArrayBufferLike | string } = {},
|
|
68
|
+
private readonly addProtocolSummaryIfMissing: (summaryTree: ISummaryTree) => ISummaryTree,
|
|
69
|
+
forceEnableSummarizeProtocolTree: boolean | undefined,
|
|
40
70
|
) {
|
|
41
71
|
this._storageService = new BlobOnlyStorage(detachedBlobStorage, logger);
|
|
72
|
+
this._summarizeProtocolTree = forceEnableSummarizeProtocolTree;
|
|
42
73
|
}
|
|
43
74
|
|
|
44
75
|
disposed: boolean = false;
|
|
@@ -58,11 +89,13 @@ export class ContainerStorageAdapter implements IDocumentStorageService, IDispos
|
|
|
58
89
|
this.logger,
|
|
59
90
|
));
|
|
60
91
|
|
|
61
|
-
|
|
92
|
+
this._summarizeProtocolTree =
|
|
93
|
+
this._summarizeProtocolTree ?? service.policies?.summarizeProtocolTree;
|
|
94
|
+
if (this.summarizeProtocolTree) {
|
|
62
95
|
this.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
|
|
63
96
|
this._storageService = new ProtocolTreeStorageService(
|
|
64
97
|
retriableStorage,
|
|
65
|
-
this.
|
|
98
|
+
this.addProtocolSummaryIfMissing,
|
|
66
99
|
);
|
|
67
100
|
}
|
|
68
101
|
|
|
@@ -107,9 +140,13 @@ export class ContainerStorageAdapter implements IDocumentStorageService, IDispos
|
|
|
107
140
|
}
|
|
108
141
|
|
|
109
142
|
public async readBlob(id: string): Promise<ArrayBufferLike> {
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
|
|
143
|
+
const maybeBlob = this.blobContents[id];
|
|
144
|
+
if (maybeBlob !== undefined) {
|
|
145
|
+
if (typeof maybeBlob === "string") {
|
|
146
|
+
const blob = stringToBuffer(maybeBlob, "utf8");
|
|
147
|
+
return blob;
|
|
148
|
+
}
|
|
149
|
+
return maybeBlob;
|
|
113
150
|
}
|
|
114
151
|
return this._storageService.readBlob(id);
|
|
115
152
|
}
|
|
@@ -191,3 +228,70 @@ class BlobOnlyStorage implements IDocumentStorageService {
|
|
|
191
228
|
}
|
|
192
229
|
}
|
|
193
230
|
}
|
|
231
|
+
|
|
232
|
+
// runtime will write a tree to the summary containing only "attachment" type entries
|
|
233
|
+
// which reference attachment blobs by ID. However, some drivers do not support this type
|
|
234
|
+
// and will convert them to "blob" type entries. We want to avoid saving these to reduce
|
|
235
|
+
// the size of stashed change blobs.
|
|
236
|
+
const blobsTreeName = ".blobs";
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get blob contents of a snapshot tree from storage (or, ideally, cache)
|
|
240
|
+
*/
|
|
241
|
+
export async function getBlobContentsFromTree(
|
|
242
|
+
snapshot: ISnapshotTree,
|
|
243
|
+
storage: IDocumentStorageService,
|
|
244
|
+
): Promise<ISerializableBlobContents> {
|
|
245
|
+
const blobs = {};
|
|
246
|
+
await getBlobContentsFromTreeCore(snapshot, blobs, storage);
|
|
247
|
+
return blobs;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function getBlobContentsFromTreeCore(
|
|
251
|
+
tree: ISnapshotTree,
|
|
252
|
+
blobs: ISerializableBlobContents,
|
|
253
|
+
storage: IDocumentStorageService,
|
|
254
|
+
root = true,
|
|
255
|
+
) {
|
|
256
|
+
const treePs: Promise<any>[] = [];
|
|
257
|
+
for (const [key, subTree] of Object.entries(tree.trees)) {
|
|
258
|
+
if (!root || key !== blobsTreeName) {
|
|
259
|
+
treePs.push(getBlobContentsFromTreeCore(subTree, blobs, storage, false));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
for (const id of Object.values(tree.blobs)) {
|
|
263
|
+
const blob = await storage.readBlob(id);
|
|
264
|
+
// ArrayBufferLike will not survive JSON.stringify()
|
|
265
|
+
blobs[id] = bufferToString(blob, "utf8");
|
|
266
|
+
}
|
|
267
|
+
return Promise.all(treePs);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Extract blob contents from a snapshot tree with blob contents
|
|
272
|
+
*/
|
|
273
|
+
export function getBlobContentsFromTreeWithBlobContents(
|
|
274
|
+
snapshot: ISnapshotTreeWithBlobContents,
|
|
275
|
+
): ISerializableBlobContents {
|
|
276
|
+
const blobs = {};
|
|
277
|
+
getBlobContentsFromTreeWithBlobContentsCore(snapshot, blobs);
|
|
278
|
+
return blobs;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function getBlobContentsFromTreeWithBlobContentsCore(
|
|
282
|
+
tree: ISnapshotTreeWithBlobContents,
|
|
283
|
+
blobs: ISerializableBlobContents,
|
|
284
|
+
root = true,
|
|
285
|
+
) {
|
|
286
|
+
for (const [key, subTree] of Object.entries(tree.trees)) {
|
|
287
|
+
if (!root || key !== blobsTreeName) {
|
|
288
|
+
getBlobContentsFromTreeWithBlobContentsCore(subTree, blobs, false);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
for (const id of Object.values(tree.blobs)) {
|
|
292
|
+
const blob = tree.blobsContents[id];
|
|
293
|
+
assert(blob !== undefined, 0x2ec /* "Blob must be present in blobsContents" */);
|
|
294
|
+
// ArrayBufferLike will not survive JSON.stringify()
|
|
295
|
+
blobs[id] = bufferToString(blob, "utf8");
|
|
296
|
+
}
|
|
297
|
+
}
|
package/src/contracts.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { ITelemetryProperties } from "@fluidframework/common-definitions";
|
|
|
7
7
|
import {
|
|
8
8
|
IDeltaQueue,
|
|
9
9
|
ReadOnlyInfo,
|
|
10
|
-
|
|
10
|
+
IConnectionDetailsInternal,
|
|
11
11
|
ICriticalContainerError,
|
|
12
12
|
IFluidCodeDetails,
|
|
13
13
|
isFluidPackage,
|
|
@@ -144,7 +144,7 @@ export interface IConnectionManagerFactoryArgs {
|
|
|
144
144
|
/**
|
|
145
145
|
* Called whenever new connection to rely service is established
|
|
146
146
|
*/
|
|
147
|
-
readonly connectHandler: (connection:
|
|
147
|
+
readonly connectHandler: (connection: IConnectionDetailsInternal) => void;
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
150
|
* Called whenever ping/pong messages are roundtripped on connection.
|
package/src/deltaManager.ts
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
IDeltaQueue,
|
|
19
19
|
ICriticalContainerError,
|
|
20
20
|
IThrottlingWarning,
|
|
21
|
-
|
|
21
|
+
IConnectionDetailsInternal,
|
|
22
22
|
} from "@fluidframework/container-definitions";
|
|
23
23
|
import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
|
|
24
24
|
import { normalizeError, logIfFalse, safeRaiseEvent } from "@fluidframework/telemetry-utils";
|
|
@@ -57,6 +57,7 @@ export interface IConnectionArgs {
|
|
|
57
57
|
export interface IDeltaManagerInternalEvents extends IDeltaManagerEvents {
|
|
58
58
|
(event: "throttled", listener: (error: IThrottlingWarning) => void);
|
|
59
59
|
(event: "closed" | "disposed", listener: (error?: ICriticalContainerError) => void);
|
|
60
|
+
(event: "connect", listener: (details: IConnectionDetailsInternal, opsBehind?: number) => void);
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
/**
|
|
@@ -230,11 +231,14 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
230
231
|
batch = false,
|
|
231
232
|
metadata?: any,
|
|
232
233
|
compression?: string,
|
|
234
|
+
referenceSequenceNumber?: number,
|
|
233
235
|
) {
|
|
236
|
+
// Back-compat ADO:3455
|
|
237
|
+
const backCompatRefSeqNum = referenceSequenceNumber ?? this.lastProcessedSequenceNumber;
|
|
234
238
|
const messagePartial: Omit<IDocumentMessage, "clientSequenceNumber"> = {
|
|
235
239
|
contents,
|
|
236
240
|
metadata,
|
|
237
|
-
referenceSequenceNumber:
|
|
241
|
+
referenceSequenceNumber: backCompatRefSeqNum,
|
|
238
242
|
type,
|
|
239
243
|
compression,
|
|
240
244
|
};
|
|
@@ -358,7 +362,8 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
358
362
|
this.emitDelayInfo(this.deltaStreamDelayId, delayMs, error),
|
|
359
363
|
closeHandler: (error: any) => this.close(error),
|
|
360
364
|
disconnectHandler: (reason: string) => this.disconnectHandler(reason),
|
|
361
|
-
connectHandler: (connection:
|
|
365
|
+
connectHandler: (connection: IConnectionDetailsInternal) =>
|
|
366
|
+
this.connectHandler(connection),
|
|
362
367
|
pongHandler: (latency: number) => this.emit("pong", latency),
|
|
363
368
|
readonlyChangeHandler: (readonly?: boolean) =>
|
|
364
369
|
safeRaiseEvent(this, this.logger, "readonly", readonly),
|
|
@@ -401,7 +406,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
401
406
|
// - inbound & inboundSignal are resumed in attachOpHandler() when we have handler setup
|
|
402
407
|
}
|
|
403
408
|
|
|
404
|
-
private connectHandler(connection:
|
|
409
|
+
private connectHandler(connection: IConnectionDetailsInternal) {
|
|
405
410
|
this.refreshDelayInfo(this.deltaStreamDelayId);
|
|
406
411
|
|
|
407
412
|
const props = this.connectionManager.connectionVerboseProps;
|
package/src/deltaManagerProxy.ts
CHANGED
|
@@ -5,20 +5,16 @@
|
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
IDeltaManager,
|
|
8
|
-
IDeltaManagerEvents,
|
|
9
8
|
IDeltaQueue,
|
|
10
|
-
IDeltaSender,
|
|
11
9
|
IDeltaQueueEvents,
|
|
12
|
-
ReadOnlyInfo,
|
|
13
10
|
} from "@fluidframework/container-definitions";
|
|
14
11
|
import { EventForwarder } from "@fluidframework/common-utils";
|
|
15
12
|
import {
|
|
16
|
-
IClientConfiguration,
|
|
17
|
-
IClientDetails,
|
|
18
13
|
IDocumentMessage,
|
|
19
14
|
ISequencedDocumentMessage,
|
|
20
15
|
ISignalMessage,
|
|
21
16
|
} from "@fluidframework/protocol-definitions";
|
|
17
|
+
import { DeltaManagerProxyBase } from "@fluidframework/container-utils";
|
|
22
18
|
|
|
23
19
|
/**
|
|
24
20
|
* Proxy to the real IDeltaQueue - used to restrict access
|
|
@@ -78,87 +74,36 @@ export class DeltaQueueProxy<T>
|
|
|
78
74
|
* Proxy to the real IDeltaManager - used to restrict access
|
|
79
75
|
*/
|
|
80
76
|
export class DeltaManagerProxy
|
|
81
|
-
extends
|
|
77
|
+
extends DeltaManagerProxyBase
|
|
82
78
|
implements IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>
|
|
83
79
|
{
|
|
84
|
-
public
|
|
85
|
-
|
|
86
|
-
public readonly inboundSignal: IDeltaQueue<ISignalMessage>;
|
|
87
|
-
|
|
88
|
-
public get IDeltaSender(): IDeltaSender {
|
|
89
|
-
return this;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
public get minimumSequenceNumber(): number {
|
|
93
|
-
return this.deltaManager.minimumSequenceNumber;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
public get lastSequenceNumber(): number {
|
|
97
|
-
return this.deltaManager.lastSequenceNumber;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
public get lastMessage() {
|
|
101
|
-
return this.deltaManager.lastMessage;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
public get lastKnownSeqNumber() {
|
|
105
|
-
return this.deltaManager.lastKnownSeqNumber;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
public get initialSequenceNumber(): number {
|
|
109
|
-
return this.deltaManager.initialSequenceNumber;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
public get hasCheckpointSequenceNumber() {
|
|
113
|
-
return this.deltaManager.hasCheckpointSequenceNumber;
|
|
80
|
+
public get inbound(): IDeltaQueue<ISequencedDocumentMessage> {
|
|
81
|
+
return this._inbound;
|
|
114
82
|
}
|
|
83
|
+
private readonly _inbound: IDeltaQueue<ISequencedDocumentMessage>;
|
|
115
84
|
|
|
116
|
-
public get
|
|
117
|
-
return this.
|
|
85
|
+
public get outbound(): IDeltaQueue<IDocumentMessage[]> {
|
|
86
|
+
return this._outbound;
|
|
118
87
|
}
|
|
88
|
+
private readonly _outbound: IDeltaQueue<IDocumentMessage[]>;
|
|
119
89
|
|
|
120
|
-
public get
|
|
121
|
-
return this.
|
|
90
|
+
public get inboundSignal(): IDeltaQueue<ISignalMessage> {
|
|
91
|
+
return this._inboundSignal;
|
|
122
92
|
}
|
|
93
|
+
private readonly _inboundSignal: IDeltaQueue<ISignalMessage>;
|
|
123
94
|
|
|
124
|
-
|
|
125
|
-
return this.deltaManager.maxMessageSize;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
public get serviceConfiguration(): IClientConfiguration | undefined {
|
|
129
|
-
return this.deltaManager.serviceConfiguration;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
public get active(): boolean {
|
|
133
|
-
return this.deltaManager.active;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
public get readOnlyInfo(): ReadOnlyInfo {
|
|
137
|
-
return this.deltaManager.readOnlyInfo;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
constructor(
|
|
141
|
-
private readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
|
|
142
|
-
) {
|
|
95
|
+
constructor(deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>) {
|
|
143
96
|
super(deltaManager);
|
|
144
97
|
|
|
145
|
-
this.
|
|
146
|
-
this.
|
|
147
|
-
this.
|
|
98
|
+
this._inbound = new DeltaQueueProxy(deltaManager.inbound);
|
|
99
|
+
this._outbound = new DeltaQueueProxy(deltaManager.outbound);
|
|
100
|
+
this._inboundSignal = new DeltaQueueProxy(deltaManager.inboundSignal);
|
|
148
101
|
}
|
|
149
102
|
|
|
150
103
|
public dispose(): void {
|
|
151
|
-
this.
|
|
152
|
-
this.
|
|
153
|
-
this.
|
|
104
|
+
this._inbound.dispose();
|
|
105
|
+
this._outbound.dispose();
|
|
106
|
+
this._inboundSignal.dispose();
|
|
154
107
|
super.dispose();
|
|
155
108
|
}
|
|
156
|
-
|
|
157
|
-
public submitSignal(content: any): void {
|
|
158
|
-
return this.deltaManager.submitSignal(content);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
public flush(): void {
|
|
162
|
-
return this.deltaManager.flush();
|
|
163
|
-
}
|
|
164
109
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
export { ConnectionState } from "./connectionState";
|
|
7
7
|
export {
|
|
8
|
-
Container,
|
|
9
|
-
IContainerLoadOptions,
|
|
10
8
|
IContainerConfig,
|
|
9
|
+
IContainerLoadOptions,
|
|
11
10
|
IPendingContainerState,
|
|
12
11
|
waitContainerToCatchUp,
|
|
13
12
|
} from "./container";
|
|
13
|
+
export { ISerializableBlobContents } from "./containerStorageAdapter";
|
|
14
14
|
export {
|
|
15
15
|
ICodeDetailsLoader,
|
|
16
16
|
IDetachedBlobStorage,
|
|
@@ -19,6 +19,5 @@ export {
|
|
|
19
19
|
ILoaderProps,
|
|
20
20
|
ILoaderServices,
|
|
21
21
|
Loader,
|
|
22
|
-
RelativeLoader,
|
|
23
22
|
} from "./loader";
|
|
24
23
|
export { IProtocolHandler, ProtocolHandlerBuilder } from "./protocol";
|
package/src/loader.ts
CHANGED
|
@@ -36,15 +36,10 @@ import {
|
|
|
36
36
|
IDocumentServiceFactory,
|
|
37
37
|
IDocumentStorageService,
|
|
38
38
|
IFluidResolvedUrl,
|
|
39
|
-
IResolvedUrl,
|
|
40
39
|
IUrlResolver,
|
|
41
40
|
} from "@fluidframework/driver-definitions";
|
|
42
41
|
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
43
|
-
import {
|
|
44
|
-
ensureFluidResolvedUrl,
|
|
45
|
-
MultiUrlResolver,
|
|
46
|
-
MultiDocumentServiceFactory,
|
|
47
|
-
} from "@fluidframework/driver-utils";
|
|
42
|
+
import { ensureFluidResolvedUrl } from "@fluidframework/driver-utils";
|
|
48
43
|
import { Container, IPendingContainerState } from "./container";
|
|
49
44
|
import { IParsedUrl, parseUrl } from "./utils";
|
|
50
45
|
import { pkgVersion } from "./packageVersion";
|
|
@@ -58,6 +53,9 @@ function canUseCache(request: IRequest): boolean {
|
|
|
58
53
|
return request.headers[LoaderHeader.cache] !== false;
|
|
59
54
|
}
|
|
60
55
|
|
|
56
|
+
/**
|
|
57
|
+
* @deprecated - In the next release RelativeLoader will no longer be exported. It is an internal class that should not be used directly.
|
|
58
|
+
*/
|
|
61
59
|
export class RelativeLoader implements ILoader {
|
|
62
60
|
constructor(
|
|
63
61
|
private readonly container: Container,
|
|
@@ -109,22 +107,6 @@ export class RelativeLoader implements ILoader {
|
|
|
109
107
|
}
|
|
110
108
|
}
|
|
111
109
|
|
|
112
|
-
function createCachedResolver(resolver: IUrlResolver) {
|
|
113
|
-
const cacheResolver = Object.create(resolver) as IUrlResolver;
|
|
114
|
-
const resolveCache = new Map<string, Promise<IResolvedUrl | undefined>>();
|
|
115
|
-
cacheResolver.resolve = async (request: IRequest): Promise<IResolvedUrl | undefined> => {
|
|
116
|
-
if (!canUseCache(request)) {
|
|
117
|
-
return resolver.resolve(request);
|
|
118
|
-
}
|
|
119
|
-
if (!resolveCache.has(request.url)) {
|
|
120
|
-
resolveCache.set(request.url, resolver.resolve(request));
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return resolveCache.get(request.url);
|
|
124
|
-
};
|
|
125
|
-
return cacheResolver;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
110
|
export interface ILoaderOptions extends ILoaderOptions1 {
|
|
129
111
|
summarizeProtocolTree?: boolean;
|
|
130
112
|
}
|
|
@@ -275,6 +257,28 @@ export type IDetachedBlobStorage = Pick<IDocumentStorageService, "createBlob" |
|
|
|
275
257
|
getBlobIds(): string[];
|
|
276
258
|
};
|
|
277
259
|
|
|
260
|
+
/**
|
|
261
|
+
* With an already-resolved container, we can request a component directly, without loading the container again
|
|
262
|
+
* @param container - a resolved container
|
|
263
|
+
* @returns component on the container
|
|
264
|
+
*/
|
|
265
|
+
export async function requestResolvedObjectFromContainer(
|
|
266
|
+
container: IContainer,
|
|
267
|
+
headers?: IRequestHeader,
|
|
268
|
+
): Promise<IResponse> {
|
|
269
|
+
ensureFluidResolvedUrl(container.resolvedUrl);
|
|
270
|
+
const parsedUrl = parseUrl(container.resolvedUrl.url);
|
|
271
|
+
|
|
272
|
+
if (parsedUrl === undefined) {
|
|
273
|
+
throw new Error(`Invalid URL ${container.resolvedUrl.url}`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return container.request({
|
|
277
|
+
url: `${parsedUrl.path}${parsedUrl.query}`,
|
|
278
|
+
headers,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
278
282
|
/**
|
|
279
283
|
* Manages Fluid resource loading
|
|
280
284
|
*/
|
|
@@ -303,10 +307,8 @@ export class Loader implements IHostLoader {
|
|
|
303
307
|
);
|
|
304
308
|
|
|
305
309
|
this.services = {
|
|
306
|
-
urlResolver:
|
|
307
|
-
documentServiceFactory:
|
|
308
|
-
loaderProps.documentServiceFactory,
|
|
309
|
-
),
|
|
310
|
+
urlResolver: loaderProps.urlResolver,
|
|
311
|
+
documentServiceFactory: loaderProps.documentServiceFactory,
|
|
310
312
|
codeLoader: loaderProps.codeLoader,
|
|
311
313
|
options: loaderProps.options ?? {},
|
|
312
314
|
scope,
|
package/src/packageVersion.ts
CHANGED
|
@@ -5,13 +5,16 @@
|
|
|
5
5
|
|
|
6
6
|
import { IDisposable } from "@fluidframework/common-definitions";
|
|
7
7
|
import { IDocumentStorageService, ISummaryContext } from "@fluidframework/driver-definitions";
|
|
8
|
-
import { combineAppAndProtocolSummary } from "@fluidframework/driver-utils";
|
|
9
8
|
import { ISummaryTree } from "@fluidframework/protocol-definitions";
|
|
10
9
|
|
|
10
|
+
/**
|
|
11
|
+
* A storage service wrapper whose sole job is to intercept calls to uploadSummaryWithContext and ensure they include
|
|
12
|
+
* the protocol summary, using the provided callback to add it if necessary.
|
|
13
|
+
*/
|
|
11
14
|
export class ProtocolTreeStorageService implements IDocumentStorageService, IDisposable {
|
|
12
15
|
constructor(
|
|
13
16
|
private readonly internalStorageService: IDocumentStorageService & IDisposable,
|
|
14
|
-
private readonly
|
|
17
|
+
private readonly addProtocolSummaryIfMissing: (summaryTree: ISummaryTree) => ISummaryTree,
|
|
15
18
|
) {}
|
|
16
19
|
public get policies() {
|
|
17
20
|
return this.internalStorageService.policies;
|
|
@@ -35,7 +38,7 @@ export class ProtocolTreeStorageService implements IDocumentStorageService, IDis
|
|
|
35
38
|
context: ISummaryContext,
|
|
36
39
|
): Promise<string> {
|
|
37
40
|
return this.internalStorageService.uploadSummaryWithContext(
|
|
38
|
-
|
|
41
|
+
this.addProtocolSummaryIfMissing(summary),
|
|
39
42
|
context,
|
|
40
43
|
);
|
|
41
44
|
}
|