@fluidframework/datastore 2.0.0-internal.3.0.5 → 2.0.0-internal.3.1.1
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/.eslintrc.js +5 -7
- package/.mocharc.js +2 -2
- package/README.md +3 -0
- package/api-extractor.json +2 -2
- package/dist/channelContext.d.ts.map +1 -1
- package/dist/channelContext.js.map +1 -1
- package/dist/channelDeltaConnection.d.ts.map +1 -1
- package/dist/channelDeltaConnection.js.map +1 -1
- package/dist/channelStorageService.d.ts.map +1 -1
- package/dist/channelStorageService.js +1 -3
- package/dist/channelStorageService.js.map +1 -1
- package/dist/dataStoreRuntime.d.ts +11 -24
- package/dist/dataStoreRuntime.d.ts.map +1 -1
- package/dist/dataStoreRuntime.js +68 -86
- package/dist/dataStoreRuntime.js.map +1 -1
- package/dist/fluidHandle.d.ts.map +1 -1
- package/dist/fluidHandle.js.map +1 -1
- package/dist/localChannelContext.d.ts.map +1 -1
- package/dist/localChannelContext.js +9 -5
- package/dist/localChannelContext.js.map +1 -1
- package/dist/localChannelStorageService.d.ts.map +1 -1
- package/dist/localChannelStorageService.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/remoteChannelContext.d.ts.map +1 -1
- package/dist/remoteChannelContext.js +2 -2
- package/dist/remoteChannelContext.js.map +1 -1
- package/lib/channelContext.d.ts.map +1 -1
- package/lib/channelContext.js.map +1 -1
- package/lib/channelDeltaConnection.d.ts.map +1 -1
- package/lib/channelDeltaConnection.js.map +1 -1
- package/lib/channelStorageService.d.ts.map +1 -1
- package/lib/channelStorageService.js +1 -3
- package/lib/channelStorageService.js.map +1 -1
- package/lib/dataStoreRuntime.d.ts +11 -24
- package/lib/dataStoreRuntime.d.ts.map +1 -1
- package/lib/dataStoreRuntime.js +71 -89
- package/lib/dataStoreRuntime.js.map +1 -1
- package/lib/fluidHandle.d.ts.map +1 -1
- package/lib/fluidHandle.js.map +1 -1
- package/lib/localChannelContext.d.ts.map +1 -1
- package/lib/localChannelContext.js +9 -5
- package/lib/localChannelContext.js.map +1 -1
- package/lib/localChannelStorageService.d.ts.map +1 -1
- package/lib/localChannelStorageService.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/remoteChannelContext.d.ts.map +1 -1
- package/lib/remoteChannelContext.js +2 -2
- package/lib/remoteChannelContext.js.map +1 -1
- package/package.json +111 -110
- package/prettier.config.cjs +1 -1
- package/src/channelContext.ts +66 -65
- package/src/channelDeltaConnection.ts +50 -44
- package/src/channelStorageService.ts +58 -54
- package/src/dataStoreRuntime.ts +1122 -1086
- package/src/fluidHandle.ts +87 -91
- package/src/localChannelContext.ts +302 -255
- package/src/localChannelStorageService.ts +47 -45
- package/src/packageVersion.ts +1 -1
- package/src/remoteChannelContext.ts +300 -291
- package/tsconfig.esnext.json +6 -6
- package/tsconfig.json +9 -13
|
@@ -9,26 +9,26 @@ import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
|
9
9
|
import { IDocumentStorageService } from "@fluidframework/driver-definitions";
|
|
10
10
|
import { ISequencedDocumentMessage, ISnapshotTree } from "@fluidframework/protocol-definitions";
|
|
11
11
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
IChannel,
|
|
13
|
+
IFluidDataStoreRuntime,
|
|
14
|
+
IChannelFactory,
|
|
15
|
+
IChannelAttributes,
|
|
16
16
|
} from "@fluidframework/datastore-definitions";
|
|
17
17
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
IFluidDataStoreContext,
|
|
19
|
+
IGarbageCollectionData,
|
|
20
|
+
ISummarizeResult,
|
|
21
|
+
ITelemetryContext,
|
|
22
22
|
} from "@fluidframework/runtime-definitions";
|
|
23
23
|
import { readAndParse } from "@fluidframework/driver-utils";
|
|
24
24
|
import { DataProcessingError } from "@fluidframework/container-utils";
|
|
25
25
|
import { assert, Lazy } from "@fluidframework/common-utils";
|
|
26
26
|
import { IFluidHandle } from "@fluidframework/core-interfaces";
|
|
27
27
|
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
createServiceEndpoints,
|
|
29
|
+
IChannelContext,
|
|
30
|
+
summarizeChannel,
|
|
31
|
+
summarizeChannelAsync,
|
|
32
32
|
} from "./channelContext";
|
|
33
33
|
import { ChannelDeltaConnection } from "./channelDeltaConnection";
|
|
34
34
|
import { ISharedObjectRegistry } from "./dataStoreRuntime";
|
|
@@ -38,276 +38,323 @@ import { ChannelStorageService } from "./channelStorageService";
|
|
|
38
38
|
* Channel context for a locally created channel
|
|
39
39
|
*/
|
|
40
40
|
export abstract class LocalChannelContextBase implements IChannelContext {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
41
|
+
public channel: IChannel | undefined;
|
|
42
|
+
private globallyVisible = false;
|
|
43
|
+
protected readonly pending: ISequencedDocumentMessage[] = [];
|
|
44
|
+
protected factory: IChannelFactory | undefined;
|
|
45
|
+
constructor(
|
|
46
|
+
protected readonly id: string,
|
|
47
|
+
protected readonly registry: ISharedObjectRegistry,
|
|
48
|
+
protected readonly runtime: IFluidDataStoreRuntime,
|
|
49
|
+
private readonly servicesGetter: () => Lazy<{
|
|
50
|
+
readonly deltaConnection: ChannelDeltaConnection;
|
|
51
|
+
readonly objectStorage: ChannelStorageService;
|
|
52
|
+
}>,
|
|
53
|
+
) {
|
|
54
|
+
assert(!this.id.includes("/"), 0x30f /* Channel context ID cannot contain slashes */);
|
|
55
|
+
}
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
public async getChannel(): Promise<IChannel> {
|
|
58
|
+
assert(this.channel !== undefined, 0x207 /* "Channel should be defined" */);
|
|
59
|
+
return this.channel;
|
|
60
|
+
}
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
public get isLoaded(): boolean {
|
|
63
|
+
return this.channel !== undefined;
|
|
64
|
+
}
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
public setConnectionState(connected: boolean, clientId?: string) {
|
|
67
|
+
// Connection events are ignored if the data store is not yet globallyVisible or loaded
|
|
68
|
+
if (this.globallyVisible && this.isLoaded) {
|
|
69
|
+
this.servicesGetter().value.deltaConnection.setConnectionState(connected);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
public processOp(
|
|
74
|
+
message: ISequencedDocumentMessage,
|
|
75
|
+
local: boolean,
|
|
76
|
+
localOpMetadata: unknown,
|
|
77
|
+
): void {
|
|
78
|
+
assert(
|
|
79
|
+
this.globallyVisible,
|
|
80
|
+
0x2d3 /* "Local channel must be globally visible when processing op" */,
|
|
81
|
+
);
|
|
75
82
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
// A local channel may not be loaded in case where we rehydrate the container from a snapshot because of
|
|
84
|
+
// delay loading. So after the container is attached and some other client joins which start generating
|
|
85
|
+
// ops for this channel. So not loaded local channel can still receive ops and we store them to process later.
|
|
86
|
+
if (this.isLoaded) {
|
|
87
|
+
this.servicesGetter().value.deltaConnection.process(message, local, localOpMetadata);
|
|
88
|
+
} else {
|
|
89
|
+
assert(
|
|
90
|
+
local === false,
|
|
91
|
+
0x189 /* "Should always be remote because a local dds shouldn't generate ops before loading" */,
|
|
92
|
+
);
|
|
93
|
+
this.pending.push(message);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
87
96
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
public reSubmit(content: any, localOpMetadata: unknown) {
|
|
98
|
+
assert(this.isLoaded, 0x18a /* "Channel should be loaded to resubmit ops" */);
|
|
99
|
+
assert(
|
|
100
|
+
this.globallyVisible,
|
|
101
|
+
0x2d4 /* "Local channel must be globally visible when resubmitting op" */,
|
|
102
|
+
);
|
|
103
|
+
this.servicesGetter().value.deltaConnection.reSubmit(content, localOpMetadata);
|
|
104
|
+
}
|
|
105
|
+
public rollback(content: any, localOpMetadata: unknown) {
|
|
106
|
+
assert(this.isLoaded, 0x2ee /* "Channel should be loaded to rollback ops" */);
|
|
107
|
+
assert(
|
|
108
|
+
this.globallyVisible,
|
|
109
|
+
0x2ef /* "Local channel must be globally visible when rolling back op" */,
|
|
110
|
+
);
|
|
111
|
+
this.servicesGetter().value.deltaConnection.rollback(content, localOpMetadata);
|
|
112
|
+
}
|
|
98
113
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
114
|
+
public applyStashedOp() {
|
|
115
|
+
throw new Error("no stashed ops on local channel");
|
|
116
|
+
}
|
|
102
117
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Returns a summary at the current sequence number.
|
|
120
|
+
* @param fullTree - true to bypass optimizations and force a full summary tree
|
|
121
|
+
* @param trackState - This tells whether we should track state from this summary.
|
|
122
|
+
* @param telemetryContext - summary data passed through the layers for telemetry purposes
|
|
123
|
+
*/
|
|
124
|
+
public async summarize(
|
|
125
|
+
fullTree: boolean = false,
|
|
126
|
+
trackState: boolean = false,
|
|
127
|
+
telemetryContext?: ITelemetryContext,
|
|
128
|
+
): Promise<ISummarizeResult> {
|
|
129
|
+
assert(
|
|
130
|
+
this.isLoaded && this.channel !== undefined,
|
|
131
|
+
0x18c /* "Channel should be loaded to summarize" */,
|
|
132
|
+
);
|
|
133
|
+
return summarizeChannelAsync(this.channel, fullTree, trackState, telemetryContext);
|
|
134
|
+
}
|
|
117
135
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
136
|
+
public getAttachSummary(telemetryContext?: ITelemetryContext): ISummarizeResult {
|
|
137
|
+
assert(
|
|
138
|
+
this.isLoaded && this.channel !== undefined,
|
|
139
|
+
0x18d /* "Channel should be loaded to take snapshot" */,
|
|
140
|
+
);
|
|
141
|
+
return summarizeChannel(
|
|
142
|
+
this.channel,
|
|
143
|
+
true /* fullTree */,
|
|
144
|
+
false /* trackState */,
|
|
145
|
+
telemetryContext,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
122
148
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
149
|
+
public makeVisible(): void {
|
|
150
|
+
if (this.globallyVisible) {
|
|
151
|
+
throw new Error("Channel is already globally visible");
|
|
152
|
+
}
|
|
127
153
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
154
|
+
if (this.isLoaded) {
|
|
155
|
+
assert(!!this.channel, 0x192 /* "Channel should be there if loaded!!" */);
|
|
156
|
+
this.channel.connect(this.servicesGetter().value);
|
|
157
|
+
}
|
|
158
|
+
this.globallyVisible = true;
|
|
159
|
+
}
|
|
134
160
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Returns the data used for garbage collection. This includes a list of GC nodes that represent this context.
|
|
163
|
+
* Each node has a set of outbound routes to other GC nodes in the document. This should be called only after
|
|
164
|
+
* the context has loaded.
|
|
165
|
+
* @param fullGC - true to bypass optimizations and force full generation of GC data.
|
|
166
|
+
*/
|
|
167
|
+
public async getGCData(fullGC: boolean = false): Promise<IGarbageCollectionData> {
|
|
168
|
+
assert(
|
|
169
|
+
this.isLoaded && this.channel !== undefined,
|
|
170
|
+
0x193 /* "Channel should be loaded to run GC" */,
|
|
171
|
+
);
|
|
172
|
+
return this.channel.getGCData(fullGC);
|
|
173
|
+
}
|
|
145
174
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
175
|
+
public updateUsedRoutes(usedRoutes: string[]) {
|
|
176
|
+
/**
|
|
177
|
+
* Currently, DDSes are always considered referenced and are not garbage collected.
|
|
178
|
+
* Once we have GC at DDS level, this channel context's used routes will be updated as per the passed
|
|
179
|
+
* value. See - https://github.com/microsoft/FluidFramework/issues/4611
|
|
180
|
+
*/
|
|
181
|
+
}
|
|
153
182
|
}
|
|
154
183
|
|
|
155
184
|
export class RehydratedLocalChannelContext extends LocalChannelContextBase {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
185
|
+
private readonly services: Lazy<{
|
|
186
|
+
readonly deltaConnection: ChannelDeltaConnection;
|
|
187
|
+
readonly objectStorage: ChannelStorageService;
|
|
188
|
+
}>;
|
|
160
189
|
|
|
161
|
-
|
|
190
|
+
private readonly dirtyFn: () => void;
|
|
162
191
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
192
|
+
constructor(
|
|
193
|
+
id: string,
|
|
194
|
+
registry: ISharedObjectRegistry,
|
|
195
|
+
runtime: IFluidDataStoreRuntime,
|
|
196
|
+
dataStoreContext: IFluidDataStoreContext,
|
|
197
|
+
storageService: IDocumentStorageService,
|
|
198
|
+
logger: ITelemetryLogger,
|
|
199
|
+
submitFn: (content: any, localOpMetadata: unknown) => void,
|
|
200
|
+
dirtyFn: (address: string) => void,
|
|
201
|
+
addedGCOutboundReferenceFn: (srcHandle: IFluidHandle, outboundHandle: IFluidHandle) => void,
|
|
202
|
+
private readonly snapshotTree: ISnapshotTree,
|
|
203
|
+
) {
|
|
204
|
+
super(id, registry, runtime, () => this.services);
|
|
205
|
+
const blobMap: Map<string, ArrayBufferLike> = new Map<string, ArrayBufferLike>();
|
|
206
|
+
const clonedSnapshotTree = cloneDeep(this.snapshotTree);
|
|
207
|
+
// 0.47 back-compat Need to sanitize if snapshotTree.blobs still contains blob contents too.
|
|
208
|
+
// This is for older snapshot which is generated by loader <=0.47 version which still contains
|
|
209
|
+
// the contents within blobs. After a couple of revisions we can remove it.
|
|
210
|
+
if (this.isSnapshotInOldFormatAndCollectBlobs(clonedSnapshotTree, blobMap)) {
|
|
211
|
+
this.sanitizeSnapshot(clonedSnapshotTree);
|
|
212
|
+
}
|
|
184
213
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
214
|
+
this.services = new Lazy(() => {
|
|
215
|
+
return createServiceEndpoints(
|
|
216
|
+
this.id,
|
|
217
|
+
dataStoreContext.connected,
|
|
218
|
+
submitFn,
|
|
219
|
+
this.dirtyFn,
|
|
220
|
+
addedGCOutboundReferenceFn,
|
|
221
|
+
storageService,
|
|
222
|
+
logger,
|
|
223
|
+
clonedSnapshotTree,
|
|
224
|
+
blobMap,
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
this.dirtyFn = () => {
|
|
228
|
+
dirtyFn(id);
|
|
229
|
+
};
|
|
230
|
+
}
|
|
200
231
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
232
|
+
public async getChannel(): Promise<IChannel> {
|
|
233
|
+
if (this.channel === undefined) {
|
|
234
|
+
this.channel = await this.loadChannel().catch((err) => {
|
|
235
|
+
throw DataProcessingError.wrapIfUnrecognized(
|
|
236
|
+
err,
|
|
237
|
+
"rehydratedLocalChannelContextFailedToLoadChannel",
|
|
238
|
+
undefined,
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return this.channel;
|
|
243
|
+
}
|
|
211
244
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
245
|
+
private async loadChannel(): Promise<IChannel> {
|
|
246
|
+
assert(!this.isLoaded, 0x18e /* "Channel must not already be loaded when loading" */);
|
|
247
|
+
assert(
|
|
248
|
+
await this.services.value.objectStorage.contains(".attributes"),
|
|
249
|
+
0x190 /* ".attributes blob should be present" */,
|
|
250
|
+
);
|
|
251
|
+
const attributes = await readAndParse<IChannelAttributes>(
|
|
252
|
+
this.services.value.objectStorage,
|
|
253
|
+
".attributes",
|
|
254
|
+
);
|
|
219
255
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
256
|
+
assert(
|
|
257
|
+
this.factory === undefined,
|
|
258
|
+
0x208 /* "Factory should be undefined before loading" */,
|
|
259
|
+
);
|
|
260
|
+
this.factory = this.registry.get(attributes.type);
|
|
261
|
+
if (this.factory === undefined) {
|
|
262
|
+
throw new Error(`Channel Factory ${attributes.type} not registered`);
|
|
263
|
+
}
|
|
264
|
+
// Services will be assigned during this load.
|
|
265
|
+
const channel = await this.factory.load(
|
|
266
|
+
this.runtime,
|
|
267
|
+
this.id,
|
|
268
|
+
this.services.value,
|
|
269
|
+
attributes,
|
|
270
|
+
);
|
|
231
271
|
|
|
232
|
-
|
|
233
|
-
|
|
272
|
+
// Commit changes.
|
|
273
|
+
this.channel = channel;
|
|
234
274
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
275
|
+
// Send all pending messages to the channel
|
|
276
|
+
for (const message of this.pending) {
|
|
277
|
+
this.services.value.deltaConnection.process(
|
|
278
|
+
message,
|
|
279
|
+
false,
|
|
280
|
+
undefined /* localOpMetadata */,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
return this.channel;
|
|
284
|
+
}
|
|
241
285
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
286
|
+
private isSnapshotInOldFormatAndCollectBlobs(
|
|
287
|
+
snapshotTree: ISnapshotTree,
|
|
288
|
+
blobMap: Map<string, ArrayBufferLike>,
|
|
289
|
+
): boolean {
|
|
290
|
+
let sanitize = false;
|
|
291
|
+
const blobsContents: { [path: string]: ArrayBufferLike } = (snapshotTree as any)
|
|
292
|
+
.blobsContents;
|
|
293
|
+
Object.entries(blobsContents).forEach(([key, value]) => {
|
|
294
|
+
blobMap.set(key, value);
|
|
295
|
+
if (snapshotTree.blobs[key] !== undefined) {
|
|
296
|
+
sanitize = true;
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
for (const value of Object.values(snapshotTree.trees)) {
|
|
300
|
+
sanitize = sanitize || this.isSnapshotInOldFormatAndCollectBlobs(value, blobMap);
|
|
301
|
+
}
|
|
302
|
+
return sanitize;
|
|
303
|
+
}
|
|
259
304
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
305
|
+
private sanitizeSnapshot(snapshotTree: ISnapshotTree) {
|
|
306
|
+
const blobMapInitial = new Map(Object.entries(snapshotTree.blobs));
|
|
307
|
+
for (const [blobName, blobId] of blobMapInitial.entries()) {
|
|
308
|
+
const blobValue = blobMapInitial.get(blobId);
|
|
309
|
+
if (blobValue === undefined) {
|
|
310
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
311
|
+
delete snapshotTree.blobs[blobName];
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
for (const value of Object.values(snapshotTree.trees)) {
|
|
315
|
+
this.sanitizeSnapshot(value);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
273
318
|
}
|
|
274
319
|
|
|
275
320
|
export class LocalChannelContext extends LocalChannelContextBase {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
321
|
+
private readonly services: Lazy<{
|
|
322
|
+
readonly deltaConnection: ChannelDeltaConnection;
|
|
323
|
+
readonly objectStorage: ChannelStorageService;
|
|
324
|
+
}>;
|
|
325
|
+
private readonly dirtyFn: () => void;
|
|
326
|
+
constructor(
|
|
327
|
+
id: string,
|
|
328
|
+
registry: ISharedObjectRegistry,
|
|
329
|
+
type: string,
|
|
330
|
+
runtime: IFluidDataStoreRuntime,
|
|
331
|
+
dataStoreContext: IFluidDataStoreContext,
|
|
332
|
+
storageService: IDocumentStorageService,
|
|
333
|
+
logger: ITelemetryLogger,
|
|
334
|
+
submitFn: (content: any, localOpMetadata: unknown) => void,
|
|
335
|
+
dirtyFn: (address: string) => void,
|
|
336
|
+
addedGCOutboundReferenceFn: (srcHandle: IFluidHandle, outboundHandle: IFluidHandle) => void,
|
|
337
|
+
) {
|
|
338
|
+
super(id, registry, runtime, () => this.services);
|
|
339
|
+
assert(type !== undefined, 0x209 /* "Factory Type should be defined" */);
|
|
340
|
+
this.factory = registry.get(type);
|
|
341
|
+
if (this.factory === undefined) {
|
|
342
|
+
throw new Error(`Channel Factory ${type} not registered`);
|
|
343
|
+
}
|
|
344
|
+
this.channel = this.factory.create(runtime, id);
|
|
345
|
+
this.services = new Lazy(() => {
|
|
346
|
+
return createServiceEndpoints(
|
|
347
|
+
this.id,
|
|
348
|
+
dataStoreContext.connected,
|
|
349
|
+
submitFn,
|
|
350
|
+
this.dirtyFn,
|
|
351
|
+
addedGCOutboundReferenceFn,
|
|
352
|
+
storageService,
|
|
353
|
+
logger,
|
|
354
|
+
);
|
|
355
|
+
});
|
|
356
|
+
this.dirtyFn = () => {
|
|
357
|
+
dirtyFn(id);
|
|
358
|
+
};
|
|
359
|
+
}
|
|
313
360
|
}
|