@fluidframework/container-loader 2.0.0-dev-rc.1.0.0.228517 → 2.0.0-dev-rc.1.0.0.232845

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 (90) hide show
  1. package/README.md +3 -3
  2. package/dist/attachment.d.ts +116 -0
  3. package/dist/attachment.d.ts.map +1 -0
  4. package/dist/attachment.js +82 -0
  5. package/dist/attachment.js.map +1 -0
  6. package/dist/connectionManager.d.ts.map +1 -1
  7. package/dist/connectionManager.js +1 -2
  8. package/dist/connectionManager.js.map +1 -1
  9. package/dist/container-loader-alpha.d.ts +1 -1
  10. package/dist/container-loader-untrimmed.d.ts +1 -1
  11. package/dist/container.d.ts +21 -8
  12. package/dist/container.d.ts.map +1 -1
  13. package/dist/container.js +177 -149
  14. package/dist/container.js.map +1 -1
  15. package/dist/containerContext.d.ts +3 -2
  16. package/dist/containerContext.d.ts.map +1 -1
  17. package/dist/containerContext.js +2 -1
  18. package/dist/containerContext.js.map +1 -1
  19. package/dist/containerStorageAdapter.d.ts +3 -3
  20. package/dist/containerStorageAdapter.d.ts.map +1 -1
  21. package/dist/containerStorageAdapter.js +10 -11
  22. package/dist/containerStorageAdapter.js.map +1 -1
  23. package/dist/loader.d.ts +1 -1
  24. package/dist/loader.js.map +1 -1
  25. package/dist/packageVersion.d.ts +1 -1
  26. package/dist/packageVersion.js +1 -1
  27. package/dist/packageVersion.js.map +1 -1
  28. package/dist/protocolTreeDocumentStorageService.d.ts +1 -0
  29. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  30. package/dist/protocolTreeDocumentStorageService.js +1 -0
  31. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  32. package/dist/retriableDocumentStorageService.d.ts +2 -1
  33. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  34. package/dist/retriableDocumentStorageService.js +8 -0
  35. package/dist/retriableDocumentStorageService.js.map +1 -1
  36. package/dist/tsdoc-metadata.json +1 -1
  37. package/dist/utils.d.ts +13 -7
  38. package/dist/utils.d.ts.map +1 -1
  39. package/dist/utils.js +98 -29
  40. package/dist/utils.js.map +1 -1
  41. package/lib/attachment.d.mts +112 -0
  42. package/lib/attachment.d.mts.map +1 -0
  43. package/lib/attachment.mjs +74 -0
  44. package/lib/attachment.mjs.map +1 -0
  45. package/lib/connectionManager.d.mts.map +1 -1
  46. package/lib/connectionManager.mjs +2 -5
  47. package/lib/connectionManager.mjs.map +1 -1
  48. package/lib/container-loader-alpha.d.mts +1 -1
  49. package/lib/container-loader-untrimmed.d.mts +1 -1
  50. package/lib/container.d.mts +21 -8
  51. package/lib/container.d.mts.map +1 -1
  52. package/lib/container.mjs +176 -151
  53. package/lib/container.mjs.map +1 -1
  54. package/lib/containerContext.d.mts +3 -2
  55. package/lib/containerContext.d.mts.map +1 -1
  56. package/lib/containerContext.mjs +2 -1
  57. package/lib/containerContext.mjs.map +1 -1
  58. package/lib/containerStorageAdapter.d.mts +3 -3
  59. package/lib/containerStorageAdapter.d.mts.map +1 -1
  60. package/lib/containerStorageAdapter.mjs +10 -11
  61. package/lib/containerStorageAdapter.mjs.map +1 -1
  62. package/lib/loader.d.mts +1 -1
  63. package/lib/loader.mjs.map +1 -1
  64. package/lib/packageVersion.d.mts +1 -1
  65. package/lib/packageVersion.mjs +1 -1
  66. package/lib/packageVersion.mjs.map +1 -1
  67. package/lib/protocolTreeDocumentStorageService.d.mts +1 -0
  68. package/lib/protocolTreeDocumentStorageService.d.mts.map +1 -1
  69. package/lib/protocolTreeDocumentStorageService.mjs +1 -0
  70. package/lib/protocolTreeDocumentStorageService.mjs.map +1 -1
  71. package/lib/retriableDocumentStorageService.d.mts +2 -1
  72. package/lib/retriableDocumentStorageService.d.mts.map +1 -1
  73. package/lib/retriableDocumentStorageService.mjs +9 -1
  74. package/lib/retriableDocumentStorageService.mjs.map +1 -1
  75. package/lib/utils.d.mts +13 -7
  76. package/lib/utils.d.mts.map +1 -1
  77. package/lib/utils.mjs +96 -29
  78. package/lib/utils.mjs.map +1 -1
  79. package/package.json +21 -15
  80. package/src/attachment.ts +219 -0
  81. package/src/connectionManager.ts +2 -4
  82. package/src/container.ts +260 -191
  83. package/src/containerContext.ts +2 -1
  84. package/src/containerStorageAdapter.ts +15 -12
  85. package/src/loader.ts +1 -1
  86. package/src/packageVersion.ts +1 -1
  87. package/src/protocolTreeDocumentStorageService.ts +1 -0
  88. package/src/retriableDocumentStorageService.ts +18 -1
  89. package/src/utils.ts +128 -36
  90. /package/{.eslintrc.js → .eslintrc.cjs} +0 -0
@@ -16,7 +16,7 @@ import {
16
16
  IBatchMessage,
17
17
  } from "@fluidframework/container-definitions";
18
18
  import { FluidObject } from "@fluidframework/core-interfaces";
19
- import { IDocumentStorageService } from "@fluidframework/driver-definitions";
19
+ import { IDocumentStorageService, ISnapshot } from "@fluidframework/driver-definitions";
20
20
  import {
21
21
  IClientDetails,
22
22
  IDocumentMessage,
@@ -99,6 +99,7 @@ export class ContainerContext implements IContainerContext {
99
99
  public readonly existing: boolean,
100
100
  public readonly taggedLogger: ITelemetryLoggerExt,
101
101
  public readonly pendingLocalState?: unknown,
102
+ public readonly snapshotWithContents?: ISnapshot,
102
103
  ) {}
103
104
 
104
105
  public getLoadedFromVersion(): IVersion | undefined {
@@ -13,6 +13,8 @@ import {
13
13
  IDocumentService,
14
14
  IDocumentStorageService,
15
15
  IDocumentStorageServicePolicies,
16
+ ISnapshot,
17
+ ISnapshotFetchOptions,
16
18
  ISummaryContext,
17
19
  } from "@fluidframework/driver-definitions";
18
20
  import { UsageError } from "@fluidframework/driver-utils";
@@ -101,18 +103,9 @@ export class ContainerStorageAdapter implements IDocumentStorageService, IDispos
101
103
  }
102
104
  }
103
105
 
104
- public loadSnapshotForRehydratingContainer(snapshotTree: ISnapshotTreeWithBlobContents) {
105
- this.getBlobContents(snapshotTree);
106
- }
107
-
108
- private getBlobContents(snapshotTree: ISnapshotTreeWithBlobContents) {
109
- if (snapshotTree.blobsContents !== undefined) {
110
- for (const [id, value] of Object.entries(snapshotTree.blobsContents ?? {})) {
111
- this.blobContents[id] = value;
112
- }
113
- }
114
- for (const [_, tree] of Object.entries(snapshotTree.trees)) {
115
- this.getBlobContents(tree);
106
+ public loadSnapshotFromSnapshotBlobs(snapshotBlobs: ISerializableBlobContents) {
107
+ for (const [id, value] of Object.entries(snapshotBlobs)) {
108
+ this.blobContents[id] = value;
116
109
  }
117
110
  }
118
111
 
@@ -136,6 +129,15 @@ export class ContainerStorageAdapter implements IDocumentStorageService, IDispos
136
129
  return this._storageService.getSnapshotTree(version, scenarioName);
137
130
  }
138
131
 
132
+ public async getSnapshot(snapshotFetchOptions?: ISnapshotFetchOptions): Promise<ISnapshot> {
133
+ if (this._storageService.getSnapshot !== undefined) {
134
+ return this._storageService.getSnapshot(snapshotFetchOptions);
135
+ }
136
+ throw new UsageError(
137
+ "getSnapshot api should exist in internal storage in ContainerStorageAdapter",
138
+ );
139
+ }
140
+
139
141
  public async readBlob(id: string): Promise<ArrayBufferLike> {
140
142
  const maybeBlob = this.blobContents[id];
141
143
  if (maybeBlob !== undefined) {
@@ -208,6 +210,7 @@ class BlobOnlyStorage implements IDocumentStorageService {
208
210
 
209
211
  /* eslint-disable @typescript-eslint/unbound-method */
210
212
  public getSnapshotTree: () => Promise<ISnapshotTree | null> = this.notCalled;
213
+ public getSnapshot: () => Promise<ISnapshot> = this.notCalled;
211
214
  public getVersions: () => Promise<IVersion[]> = this.notCalled;
212
215
  public write: () => Promise<IVersion> = this.notCalled;
213
216
  public uploadSummaryWithContext: () => Promise<string> = this.notCalled;
package/src/loader.ts CHANGED
@@ -108,7 +108,7 @@ export interface IFluidModuleWithDetails {
108
108
  }
109
109
 
110
110
  /**
111
- * @deprecated ICodeDetailsLoader interface is moved to {@link @fluidframework/container-definition#ICodeDetailsLoader}
111
+ * @deprecated ICodeDetailsLoader interface is moved to {@link @fluidframework/container-definitions#ICodeDetailsLoader}
112
112
  * to have code loading modules in one package. #8193
113
113
  * Fluid code loader resolves a code module matching the document schema, i.e. code details, such as
114
114
  * a package name and package version range.
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "2.0.0-dev-rc.1.0.0.228517";
9
+ export const pkgVersion = "2.0.0-dev-rc.1.0.0.232845";
@@ -27,6 +27,7 @@ export class ProtocolTreeStorageService implements IDocumentStorageService, IDis
27
27
  }
28
28
 
29
29
  getSnapshotTree = this.internalStorageService.getSnapshotTree.bind(this.internalStorageService);
30
+ getSnapshot = this.internalStorageService.getSnapshot?.bind(this.internalStorageService);
30
31
  getVersions = this.internalStorageService.getVersions.bind(this.internalStorageService);
31
32
  createBlob = this.internalStorageService.createBlob.bind(this.internalStorageService);
32
33
  readBlob = this.internalStorageService.readBlob.bind(this.internalStorageService);
@@ -8,6 +8,8 @@ import {
8
8
  FetchSource,
9
9
  IDocumentStorageService,
10
10
  IDocumentStorageServicePolicies,
11
+ ISnapshot,
12
+ ISnapshotFetchOptions,
11
13
  ISummaryContext,
12
14
  } from "@fluidframework/driver-definitions";
13
15
  import {
@@ -18,7 +20,7 @@ import {
18
20
  IVersion,
19
21
  } from "@fluidframework/protocol-definitions";
20
22
  import { IDisposable } from "@fluidframework/core-interfaces";
21
- import { GenericError, ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
23
+ import { GenericError, ITelemetryLoggerExt, UsageError } from "@fluidframework/telemetry-utils";
22
24
  import { runWithRetry } from "@fluidframework/driver-utils";
23
25
 
24
26
  export class RetriableDocumentStorageService implements IDocumentStorageService, IDisposable {
@@ -64,6 +66,21 @@ export class RetriableDocumentStorageService implements IDocumentStorageService,
64
66
  );
65
67
  }
66
68
 
69
+ public async getSnapshot(snapshotFetchOptions?: ISnapshotFetchOptions): Promise<ISnapshot> {
70
+ return this.runWithRetry(
71
+ async () =>
72
+ this.internalStorageServiceP.then(async (s) => {
73
+ if (s.getSnapshot !== undefined) {
74
+ return s.getSnapshot(snapshotFetchOptions);
75
+ }
76
+ throw new UsageError(
77
+ "getSnapshot api should exist on internal storage in RetriableDocStorageService class",
78
+ );
79
+ }),
80
+ "storage_getSnapshot",
81
+ );
82
+ }
83
+
67
84
  public async readBlob(id: string): Promise<ArrayBufferLike> {
68
85
  return this.runWithRetry(
69
86
  async () => this.internalStorageServiceP.then(async (s) => s.readBlob(id)),
package/src/utils.ts CHANGED
@@ -5,16 +5,18 @@
5
5
 
6
6
  import { parse } from "url";
7
7
  import { v4 as uuid } from "uuid";
8
- import { stringToBuffer, Uint8ArrayToArrayBuffer } from "@fluid-internal/client-utils";
9
- import { assert, unreachableCase } from "@fluidframework/core-utils";
8
+ import { Uint8ArrayToString, stringToBuffer } from "@fluid-internal/client-utils";
9
+ import { assert, compareArrays, unreachableCase } from "@fluidframework/core-utils";
10
10
  import { ISummaryTree, ISnapshotTree, SummaryType } from "@fluidframework/protocol-definitions";
11
- import { LoggingError } from "@fluidframework/telemetry-utils";
11
+ import { LoggingError, UsageError } from "@fluidframework/telemetry-utils";
12
12
  import {
13
13
  CombinedAppAndProtocolSummary,
14
14
  DeltaStreamConnectionForbiddenError,
15
15
  isCombinedAppAndProtocolSummary,
16
16
  } from "@fluidframework/driver-utils";
17
17
  import { DriverErrorTypes } from "@fluidframework/driver-definitions";
18
+ import { ISerializableBlobContents } from "./containerStorageAdapter";
19
+ import { IPendingDetachedContainerState } from "./container";
18
20
 
19
21
  // This is used when we rehydrate a container from the snapshot. Here we put the blob contents
20
22
  // in separate property: blobContents.
@@ -101,21 +103,17 @@ export function combineAppAndProtocolSummary(
101
103
  }
102
104
 
103
105
  /**
104
- * Converts summary tree (for upload) to snapshot tree (for download).
105
- * Summary tree blobs contain contents, but snapshot tree blobs normally
106
- * contain IDs pointing to storage. This will create 2 blob entries in the
107
- * snapshot tree for each blob in the summary tree. One will be the regular
108
- * path pointing to a uniquely generated ID. Then there will be another
109
- * entry with the path as that uniquely generated ID, and value as the
110
- * blob contents as a base-64 string.
111
- * @param summary - summary to convert
106
+ * Converts a summary to snapshot tree and separate its blob contents
107
+ * to align detached container format with IPendingContainerState
108
+ * @param summary - ISummaryTree
112
109
  */
113
- function convertSummaryToSnapshotWithEmbeddedBlobContents(
114
- summary: ISummaryTree,
115
- ): ISnapshotTreeWithBlobContents {
116
- const treeNode: ISnapshotTreeWithBlobContents = {
110
+ function convertSummaryToSnapshotAndBlobs(summary: ISummaryTree): {
111
+ tree: ISnapshotTree;
112
+ blobs: ISerializableBlobContents;
113
+ } {
114
+ let blobContents: ISerializableBlobContents = {};
115
+ const treeNode: ISnapshotTree = {
117
116
  blobs: {},
118
- blobsContents: {},
119
117
  trees: {},
120
118
  id: uuid(),
121
119
  unreferenced: summary.unreferenced,
@@ -126,8 +124,9 @@ function convertSummaryToSnapshotWithEmbeddedBlobContents(
126
124
 
127
125
  switch (summaryObject.type) {
128
126
  case SummaryType.Tree: {
129
- treeNode.trees[key] =
130
- convertSummaryToSnapshotWithEmbeddedBlobContents(summaryObject);
127
+ const { tree, blobs } = convertSummaryToSnapshotAndBlobs(summaryObject);
128
+ treeNode.trees[key] = tree;
129
+ blobContents = { ...blobContents, ...blobs };
131
130
  break;
132
131
  }
133
132
  case SummaryType.Attachment:
@@ -136,11 +135,11 @@ function convertSummaryToSnapshotWithEmbeddedBlobContents(
136
135
  case SummaryType.Blob: {
137
136
  const blobId = uuid();
138
137
  treeNode.blobs[key] = blobId;
139
- const contentBuffer =
140
- typeof summaryObject.content === "string"
141
- ? stringToBuffer(summaryObject.content, "utf8")
142
- : Uint8ArrayToArrayBuffer(summaryObject.content);
143
- treeNode.blobsContents[blobId] = contentBuffer;
138
+ const contentString: string =
139
+ summaryObject.content instanceof Uint8Array
140
+ ? Uint8ArrayToString(summaryObject.content)
141
+ : summaryObject.content;
142
+ blobContents[blobId] = contentString;
144
143
  break;
145
144
  }
146
145
  case SummaryType.Handle:
@@ -153,42 +152,38 @@ function convertSummaryToSnapshotWithEmbeddedBlobContents(
153
152
  }
154
153
  }
155
154
  }
156
- return treeNode;
155
+ return { tree: treeNode, blobs: blobContents };
157
156
  }
158
157
 
159
158
  /**
160
- * Combine and convert protocol and app summary tree to format which is readable by container while rehydrating.
159
+ * Converts summary parts into a SnapshotTree and its blob contents.
161
160
  * @param protocolSummaryTree - Protocol Summary Tree
162
161
  * @param appSummaryTree - App Summary Tree
163
162
  */
164
- export function convertProtocolAndAppSummaryToSnapshotTree(
163
+ function convertProtocolAndAppSummaryToSnapshotAndBlobs(
165
164
  protocolSummaryTree: ISummaryTree,
166
165
  appSummaryTree: ISummaryTree,
167
- ): ISnapshotTreeWithBlobContents {
168
- // Shallow copy is fine, since we are doing a deep clone below.
166
+ ): { tree: ISnapshotTree; blobs: ISerializableBlobContents } {
169
167
  const combinedSummary: ISummaryTree = {
170
168
  type: SummaryType.Tree,
171
169
  tree: { ...appSummaryTree.tree },
172
170
  };
173
171
 
174
172
  combinedSummary.tree[".protocol"] = protocolSummaryTree;
175
- const snapshotTreeWithBlobContents =
176
- convertSummaryToSnapshotWithEmbeddedBlobContents(combinedSummary);
173
+ const snapshotTreeWithBlobContents = convertSummaryToSnapshotAndBlobs(combinedSummary);
177
174
  return snapshotTreeWithBlobContents;
178
175
  }
179
176
 
180
- // This function converts the snapshot taken in detached container(by serialize api) to snapshotTree with which
181
- // a detached container can be rehydrated.
182
- export const getSnapshotTreeFromSerializedContainer = (
177
+ export const getSnapshotTreeAndBlobsFromSerializedContainer = (
183
178
  detachedContainerSnapshot: ISummaryTree,
184
- ): ISnapshotTreeWithBlobContents => {
179
+ ): { tree: ISnapshotTree; blobs: ISerializableBlobContents } => {
185
180
  assert(
186
181
  isCombinedAppAndProtocolSummary(detachedContainerSnapshot),
187
- 0x1e0 /* "Protocol and App summary trees should be present" */,
182
+ "Protocol and App summary trees should be present",
188
183
  );
189
184
  const protocolSummaryTree = detachedContainerSnapshot.tree[".protocol"];
190
185
  const appSummaryTree = detachedContainerSnapshot.tree[".app"];
191
- const snapshotTreeWithBlobContents = convertProtocolAndAppSummaryToSnapshotTree(
186
+ const snapshotTreeWithBlobContents = convertProtocolAndAppSummaryToSnapshotAndBlobs(
192
187
  protocolSummaryTree,
193
188
  appSummaryTree,
194
189
  );
@@ -199,6 +194,35 @@ export function getProtocolSnapshotTree(snapshot: ISnapshotTree): ISnapshotTree
199
194
  return ".protocol" in snapshot.trees ? snapshot.trees[".protocol"] : snapshot;
200
195
  }
201
196
 
197
+ export const combineSnapshotTreeAndSnapshotBlobs = (
198
+ baseSnapshot: ISnapshotTree,
199
+ snapshotBlobs: ISerializableBlobContents,
200
+ ): ISnapshotTreeWithBlobContents => {
201
+ const blobsContents: { [path: string]: ArrayBufferLike } = {};
202
+
203
+ // Process blobs in the current level
204
+ for (const [, id] of Object.entries(baseSnapshot.blobs)) {
205
+ if (snapshotBlobs[id]) {
206
+ blobsContents[id] = stringToBuffer(snapshotBlobs[id], "utf8");
207
+ }
208
+ }
209
+
210
+ // Recursively process trees in the current level
211
+ const trees: { [path: string]: ISnapshotTreeWithBlobContents } = {};
212
+ for (const [path, tree] of Object.entries(baseSnapshot.trees)) {
213
+ trees[path] = combineSnapshotTreeAndSnapshotBlobs(tree, snapshotBlobs);
214
+ }
215
+
216
+ // Create a new snapshot tree with blob contents and processed trees
217
+ const snapshotTreeWithBlobContents: ISnapshotTreeWithBlobContents = {
218
+ ...baseSnapshot,
219
+ blobsContents,
220
+ trees,
221
+ };
222
+
223
+ return snapshotTreeWithBlobContents;
224
+ };
225
+
202
226
  export function isDeltaStreamConnectionForbiddenError(
203
227
  error: any,
204
228
  ): error is DeltaStreamConnectionForbiddenError {
@@ -208,3 +232,71 @@ export function isDeltaStreamConnectionForbiddenError(
208
232
  error?.errorType === DriverErrorTypes.deltaStreamConnectionForbidden
209
233
  );
210
234
  }
235
+
236
+ /**
237
+ * Validates format in parsed string get from detached container
238
+ * serialization using IPendingDetachedContainerState format.
239
+ */
240
+ function isPendingDetachedContainerState(
241
+ detachedContainerState: IPendingDetachedContainerState,
242
+ ): detachedContainerState is IPendingDetachedContainerState {
243
+ if (
244
+ detachedContainerState?.attached === undefined ||
245
+ detachedContainerState?.baseSnapshot === undefined ||
246
+ detachedContainerState?.snapshotBlobs === undefined ||
247
+ detachedContainerState?.hasAttachmentBlobs === undefined
248
+ ) {
249
+ return false;
250
+ }
251
+ return true;
252
+ }
253
+
254
+ export function getDetachedContainerStateFromSerializedContainer(
255
+ serializedContainer: string,
256
+ ): IPendingDetachedContainerState {
257
+ const hasBlobsSummaryTree = ".hasAttachmentBlobs";
258
+ const parsedContainerState = JSON.parse(serializedContainer);
259
+ if (isPendingDetachedContainerState(parsedContainerState)) {
260
+ return parsedContainerState;
261
+ } else if (isCombinedAppAndProtocolSummary(parsedContainerState)) {
262
+ const { tree, blobs } =
263
+ getSnapshotTreeAndBlobsFromSerializedContainer(parsedContainerState);
264
+ const detachedContainerState: IPendingDetachedContainerState = {
265
+ attached: false,
266
+ baseSnapshot: tree,
267
+ snapshotBlobs: blobs,
268
+ hasAttachmentBlobs: parsedContainerState.tree[hasBlobsSummaryTree] !== undefined,
269
+ };
270
+ return detachedContainerState;
271
+ } else {
272
+ throw new UsageError("Cannot rehydrate detached container. Incorrect format");
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Ensures only a single instance of the provided async function is running.
278
+ * If there are multiple calls they will all get the same promise to wait on.
279
+ */
280
+ export const runSingle = <A extends any[], R>(func: (...args: A) => Promise<R>) => {
281
+ let running:
282
+ | {
283
+ args: A;
284
+ result: Promise<R>;
285
+ }
286
+ | undefined;
287
+ // don't mark this function async, so we return the same promise,
288
+ // rather than one that is wrapped due to async
289
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
290
+ return (...args: A) => {
291
+ if (running !== undefined) {
292
+ if (!compareArrays(running.args, args)) {
293
+ return Promise.reject(
294
+ new UsageError("Subsequent calls cannot use different arguments."),
295
+ );
296
+ }
297
+ return running.result;
298
+ }
299
+ running = { args, result: func(...args).finally(() => (running = undefined)) };
300
+ return running.result;
301
+ };
302
+ };
File without changes