@fluidframework/routerlicious-driver 2.0.0-dev.4.1.0.148229 → 2.0.0-dev.4.2.0.153917

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +38 -0
  3. package/dist/deltaStorageService.d.ts +1 -1
  4. package/dist/deltaStorageService.d.ts.map +1 -1
  5. package/dist/deltaStorageService.js +7 -4
  6. package/dist/deltaStorageService.js.map +1 -1
  7. package/dist/documentService.d.ts +4 -2
  8. package/dist/documentService.d.ts.map +1 -1
  9. package/dist/documentService.js +11 -9
  10. package/dist/documentService.js.map +1 -1
  11. package/dist/documentServiceFactory.d.ts +2 -1
  12. package/dist/documentServiceFactory.d.ts.map +1 -1
  13. package/dist/documentServiceFactory.js +18 -8
  14. package/dist/documentServiceFactory.js.map +1 -1
  15. package/dist/documentStorageService.d.ts +3 -2
  16. package/dist/documentStorageService.d.ts.map +1 -1
  17. package/dist/documentStorageService.js +4 -4
  18. package/dist/documentStorageService.js.map +1 -1
  19. package/dist/gitManager.d.ts +30 -0
  20. package/dist/gitManager.d.ts.map +1 -0
  21. package/dist/gitManager.js +89 -0
  22. package/dist/gitManager.js.map +1 -0
  23. package/dist/historian.d.ts +33 -0
  24. package/dist/historian.d.ts.map +1 -0
  25. package/dist/historian.js +65 -0
  26. package/dist/historian.js.map +1 -0
  27. package/dist/packageVersion.d.ts +1 -1
  28. package/dist/packageVersion.js +1 -1
  29. package/dist/packageVersion.js.map +1 -1
  30. package/dist/restWrapper.d.ts +21 -8
  31. package/dist/restWrapper.d.ts.map +1 -1
  32. package/dist/restWrapper.js +93 -25
  33. package/dist/restWrapper.js.map +1 -1
  34. package/dist/restWrapperBase.d.ts +26 -0
  35. package/dist/restWrapperBase.d.ts.map +1 -0
  36. package/dist/restWrapperBase.js +55 -0
  37. package/dist/restWrapperBase.js.map +1 -0
  38. package/dist/retriableGitManager.d.ts +10 -21
  39. package/dist/retriableGitManager.d.ts.map +1 -1
  40. package/dist/retriableGitManager.js +0 -36
  41. package/dist/retriableGitManager.js.map +1 -1
  42. package/dist/shreddedSummaryDocumentStorageService.d.ts +1 -1
  43. package/dist/shreddedSummaryDocumentStorageService.d.ts.map +1 -1
  44. package/dist/shreddedSummaryDocumentStorageService.js +6 -6
  45. package/dist/shreddedSummaryDocumentStorageService.js.map +1 -1
  46. package/dist/storageContracts.d.ts +44 -0
  47. package/dist/storageContracts.d.ts.map +1 -0
  48. package/dist/storageContracts.js +7 -0
  49. package/dist/storageContracts.js.map +1 -0
  50. package/dist/summaryTreeUploadManager.d.ts +23 -0
  51. package/dist/summaryTreeUploadManager.d.ts.map +1 -0
  52. package/dist/summaryTreeUploadManager.js +110 -0
  53. package/dist/summaryTreeUploadManager.js.map +1 -0
  54. package/dist/treeUtils.d.ts +7 -0
  55. package/dist/treeUtils.d.ts.map +1 -1
  56. package/dist/treeUtils.js +23 -1
  57. package/dist/treeUtils.js.map +1 -1
  58. package/dist/wholeSummaryDocumentStorageService.d.ts +5 -4
  59. package/dist/wholeSummaryDocumentStorageService.d.ts.map +1 -1
  60. package/dist/wholeSummaryDocumentStorageService.js +68 -35
  61. package/dist/wholeSummaryDocumentStorageService.js.map +1 -1
  62. package/dist/wholeSummaryUploadManager.d.ts +16 -0
  63. package/dist/wholeSummaryUploadManager.d.ts.map +1 -0
  64. package/dist/wholeSummaryUploadManager.js +38 -0
  65. package/dist/wholeSummaryUploadManager.js.map +1 -0
  66. package/lib/deltaStorageService.d.ts +1 -1
  67. package/lib/deltaStorageService.d.ts.map +1 -1
  68. package/lib/deltaStorageService.js +7 -4
  69. package/lib/deltaStorageService.js.map +1 -1
  70. package/lib/documentService.d.ts +4 -2
  71. package/lib/documentService.d.ts.map +1 -1
  72. package/lib/documentService.js +9 -7
  73. package/lib/documentService.js.map +1 -1
  74. package/lib/documentServiceFactory.d.ts +2 -1
  75. package/lib/documentServiceFactory.d.ts.map +1 -1
  76. package/lib/documentServiceFactory.js +18 -8
  77. package/lib/documentServiceFactory.js.map +1 -1
  78. package/lib/documentStorageService.d.ts +3 -2
  79. package/lib/documentStorageService.d.ts.map +1 -1
  80. package/lib/documentStorageService.js +4 -4
  81. package/lib/documentStorageService.js.map +1 -1
  82. package/lib/gitManager.d.ts +30 -0
  83. package/lib/gitManager.d.ts.map +1 -0
  84. package/lib/gitManager.js +85 -0
  85. package/lib/gitManager.js.map +1 -0
  86. package/lib/historian.d.ts +33 -0
  87. package/lib/historian.d.ts.map +1 -0
  88. package/lib/historian.js +60 -0
  89. package/lib/historian.js.map +1 -0
  90. package/lib/packageVersion.d.ts +1 -1
  91. package/lib/packageVersion.js +1 -1
  92. package/lib/packageVersion.js.map +1 -1
  93. package/lib/restWrapper.d.ts +21 -8
  94. package/lib/restWrapper.d.ts.map +1 -1
  95. package/lib/restWrapper.js +92 -26
  96. package/lib/restWrapper.js.map +1 -1
  97. package/lib/restWrapperBase.d.ts +26 -0
  98. package/lib/restWrapperBase.d.ts.map +1 -0
  99. package/lib/restWrapperBase.js +50 -0
  100. package/lib/restWrapperBase.js.map +1 -0
  101. package/lib/retriableGitManager.d.ts +10 -21
  102. package/lib/retriableGitManager.d.ts.map +1 -1
  103. package/lib/retriableGitManager.js +0 -36
  104. package/lib/retriableGitManager.js.map +1 -1
  105. package/lib/shreddedSummaryDocumentStorageService.d.ts +1 -1
  106. package/lib/shreddedSummaryDocumentStorageService.d.ts.map +1 -1
  107. package/lib/shreddedSummaryDocumentStorageService.js +5 -5
  108. package/lib/shreddedSummaryDocumentStorageService.js.map +1 -1
  109. package/lib/storageContracts.d.ts +44 -0
  110. package/lib/storageContracts.d.ts.map +1 -0
  111. package/lib/storageContracts.js +6 -0
  112. package/lib/storageContracts.js.map +1 -0
  113. package/lib/summaryTreeUploadManager.d.ts +23 -0
  114. package/lib/summaryTreeUploadManager.d.ts.map +1 -0
  115. package/lib/summaryTreeUploadManager.js +106 -0
  116. package/lib/summaryTreeUploadManager.js.map +1 -0
  117. package/lib/treeUtils.d.ts +7 -0
  118. package/lib/treeUtils.d.ts.map +1 -1
  119. package/lib/treeUtils.js +20 -0
  120. package/lib/treeUtils.js.map +1 -1
  121. package/lib/wholeSummaryDocumentStorageService.d.ts +5 -4
  122. package/lib/wholeSummaryDocumentStorageService.d.ts.map +1 -1
  123. package/lib/wholeSummaryDocumentStorageService.js +70 -37
  124. package/lib/wholeSummaryDocumentStorageService.js.map +1 -1
  125. package/lib/wholeSummaryUploadManager.d.ts +16 -0
  126. package/lib/wholeSummaryUploadManager.d.ts.map +1 -0
  127. package/lib/wholeSummaryUploadManager.js +34 -0
  128. package/lib/wholeSummaryUploadManager.js.map +1 -0
  129. package/package.json +8 -9
  130. package/src/deltaStorageService.ts +11 -3
  131. package/src/documentService.ts +11 -11
  132. package/src/documentServiceFactory.ts +35 -17
  133. package/src/documentStorageService.ts +8 -4
  134. package/src/gitManager.ts +116 -0
  135. package/src/historian.ts +121 -0
  136. package/src/packageVersion.ts +1 -1
  137. package/src/restWrapper.ts +114 -38
  138. package/src/restWrapperBase.ts +146 -0
  139. package/src/retriableGitManager.ts +17 -95
  140. package/src/shreddedSummaryDocumentStorageService.ts +7 -9
  141. package/src/storageContracts.ts +63 -0
  142. package/src/summaryTreeUploadManager.ts +160 -0
  143. package/src/treeUtils.ts +30 -0
  144. package/src/wholeSummaryDocumentStorageService.ts +118 -48
  145. 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 "@fluidframework/server-services-client";
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 { assert, stringToBuffer, Uint8ArrayToString } from "@fluidframework/common-utils";
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,
@@ -19,15 +25,21 @@ import {
19
25
  } from "@fluidframework/protocol-definitions";
20
26
  import {
21
27
  convertWholeFlatSummaryToSnapshotTreeAndBlobs,
22
- GitManager,
23
- ISummaryUploadManager,
24
- WholeSummaryUploadManager,
28
+ INormalizedWholeSummary,
29
+ IWholeFlatSummary,
25
30
  } from "@fluidframework/server-services-client";
26
31
  import { PerformanceEvent } from "@fluidframework/telemetry-utils";
27
32
  import { ICache, InMemoryCache } from "./cache";
28
- import { ISnapshotTreeVersion } from "./definitions";
29
33
  import { IRouterliciousDriverPolicies } from "./policies";
30
- import { convertSnapshotAndBlobsToSummaryTree } from "./treeUtils";
34
+ import {
35
+ convertSnapshotAndBlobsToSummaryTree,
36
+ evalBlobsAndTrees,
37
+ validateBlobsAndTrees,
38
+ } from "./treeUtils";
39
+ import { GitManager } from "./gitManager";
40
+ import { WholeSummaryUploadManager } from "./wholeSummaryUploadManager";
41
+ import { ISummaryUploadManager } from "./storageContracts";
42
+ import { IR11sResponse } from "./restWrapper";
31
43
 
32
44
  const latestSnapshotId: string = "latest";
33
45
 
@@ -50,7 +62,7 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
50
62
  public readonly policies: IDocumentStorageServicePolicies,
51
63
  private readonly driverPolicies?: IRouterliciousDriverPolicies,
52
64
  private readonly blobCache: ICache<ArrayBufferLike> = new InMemoryCache(),
53
- private readonly snapshotTreeCache: ICache<ISnapshotTreeVersion> = new InMemoryCache(),
65
+ private readonly snapshotTreeCache: ICache<INormalizedWholeSummary> = new InMemoryCache(),
54
66
  private readonly noCacheGitManager?: GitManager,
55
67
  private readonly getStorageManager: (
56
68
  disableCache?: boolean,
@@ -73,14 +85,60 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
73
85
  // If this is the first versions call for the document, we know we will want the latest summary.
74
86
  // Fetch latest summary, cache it, and return its id.
75
87
  if (this.firstVersionsCall && count === 1) {
88
+ const normalizedSnapshotContents = await PerformanceEvent.timedExecAsync(
89
+ this.logger,
90
+ {
91
+ eventName: "ObtainSnapshot",
92
+ versionId: versionId ?? undefined,
93
+ count,
94
+ enableDiscovery: this.driverPolicies?.enableDiscovery,
95
+ },
96
+ async (event) => {
97
+ let method: string;
98
+ const cachedSnapshotP = this.snapshotTreeCache.get(
99
+ this.getCacheKey(latestSnapshotId),
100
+ );
101
+
102
+ const networkSnapshotP = !this.driverPolicies?.enableDiscovery
103
+ ? this.fetchSnapshotTree(latestSnapshotId, false)
104
+ : this.fetchSnapshotTree(latestSnapshotId, true);
105
+
106
+ const promiseRaceWinner = await promiseRaceWithWinner([
107
+ cachedSnapshotP.catch(() => undefined),
108
+ networkSnapshotP.catch(() => undefined),
109
+ ]);
110
+
111
+ let retrievedSnapshot = promiseRaceWinner.value;
112
+ method = promiseRaceWinner.index === 0 ? "cache" : "network";
113
+
114
+ if (retrievedSnapshot === undefined) {
115
+ // if network failed -> wait for cache ( then return network failure)
116
+ // If cache returned empty or failed -> wait for network (success of failure)
117
+ if (promiseRaceWinner.index === 1) {
118
+ retrievedSnapshot = await cachedSnapshotP;
119
+ method = "cache";
120
+ }
121
+ if (retrievedSnapshot === undefined) {
122
+ retrievedSnapshot = await networkSnapshotP;
123
+ method = "network";
124
+ }
125
+ }
126
+ event.end({
127
+ method,
128
+ });
129
+ return retrievedSnapshot;
130
+ },
131
+ );
132
+
133
+ const _id = await this.initializeFromSnapshot(
134
+ normalizedSnapshotContents,
135
+ latestSnapshotId,
136
+ );
76
137
  this.firstVersionsCall = false;
77
- const { id: _id, snapshotTree } = !this.driverPolicies?.enableDiscovery
78
- ? await this.fetchAndCacheSnapshotTree(latestSnapshotId, false)
79
- : await this.fetchAndCacheSnapshotTree(latestSnapshotId, true);
80
138
  return [
81
139
  {
82
140
  id: _id,
83
- treeId: snapshotTree.id!,
141
+ treeId: normalizedSnapshotContents.snapshotTree.id!,
84
142
  },
85
143
  ];
86
144
  }
@@ -96,7 +154,7 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
96
154
  },
97
155
  async () => {
98
156
  const manager = await this.getStorageManager();
99
- return manager.getCommits(id, count);
157
+ return (await manager.getCommits(id, count)).content;
100
158
  },
101
159
  );
102
160
  return commits.map((commit) => ({
@@ -117,7 +175,13 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
117
175
  requestVersion = versions[0];
118
176
  }
119
177
 
120
- return (await this.fetchAndCacheSnapshotTree(requestVersion.id)).snapshotTree;
178
+ const normalizedWholeSnapshot = await this.snapshotTreeCache.get(
179
+ this.getCacheKey(requestVersion.id),
180
+ );
181
+ if (normalizedWholeSnapshot !== undefined) {
182
+ return normalizedWholeSnapshot.snapshotTree;
183
+ }
184
+ return (await this.fetchSnapshotTree(requestVersion.id)).snapshotTree;
121
185
  }
122
186
 
123
187
  public async readBlob(blobId: string): Promise<ArrayBufferLike> {
@@ -134,7 +198,7 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
134
198
  },
135
199
  async (event) => {
136
200
  const manager = await this.getStorageManager();
137
- const response = await manager.getBlob(blobId);
201
+ const response = (await manager.getBlob(blobId)).content;
138
202
  event.end({
139
203
  size: response.size,
140
204
  });
@@ -183,9 +247,9 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
183
247
  const manager = await this.getStorageManager();
184
248
  const response = await manager.getSummary(summaryHandle.handle);
185
249
  event.end({
186
- size: response.trees[0]?.entries.length,
250
+ size: response.content.trees[0]?.entries.length,
187
251
  });
188
- return response;
252
+ return response.content;
189
253
  },
190
254
  );
191
255
 
@@ -208,7 +272,7 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
208
272
  const manager = await this.getStorageManager();
209
273
  const response = await manager
210
274
  .createBlob(Uint8ArrayToString(uint8ArrayFile, "base64"), "base64")
211
- .then((r) => ({ id: r.sha, url: r.url }));
275
+ .then((r) => ({ id: r.content.sha, url: r.content.url }));
212
276
  event.end({
213
277
  blobId: response.id,
214
278
  });
@@ -217,21 +281,11 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
217
281
  );
218
282
  }
219
283
 
220
- private async fetchAndCacheSnapshotTree(
284
+ private async fetchSnapshotTree(
221
285
  versionId: string,
222
286
  disableCache?: boolean,
223
- ): Promise<ISnapshotTreeVersion> {
224
- const cachedSnapshotTreeVersion = await this.snapshotTreeCache.get(
225
- this.getCacheKey(versionId),
226
- );
227
- if (cachedSnapshotTreeVersion !== undefined) {
228
- return {
229
- id: cachedSnapshotTreeVersion.id,
230
- snapshotTree: cachedSnapshotTreeVersion.snapshotTree,
231
- };
232
- }
233
-
234
- const wholeFlatSummary = await PerformanceEvent.timedExecAsync(
287
+ ): Promise<INormalizedWholeSummary> {
288
+ const normalizedWholeSummary = await PerformanceEvent.timedExecAsync(
235
289
  this.logger,
236
290
  {
237
291
  eventName: "getWholeFlatSummary",
@@ -239,42 +293,58 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
239
293
  },
240
294
  async (event) => {
241
295
  const manager = await this.getStorageManager(disableCache);
242
- const response = await manager.getSummary(versionId);
296
+ const response: IR11sResponse<IWholeFlatSummary> = await manager.getSummary(
297
+ versionId,
298
+ );
299
+ const start = performance.now();
300
+ const snapshot: INormalizedWholeSummary =
301
+ convertWholeFlatSummaryToSnapshotTreeAndBlobs(response.content);
302
+ const snapshotConversionTime = performance.now() - start;
303
+ validateBlobsAndTrees(snapshot.snapshotTree);
304
+ const { trees, numBlobs, encodedBlobsSize } = evalBlobsAndTrees(snapshot);
305
+
243
306
  event.end({
244
- size: response.trees[0]?.entries.length,
307
+ size: response.content.trees[0]?.entries.length,
308
+ trees,
309
+ blobs: numBlobs,
310
+ encodedBlobsSize,
311
+ ...response.propsToLog,
312
+ snapshotConversionTime,
313
+ ...getW3CData(response.requestUrl, "xmlhttprequest"),
245
314
  });
246
- return response;
315
+ return snapshot;
247
316
  },
248
317
  );
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
318
 
319
+ return normalizedWholeSummary;
320
+ }
321
+
322
+ private async initializeFromSnapshot(
323
+ normalizedWholeSummary: INormalizedWholeSummary,
324
+ versionId: string | null,
325
+ ): Promise<string> {
326
+ const wholeFlatSummaryId = normalizedWholeSummary.snapshotTree.id;
327
+ assert(wholeFlatSummaryId !== undefined, 0x275 /* "Root tree should contain the id" */);
261
328
  const cachePs: Promise<any>[] = [
262
- this.snapshotTreeCache.put(this.getCacheKey(wholeFlatSummaryId), snapshotTreeVersion),
329
+ this.snapshotTreeCache.put(
330
+ this.getCacheKey(wholeFlatSummaryId),
331
+ normalizedWholeSummary,
332
+ ),
263
333
  this.initBlobCache(normalizedWholeSummary.blobs),
264
334
  ];
265
- if (wholeFlatSummaryId !== versionId) {
335
+ if (wholeFlatSummaryId !== versionId && versionId !== null) {
266
336
  // versionId could be "latest". When summarizer checks cache for "latest", we want it to be available.
267
337
  // TODO: For in-memory cache, <latest,snapshotTree> will be a shared pointer with <snapshotId,snapshotTree>,
268
338
  // However, for something like Redis, this will cache the same value twice. Alternatively, could we simply
269
339
  // cache with versionId?
270
340
  cachePs.push(
271
- this.snapshotTreeCache.put(this.getCacheKey(versionId), snapshotTreeVersion),
341
+ this.snapshotTreeCache.put(this.getCacheKey(versionId), normalizedWholeSummary),
272
342
  );
273
343
  }
274
344
 
275
345
  await Promise.all(cachePs);
276
346
 
277
- return snapshotTreeVersion;
347
+ return wholeFlatSummaryId;
278
348
  }
279
349
 
280
350
  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
+ }