@fluidframework/container-loader 2.0.0-rc.2.0.2 → 2.0.0-rc.3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/api-report/container-loader.api.md +13 -13
- package/dist/attachment.d.ts +6 -9
- package/dist/attachment.d.ts.map +1 -1
- package/dist/attachment.js +5 -5
- package/dist/attachment.js.map +1 -1
- package/dist/audience.d.ts +1 -1
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js +4 -4
- package/dist/audience.js.map +1 -1
- package/dist/catchUpMonitor.d.ts +1 -1
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js +2 -2
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/connectionManager.d.ts +4 -4
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +48 -43
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +3 -3
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +27 -27
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +9 -46
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +105 -116
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +19 -7
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +7 -2
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +3 -3
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +6 -6
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +4 -3
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +2 -2
- package/dist/contracts.js.map +1 -1
- package/dist/debugLogger.d.ts +2 -1
- package/dist/debugLogger.d.ts.map +1 -1
- package/dist/debugLogger.js +4 -4
- package/dist/debugLogger.js.map +1 -1
- package/dist/deltaManager.d.ts +11 -7
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +53 -50
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaQueue.d.ts +1 -1
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js +5 -5
- package/dist/deltaQueue.js.map +1 -1
- package/dist/error.d.ts +3 -2
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +5 -5
- package/dist/error.js.map +1 -1
- package/dist/legacy.d.ts +29 -0
- package/dist/loader.d.ts +4 -4
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +23 -23
- package/dist/loader.js.map +1 -1
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts +2 -2
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -1
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.js +2 -2
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -1
- package/dist/noopHeuristic.d.ts +1 -1
- package/dist/noopHeuristic.d.ts.map +1 -1
- package/dist/noopHeuristic.js +6 -6
- package/dist/noopHeuristic.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/protocol.d.ts +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +2 -2
- package/dist/protocol.js.map +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts +4 -4
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/public.d.ts +14 -0
- package/dist/quorum.d.ts +1 -1
- package/dist/quorum.d.ts.map +1 -1
- package/dist/quorum.js +4 -0
- package/dist/quorum.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts +2 -2
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +7 -7
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/serializedStateManager.d.ts +86 -16
- package/dist/serializedStateManager.d.ts.map +1 -1
- package/dist/serializedStateManager.js +182 -82
- package/dist/serializedStateManager.js.map +1 -1
- package/dist/utils.d.ts +24 -9
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +82 -25
- package/dist/utils.js.map +1 -1
- package/internal.d.ts +11 -0
- package/legacy.d.ts +11 -0
- package/lib/attachment.d.ts +6 -9
- package/lib/attachment.d.ts.map +1 -1
- package/lib/attachment.js +1 -1
- package/lib/attachment.js.map +1 -1
- package/lib/audience.d.ts +1 -1
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js +1 -1
- package/lib/audience.js.map +1 -1
- package/lib/catchUpMonitor.d.ts +1 -1
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js +1 -1
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/connectionManager.d.ts +4 -4
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +11 -6
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +3 -3
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +2 -2
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +9 -46
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +37 -48
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +19 -7
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +7 -2
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +3 -3
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +2 -2
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +4 -3
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/debugLogger.d.ts +2 -1
- package/lib/debugLogger.d.ts.map +1 -1
- package/lib/debugLogger.js +1 -1
- package/lib/debugLogger.js.map +1 -1
- package/lib/deltaManager.d.ts +11 -7
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +13 -10
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaQueue.d.ts +1 -1
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js +2 -2
- package/lib/deltaQueue.js.map +1 -1
- package/lib/error.d.ts +3 -2
- package/lib/error.d.ts.map +1 -1
- package/lib/error.js +2 -2
- package/lib/error.js.map +1 -1
- package/lib/legacy.d.ts +29 -0
- package/lib/loader.d.ts +4 -4
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +4 -4
- package/lib/loader.js.map +1 -1
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts +2 -2
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -1
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.js +2 -2
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -1
- package/lib/noopHeuristic.d.ts +1 -1
- package/lib/noopHeuristic.d.ts.map +1 -1
- package/lib/noopHeuristic.js +2 -2
- package/lib/noopHeuristic.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/protocol.d.ts +1 -1
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +1 -1
- package/lib/protocol.js.map +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts +4 -4
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.js.map +1 -1
- package/lib/public.d.ts +14 -0
- package/lib/quorum.d.ts +1 -1
- package/lib/quorum.d.ts.map +1 -1
- package/lib/quorum.js +4 -0
- package/lib/quorum.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts +2 -2
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +3 -3
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/serializedStateManager.d.ts +86 -16
- package/lib/serializedStateManager.d.ts.map +1 -1
- package/lib/serializedStateManager.js +174 -77
- package/lib/serializedStateManager.js.map +1 -1
- package/lib/utils.d.ts +24 -9
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +69 -15
- package/lib/utils.js.map +1 -1
- package/package.json +37 -58
- package/src/attachment.ts +10 -8
- package/src/audience.ts +3 -2
- package/src/catchUpMonitor.ts +2 -2
- package/src/connectionManager.ts +27 -20
- package/src/connectionStateHandler.ts +7 -7
- package/src/container.ts +90 -143
- package/src/containerContext.ts +22 -12
- package/src/containerStorageAdapter.ts +7 -6
- package/src/contracts.ts +4 -5
- package/src/debugLogger.ts +3 -4
- package/src/deltaManager.ts +40 -30
- package/src/deltaQueue.ts +2 -2
- package/src/error.ts +5 -4
- package/src/loader.ts +25 -23
- package/src/location-redirection-utilities/resolveWithLocationRedirection.ts +4 -4
- package/src/noopHeuristic.ts +3 -3
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +2 -2
- package/src/protocolTreeDocumentStorageService.ts +4 -1
- package/src/quorum.ts +2 -1
- package/src/retriableDocumentStorageService.ts +6 -5
- package/src/serializedStateManager.ts +299 -111
- package/src/utils.ts +103 -24
- package/api-extractor-cjs.json +0 -8
- package/dist/container-loader-alpha.d.ts +0 -275
- package/dist/container-loader-beta.d.ts +0 -101
- package/dist/container-loader-public.d.ts +0 -101
- package/dist/container-loader-untrimmed.d.ts +0 -331
- package/lib/container-loader-alpha.d.ts +0 -275
- package/lib/container-loader-beta.d.ts +0 -101
- package/lib/container-loader-public.d.ts +0 -101
- package/lib/container-loader-untrimmed.d.ts +0 -331
- package/lib/test/attachment.spec.js +0 -380
- package/lib/test/attachment.spec.js.map +0 -1
- package/lib/test/catchUpMonitor.spec.js +0 -88
- package/lib/test/catchUpMonitor.spec.js.map +0 -1
- package/lib/test/connectionManager.spec.js +0 -201
- package/lib/test/connectionManager.spec.js.map +0 -1
- package/lib/test/connectionStateHandler.spec.js +0 -555
- package/lib/test/connectionStateHandler.spec.js.map +0 -1
- package/lib/test/container.spec.js +0 -64
- package/lib/test/container.spec.js.map +0 -1
- package/lib/test/deltaManager.spec.js +0 -405
- package/lib/test/deltaManager.spec.js.map +0 -1
- package/lib/test/loader.spec.js +0 -212
- package/lib/test/loader.spec.js.map +0 -1
- package/lib/test/locationRedirectionTests.spec.js +0 -44
- package/lib/test/locationRedirectionTests.spec.js.map +0 -1
- package/lib/test/serializedStateManager.spec.js +0 -148
- package/lib/test/serializedStateManager.spec.js.map +0 -1
- package/lib/test/snapshotConversionTest.spec.js +0 -79
- package/lib/test/snapshotConversionTest.spec.js.map +0 -1
- package/lib/test/types/validateContainerLoaderPrevious.generated.js +0 -38
- package/lib/test/types/validateContainerLoaderPrevious.generated.js.map +0 -1
- package/lib/test/utils.spec.js +0 -31
- package/lib/test/utils.spec.js.map +0 -1
- /package/{dist → lib}/tsdoc-metadata.json +0 -0
|
@@ -4,37 +4,83 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
IGetPendingLocalStateProps,
|
|
8
|
+
IRuntime,
|
|
9
|
+
} from "@fluidframework/container-definitions/internal";
|
|
10
|
+
import { assert } from "@fluidframework/core-utils/internal";
|
|
11
|
+
import {
|
|
12
|
+
IDocumentStorageService,
|
|
13
|
+
IResolvedUrl,
|
|
14
|
+
ISnapshot,
|
|
15
|
+
} from "@fluidframework/driver-definitions/internal";
|
|
16
|
+
import { isInstanceOfISnapshot } from "@fluidframework/driver-utils/internal";
|
|
17
|
+
import {
|
|
18
|
+
type IDocumentAttributes,
|
|
7
19
|
ISequencedDocumentMessage,
|
|
8
20
|
ISnapshotTree,
|
|
9
21
|
IVersion,
|
|
10
22
|
} from "@fluidframework/protocol-definitions";
|
|
11
|
-
import {
|
|
23
|
+
import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
|
|
12
24
|
import {
|
|
13
|
-
ITelemetryLoggerExt,
|
|
14
25
|
MonitoringContext,
|
|
15
26
|
PerformanceEvent,
|
|
16
27
|
UsageError,
|
|
17
28
|
createChildMonitoringContext,
|
|
18
|
-
} from "@fluidframework/telemetry-utils";
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
IDocumentStorageService,
|
|
22
|
-
IResolvedUrl,
|
|
23
|
-
ISnapshot,
|
|
24
|
-
} from "@fluidframework/driver-definitions";
|
|
25
|
-
import { isInstanceOfISnapshot } from "@fluidframework/driver-utils";
|
|
29
|
+
} from "@fluidframework/telemetry-utils/internal";
|
|
30
|
+
|
|
26
31
|
import { ISerializableBlobContents, getBlobContentsFromTree } from "./containerStorageAdapter.js";
|
|
27
|
-
import {
|
|
32
|
+
import { getDocumentAttributes } from "./utils.js";
|
|
33
|
+
|
|
34
|
+
export interface SnapshotWithBlobs {
|
|
35
|
+
/**
|
|
36
|
+
* Snapshot from which container initially loaded.
|
|
37
|
+
*/
|
|
38
|
+
baseSnapshot: ISnapshotTree;
|
|
39
|
+
/**
|
|
40
|
+
* Serializable blobs from the base snapshot. Used to load offline since
|
|
41
|
+
* storage is not available.
|
|
42
|
+
*/
|
|
43
|
+
snapshotBlobs: ISerializableBlobContents;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* State saved by a container at close time, to be used to load a new instance
|
|
47
|
+
* of the container to the same state
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
export interface IPendingContainerState extends SnapshotWithBlobs {
|
|
51
|
+
attached: true;
|
|
52
|
+
pendingRuntimeState: unknown;
|
|
53
|
+
/**
|
|
54
|
+
* All ops since base snapshot sequence number up to the latest op
|
|
55
|
+
* seen when the container was closed. Used to apply stashed (saved pending)
|
|
56
|
+
* ops at the same sequence number at which they were made.
|
|
57
|
+
*/
|
|
58
|
+
savedOps: ISequencedDocumentMessage[];
|
|
59
|
+
url: string;
|
|
60
|
+
clientId?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* State saved by a container in detached state, to be used to load a new instance
|
|
65
|
+
* of the container to the same state (rehydrate)
|
|
66
|
+
* @internal
|
|
67
|
+
*/
|
|
68
|
+
export interface IPendingDetachedContainerState extends SnapshotWithBlobs {
|
|
69
|
+
attached: false;
|
|
70
|
+
hasAttachmentBlobs: boolean;
|
|
71
|
+
pendingRuntimeState?: unknown;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ISnapshotInfo extends SnapshotWithBlobs {
|
|
75
|
+
snapshotSequenceNumber: number;
|
|
76
|
+
}
|
|
28
77
|
|
|
29
78
|
export class SerializedStateManager {
|
|
30
79
|
private readonly processedOps: ISequencedDocumentMessage[] = [];
|
|
31
|
-
private snapshot:
|
|
32
|
-
| {
|
|
33
|
-
tree: ISnapshotTree;
|
|
34
|
-
blobs: ISerializableBlobContents;
|
|
35
|
-
}
|
|
36
|
-
| undefined;
|
|
80
|
+
private snapshot: ISnapshotInfo | undefined;
|
|
37
81
|
private readonly mc: MonitoringContext;
|
|
82
|
+
private latestSnapshot: ISnapshotInfo | undefined;
|
|
83
|
+
private refreshSnapshot: Promise<void> | undefined;
|
|
38
84
|
|
|
39
85
|
constructor(
|
|
40
86
|
private readonly pendingLocalState: IPendingContainerState | undefined,
|
|
@@ -44,6 +90,7 @@ export class SerializedStateManager {
|
|
|
44
90
|
"readBlob" | "getSnapshotTree" | "getSnapshot" | "getVersions"
|
|
45
91
|
>,
|
|
46
92
|
private readonly _offlineLoadEnabled: boolean,
|
|
93
|
+
private readonly newSnapshotFetched?: () => void,
|
|
47
94
|
) {
|
|
48
95
|
this.mc = createChildMonitoringContext({
|
|
49
96
|
logger: subLogger,
|
|
@@ -58,107 +105,101 @@ export class SerializedStateManager {
|
|
|
58
105
|
public addProcessedOp(message: ISequencedDocumentMessage) {
|
|
59
106
|
if (this.offlineLoadEnabled) {
|
|
60
107
|
this.processedOps.push(message);
|
|
108
|
+
this.updateSnapshotAndProcessedOpsMaybe();
|
|
61
109
|
}
|
|
62
110
|
}
|
|
63
111
|
|
|
64
|
-
private async getVersion(version: string | null): Promise<IVersion | undefined> {
|
|
65
|
-
const versions = await this.storageAdapter.getVersions(version, 1);
|
|
66
|
-
return versions[0];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
112
|
public async fetchSnapshot(
|
|
70
113
|
specifiedVersion: string | undefined,
|
|
71
|
-
supportGetSnapshotApi: boolean
|
|
114
|
+
supportGetSnapshotApi: boolean,
|
|
72
115
|
) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (this.pendingLocalState) {
|
|
81
|
-
this.snapshot = {
|
|
82
|
-
tree: this.pendingLocalState.baseSnapshot,
|
|
83
|
-
blobs: this.pendingLocalState.snapshotBlobs,
|
|
84
|
-
};
|
|
85
|
-
} else {
|
|
86
|
-
assert(snapshotTree !== undefined, 0x8e4 /* Snapshot should exist */);
|
|
116
|
+
if (this.pendingLocalState === undefined) {
|
|
117
|
+
const { baseSnapshot, version } = await getSnapshotTree(
|
|
118
|
+
this.mc,
|
|
119
|
+
this.storageAdapter,
|
|
120
|
+
supportGetSnapshotApi,
|
|
121
|
+
specifiedVersion,
|
|
122
|
+
);
|
|
87
123
|
// non-interactive clients will not have any pending state we want to save
|
|
88
124
|
if (this.offlineLoadEnabled) {
|
|
89
|
-
const
|
|
90
|
-
|
|
125
|
+
const snapshotBlobs = await getBlobContentsFromTree(
|
|
126
|
+
baseSnapshot,
|
|
127
|
+
this.storageAdapter,
|
|
128
|
+
);
|
|
129
|
+
const attributes = await getDocumentAttributes(this.storageAdapter, baseSnapshot);
|
|
130
|
+
this.snapshot = {
|
|
131
|
+
baseSnapshot,
|
|
132
|
+
snapshotBlobs,
|
|
133
|
+
snapshotSequenceNumber: attributes.sequenceNumber,
|
|
134
|
+
};
|
|
91
135
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
136
|
+
return { baseSnapshot, version };
|
|
137
|
+
} else {
|
|
138
|
+
const { baseSnapshot, snapshotBlobs } = this.pendingLocalState;
|
|
139
|
+
const attributes = await getDocumentAttributes(this.storageAdapter, baseSnapshot);
|
|
140
|
+
this.snapshot = {
|
|
141
|
+
baseSnapshot,
|
|
142
|
+
snapshotBlobs,
|
|
143
|
+
snapshotSequenceNumber: attributes.sequenceNumber,
|
|
144
|
+
};
|
|
145
|
+
this.refreshSnapshot ??= (async () => {
|
|
146
|
+
this.latestSnapshot = await getLatestSnapshotInfo(
|
|
147
|
+
this.mc,
|
|
148
|
+
this.storageAdapter,
|
|
149
|
+
supportGetSnapshotApi,
|
|
150
|
+
);
|
|
151
|
+
this.newSnapshotFetched?.();
|
|
152
|
+
this.updateSnapshotAndProcessedOpsMaybe();
|
|
153
|
+
})();
|
|
95
154
|
|
|
96
|
-
|
|
97
|
-
specifiedVersion: string | undefined,
|
|
98
|
-
supportGetSnapshotApi: boolean | undefined,
|
|
99
|
-
): Promise<{ snapshot?: ISnapshot | ISnapshotTree; version?: IVersion }> {
|
|
100
|
-
if (
|
|
101
|
-
this.mc.config.getBoolean("Fluid.Container.UseLoadingGroupIdForSnapshotFetch") ===
|
|
102
|
-
true &&
|
|
103
|
-
supportGetSnapshotApi === true
|
|
104
|
-
) {
|
|
105
|
-
const snapshot =
|
|
106
|
-
(await this.storageAdapter.getSnapshot?.({
|
|
107
|
-
versionId: specifiedVersion,
|
|
108
|
-
})) ?? undefined;
|
|
109
|
-
const version: IVersion | undefined =
|
|
110
|
-
snapshot?.snapshotTree.id === undefined
|
|
111
|
-
? undefined
|
|
112
|
-
: {
|
|
113
|
-
id: snapshot.snapshotTree.id,
|
|
114
|
-
treeId: snapshot.snapshotTree.id,
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
if (snapshot === undefined && specifiedVersion !== undefined) {
|
|
118
|
-
this.mc.logger.sendErrorEvent({
|
|
119
|
-
eventName: "getSnapshotTreeFailed",
|
|
120
|
-
id: specifiedVersion,
|
|
121
|
-
});
|
|
122
|
-
// Not sure if this should be here actually
|
|
123
|
-
} else if (snapshot !== undefined && version?.id === undefined) {
|
|
124
|
-
this.mc.logger.sendErrorEvent({
|
|
125
|
-
eventName: "getSnapshotFetchedTreeWithoutVersionId",
|
|
126
|
-
hasVersion: version !== undefined, // if hasVersion is true, this means that the contract with the service was broken.
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
return { snapshot, version };
|
|
155
|
+
return { baseSnapshot, version: undefined };
|
|
130
156
|
}
|
|
131
|
-
return this.fetchSnapshotTree(specifiedVersion);
|
|
132
157
|
}
|
|
133
158
|
|
|
134
159
|
/**
|
|
135
|
-
*
|
|
136
|
-
* @param specifiedVersion - The specific version of the snapshot to retrieve
|
|
137
|
-
* @returns The snapshot requested, or the latest snapshot if no version was specified, plus version ID
|
|
160
|
+
* Updates class snapshot and processedOps if we have a new snapshot and it's among processedOps range.
|
|
138
161
|
*/
|
|
139
|
-
private
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (version === undefined && specifiedVersion !== undefined) {
|
|
145
|
-
// We should have a defined version to load from if specified version requested
|
|
146
|
-
this.mc.logger.sendErrorEvent({
|
|
147
|
-
eventName: "NoVersionFoundWhenSpecified",
|
|
148
|
-
id: specifiedVersion,
|
|
149
|
-
});
|
|
162
|
+
private updateSnapshotAndProcessedOpsMaybe() {
|
|
163
|
+
if (this.latestSnapshot === undefined || this.processedOps.length === 0) {
|
|
164
|
+
// can't refresh latest snapshot until we have processed the ops up to it.
|
|
165
|
+
// Pending state would be behind the latest snapshot.
|
|
166
|
+
return;
|
|
150
167
|
}
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
this.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
168
|
+
const snapshotSequenceNumber = this.latestSnapshot.snapshotSequenceNumber;
|
|
169
|
+
const firstProcessedOpSequenceNumber = this.processedOps[0].sequenceNumber;
|
|
170
|
+
const lastProcessedOpSequenceNumber =
|
|
171
|
+
this.processedOps[this.processedOps.length - 1].sequenceNumber;
|
|
172
|
+
|
|
173
|
+
if (snapshotSequenceNumber < firstProcessedOpSequenceNumber) {
|
|
174
|
+
// Snapshot seq number is older than our first processed op, which could mean we're fetching
|
|
175
|
+
// the same snapshot that we already have or snapshot is too old, implicating an unexpected behavior.
|
|
176
|
+
this.mc.logger.sendTelemetryEvent({
|
|
177
|
+
eventName: "OldSnapshotFetchWhileRefreshing",
|
|
178
|
+
snapshotSequenceNumber,
|
|
179
|
+
firstProcessedOpSequenceNumber,
|
|
180
|
+
lastProcessedOpSequenceNumber,
|
|
181
|
+
stashedSnapshotSequenceNumber: this.snapshot?.snapshotSequenceNumber,
|
|
182
|
+
});
|
|
183
|
+
this.latestSnapshot = undefined;
|
|
184
|
+
} else if (snapshotSequenceNumber <= lastProcessedOpSequenceNumber) {
|
|
185
|
+
// Snapshot seq num is between the first and last processed op.
|
|
186
|
+
// Remove the ops that are already part of the snapshot
|
|
187
|
+
this.processedOps.splice(
|
|
188
|
+
0,
|
|
189
|
+
snapshotSequenceNumber - firstProcessedOpSequenceNumber + 1,
|
|
190
|
+
);
|
|
191
|
+
this.snapshot = this.latestSnapshot;
|
|
192
|
+
this.latestSnapshot = undefined;
|
|
193
|
+
this.mc.logger.sendTelemetryEvent({
|
|
194
|
+
eventName: "SnapshotRefreshed",
|
|
195
|
+
snapshotSequenceNumber,
|
|
196
|
+
firstProcessedOpSequenceNumber,
|
|
197
|
+
newFirstProcessedOpSequenceNumber:
|
|
198
|
+
this.processedOps.length === 0
|
|
199
|
+
? undefined
|
|
200
|
+
: this.processedOps[0].sequenceNumber,
|
|
159
201
|
});
|
|
160
202
|
}
|
|
161
|
-
return { snapshot, version };
|
|
162
203
|
}
|
|
163
204
|
|
|
164
205
|
/**
|
|
@@ -166,15 +207,25 @@ export class SerializedStateManager {
|
|
|
166
207
|
* base snapshot when attaching.
|
|
167
208
|
* @param snapshot - snapshot and blobs collected while attaching
|
|
168
209
|
*/
|
|
169
|
-
public
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
210
|
+
public setInitialSnapshot(snapshot: SnapshotWithBlobs | undefined) {
|
|
211
|
+
if (this.offlineLoadEnabled) {
|
|
212
|
+
assert(
|
|
213
|
+
this.snapshot === undefined,
|
|
214
|
+
0x937 /* inital snapshot should only be defined once */,
|
|
215
|
+
);
|
|
216
|
+
assert(snapshot !== undefined, 0x938 /* attachment snapshot should be defined */);
|
|
217
|
+
const { baseSnapshot, snapshotBlobs } = snapshot;
|
|
218
|
+
const attributesHash =
|
|
219
|
+
".protocol" in baseSnapshot.trees
|
|
220
|
+
? baseSnapshot.trees[".protocol"].blobs.attributes
|
|
221
|
+
: baseSnapshot.blobs[".attributes"];
|
|
222
|
+
const attributes = JSON.parse(snapshotBlobs[attributesHash]);
|
|
223
|
+
assert(
|
|
224
|
+
attributes.sequenceNumber === 0,
|
|
225
|
+
0x939 /* trying to set a non attachment snapshot */,
|
|
226
|
+
);
|
|
227
|
+
this.snapshot = { ...snapshot, snapshotSequenceNumber: attributes.sequenceNumber };
|
|
228
|
+
}
|
|
178
229
|
}
|
|
179
230
|
|
|
180
231
|
public async getPendingLocalStateCore(
|
|
@@ -202,8 +253,8 @@ export class SerializedStateManager {
|
|
|
202
253
|
const pendingState: IPendingContainerState = {
|
|
203
254
|
attached: true,
|
|
204
255
|
pendingRuntimeState,
|
|
205
|
-
baseSnapshot: this.snapshot.
|
|
206
|
-
snapshotBlobs: this.snapshot.
|
|
256
|
+
baseSnapshot: this.snapshot.baseSnapshot,
|
|
257
|
+
snapshotBlobs: this.snapshot.snapshotBlobs,
|
|
207
258
|
savedOps: this.processedOps,
|
|
208
259
|
url: resolvedUrl.url,
|
|
209
260
|
// no need to save this if there is no pending runtime state
|
|
@@ -215,3 +266,140 @@ export class SerializedStateManager {
|
|
|
215
266
|
);
|
|
216
267
|
}
|
|
217
268
|
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Retrieves the most recent snapshot and returns its info.
|
|
272
|
+
*
|
|
273
|
+
* @param mc - The monitoring context.
|
|
274
|
+
* @param storageAdapter - The storage adapter providing methods to retrieve the snapshot.
|
|
275
|
+
* @param supportGetSnapshotApi - a boolean indicating whether to use the fetchISnapshot or fetchISnapshotTree.
|
|
276
|
+
* @returns a SnapshotInfo object containing the snapshot tree, snapshot blobs and its sequence number.
|
|
277
|
+
*/
|
|
278
|
+
export async function getLatestSnapshotInfo(
|
|
279
|
+
mc: MonitoringContext,
|
|
280
|
+
storageAdapter: Pick<
|
|
281
|
+
IDocumentStorageService,
|
|
282
|
+
"getSnapshot" | "getSnapshotTree" | "getVersions" | "readBlob"
|
|
283
|
+
>,
|
|
284
|
+
supportGetSnapshotApi: boolean,
|
|
285
|
+
): Promise<ISnapshotInfo | undefined> {
|
|
286
|
+
return PerformanceEvent.timedExecAsync(
|
|
287
|
+
mc.logger,
|
|
288
|
+
{ eventName: "GetLatestSnapshotInfo" },
|
|
289
|
+
async () => {
|
|
290
|
+
const { baseSnapshot } = await getSnapshotTree(
|
|
291
|
+
mc,
|
|
292
|
+
storageAdapter,
|
|
293
|
+
supportGetSnapshotApi,
|
|
294
|
+
undefined,
|
|
295
|
+
);
|
|
296
|
+
const snapshotBlobs = await getBlobContentsFromTree(baseSnapshot, storageAdapter);
|
|
297
|
+
const attributes: IDocumentAttributes = await getDocumentAttributes(
|
|
298
|
+
storageAdapter,
|
|
299
|
+
baseSnapshot,
|
|
300
|
+
);
|
|
301
|
+
const snapshotSequenceNumber = attributes.sequenceNumber;
|
|
302
|
+
return { baseSnapshot, snapshotBlobs, snapshotSequenceNumber };
|
|
303
|
+
},
|
|
304
|
+
).catch(() => undefined);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Retrieves a snapshot from the storage adapter and transforms it into an ISnapshotTree object.
|
|
309
|
+
*
|
|
310
|
+
* @param mc - The monitoring context.
|
|
311
|
+
* @param storageAdapter - The storage adapter providing methods to retrieve the snapshot.
|
|
312
|
+
* @param supportGetSnapshotApi - a boolean indicating whether to use the fetchISnapshot or fetchISnapshotTree.
|
|
313
|
+
* @param specifiedVersion - An optional version string specifying the version of the snapshot tree to fetch.
|
|
314
|
+
* @returns - An ISnapshotTree and its version.
|
|
315
|
+
*/
|
|
316
|
+
async function getSnapshotTree(
|
|
317
|
+
mc: MonitoringContext,
|
|
318
|
+
storageAdapter: Pick<
|
|
319
|
+
IDocumentStorageService,
|
|
320
|
+
"getSnapshot" | "getSnapshotTree" | "getVersions"
|
|
321
|
+
>,
|
|
322
|
+
supportGetSnapshotApi: boolean,
|
|
323
|
+
specifiedVersion: string | undefined,
|
|
324
|
+
): Promise<{ baseSnapshot: ISnapshotTree; version?: IVersion }> {
|
|
325
|
+
const { snapshot, version } = supportGetSnapshotApi
|
|
326
|
+
? await fetchISnapshot(mc, storageAdapter, specifiedVersion)
|
|
327
|
+
: await fetchISnapshotTree(mc, storageAdapter, specifiedVersion);
|
|
328
|
+
const baseSnapshot: ISnapshotTree | undefined = isInstanceOfISnapshot(snapshot)
|
|
329
|
+
? snapshot.snapshotTree
|
|
330
|
+
: snapshot;
|
|
331
|
+
assert(baseSnapshot !== undefined, 0x8e4 /* Snapshot should exist */);
|
|
332
|
+
return { baseSnapshot, version };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Fetches an ISnapshot from a storage adapter based on the specified version.
|
|
337
|
+
*
|
|
338
|
+
* @param mc - The monitoring context.
|
|
339
|
+
* @param storageAdapter - The storage adapter providing a getSnapshot method to retrieve the ISnapshot and version.
|
|
340
|
+
* @param specifiedVersion - An optional version string specifying the version of the snapshot tree to fetch.
|
|
341
|
+
* @returns - The fetched snapshot tree and its version.
|
|
342
|
+
*/
|
|
343
|
+
export async function fetchISnapshot(
|
|
344
|
+
mc: MonitoringContext,
|
|
345
|
+
storageAdapter: Pick<IDocumentStorageService, "getSnapshot">,
|
|
346
|
+
specifiedVersion: string | undefined,
|
|
347
|
+
): Promise<{ snapshot?: ISnapshot; version?: IVersion }> {
|
|
348
|
+
const snapshot = await storageAdapter.getSnapshot?.({ versionId: specifiedVersion });
|
|
349
|
+
const version: IVersion | undefined =
|
|
350
|
+
snapshot?.snapshotTree.id === undefined
|
|
351
|
+
? undefined
|
|
352
|
+
: {
|
|
353
|
+
id: snapshot.snapshotTree.id,
|
|
354
|
+
treeId: snapshot.snapshotTree.id,
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
if (snapshot === undefined && specifiedVersion !== undefined) {
|
|
358
|
+
mc.logger.sendErrorEvent({
|
|
359
|
+
eventName: "getSnapshotTreeFailed",
|
|
360
|
+
id: specifiedVersion,
|
|
361
|
+
});
|
|
362
|
+
} else if (snapshot !== undefined && version?.id === undefined) {
|
|
363
|
+
mc.logger.sendErrorEvent({
|
|
364
|
+
eventName: "getSnapshotFetchedTreeWithoutVersionId",
|
|
365
|
+
hasVersion: version !== undefined, // if hasVersion is true, this means that the contract with the service was broken.
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
return { snapshot, version };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Fetches an ISnapshotTree from a storage adapter based on the specified version.
|
|
373
|
+
*
|
|
374
|
+
* @param mc - The monitoring context.
|
|
375
|
+
* @param storageAdapter - The storage adapter providing methods to retrieve the ISnapshotTree and version.
|
|
376
|
+
* @param specifiedVersion - An optional version string specifying the version of the snapshot tree to fetch.
|
|
377
|
+
* @returns - The fetched snapshot tree and its version.
|
|
378
|
+
*/
|
|
379
|
+
export async function fetchISnapshotTree(
|
|
380
|
+
mc: MonitoringContext,
|
|
381
|
+
storageAdapter: Pick<IDocumentStorageService, "getSnapshotTree" | "getVersions">,
|
|
382
|
+
specifiedVersion: string | undefined,
|
|
383
|
+
): Promise<{ snapshot?: ISnapshotTree; version?: IVersion | undefined }> {
|
|
384
|
+
const versions = await storageAdapter.getVersions(specifiedVersion ?? null, 1);
|
|
385
|
+
const version = versions[0];
|
|
386
|
+
|
|
387
|
+
if (version === undefined && specifiedVersion !== undefined) {
|
|
388
|
+
// We should have a defined version to load from if specified version requested
|
|
389
|
+
mc.logger.sendErrorEvent({
|
|
390
|
+
eventName: "NoVersionFoundWhenSpecified",
|
|
391
|
+
id: specifiedVersion,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
const snapshot = (await storageAdapter.getSnapshotTree(version)) ?? undefined;
|
|
395
|
+
|
|
396
|
+
if (snapshot === undefined && version !== undefined) {
|
|
397
|
+
mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
|
|
398
|
+
} else if (snapshot !== undefined && version?.id === undefined) {
|
|
399
|
+
mc.logger.sendErrorEvent({
|
|
400
|
+
eventName: "getSnapshotFetchedTreeWithoutVersionId",
|
|
401
|
+
hasVersion: version !== undefined, // if hasVersion is true, this means that the contract with the service was broken.
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
return { snapshot, version };
|
|
405
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -3,19 +3,34 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
6
|
+
import { Uint8ArrayToString, bufferToString, stringToBuffer } from "@fluid-internal/client-utils";
|
|
7
|
+
import { assert, compareArrays, unreachableCase } from "@fluidframework/core-utils/internal";
|
|
8
|
+
import { DriverErrorTypes } from "@fluidframework/driver-definitions";
|
|
9
|
+
import {
|
|
10
|
+
IDocumentStorageService,
|
|
11
|
+
type ISnapshot,
|
|
12
|
+
} from "@fluidframework/driver-definitions/internal";
|
|
11
13
|
import {
|
|
12
14
|
CombinedAppAndProtocolSummary,
|
|
13
15
|
DeltaStreamConnectionForbiddenError,
|
|
14
16
|
isCombinedAppAndProtocolSummary,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
readAndParse,
|
|
18
|
+
} from "@fluidframework/driver-utils/internal";
|
|
19
|
+
import {
|
|
20
|
+
IDocumentAttributes,
|
|
21
|
+
ISnapshotTree,
|
|
22
|
+
ISummaryTree,
|
|
23
|
+
SummaryType,
|
|
24
|
+
} from "@fluidframework/protocol-definitions";
|
|
25
|
+
import { LoggingError, UsageError } from "@fluidframework/telemetry-utils/internal";
|
|
26
|
+
import { v4 as uuid } from "uuid";
|
|
27
|
+
|
|
17
28
|
import { ISerializableBlobContents } from "./containerStorageAdapter.js";
|
|
18
|
-
import {
|
|
29
|
+
import type {
|
|
30
|
+
IPendingDetachedContainerState,
|
|
31
|
+
ISnapshotInfo,
|
|
32
|
+
SnapshotWithBlobs,
|
|
33
|
+
} from "./serializedStateManager.js";
|
|
19
34
|
|
|
20
35
|
// This is used when we rehydrate a container from the snapshot. Here we put the blob contents
|
|
21
36
|
// in separate property: blobContents.
|
|
@@ -28,7 +43,7 @@ export interface ISnapshotTreeWithBlobContents extends ISnapshotTree {
|
|
|
28
43
|
* Interface to represent the parsed parts of IResolvedUrl.url to help
|
|
29
44
|
* in getting info about different parts of the url.
|
|
30
45
|
* May not be compatible or relevant for any Url Resolver
|
|
31
|
-
* @
|
|
46
|
+
* @alpha
|
|
32
47
|
*/
|
|
33
48
|
export interface IParsedUrl {
|
|
34
49
|
/**
|
|
@@ -57,7 +72,7 @@ export interface IParsedUrl {
|
|
|
57
72
|
* with urls of type: protocol://<string>/.../..?<querystring>
|
|
58
73
|
* @param url - This is the IResolvedUrl.url part of the resolved url.
|
|
59
74
|
* @returns The IParsedUrl representing the input URL, or undefined if the format was not supported
|
|
60
|
-
* @
|
|
75
|
+
* @alpha
|
|
61
76
|
*/
|
|
62
77
|
export function tryParseCompatibleResolvedUrl(url: string): IParsedUrl | undefined {
|
|
63
78
|
const parsed = new URL(url);
|
|
@@ -111,10 +126,7 @@ export function combineAppAndProtocolSummary(
|
|
|
111
126
|
* to align detached container format with IPendingContainerState
|
|
112
127
|
* @param summary - ISummaryTree
|
|
113
128
|
*/
|
|
114
|
-
function convertSummaryToSnapshotAndBlobs(summary: ISummaryTree): {
|
|
115
|
-
tree: ISnapshotTree;
|
|
116
|
-
blobs: ISerializableBlobContents;
|
|
117
|
-
} {
|
|
129
|
+
function convertSummaryToSnapshotAndBlobs(summary: ISummaryTree): SnapshotWithBlobs {
|
|
118
130
|
let blobContents: ISerializableBlobContents = {};
|
|
119
131
|
const treeNode: ISnapshotTree = {
|
|
120
132
|
blobs: {},
|
|
@@ -129,9 +141,9 @@ function convertSummaryToSnapshotAndBlobs(summary: ISummaryTree): {
|
|
|
129
141
|
|
|
130
142
|
switch (summaryObject.type) {
|
|
131
143
|
case SummaryType.Tree: {
|
|
132
|
-
const
|
|
133
|
-
treeNode.trees[key] =
|
|
134
|
-
blobContents = { ...blobContents, ...
|
|
144
|
+
const innerSnapshot = convertSummaryToSnapshotAndBlobs(summaryObject);
|
|
145
|
+
treeNode.trees[key] = innerSnapshot.baseSnapshot;
|
|
146
|
+
blobContents = { ...blobContents, ...innerSnapshot.snapshotBlobs };
|
|
135
147
|
break;
|
|
136
148
|
}
|
|
137
149
|
case SummaryType.Attachment:
|
|
@@ -151,13 +163,58 @@ function convertSummaryToSnapshotAndBlobs(summary: ISummaryTree): {
|
|
|
151
163
|
throw new LoggingError(
|
|
152
164
|
"No handles should be there in summary in detached container!!",
|
|
153
165
|
);
|
|
154
|
-
break;
|
|
155
166
|
default: {
|
|
156
167
|
unreachableCase(summaryObject, `Unknown tree type ${(summaryObject as any).type}`);
|
|
157
168
|
}
|
|
158
169
|
}
|
|
159
170
|
}
|
|
160
|
-
|
|
171
|
+
const pendingSnapshot = { baseSnapshot: treeNode, snapshotBlobs: blobContents };
|
|
172
|
+
return pendingSnapshot;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Converts a snapshot to snapshotInfo with its blob contents
|
|
177
|
+
* to align detached container format with IPendingContainerState
|
|
178
|
+
*
|
|
179
|
+
* Note, this assumes the ISnapshot sequence number is defined. Otherwise an assert will be thrown
|
|
180
|
+
* @param snapshot - ISnapshot
|
|
181
|
+
*/
|
|
182
|
+
export function convertSnapshotToSnapshotInfo(snapshot: ISnapshot): ISnapshotInfo {
|
|
183
|
+
assert(snapshot.sequenceNumber !== undefined, 0x93a /* Snapshot sequence number is missing */);
|
|
184
|
+
const snapshotBlobs: ISerializableBlobContents = {};
|
|
185
|
+
for (const [blobId, arrayBufferLike] of snapshot.blobContents.entries()) {
|
|
186
|
+
snapshotBlobs[blobId] = bufferToString(arrayBufferLike, "utf8");
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
baseSnapshot: snapshot.snapshotTree,
|
|
190
|
+
snapshotBlobs,
|
|
191
|
+
snapshotSequenceNumber: snapshot.sequenceNumber,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Converts a snapshot to snapshotInfo with its blob contents
|
|
197
|
+
* to align detached container format with IPendingContainerState
|
|
198
|
+
*
|
|
199
|
+
* Note, this assumes the ISnapshot sequence number is defined. Otherwise an assert will be thrown
|
|
200
|
+
* @param snapshot - ISnapshot
|
|
201
|
+
*/
|
|
202
|
+
export function convertSnapshotInfoToSnapshot(
|
|
203
|
+
snapshotInfo: ISnapshotInfo,
|
|
204
|
+
snapshotSequenceNumber: number,
|
|
205
|
+
): ISnapshot {
|
|
206
|
+
const blobContents = new Map<string, ArrayBufferLike>();
|
|
207
|
+
for (const [blobId, serializedContent] of Object.entries(snapshotInfo.snapshotBlobs)) {
|
|
208
|
+
blobContents.set(blobId, stringToBuffer(serializedContent, "utf8"));
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
snapshotTree: snapshotInfo.baseSnapshot,
|
|
212
|
+
blobContents,
|
|
213
|
+
ops: [],
|
|
214
|
+
sequenceNumber: snapshotSequenceNumber,
|
|
215
|
+
latestSequenceNumber: undefined,
|
|
216
|
+
snapshotFormatV: 1,
|
|
217
|
+
};
|
|
161
218
|
}
|
|
162
219
|
|
|
163
220
|
/**
|
|
@@ -168,7 +225,7 @@ function convertSummaryToSnapshotAndBlobs(summary: ISummaryTree): {
|
|
|
168
225
|
function convertProtocolAndAppSummaryToSnapshotAndBlobs(
|
|
169
226
|
protocolSummaryTree: ISummaryTree,
|
|
170
227
|
appSummaryTree: ISummaryTree,
|
|
171
|
-
):
|
|
228
|
+
): SnapshotWithBlobs {
|
|
172
229
|
const combinedSummary: ISummaryTree = {
|
|
173
230
|
type: SummaryType.Tree,
|
|
174
231
|
tree: { ...appSummaryTree.tree },
|
|
@@ -181,7 +238,7 @@ function convertProtocolAndAppSummaryToSnapshotAndBlobs(
|
|
|
181
238
|
|
|
182
239
|
export const getSnapshotTreeAndBlobsFromSerializedContainer = (
|
|
183
240
|
detachedContainerSnapshot: ISummaryTree,
|
|
184
|
-
):
|
|
241
|
+
): SnapshotWithBlobs => {
|
|
185
242
|
assert(
|
|
186
243
|
isCombinedAppAndProtocolSummary(detachedContainerSnapshot),
|
|
187
244
|
0x8e6 /* Protocol and App summary trees should be present */,
|
|
@@ -264,12 +321,12 @@ export function getDetachedContainerStateFromSerializedContainer(
|
|
|
264
321
|
if (isPendingDetachedContainerState(parsedContainerState)) {
|
|
265
322
|
return parsedContainerState;
|
|
266
323
|
} else if (isCombinedAppAndProtocolSummary(parsedContainerState)) {
|
|
267
|
-
const {
|
|
324
|
+
const { baseSnapshot, snapshotBlobs } =
|
|
268
325
|
getSnapshotTreeAndBlobsFromSerializedContainer(parsedContainerState);
|
|
269
326
|
const detachedContainerState: IPendingDetachedContainerState = {
|
|
270
327
|
attached: false,
|
|
271
|
-
baseSnapshot
|
|
272
|
-
snapshotBlobs
|
|
328
|
+
baseSnapshot,
|
|
329
|
+
snapshotBlobs,
|
|
273
330
|
hasAttachmentBlobs: parsedContainerState.tree[hasBlobsSummaryTree] !== undefined,
|
|
274
331
|
};
|
|
275
332
|
return detachedContainerState;
|
|
@@ -305,3 +362,25 @@ export const runSingle = <A extends any[], R>(func: (...args: A) => Promise<R>)
|
|
|
305
362
|
return running.result;
|
|
306
363
|
};
|
|
307
364
|
};
|
|
365
|
+
|
|
366
|
+
export async function getDocumentAttributes(
|
|
367
|
+
storage: Pick<IDocumentStorageService, "readBlob">,
|
|
368
|
+
tree: ISnapshotTree | undefined,
|
|
369
|
+
): Promise<IDocumentAttributes> {
|
|
370
|
+
if (tree === undefined) {
|
|
371
|
+
return {
|
|
372
|
+
minimumSequenceNumber: 0,
|
|
373
|
+
sequenceNumber: 0,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Backward compatibility: old docs would have ".attributes" instead of "attributes"
|
|
378
|
+
const attributesHash =
|
|
379
|
+
".protocol" in tree.trees
|
|
380
|
+
? tree.trees[".protocol"].blobs.attributes
|
|
381
|
+
: tree.blobs[".attributes"];
|
|
382
|
+
|
|
383
|
+
const attributes = await readAndParse<IDocumentAttributes>(storage, attributesHash);
|
|
384
|
+
|
|
385
|
+
return attributes;
|
|
386
|
+
}
|