@fluidframework/container-loader 2.0.0-rc.4.0.0 → 2.0.0-rc.4.0.10

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 (64) hide show
  1. package/api-report/container-loader.api.md +3 -1
  2. package/dist/attachment.d.ts.map +1 -1
  3. package/dist/attachment.js.map +1 -1
  4. package/dist/connectionStateHandler.d.ts +3 -1
  5. package/dist/connectionStateHandler.d.ts.map +1 -1
  6. package/dist/connectionStateHandler.js +18 -12
  7. package/dist/connectionStateHandler.js.map +1 -1
  8. package/dist/container.d.ts.map +1 -1
  9. package/dist/container.js +27 -7
  10. package/dist/container.js.map +1 -1
  11. package/dist/containerStorageAdapter.d.ts.map +1 -1
  12. package/dist/containerStorageAdapter.js +7 -2
  13. package/dist/containerStorageAdapter.js.map +1 -1
  14. package/dist/loader.d.ts +7 -0
  15. package/dist/loader.d.ts.map +1 -1
  16. package/dist/loader.js.map +1 -1
  17. package/dist/memoryBlobStorage.d.ts +9 -0
  18. package/dist/memoryBlobStorage.d.ts.map +1 -0
  19. package/dist/memoryBlobStorage.js +60 -0
  20. package/dist/memoryBlobStorage.js.map +1 -0
  21. package/dist/packageVersion.d.ts +1 -1
  22. package/dist/packageVersion.d.ts.map +1 -1
  23. package/dist/packageVersion.js +1 -1
  24. package/dist/packageVersion.js.map +1 -1
  25. package/dist/serializedStateManager.d.ts +2 -0
  26. package/dist/serializedStateManager.d.ts.map +1 -1
  27. package/dist/serializedStateManager.js.map +1 -1
  28. package/lib/attachment.d.ts.map +1 -1
  29. package/lib/attachment.js.map +1 -1
  30. package/lib/connectionStateHandler.d.ts +3 -1
  31. package/lib/connectionStateHandler.d.ts.map +1 -1
  32. package/lib/connectionStateHandler.js +19 -13
  33. package/lib/connectionStateHandler.js.map +1 -1
  34. package/lib/container.d.ts.map +1 -1
  35. package/lib/container.js +28 -8
  36. package/lib/container.js.map +1 -1
  37. package/lib/containerStorageAdapter.d.ts.map +1 -1
  38. package/lib/containerStorageAdapter.js +7 -2
  39. package/lib/containerStorageAdapter.js.map +1 -1
  40. package/lib/loader.d.ts +7 -0
  41. package/lib/loader.d.ts.map +1 -1
  42. package/lib/loader.js.map +1 -1
  43. package/lib/memoryBlobStorage.d.ts +9 -0
  44. package/lib/memoryBlobStorage.d.ts.map +1 -0
  45. package/lib/memoryBlobStorage.js +54 -0
  46. package/lib/memoryBlobStorage.js.map +1 -0
  47. package/lib/packageVersion.d.ts +1 -1
  48. package/lib/packageVersion.d.ts.map +1 -1
  49. package/lib/packageVersion.js +1 -1
  50. package/lib/packageVersion.js.map +1 -1
  51. package/lib/serializedStateManager.d.ts +2 -0
  52. package/lib/serializedStateManager.d.ts.map +1 -1
  53. package/lib/serializedStateManager.js.map +1 -1
  54. package/package.json +13 -13
  55. package/src/attachment.ts +2 -0
  56. package/src/connectionStateHandler.ts +26 -18
  57. package/src/container.ts +37 -7
  58. package/src/containerStorageAdapter.ts +4 -0
  59. package/src/loader.ts +8 -0
  60. package/src/memoryBlobStorage.ts +83 -0
  61. package/src/packageVersion.ts +1 -1
  62. package/src/serializedStateManager.ts +2 -0
  63. package/dist/public.d.ts +0 -14
  64. package/lib/public.d.ts +0 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-loader",
3
- "version": "2.0.0-rc.4.0.0",
3
+ "version": "2.0.0-rc.4.0.10",
4
4
  "description": "Fluid container loader",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -15,11 +15,11 @@
15
15
  "exports": {
16
16
  ".": {
17
17
  "import": {
18
- "types": "./lib/public.d.ts",
18
+ "types": "./lib/index.d.ts",
19
19
  "default": "./lib/index.js"
20
20
  },
21
21
  "require": {
22
- "types": "./dist/public.d.ts",
22
+ "types": "./dist/index.d.ts",
23
23
  "default": "./dist/index.js"
24
24
  }
25
25
  },
@@ -95,7 +95,7 @@
95
95
  }
96
96
  },
97
97
  "main": "lib/index.js",
98
- "types": "lib/public.d.ts",
98
+ "types": "lib/index.d.ts",
99
99
  "c8": {
100
100
  "all": true,
101
101
  "cache-dir": "nyc/.cache",
@@ -117,15 +117,15 @@
117
117
  "temp-directory": "nyc/.nyc_output"
118
118
  },
119
119
  "dependencies": {
120
- "@fluid-internal/client-utils": ">=2.0.0-rc.4.0.0 <2.0.0-rc.4.1.0",
121
- "@fluidframework/container-definitions": ">=2.0.0-rc.4.0.0 <2.0.0-rc.4.1.0",
122
- "@fluidframework/core-interfaces": ">=2.0.0-rc.4.0.0 <2.0.0-rc.4.1.0",
123
- "@fluidframework/core-utils": ">=2.0.0-rc.4.0.0 <2.0.0-rc.4.1.0",
124
- "@fluidframework/driver-definitions": ">=2.0.0-rc.4.0.0 <2.0.0-rc.4.1.0",
125
- "@fluidframework/driver-utils": ">=2.0.0-rc.4.0.0 <2.0.0-rc.4.1.0",
120
+ "@fluid-internal/client-utils": ">=2.0.0-rc.4.0.10 <2.0.0-rc.4.1.0",
121
+ "@fluidframework/container-definitions": ">=2.0.0-rc.4.0.10 <2.0.0-rc.4.1.0",
122
+ "@fluidframework/core-interfaces": ">=2.0.0-rc.4.0.10 <2.0.0-rc.4.1.0",
123
+ "@fluidframework/core-utils": ">=2.0.0-rc.4.0.10 <2.0.0-rc.4.1.0",
124
+ "@fluidframework/driver-definitions": ">=2.0.0-rc.4.0.10 <2.0.0-rc.4.1.0",
125
+ "@fluidframework/driver-utils": ">=2.0.0-rc.4.0.10 <2.0.0-rc.4.1.0",
126
126
  "@fluidframework/protocol-base": "^4.0.0",
127
127
  "@fluidframework/protocol-definitions": "^3.2.0",
128
- "@fluidframework/telemetry-utils": ">=2.0.0-rc.4.0.0 <2.0.0-rc.4.1.0",
128
+ "@fluidframework/telemetry-utils": ">=2.0.0-rc.4.0.10 <2.0.0-rc.4.1.0",
129
129
  "@ungap/structured-clone": "^1.2.0",
130
130
  "debug": "^4.3.4",
131
131
  "double-ended-queue": "^2.1.0-0",
@@ -134,8 +134,8 @@
134
134
  "devDependencies": {
135
135
  "@arethetypeswrong/cli": "^0.15.2",
136
136
  "@biomejs/biome": "^1.6.2",
137
- "@fluid-internal/mocha-test-setup": ">=2.0.0-rc.4.0.0 <2.0.0-rc.4.1.0",
138
- "@fluid-private/test-loader-utils": ">=2.0.0-rc.4.0.0 <2.0.0-rc.4.1.0",
137
+ "@fluid-internal/mocha-test-setup": ">=2.0.0-rc.4.0.10 <2.0.0-rc.4.1.0",
138
+ "@fluid-private/test-loader-utils": ">=2.0.0-rc.4.0.10 <2.0.0-rc.4.1.0",
139
139
  "@fluid-tools/build-cli": "^0.38.0",
140
140
  "@fluidframework/build-common": "^2.0.3",
141
141
  "@fluidframework/build-tools": "^0.38.0",
package/src/attachment.ts CHANGED
@@ -9,6 +9,7 @@ import { IDocumentStorageService } from "@fluidframework/driver-definitions/inte
9
9
  import { CombinedAppAndProtocolSummary } from "@fluidframework/driver-utils/internal";
10
10
  import { ISummaryTree } from "@fluidframework/protocol-definitions";
11
11
 
12
+ // eslint-disable-next-line import/no-deprecated
12
13
  import { IDetachedBlobStorage } from "./loader.js";
13
14
  import type { SnapshotWithBlobs } from "./serializedStateManager.js";
14
15
  import { getSnapshotTreeAndBlobsFromSerializedContainer } from "./utils.js";
@@ -110,6 +111,7 @@ export interface AttachProcessProps {
110
111
  /**
111
112
  * The detached blob storage if it exists.
112
113
  */
114
+ // eslint-disable-next-line import/no-deprecated
113
115
  readonly detachedBlobStorage?: Pick<IDetachedBlobStorage, "getBlobIds" | "readBlob" | "size">;
114
116
 
115
117
  /**
@@ -11,8 +11,8 @@ import { IClient, ISequencedClient } from "@fluidframework/protocol-definitions"
11
11
  import {
12
12
  type TelemetryEventCategory,
13
13
  ITelemetryLoggerExt,
14
+ MonitoringContext,
14
15
  PerformanceEvent,
15
- loggerToMonitoringContext,
16
16
  } from "@fluidframework/telemetry-utils/internal";
17
17
 
18
18
  import { CatchUpMonitor, ICatchUpMonitor } from "./catchUpMonitor.js";
@@ -31,6 +31,7 @@ const JoinSignalTimeoutMs = 10000;
31
31
  /** Constructor parameter type for passing in dependencies needed by the ConnectionStateHandler */
32
32
  export interface IConnectionStateHandlerInputs {
33
33
  logger: ITelemetryLoggerExt;
34
+ mc: MonitoringContext;
34
35
  /** Log to telemetry any change in state, included to Connecting */
35
36
  connectionStateChanged: (
36
37
  value: ConnectionState,
@@ -92,10 +93,10 @@ export function createConnectionStateHandler(
92
93
  deltaManager: IDeltaManager<any, any>,
93
94
  clientId?: string,
94
95
  ) {
95
- const mc = loggerToMonitoringContext(inputs.logger);
96
+ const config = inputs.mc.config;
96
97
  return createConnectionStateHandlerCore(
97
- mc.config.getBoolean("Fluid.Container.DisableCatchUpBeforeDeclaringConnected") !== true, // connectedRaisedWhenCaughtUp
98
- mc.config.getBoolean("Fluid.Container.DisableJoinSignalWait") !== true, // readClientsWaitForJoinSignal
98
+ config.getBoolean("Fluid.Container.DisableCatchUpBeforeDeclaringConnected") !== true, // connectedRaisedWhenCaughtUp
99
+ config.getBoolean("Fluid.Container.DisableJoinSignalWait") !== true, // readClientsWaitForJoinSignal
99
100
  inputs,
100
101
  deltaManager,
101
102
  clientId,
@@ -189,6 +190,9 @@ class ConnectionStateHandlerPassThrough
189
190
  public get logger() {
190
191
  return this.inputs.logger;
191
192
  }
193
+ public get mc() {
194
+ return this.inputs.mc;
195
+ }
192
196
  public connectionStateChanged(
193
197
  value: ConnectionState,
194
198
  oldState: ConnectionState,
@@ -469,18 +473,6 @@ class ConnectionStateHandler implements IConnectionStateHandler {
469
473
  });
470
474
  }
471
475
  this.applyForConnectedState("addMemberEvent");
472
- } else if (clientId === this.clientId) {
473
- // If we see our clientId and it's not also our pending ID, it's our own join op
474
- // being replayed, so start the timer in case our previous client is still in quorum
475
- assert(
476
- !this.waitingForLeaveOp,
477
- 0x5d2 /* Unexpected join op with current clientId while waiting */,
478
- );
479
- assert(
480
- this.connectionState !== ConnectionState.Connected,
481
- 0x5d3 /* Unexpected join op with current clientId while connected */,
482
- );
483
- this.prevClientLeftTimer.restart();
484
476
  }
485
477
  }
486
478
 
@@ -528,7 +520,7 @@ class ConnectionStateHandler implements IConnectionStateHandler {
528
520
 
529
521
  private receivedRemoveMemberEvent(clientId: string) {
530
522
  // If the client which has left was us, then finish the timer.
531
- if (this.clientId === clientId) {
523
+ if (this.clientId === clientId && this.waitingForLeaveOp) {
532
524
  this.prevClientLeftTimer.clear();
533
525
  this.applyForConnectedState("removeMemberEvent");
534
526
  }
@@ -737,8 +729,24 @@ class ConnectionStateHandler implements IConnectionStateHandler {
737
729
  this.receivedAddMemberEvent(this.pendingClientId!);
738
730
  }
739
731
 
732
+ assert(
733
+ !this.waitingForLeaveOp,
734
+ "leave timer can't be set as we have not had access to quorum",
735
+ );
736
+
737
+ // This check is required for scenario of loading container from pending state, and ensuring there is no way
738
+ // old clientId is still in the quorum (very unlikely, but you never know)
740
739
  // if we have a clientId from a previous container we need to wait for its leave message
741
- if (this.clientId !== undefined && this.hasMember(this.clientId)) {
740
+ // This mimicks check in setConnectionState()
741
+ // Note that we are not consulting this.handler.shouldClientJoinWrite() here
742
+ // It could produce wrong results for stashed ops were never sent to Loader yet, and if this check
743
+ // makes determination only on that (and not uses "dirty" events), then it can produce wrong result.
744
+ // In most cases it does not matter, as this client already left quorum. But in really unfortunate case,
745
+ // we might wait even if we could avoid such wait.
746
+ if (
747
+ this._clientId !== undefined &&
748
+ protocol.quorum?.getMember(this._clientId) !== undefined
749
+ ) {
742
750
  this.prevClientLeftTimer.restart();
743
751
  }
744
752
  }
package/src/container.ts CHANGED
@@ -92,6 +92,7 @@ import {
92
92
  normalizeError,
93
93
  raiseConnectedEvent,
94
94
  wrapError,
95
+ loggerToMonitoringContext,
95
96
  } from "@fluidframework/telemetry-utils/internal";
96
97
  import structuredClone from "@ungap/structured-clone";
97
98
  import { v4 as uuid } from "uuid";
@@ -111,6 +112,7 @@ import {
111
112
  getPackageName,
112
113
  } from "./contracts.js";
113
114
  import { DeltaManager, IConnectionArgs } from "./deltaManager.js";
115
+ // eslint-disable-next-line import/no-deprecated
114
116
  import { IDetachedBlobStorage, ILoaderOptions, RelativeLoader } from "./loader.js";
115
117
  import { NoopHeuristic } from "./noopHeuristic.js";
116
118
  import { pkgVersion } from "./packageVersion.js";
@@ -136,6 +138,11 @@ import {
136
138
  getSnapshotTreeAndBlobsFromSerializedContainer,
137
139
  runSingle,
138
140
  } from "./utils.js";
141
+ import {
142
+ serializeMemoryDetachedBlobStorage,
143
+ createMemoryDetachedBlobStorage,
144
+ tryInitializeMemoryDetachedBlobStorage,
145
+ } from "./memoryBlobStorage.js";
139
146
 
140
147
  const detachedContainerRefSeqNumber = 0;
141
148
 
@@ -218,6 +225,7 @@ export interface IContainerCreateProps {
218
225
  /**
219
226
  * Blobs storage for detached containers.
220
227
  */
228
+ // eslint-disable-next-line import/no-deprecated
221
229
  readonly detachedBlobStorage?: IDetachedBlobStorage;
222
230
 
223
231
  /**
@@ -465,6 +473,7 @@ export class Container
465
473
  private readonly options: ILoaderOptions;
466
474
  private readonly scope: FluidObject;
467
475
  private readonly subLogger: ITelemetryLoggerExt;
476
+ // eslint-disable-next-line import/no-deprecated
468
477
  private readonly detachedBlobStorage: IDetachedBlobStorage | undefined;
469
478
  private readonly protocolHandlerBuilder: ProtocolHandlerBuilder;
470
479
  private readonly client: IClient;
@@ -517,6 +526,15 @@ export class Container
517
526
  0x969 /* not connected yet */,
518
527
  );
519
528
 
529
+ // Track membership changes and update connection state accordingly
530
+ // We do this call here, instead of doing it in initializeProtocolState() due to pendingLocalState.
531
+ // When we load from stashed state, we let connectionStateHandler know about clientId from previous container instance.
532
+ // But we will play trailing ops from snapshot, including potentially playing join & leave ops for that same clientId!
533
+ // In other words, if connectionStateHandler has access to Quorum early in load sequence, it will see events (in stashed ops mode)
534
+ // in the order that is not possible in real life, that it may not expect.
535
+ // Ideally, we should supply pendingLocalState?.clientId here as well, not in constructor, but it does not matter (at least today)
536
+ this.connectionStateHandler.initProtocol(this.protocolHandler);
537
+
520
538
  // Propagate current connection state through the system.
521
539
  const readonly = this.readOnlyInfo.readonly ?? false;
522
540
  // This call does not look like needed any more, with delaying all connection-related events past loaded phase.
@@ -767,7 +785,6 @@ export class Container
767
785
  // Tracking alternative ways to handle this in AB#4129.
768
786
  this.options = { ...options };
769
787
  this.scope = scope;
770
- this.detachedBlobStorage = detachedBlobStorage;
771
788
  this.protocolHandlerBuilder =
772
789
  protocolHandlerBuilder ??
773
790
  ((
@@ -856,6 +873,9 @@ export class Container
856
873
  this.connectionStateHandler = createConnectionStateHandler(
857
874
  {
858
875
  logger: this.mc.logger,
876
+ // WARNING: logger on this context should not including getters like containerConnectionState above (on this.subLogger),
877
+ // as that will result in attempt to dereference this.connectionStateHandler from this call while it's still undefined.
878
+ mc: loggerToMonitoringContext(subLogger),
859
879
  connectionStateChanged: (value, oldState, reason) => {
860
880
  this.logConnectionStateChangeTelemetry(value, oldState, reason);
861
881
  if (this.loaded) {
@@ -943,8 +963,14 @@ export class Container
943
963
  this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
944
964
  options.summarizeProtocolTree;
945
965
 
966
+ this.detachedBlobStorage =
967
+ detachedBlobStorage ??
968
+ (this.mc.config.getBoolean("Fluid.Container.MemoryBlobStorageEnabled") === true
969
+ ? createMemoryDetachedBlobStorage()
970
+ : undefined);
971
+
946
972
  this.storageAdapter = new ContainerStorageAdapter(
947
- detachedBlobStorage,
973
+ this.detachedBlobStorage,
948
974
  this.mc.logger,
949
975
  pendingLocalState?.snapshotBlobs,
950
976
  pendingLocalState?.loadedGroupIdSnapshots,
@@ -1208,7 +1234,9 @@ export class Container
1208
1234
  baseSnapshot,
1209
1235
  snapshotBlobs,
1210
1236
  pendingRuntimeState,
1211
- hasAttachmentBlobs: !!this.detachedBlobStorage && this.detachedBlobStorage.size > 0,
1237
+ hasAttachmentBlobs:
1238
+ this.detachedBlobStorage !== undefined && this.detachedBlobStorage.size > 0,
1239
+ attachmentBlobs: serializeMemoryDetachedBlobStorage(this.detachedBlobStorage),
1212
1240
  };
1213
1241
  return JSON.stringify(detachedContainerState);
1214
1242
  }
@@ -1328,6 +1356,7 @@ export class Container
1328
1356
  this.serializedStateManager.setInitialSnapshot(snapshotWithBlobs);
1329
1357
 
1330
1358
  if (!this.closed) {
1359
+ this.detachedBlobStorage?.dispose?.();
1331
1360
  this.handleDeltaConnectionArg(attachProps?.deltaConnection, {
1332
1361
  fetchOpsFromStorage: false,
1333
1362
  reason: { text: "createDetached" },
@@ -1760,11 +1789,15 @@ export class Container
1760
1789
  baseSnapshot,
1761
1790
  snapshotBlobs,
1762
1791
  hasAttachmentBlobs,
1792
+ attachmentBlobs,
1763
1793
  pendingRuntimeState,
1764
1794
  }: IPendingDetachedContainerState) {
1765
1795
  if (hasAttachmentBlobs) {
1796
+ if (attachmentBlobs !== undefined) {
1797
+ tryInitializeMemoryDetachedBlobStorage(this.detachedBlobStorage, attachmentBlobs);
1798
+ }
1766
1799
  assert(
1767
- !!this.detachedBlobStorage && this.detachedBlobStorage.size > 0,
1800
+ this.detachedBlobStorage !== undefined && this.detachedBlobStorage.size > 0,
1768
1801
  0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */,
1769
1802
  );
1770
1803
  }
@@ -1853,9 +1886,6 @@ export class Container
1853
1886
  protocolLogger.sendErrorEvent(error);
1854
1887
  });
1855
1888
 
1856
- // Track membership changes and update connection state accordingly
1857
- this.connectionStateHandler.initProtocol(protocol);
1858
-
1859
1889
  protocol.quorum.on("addProposal", (proposal: ISequencedProposal) => {
1860
1890
  if (proposal.key === "code" || proposal.key === "code2") {
1861
1891
  this.emit("codeDetailsProposed", proposal.value, proposal);
@@ -26,6 +26,7 @@ import {
26
26
  } from "@fluidframework/protocol-definitions";
27
27
  import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
28
28
 
29
+ // eslint-disable-next-line import/no-deprecated
29
30
  import { IDetachedBlobStorage } from "./loader.js";
30
31
  import { ProtocolTreeStorageService } from "./protocolTreeDocumentStorageService.js";
31
32
  import { RetriableDocumentStorageService } from "./retriableDocumentStorageService.js";
@@ -79,6 +80,7 @@ export class ContainerStorageAdapter
79
80
  * @param forceEnableSummarizeProtocolTree - Enforce uploading a protocol summary regardless of the service's policy
80
81
  */
81
82
  public constructor(
83
+ // eslint-disable-next-line import/no-deprecated
82
84
  detachedBlobStorage: IDetachedBlobStorage | undefined,
83
85
  private readonly logger: ITelemetryLoggerExt,
84
86
  /**
@@ -233,6 +235,7 @@ export class ContainerStorageAdapter
233
235
  */
234
236
  class BlobOnlyStorage implements IDocumentStorageService {
235
237
  constructor(
238
+ // eslint-disable-next-line import/no-deprecated
236
239
  private readonly detachedStorage: IDetachedBlobStorage | undefined,
237
240
  private readonly logger: ITelemetryLoggerExt,
238
241
  ) {}
@@ -245,6 +248,7 @@ class BlobOnlyStorage implements IDocumentStorageService {
245
248
  return this.verifyStorage().readBlob(blobId);
246
249
  }
247
250
 
251
+ // eslint-disable-next-line import/no-deprecated
248
252
  private verifyStorage(): IDetachedBlobStorage {
249
253
  if (this.detachedStorage === undefined) {
250
254
  throw new UsageError("Real storage calls not allowed in Unattached container");
package/src/loader.ts CHANGED
@@ -227,6 +227,7 @@ export interface ILoaderServices {
227
227
 
228
228
  /**
229
229
  * Blobs storage for detached containers.
230
+ * @deprecated - IDetachedBlobStorage will be removed in a future release without a replacement. Blobs created while detached will be stored in memory to align with attached container behavior. AB#8049
230
231
  */
231
232
  readonly detachedBlobStorage?: IDetachedBlobStorage;
232
233
 
@@ -241,6 +242,8 @@ export interface ILoaderServices {
241
242
  * Subset of IDocumentStorageService which only supports createBlob() and readBlob(). This is used to support
242
243
  * blobs in detached containers.
243
244
  * @alpha
245
+ *
246
+ * @deprecated - IDetachedBlobStorage will be removed in a future release without a replacement. Blobs created while detached will be stored in memory to align with attached container behavior. AB#8049
244
247
  */
245
248
  export type IDetachedBlobStorage = Pick<IDocumentStorageService, "createBlob" | "readBlob"> & {
246
249
  size: number;
@@ -248,6 +251,11 @@ export type IDetachedBlobStorage = Pick<IDocumentStorageService, "createBlob" |
248
251
  * Return an array of all blob IDs present in storage
249
252
  */
250
253
  getBlobIds(): string[];
254
+
255
+ /**
256
+ * After the container is attached, the detached blob storage is no longer needed and will be disposed.
257
+ */
258
+ dispose?(): void;
251
259
  };
252
260
 
253
261
  /**
@@ -0,0 +1,83 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import type { ICreateBlobResponse } from "@fluidframework/protocol-definitions";
7
+ import { bufferToString, stringToBuffer } from "@fluid-internal/client-utils";
8
+ import { assert, isObject } from "@fluidframework/core-utils/internal";
9
+ // eslint-disable-next-line import/no-deprecated
10
+ import type { IDetachedBlobStorage } from "./loader.js";
11
+
12
+ const MemoryDetachedBlobStorageIdentifier = Symbol();
13
+
14
+ // eslint-disable-next-line import/no-deprecated
15
+ interface MemoryDetachedBlobStorage extends IDetachedBlobStorage {
16
+ [MemoryDetachedBlobStorageIdentifier]: typeof MemoryDetachedBlobStorageIdentifier;
17
+ initialize(attachmentBlobs: string[]): void;
18
+ serialize(): string | undefined;
19
+ }
20
+
21
+ function isMemoryDetachedBlobStorage(
22
+ // eslint-disable-next-line import/no-deprecated
23
+ detachedStorage: IDetachedBlobStorage | undefined,
24
+ ): detachedStorage is MemoryDetachedBlobStorage {
25
+ return (
26
+ isObject(detachedStorage) &&
27
+ MemoryDetachedBlobStorageIdentifier in detachedStorage &&
28
+ detachedStorage[MemoryDetachedBlobStorageIdentifier] === MemoryDetachedBlobStorageIdentifier
29
+ );
30
+ }
31
+
32
+ export function serializeMemoryDetachedBlobStorage(
33
+ // eslint-disable-next-line import/no-deprecated
34
+ detachedStorage: IDetachedBlobStorage | undefined,
35
+ ): string | undefined {
36
+ if (
37
+ detachedStorage !== undefined &&
38
+ detachedStorage.size > 0 &&
39
+ isMemoryDetachedBlobStorage(detachedStorage)
40
+ ) {
41
+ return detachedStorage.serialize();
42
+ }
43
+ }
44
+
45
+ export function tryInitializeMemoryDetachedBlobStorage(
46
+ // eslint-disable-next-line import/no-deprecated
47
+ detachedStorage: IDetachedBlobStorage | undefined,
48
+ attachmentBlobs: string,
49
+ ) {
50
+ if (!isMemoryDetachedBlobStorage(detachedStorage)) {
51
+ throw new Error(
52
+ "DetachedBlobStorage was not provided to the loader during serialize so cannot be provided during rehydrate.",
53
+ );
54
+ }
55
+
56
+ assert(detachedStorage.size === 0, "Blob storage already initialized");
57
+ const maybeAttachmentBlobs = JSON.parse(attachmentBlobs);
58
+ assert(Array.isArray(maybeAttachmentBlobs), "Invalid attachmentBlobs");
59
+
60
+ detachedStorage.initialize(maybeAttachmentBlobs);
61
+ }
62
+
63
+ // eslint-disable-next-line import/no-deprecated
64
+ export function createMemoryDetachedBlobStorage(): IDetachedBlobStorage {
65
+ const blobs: ArrayBufferLike[] = [];
66
+ const storage: MemoryDetachedBlobStorage = {
67
+ [MemoryDetachedBlobStorageIdentifier]: MemoryDetachedBlobStorageIdentifier,
68
+ createBlob: async (file: ArrayBufferLike): Promise<ICreateBlobResponse> => ({
69
+ id: `${blobs.push(file) - 1}`,
70
+ }),
71
+ readBlob: async (id: string): Promise<ArrayBufferLike> =>
72
+ blobs[Number(id)] ?? Promise.reject(new Error(`Blob not found: ${id}`)),
73
+ get size() {
74
+ return blobs.length;
75
+ },
76
+ getBlobIds: (): string[] => blobs.map((_, i) => `${i}`),
77
+ dispose: () => blobs.splice(0),
78
+ serialize: () => JSON.stringify(blobs.map((b) => bufferToString(b, "utf-8"))),
79
+ initialize: (attachmentBlobs: string[]) =>
80
+ blobs.push(...attachmentBlobs.map((maybeBlob) => stringToBuffer(maybeBlob, "utf-8"))),
81
+ };
82
+ return storage;
83
+ }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "2.0.0-rc.4.0.0";
9
+ export const pkgVersion = "2.0.0-rc.4.0.10";
@@ -93,6 +93,8 @@ export interface IPendingDetachedContainerState extends SnapshotWithBlobs {
93
93
  attached: false;
94
94
  /** Indicates whether we expect the rehydrated container to have non-empty Detached Blob Storage */
95
95
  hasAttachmentBlobs: boolean;
96
+ /** Used by the memory blob storage to persisted attachment blobs */
97
+ attachmentBlobs?: string;
96
98
  /**
97
99
  * Runtime-specific state that will be needed to properly rehydrate
98
100
  * (it's included in ContainerContext passed to instantiateRuntime)
package/dist/public.d.ts DELETED
@@ -1,14 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
-
6
- /*
7
- * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
8
- * Generated by "flub generate entrypoints" in @fluidframework/build-tools.
9
- */
10
-
11
- export {
12
- // @public APIs
13
- ConnectionState
14
- } from "./index.js";
package/lib/public.d.ts DELETED
@@ -1,14 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
-
6
- /*
7
- * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
8
- * Generated by "flub generate entrypoints" in @fluidframework/build-tools.
9
- */
10
-
11
- export {
12
- // @public APIs
13
- ConnectionState
14
- } from "./index.js";