@fluidframework/routerlicious-driver 2.0.0-dev.4.1.0.148229 → 2.0.0-dev.4.3.0.157531
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 +5 -0
- package/README.md +38 -0
- package/dist/contracts.d.ts +15 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +7 -0
- package/dist/contracts.js.map +1 -0
- package/dist/deltaStorageService.d.ts +1 -1
- package/dist/deltaStorageService.d.ts.map +1 -1
- package/dist/deltaStorageService.js +7 -4
- package/dist/deltaStorageService.js.map +1 -1
- package/dist/documentService.d.ts +4 -2
- package/dist/documentService.d.ts.map +1 -1
- package/dist/documentService.js +11 -9
- package/dist/documentService.js.map +1 -1
- package/dist/documentServiceFactory.d.ts +2 -1
- package/dist/documentServiceFactory.d.ts.map +1 -1
- package/dist/documentServiceFactory.js +18 -8
- package/dist/documentServiceFactory.js.map +1 -1
- package/dist/documentStorageService.d.ts +3 -2
- package/dist/documentStorageService.d.ts.map +1 -1
- package/dist/documentStorageService.js +4 -4
- package/dist/documentStorageService.js.map +1 -1
- package/dist/gitManager.d.ts +30 -0
- package/dist/gitManager.d.ts.map +1 -0
- package/dist/gitManager.js +89 -0
- package/dist/gitManager.js.map +1 -0
- package/dist/historian.d.ts +33 -0
- package/dist/historian.d.ts.map +1 -0
- package/dist/historian.js +65 -0
- package/dist/historian.js.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/r11sSnapshotParser.d.ts +15 -0
- package/dist/r11sSnapshotParser.d.ts.map +1 -0
- package/dist/r11sSnapshotParser.js +75 -0
- package/dist/r11sSnapshotParser.js.map +1 -0
- package/dist/restWrapper.d.ts +21 -8
- package/dist/restWrapper.d.ts.map +1 -1
- package/dist/restWrapper.js +93 -25
- package/dist/restWrapper.js.map +1 -1
- package/dist/restWrapperBase.d.ts +26 -0
- package/dist/restWrapperBase.d.ts.map +1 -0
- package/dist/restWrapperBase.js +55 -0
- package/dist/restWrapperBase.js.map +1 -0
- package/dist/retriableGitManager.d.ts +10 -21
- package/dist/retriableGitManager.d.ts.map +1 -1
- package/dist/retriableGitManager.js +0 -36
- package/dist/retriableGitManager.js.map +1 -1
- package/dist/shreddedSummaryDocumentStorageService.d.ts +1 -1
- package/dist/shreddedSummaryDocumentStorageService.d.ts.map +1 -1
- package/dist/shreddedSummaryDocumentStorageService.js +6 -6
- package/dist/shreddedSummaryDocumentStorageService.js.map +1 -1
- package/dist/storageContracts.d.ts +44 -0
- package/dist/storageContracts.d.ts.map +1 -0
- package/dist/storageContracts.js +7 -0
- package/dist/storageContracts.js.map +1 -0
- package/dist/summaryTreeUploadManager.d.ts +23 -0
- package/dist/summaryTreeUploadManager.d.ts.map +1 -0
- package/dist/summaryTreeUploadManager.js +110 -0
- package/dist/summaryTreeUploadManager.js.map +1 -0
- package/dist/treeUtils.d.ts +7 -0
- package/dist/treeUtils.d.ts.map +1 -1
- package/dist/treeUtils.js +23 -1
- package/dist/treeUtils.js.map +1 -1
- package/dist/wholeSummaryDocumentStorageService.d.ts +5 -4
- package/dist/wholeSummaryDocumentStorageService.d.ts.map +1 -1
- package/dist/wholeSummaryDocumentStorageService.js +74 -42
- package/dist/wholeSummaryDocumentStorageService.js.map +1 -1
- package/dist/wholeSummaryUploadManager.d.ts +16 -0
- package/dist/wholeSummaryUploadManager.d.ts.map +1 -0
- package/dist/wholeSummaryUploadManager.js +38 -0
- package/dist/wholeSummaryUploadManager.js.map +1 -0
- package/lib/contracts.d.ts +15 -0
- package/lib/contracts.d.ts.map +1 -0
- package/lib/contracts.js +6 -0
- package/lib/contracts.js.map +1 -0
- package/lib/deltaStorageService.d.ts +1 -1
- package/lib/deltaStorageService.d.ts.map +1 -1
- package/lib/deltaStorageService.js +7 -4
- package/lib/deltaStorageService.js.map +1 -1
- package/lib/documentService.d.ts +4 -2
- package/lib/documentService.d.ts.map +1 -1
- package/lib/documentService.js +9 -7
- package/lib/documentService.js.map +1 -1
- package/lib/documentServiceFactory.d.ts +2 -1
- package/lib/documentServiceFactory.d.ts.map +1 -1
- package/lib/documentServiceFactory.js +18 -8
- package/lib/documentServiceFactory.js.map +1 -1
- package/lib/documentStorageService.d.ts +3 -2
- package/lib/documentStorageService.d.ts.map +1 -1
- package/lib/documentStorageService.js +4 -4
- package/lib/documentStorageService.js.map +1 -1
- package/lib/gitManager.d.ts +30 -0
- package/lib/gitManager.d.ts.map +1 -0
- package/lib/gitManager.js +85 -0
- package/lib/gitManager.js.map +1 -0
- package/lib/historian.d.ts +33 -0
- package/lib/historian.d.ts.map +1 -0
- package/lib/historian.js +60 -0
- package/lib/historian.js.map +1 -0
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/r11sSnapshotParser.d.ts +15 -0
- package/lib/r11sSnapshotParser.d.ts.map +1 -0
- package/lib/r11sSnapshotParser.js +71 -0
- package/lib/r11sSnapshotParser.js.map +1 -0
- package/lib/restWrapper.d.ts +21 -8
- package/lib/restWrapper.d.ts.map +1 -1
- package/lib/restWrapper.js +92 -26
- package/lib/restWrapper.js.map +1 -1
- package/lib/restWrapperBase.d.ts +26 -0
- package/lib/restWrapperBase.d.ts.map +1 -0
- package/lib/restWrapperBase.js +50 -0
- package/lib/restWrapperBase.js.map +1 -0
- package/lib/retriableGitManager.d.ts +10 -21
- package/lib/retriableGitManager.d.ts.map +1 -1
- package/lib/retriableGitManager.js +0 -36
- package/lib/retriableGitManager.js.map +1 -1
- package/lib/shreddedSummaryDocumentStorageService.d.ts +1 -1
- package/lib/shreddedSummaryDocumentStorageService.d.ts.map +1 -1
- package/lib/shreddedSummaryDocumentStorageService.js +5 -5
- package/lib/shreddedSummaryDocumentStorageService.js.map +1 -1
- package/lib/storageContracts.d.ts +44 -0
- package/lib/storageContracts.d.ts.map +1 -0
- package/lib/storageContracts.js +6 -0
- package/lib/storageContracts.js.map +1 -0
- package/lib/summaryTreeUploadManager.d.ts +23 -0
- package/lib/summaryTreeUploadManager.d.ts.map +1 -0
- package/lib/summaryTreeUploadManager.js +106 -0
- package/lib/summaryTreeUploadManager.js.map +1 -0
- package/lib/treeUtils.d.ts +7 -0
- package/lib/treeUtils.d.ts.map +1 -1
- package/lib/treeUtils.js +20 -0
- package/lib/treeUtils.js.map +1 -1
- package/lib/wholeSummaryDocumentStorageService.d.ts +5 -4
- package/lib/wholeSummaryDocumentStorageService.d.ts.map +1 -1
- package/lib/wholeSummaryDocumentStorageService.js +74 -42
- package/lib/wholeSummaryDocumentStorageService.js.map +1 -1
- package/lib/wholeSummaryUploadManager.d.ts +16 -0
- package/lib/wholeSummaryUploadManager.d.ts.map +1 -0
- package/lib/wholeSummaryUploadManager.js +34 -0
- package/lib/wholeSummaryUploadManager.js.map +1 -0
- package/package.json +9 -10
- package/src/contracts.ts +16 -0
- package/src/deltaStorageService.ts +11 -3
- package/src/documentService.ts +11 -11
- package/src/documentServiceFactory.ts +35 -16
- package/src/documentStorageService.ts +8 -4
- package/src/gitManager.ts +116 -0
- package/src/historian.ts +121 -0
- package/src/packageVersion.ts +1 -1
- package/src/r11sSnapshotParser.ts +83 -0
- package/src/restWrapper.ts +114 -38
- package/src/restWrapperBase.ts +146 -0
- package/src/retriableGitManager.ts +17 -95
- package/src/shreddedSummaryDocumentStorageService.ts +7 -9
- package/src/storageContracts.ts +63 -0
- package/src/summaryTreeUploadManager.ts +160 -0
- package/src/treeUtils.ts +30 -0
- package/src/wholeSummaryDocumentStorageService.ts +117 -58
- package/src/wholeSummaryUploadManager.ts +64 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as git from "@fluidframework/gitresources";
|
|
7
|
+
import * as api from "@fluidframework/protocol-definitions";
|
|
8
|
+
import {
|
|
9
|
+
IWholeFlatSummary,
|
|
10
|
+
IWholeSummaryPayload,
|
|
11
|
+
IWholeSummaryPayloadType,
|
|
12
|
+
IWriteSummaryResponse,
|
|
13
|
+
} from "@fluidframework/server-services-client";
|
|
14
|
+
import { IR11sResponse } from "./restWrapper";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Interface to a generic Git provider
|
|
18
|
+
*/
|
|
19
|
+
export interface IHistorian {
|
|
20
|
+
getBlob(sha: string): Promise<IR11sResponse<git.IBlob>>;
|
|
21
|
+
createBlob(blob: git.ICreateBlobParams): Promise<IR11sResponse<git.ICreateBlobResponse>>;
|
|
22
|
+
getCommits(sha: string, count: number): Promise<IR11sResponse<git.ICommitDetails[]>>;
|
|
23
|
+
createTree(tree: git.ICreateTreeParams): Promise<IR11sResponse<git.ITree>>;
|
|
24
|
+
getTree(sha: string, recursive: boolean): Promise<IR11sResponse<git.ITree>>;
|
|
25
|
+
createSummary(
|
|
26
|
+
summary: IWholeSummaryPayload,
|
|
27
|
+
initial?: boolean,
|
|
28
|
+
): Promise<IR11sResponse<IWriteSummaryResponse>>;
|
|
29
|
+
getSummary(sha: string): Promise<IR11sResponse<IWholeFlatSummary>>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface IGitManager {
|
|
33
|
+
getCommits(sha: string, count: number): Promise<IR11sResponse<git.ICommitDetails[]>>;
|
|
34
|
+
getTree(root: string, recursive: boolean): Promise<IR11sResponse<git.ITree>>;
|
|
35
|
+
getBlob(sha: string): Promise<IR11sResponse<git.IBlob>>;
|
|
36
|
+
createBlob(content: string, encoding: string): Promise<IR11sResponse<git.ICreateBlobResponse>>;
|
|
37
|
+
createGitTree(params: git.ICreateTreeParams): Promise<IR11sResponse<git.ITree>>;
|
|
38
|
+
createSummary(
|
|
39
|
+
summary: IWholeSummaryPayload,
|
|
40
|
+
initial?: boolean,
|
|
41
|
+
): Promise<IR11sResponse<IWriteSummaryResponse>>;
|
|
42
|
+
getSummary(sha: string): Promise<IR11sResponse<IWholeFlatSummary>>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Uploads a summary to storage.
|
|
47
|
+
*/
|
|
48
|
+
export interface ISummaryUploadManager {
|
|
49
|
+
/**
|
|
50
|
+
* Writes summary tree to storage.
|
|
51
|
+
* @param summaryTree - Summary tree to write to storage
|
|
52
|
+
* @param parentHandle - Parent summary acked handle (if available from summary ack)
|
|
53
|
+
* @param summaryType - type of summary being uploaded
|
|
54
|
+
* @param sequenceNumber - optional reference sequence number of the summary
|
|
55
|
+
* @returns Id of created tree as a string.
|
|
56
|
+
*/
|
|
57
|
+
writeSummaryTree(
|
|
58
|
+
summaryTree: api.ISummaryTree,
|
|
59
|
+
parentHandle: string,
|
|
60
|
+
summaryType: IWholeSummaryPayloadType,
|
|
61
|
+
sequenceNumber?: number,
|
|
62
|
+
): Promise<string>;
|
|
63
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
assert,
|
|
8
|
+
gitHashFile,
|
|
9
|
+
IsoBuffer,
|
|
10
|
+
Uint8ArrayToString,
|
|
11
|
+
unreachableCase,
|
|
12
|
+
} from "@fluidframework/common-utils";
|
|
13
|
+
import { ICreateTreeEntry } from "@fluidframework/gitresources";
|
|
14
|
+
import { getGitMode, getGitType } from "@fluidframework/protocol-base";
|
|
15
|
+
import {
|
|
16
|
+
ISnapshotTreeEx,
|
|
17
|
+
ISummaryTree,
|
|
18
|
+
SummaryObject,
|
|
19
|
+
SummaryType,
|
|
20
|
+
} from "@fluidframework/protocol-definitions";
|
|
21
|
+
import { IWholeSummaryPayloadType } from "@fluidframework/server-services-client";
|
|
22
|
+
import { IGitManager, ISummaryUploadManager } from "./storageContracts";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Recursively writes summary tree as individual summary blobs.
|
|
26
|
+
*/
|
|
27
|
+
export class SummaryTreeUploadManager implements ISummaryUploadManager {
|
|
28
|
+
constructor(
|
|
29
|
+
private readonly manager: IGitManager,
|
|
30
|
+
private readonly blobsShaCache: Map<string, string>,
|
|
31
|
+
private readonly getPreviousFullSnapshot: (
|
|
32
|
+
parentHandle: string,
|
|
33
|
+
) => Promise<ISnapshotTreeEx | null | undefined>,
|
|
34
|
+
) {}
|
|
35
|
+
|
|
36
|
+
public async writeSummaryTree(
|
|
37
|
+
summaryTree: ISummaryTree,
|
|
38
|
+
parentHandle: string,
|
|
39
|
+
summaryType: IWholeSummaryPayloadType,
|
|
40
|
+
sequenceNumber?: number,
|
|
41
|
+
initial?: boolean,
|
|
42
|
+
): Promise<string> {
|
|
43
|
+
const previousFullSnapshot = await this.getPreviousFullSnapshot(parentHandle);
|
|
44
|
+
return this.writeSummaryTreeCore(summaryTree, previousFullSnapshot ?? undefined);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private async writeSummaryTreeCore(
|
|
48
|
+
summaryTree: ISummaryTree,
|
|
49
|
+
previousFullSnapshot: ISnapshotTreeEx | undefined,
|
|
50
|
+
): Promise<string> {
|
|
51
|
+
const entries = await Promise.all(
|
|
52
|
+
Object.keys(summaryTree.tree).map(async (key) => {
|
|
53
|
+
const entry = summaryTree.tree[key];
|
|
54
|
+
const pathHandle = await this.writeSummaryTreeObject(entry, previousFullSnapshot);
|
|
55
|
+
const treeEntry: ICreateTreeEntry = {
|
|
56
|
+
mode: getGitMode(entry),
|
|
57
|
+
path: encodeURIComponent(key),
|
|
58
|
+
sha: pathHandle,
|
|
59
|
+
type: getGitType(entry),
|
|
60
|
+
};
|
|
61
|
+
return treeEntry;
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const treeHandle = (await this.manager.createGitTree({ tree: entries })).content;
|
|
66
|
+
return treeHandle.sha;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private async writeSummaryTreeObject(
|
|
70
|
+
object: SummaryObject,
|
|
71
|
+
previousFullSnapshot: ISnapshotTreeEx | undefined,
|
|
72
|
+
): Promise<string> {
|
|
73
|
+
switch (object.type) {
|
|
74
|
+
case SummaryType.Blob: {
|
|
75
|
+
return this.writeSummaryBlob(object.content);
|
|
76
|
+
}
|
|
77
|
+
case SummaryType.Handle: {
|
|
78
|
+
if (previousFullSnapshot === undefined) {
|
|
79
|
+
throw Error("Parent summary does not exist to reference by handle.");
|
|
80
|
+
}
|
|
81
|
+
return this.getIdFromPath(object.handleType, object.handle, previousFullSnapshot);
|
|
82
|
+
}
|
|
83
|
+
case SummaryType.Tree: {
|
|
84
|
+
return this.writeSummaryTreeCore(object, previousFullSnapshot);
|
|
85
|
+
}
|
|
86
|
+
case SummaryType.Attachment: {
|
|
87
|
+
return object.id;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
default:
|
|
91
|
+
unreachableCase(object, `Unknown type: ${(object as any).type}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private async writeSummaryBlob(content: string | Uint8Array): Promise<string> {
|
|
96
|
+
const { parsedContent, encoding } =
|
|
97
|
+
typeof content === "string"
|
|
98
|
+
? { parsedContent: content, encoding: "utf-8" }
|
|
99
|
+
: { parsedContent: Uint8ArrayToString(content, "base64"), encoding: "base64" };
|
|
100
|
+
|
|
101
|
+
// The gitHashFile would return the same hash as returned by the server as blob.sha
|
|
102
|
+
const hash = await gitHashFile(IsoBuffer.from(parsedContent, encoding));
|
|
103
|
+
if (!this.blobsShaCache.has(hash)) {
|
|
104
|
+
this.blobsShaCache.set(hash, "");
|
|
105
|
+
const blob = (await this.manager.createBlob(parsedContent, encoding)).content;
|
|
106
|
+
assert(hash === blob.sha, 0x0b6 /* "Blob.sha and hash do not match!!" */);
|
|
107
|
+
}
|
|
108
|
+
return hash;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private getIdFromPath(
|
|
112
|
+
handleType: SummaryType,
|
|
113
|
+
handlePath: string,
|
|
114
|
+
previousFullSnapshot: ISnapshotTreeEx,
|
|
115
|
+
): string {
|
|
116
|
+
const path = handlePath.split("/").map((part) => decodeURIComponent(part));
|
|
117
|
+
if (path[0] === "") {
|
|
118
|
+
// root of tree should be unnamed
|
|
119
|
+
path.shift();
|
|
120
|
+
}
|
|
121
|
+
if (path.length === 0) {
|
|
122
|
+
return previousFullSnapshot.id;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return this.getIdFromPathCore(handleType, path, previousFullSnapshot);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private getIdFromPathCore(
|
|
129
|
+
handleType: SummaryType,
|
|
130
|
+
path: string[],
|
|
131
|
+
/** Previous snapshot, subtree relative to this path part */
|
|
132
|
+
previousSnapshot: ISnapshotTreeEx,
|
|
133
|
+
): string {
|
|
134
|
+
assert(path.length > 0, 0x0b3 /* "Expected at least 1 path part" */);
|
|
135
|
+
const key = path[0];
|
|
136
|
+
if (path.length === 1) {
|
|
137
|
+
switch (handleType) {
|
|
138
|
+
case SummaryType.Blob: {
|
|
139
|
+
const tryId = previousSnapshot.blobs[key];
|
|
140
|
+
assert(
|
|
141
|
+
!!tryId,
|
|
142
|
+
0x0b4 /* "Parent summary does not have blob handle for specified path." */,
|
|
143
|
+
);
|
|
144
|
+
return tryId;
|
|
145
|
+
}
|
|
146
|
+
case SummaryType.Tree: {
|
|
147
|
+
const tryId = previousSnapshot.trees[key]?.id;
|
|
148
|
+
assert(
|
|
149
|
+
!!tryId,
|
|
150
|
+
0x0b5 /* "Parent summary does not have tree handle for specified path." */,
|
|
151
|
+
);
|
|
152
|
+
return tryId;
|
|
153
|
+
}
|
|
154
|
+
default:
|
|
155
|
+
throw Error(`Unexpected handle summary object type: "${handleType}".`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return this.getIdFromPathCore(handleType, path.slice(1), previousSnapshot.trees[key]);
|
|
159
|
+
}
|
|
160
|
+
}
|
package/src/treeUtils.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
ISummaryTree,
|
|
11
11
|
SummaryObject,
|
|
12
12
|
} from "@fluidframework/protocol-definitions";
|
|
13
|
+
import { INormalizedWholeSummary } from "./contracts";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Summary tree assembler props
|
|
@@ -105,3 +106,32 @@ export function convertSnapshotAndBlobsToSummaryTree(
|
|
|
105
106
|
}
|
|
106
107
|
return assembler.summary;
|
|
107
108
|
}
|
|
109
|
+
|
|
110
|
+
export function evalBlobsAndTrees(snapshot: INormalizedWholeSummary) {
|
|
111
|
+
const trees = countTreesInSnapshotTree(snapshot.snapshotTree);
|
|
112
|
+
const numBlobs = snapshot.blobs.size;
|
|
113
|
+
let encodedBlobsSize = 0;
|
|
114
|
+
for (const [_, blobContent] of snapshot.blobs) {
|
|
115
|
+
encodedBlobsSize += blobContent.byteLength;
|
|
116
|
+
}
|
|
117
|
+
return { trees, numBlobs, encodedBlobsSize };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function validateBlobsAndTrees(snapshot: ISnapshotTree) {
|
|
121
|
+
assert(
|
|
122
|
+
snapshot.trees !== undefined,
|
|
123
|
+
0x5d0 /* Returned r11s snapshot is malformed. No trees! */,
|
|
124
|
+
);
|
|
125
|
+
assert(
|
|
126
|
+
snapshot.blobs !== undefined,
|
|
127
|
+
0x5d1 /* Returned r11s snapshot is malformed. No blobs! */,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function countTreesInSnapshotTree(snapshotTree: ISnapshotTree): number {
|
|
132
|
+
let numTrees = 0;
|
|
133
|
+
for (const [_, tree] of Object.entries(snapshotTree.trees)) {
|
|
134
|
+
numTrees += 1 + countTreesInSnapshotTree(tree);
|
|
135
|
+
}
|
|
136
|
+
return numTrees;
|
|
137
|
+
}
|
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
assert,
|
|
9
|
+
performance,
|
|
10
|
+
stringToBuffer,
|
|
11
|
+
Uint8ArrayToString,
|
|
12
|
+
} from "@fluidframework/common-utils";
|
|
13
|
+
import { getW3CData, promiseRaceWithWinner } from "@fluidframework/driver-base";
|
|
8
14
|
import {
|
|
9
15
|
IDocumentStorageService,
|
|
10
16
|
ISummaryContext,
|
|
@@ -17,17 +23,21 @@ import {
|
|
|
17
23
|
ISummaryTree,
|
|
18
24
|
IVersion,
|
|
19
25
|
} from "@fluidframework/protocol-definitions";
|
|
20
|
-
import {
|
|
21
|
-
convertWholeFlatSummaryToSnapshotTreeAndBlobs,
|
|
22
|
-
GitManager,
|
|
23
|
-
ISummaryUploadManager,
|
|
24
|
-
WholeSummaryUploadManager,
|
|
25
|
-
} from "@fluidframework/server-services-client";
|
|
26
|
+
import { IWholeFlatSummary } from "@fluidframework/server-services-client";
|
|
26
27
|
import { PerformanceEvent } from "@fluidframework/telemetry-utils";
|
|
27
28
|
import { ICache, InMemoryCache } from "./cache";
|
|
28
|
-
import { ISnapshotTreeVersion } from "./definitions";
|
|
29
29
|
import { IRouterliciousDriverPolicies } from "./policies";
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
convertSnapshotAndBlobsToSummaryTree,
|
|
32
|
+
evalBlobsAndTrees,
|
|
33
|
+
validateBlobsAndTrees,
|
|
34
|
+
} from "./treeUtils";
|
|
35
|
+
import { GitManager } from "./gitManager";
|
|
36
|
+
import { WholeSummaryUploadManager } from "./wholeSummaryUploadManager";
|
|
37
|
+
import { ISummaryUploadManager } from "./storageContracts";
|
|
38
|
+
import { IR11sResponse } from "./restWrapper";
|
|
39
|
+
import { INormalizedWholeSummary } from "./contracts";
|
|
40
|
+
import { convertWholeFlatSummaryToSnapshotTreeAndBlobs } from "./r11sSnapshotParser";
|
|
31
41
|
|
|
32
42
|
const latestSnapshotId: string = "latest";
|
|
33
43
|
|
|
@@ -50,7 +60,7 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
|
|
|
50
60
|
public readonly policies: IDocumentStorageServicePolicies,
|
|
51
61
|
private readonly driverPolicies?: IRouterliciousDriverPolicies,
|
|
52
62
|
private readonly blobCache: ICache<ArrayBufferLike> = new InMemoryCache(),
|
|
53
|
-
private readonly snapshotTreeCache: ICache<
|
|
63
|
+
private readonly snapshotTreeCache: ICache<INormalizedWholeSummary> = new InMemoryCache(),
|
|
54
64
|
private readonly noCacheGitManager?: GitManager,
|
|
55
65
|
private readonly getStorageManager: (
|
|
56
66
|
disableCache?: boolean,
|
|
@@ -73,14 +83,57 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
|
|
|
73
83
|
// If this is the first versions call for the document, we know we will want the latest summary.
|
|
74
84
|
// Fetch latest summary, cache it, and return its id.
|
|
75
85
|
if (this.firstVersionsCall && count === 1) {
|
|
86
|
+
const normalizedSnapshotContents = await PerformanceEvent.timedExecAsync(
|
|
87
|
+
this.logger,
|
|
88
|
+
{
|
|
89
|
+
eventName: "ObtainSnapshot",
|
|
90
|
+
versionId: versionId ?? undefined,
|
|
91
|
+
count,
|
|
92
|
+
enableDiscovery: this.driverPolicies?.enableDiscovery,
|
|
93
|
+
},
|
|
94
|
+
async (event) => {
|
|
95
|
+
let method: string;
|
|
96
|
+
const cachedSnapshotP = this.snapshotTreeCache.get(
|
|
97
|
+
this.getCacheKey(latestSnapshotId),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const networkSnapshotP = !this.driverPolicies?.enableDiscovery
|
|
101
|
+
? this.fetchSnapshotTree(latestSnapshotId, false, "getVersions")
|
|
102
|
+
: this.fetchSnapshotTree(latestSnapshotId, true, "getVersions");
|
|
103
|
+
|
|
104
|
+
const promiseRaceWinner = await promiseRaceWithWinner([
|
|
105
|
+
cachedSnapshotP.catch(() => undefined),
|
|
106
|
+
networkSnapshotP.catch(() => undefined),
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
let retrievedSnapshot = promiseRaceWinner.value;
|
|
110
|
+
method = promiseRaceWinner.index === 0 ? "cache" : "network";
|
|
111
|
+
|
|
112
|
+
if (retrievedSnapshot === undefined) {
|
|
113
|
+
// if network failed -> wait for cache ( then return network failure)
|
|
114
|
+
// If cache returned empty or failed -> wait for network (success of failure)
|
|
115
|
+
if (promiseRaceWinner.index === 1) {
|
|
116
|
+
retrievedSnapshot = await cachedSnapshotP;
|
|
117
|
+
method = "cache";
|
|
118
|
+
}
|
|
119
|
+
if (retrievedSnapshot === undefined) {
|
|
120
|
+
retrievedSnapshot = await networkSnapshotP;
|
|
121
|
+
method = "network";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
event.end({
|
|
125
|
+
method,
|
|
126
|
+
});
|
|
127
|
+
return retrievedSnapshot;
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const _id = await this.initializeFromSnapshot(normalizedSnapshotContents);
|
|
76
132
|
this.firstVersionsCall = false;
|
|
77
|
-
const { id: _id, snapshotTree } = !this.driverPolicies?.enableDiscovery
|
|
78
|
-
? await this.fetchAndCacheSnapshotTree(latestSnapshotId, false)
|
|
79
|
-
: await this.fetchAndCacheSnapshotTree(latestSnapshotId, true);
|
|
80
133
|
return [
|
|
81
134
|
{
|
|
82
135
|
id: _id,
|
|
83
|
-
treeId: snapshotTree.id!,
|
|
136
|
+
treeId: normalizedSnapshotContents.snapshotTree.id!,
|
|
84
137
|
},
|
|
85
138
|
];
|
|
86
139
|
}
|
|
@@ -96,7 +149,7 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
|
|
|
96
149
|
},
|
|
97
150
|
async () => {
|
|
98
151
|
const manager = await this.getStorageManager();
|
|
99
|
-
return manager.getCommits(id, count);
|
|
152
|
+
return (await manager.getCommits(id, count)).content;
|
|
100
153
|
},
|
|
101
154
|
);
|
|
102
155
|
return commits.map((commit) => ({
|
|
@@ -117,7 +170,14 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
|
|
|
117
170
|
requestVersion = versions[0];
|
|
118
171
|
}
|
|
119
172
|
|
|
120
|
-
|
|
173
|
+
const normalizedWholeSnapshot = await this.snapshotTreeCache.get(
|
|
174
|
+
this.getCacheKey(requestVersion.id),
|
|
175
|
+
);
|
|
176
|
+
if (normalizedWholeSnapshot !== undefined) {
|
|
177
|
+
return normalizedWholeSnapshot.snapshotTree;
|
|
178
|
+
}
|
|
179
|
+
return (await this.fetchSnapshotTree(requestVersion.id, undefined, "getSnapshotTree"))
|
|
180
|
+
.snapshotTree;
|
|
121
181
|
}
|
|
122
182
|
|
|
123
183
|
public async readBlob(blobId: string): Promise<ArrayBufferLike> {
|
|
@@ -134,7 +194,7 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
|
|
|
134
194
|
},
|
|
135
195
|
async (event) => {
|
|
136
196
|
const manager = await this.getStorageManager();
|
|
137
|
-
const response = await manager.getBlob(blobId);
|
|
197
|
+
const response = (await manager.getBlob(blobId)).content;
|
|
138
198
|
event.end({
|
|
139
199
|
size: response.size,
|
|
140
200
|
});
|
|
@@ -183,9 +243,9 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
|
|
|
183
243
|
const manager = await this.getStorageManager();
|
|
184
244
|
const response = await manager.getSummary(summaryHandle.handle);
|
|
185
245
|
event.end({
|
|
186
|
-
size: response.trees[0]?.entries.length,
|
|
246
|
+
size: response.content.trees[0]?.entries.length,
|
|
187
247
|
});
|
|
188
|
-
return response;
|
|
248
|
+
return response.content;
|
|
189
249
|
},
|
|
190
250
|
);
|
|
191
251
|
|
|
@@ -208,7 +268,7 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
|
|
|
208
268
|
const manager = await this.getStorageManager();
|
|
209
269
|
const response = await manager
|
|
210
270
|
.createBlob(Uint8ArrayToString(uint8ArrayFile, "base64"), "base64")
|
|
211
|
-
.then((r) => ({ id: r.sha, url: r.url }));
|
|
271
|
+
.then((r) => ({ id: r.content.sha, url: r.content.url }));
|
|
212
272
|
event.end({
|
|
213
273
|
blobId: response.id,
|
|
214
274
|
});
|
|
@@ -217,64 +277,63 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
|
|
|
217
277
|
);
|
|
218
278
|
}
|
|
219
279
|
|
|
220
|
-
private async
|
|
280
|
+
private async fetchSnapshotTree(
|
|
221
281
|
versionId: string,
|
|
222
282
|
disableCache?: boolean,
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
);
|
|
227
|
-
if (cachedSnapshotTreeVersion !== undefined) {
|
|
228
|
-
return {
|
|
229
|
-
id: cachedSnapshotTreeVersion.id,
|
|
230
|
-
snapshotTree: cachedSnapshotTreeVersion.snapshotTree,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const wholeFlatSummary = await PerformanceEvent.timedExecAsync(
|
|
283
|
+
scenarioName?: string,
|
|
284
|
+
): Promise<INormalizedWholeSummary> {
|
|
285
|
+
const normalizedWholeSummary = await PerformanceEvent.timedExecAsync(
|
|
235
286
|
this.logger,
|
|
236
287
|
{
|
|
237
288
|
eventName: "getWholeFlatSummary",
|
|
238
289
|
treeId: versionId,
|
|
290
|
+
scenarioName,
|
|
239
291
|
},
|
|
240
292
|
async (event) => {
|
|
241
293
|
const manager = await this.getStorageManager(disableCache);
|
|
242
|
-
const response = await manager.getSummary(
|
|
294
|
+
const response: IR11sResponse<IWholeFlatSummary> = await manager.getSummary(
|
|
295
|
+
versionId,
|
|
296
|
+
);
|
|
297
|
+
const start = performance.now();
|
|
298
|
+
const snapshot: INormalizedWholeSummary =
|
|
299
|
+
convertWholeFlatSummaryToSnapshotTreeAndBlobs(response.content);
|
|
300
|
+
const snapshotConversionTime = performance.now() - start;
|
|
301
|
+
validateBlobsAndTrees(snapshot.snapshotTree);
|
|
302
|
+
const { trees, numBlobs, encodedBlobsSize } = evalBlobsAndTrees(snapshot);
|
|
303
|
+
|
|
243
304
|
event.end({
|
|
244
|
-
size: response.trees[0]?.entries.length,
|
|
305
|
+
size: response.content.trees[0]?.entries.length,
|
|
306
|
+
trees,
|
|
307
|
+
blobs: numBlobs,
|
|
308
|
+
encodedBlobsSize,
|
|
309
|
+
...response.propsToLog,
|
|
310
|
+
snapshotConversionTime,
|
|
311
|
+
...getW3CData(response.requestUrl, "xmlhttprequest"),
|
|
245
312
|
});
|
|
246
|
-
return
|
|
313
|
+
return snapshot;
|
|
247
314
|
},
|
|
248
315
|
);
|
|
249
|
-
const normalizedWholeSummary =
|
|
250
|
-
convertWholeFlatSummaryToSnapshotTreeAndBlobs(wholeFlatSummary);
|
|
251
|
-
assert(
|
|
252
|
-
normalizedWholeSummary.snapshotTree.id !== undefined,
|
|
253
|
-
0x275 /* "Root tree should contain the id" */,
|
|
254
|
-
);
|
|
255
|
-
const wholeFlatSummaryId: string = wholeFlatSummary.id;
|
|
256
|
-
const snapshotTreeVersion = {
|
|
257
|
-
id: wholeFlatSummaryId,
|
|
258
|
-
snapshotTree: normalizedWholeSummary.snapshotTree,
|
|
259
|
-
};
|
|
260
316
|
|
|
317
|
+
// Also add the result into the cache.
|
|
318
|
+
await this.snapshotTreeCache
|
|
319
|
+
.put(this.getCacheKey(versionId), normalizedWholeSummary)
|
|
320
|
+
.catch(() => undefined);
|
|
321
|
+
return normalizedWholeSummary;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private async initializeFromSnapshot(
|
|
325
|
+
normalizedWholeSummary: INormalizedWholeSummary,
|
|
326
|
+
): Promise<string> {
|
|
327
|
+
const snapshotId = normalizedWholeSummary.id;
|
|
328
|
+
assert(snapshotId !== undefined, 0x275 /* "Root tree should contain the id" */);
|
|
261
329
|
const cachePs: Promise<any>[] = [
|
|
262
|
-
this.snapshotTreeCache.put(this.getCacheKey(
|
|
330
|
+
this.snapshotTreeCache.put(this.getCacheKey(snapshotId), normalizedWholeSummary),
|
|
263
331
|
this.initBlobCache(normalizedWholeSummary.blobs),
|
|
264
332
|
];
|
|
265
|
-
if (wholeFlatSummaryId !== versionId) {
|
|
266
|
-
// versionId could be "latest". When summarizer checks cache for "latest", we want it to be available.
|
|
267
|
-
// TODO: For in-memory cache, <latest,snapshotTree> will be a shared pointer with <snapshotId,snapshotTree>,
|
|
268
|
-
// However, for something like Redis, this will cache the same value twice. Alternatively, could we simply
|
|
269
|
-
// cache with versionId?
|
|
270
|
-
cachePs.push(
|
|
271
|
-
this.snapshotTreeCache.put(this.getCacheKey(versionId), snapshotTreeVersion),
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
333
|
|
|
275
334
|
await Promise.all(cachePs);
|
|
276
335
|
|
|
277
|
-
return
|
|
336
|
+
return snapshotId;
|
|
278
337
|
}
|
|
279
338
|
|
|
280
339
|
private async initBlobCache(blobs: Map<string, ArrayBuffer>): Promise<void> {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
ISummaryTree,
|
|
8
|
+
IWholeSummaryPayload,
|
|
9
|
+
IWholeSummaryPayloadType,
|
|
10
|
+
convertSummaryTreeToWholeSummaryTree,
|
|
11
|
+
} from "@fluidframework/server-services-client";
|
|
12
|
+
import { IGitManager, ISummaryUploadManager } from "./storageContracts";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Converts summary to snapshot tree and uploads with single snaphot tree payload.
|
|
16
|
+
*/
|
|
17
|
+
export class WholeSummaryUploadManager implements ISummaryUploadManager {
|
|
18
|
+
constructor(private readonly manager: IGitManager) {}
|
|
19
|
+
|
|
20
|
+
public async writeSummaryTree(
|
|
21
|
+
summaryTree: ISummaryTree,
|
|
22
|
+
parentHandle: string | undefined,
|
|
23
|
+
summaryType: IWholeSummaryPayloadType,
|
|
24
|
+
sequenceNumber: number = 0,
|
|
25
|
+
initial: boolean = false,
|
|
26
|
+
): Promise<string> {
|
|
27
|
+
const id = await this.writeSummaryTreeCore(
|
|
28
|
+
parentHandle,
|
|
29
|
+
summaryTree,
|
|
30
|
+
summaryType,
|
|
31
|
+
sequenceNumber,
|
|
32
|
+
initial,
|
|
33
|
+
);
|
|
34
|
+
if (!id) {
|
|
35
|
+
throw new Error(`Failed to write summary tree`);
|
|
36
|
+
}
|
|
37
|
+
return id;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private async writeSummaryTreeCore(
|
|
41
|
+
parentHandle: string | undefined,
|
|
42
|
+
tree: ISummaryTree,
|
|
43
|
+
type: IWholeSummaryPayloadType,
|
|
44
|
+
sequenceNumber: number,
|
|
45
|
+
initial: boolean,
|
|
46
|
+
): Promise<string> {
|
|
47
|
+
const snapshotTree = convertSummaryTreeToWholeSummaryTree(
|
|
48
|
+
parentHandle,
|
|
49
|
+
tree,
|
|
50
|
+
"",
|
|
51
|
+
type === "channel" ? ".app" : "",
|
|
52
|
+
);
|
|
53
|
+
const snapshotPayload: IWholeSummaryPayload = {
|
|
54
|
+
entries: snapshotTree.entries ?? [],
|
|
55
|
+
message: "",
|
|
56
|
+
sequenceNumber,
|
|
57
|
+
type,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return this.manager
|
|
61
|
+
.createSummary(snapshotPayload, initial)
|
|
62
|
+
.then((response) => response.content.id);
|
|
63
|
+
}
|
|
64
|
+
}
|