@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.
- package/api-report/container-loader.api.md +3 -1
- package/dist/attachment.d.ts.map +1 -1
- package/dist/attachment.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +3 -1
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +18 -12
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +27 -7
- package/dist/container.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +7 -2
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/loader.d.ts +7 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js.map +1 -1
- package/dist/memoryBlobStorage.d.ts +9 -0
- package/dist/memoryBlobStorage.d.ts.map +1 -0
- package/dist/memoryBlobStorage.js +60 -0
- package/dist/memoryBlobStorage.js.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/serializedStateManager.d.ts +2 -0
- package/dist/serializedStateManager.d.ts.map +1 -1
- package/dist/serializedStateManager.js.map +1 -1
- package/lib/attachment.d.ts.map +1 -1
- package/lib/attachment.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +3 -1
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +19 -13
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +28 -8
- package/lib/container.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +7 -2
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/loader.d.ts +7 -0
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js.map +1 -1
- package/lib/memoryBlobStorage.d.ts +9 -0
- package/lib/memoryBlobStorage.d.ts.map +1 -0
- package/lib/memoryBlobStorage.js +54 -0
- package/lib/memoryBlobStorage.js.map +1 -0
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/serializedStateManager.d.ts +2 -0
- package/lib/serializedStateManager.d.ts.map +1 -1
- package/lib/serializedStateManager.js.map +1 -1
- package/package.json +13 -13
- package/src/attachment.ts +2 -0
- package/src/connectionStateHandler.ts +26 -18
- package/src/container.ts +37 -7
- package/src/containerStorageAdapter.ts +4 -0
- package/src/loader.ts +8 -0
- package/src/memoryBlobStorage.ts +83 -0
- package/src/packageVersion.ts +1 -1
- package/src/serializedStateManager.ts +2 -0
- package/dist/public.d.ts +0 -14
- 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.
|
|
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/
|
|
18
|
+
"types": "./lib/index.d.ts",
|
|
19
19
|
"default": "./lib/index.js"
|
|
20
20
|
},
|
|
21
21
|
"require": {
|
|
22
|
-
"types": "./dist/
|
|
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/
|
|
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.
|
|
121
|
-
"@fluidframework/container-definitions": ">=2.0.0-rc.4.0.
|
|
122
|
-
"@fluidframework/core-interfaces": ">=2.0.0-rc.4.0.
|
|
123
|
-
"@fluidframework/core-utils": ">=2.0.0-rc.4.0.
|
|
124
|
-
"@fluidframework/driver-definitions": ">=2.0.0-rc.4.0.
|
|
125
|
-
"@fluidframework/driver-utils": ">=2.0.0-rc.4.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.
|
|
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.
|
|
138
|
-
"@fluid-private/test-loader-utils": ">=2.0.0-rc.4.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
|
|
96
|
+
const config = inputs.mc.config;
|
|
96
97
|
return createConnectionStateHandlerCore(
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
+
}
|
package/src/packageVersion.ts
CHANGED
|
@@ -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";
|