-
Notifications
You must be signed in to change notification settings - Fork 585
Expand file tree
/
Copy pathrootDataObject.ts
More file actions
333 lines (308 loc) · 11.2 KB
/
Copy pathrootDataObject.ts
File metadata and controls
333 lines (308 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
/*!
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/
import {
BaseContainerRuntimeFactory,
DataObject,
DataObjectFactory,
} from "@fluidframework/aqueduct/internal";
import type { IRuntimeFactory } from "@fluidframework/container-definitions/internal";
import {
FluidDataStoreRegistry,
type IContainerRuntimeOptions,
} from "@fluidframework/container-runtime/internal";
import type {
IContainerRuntime,
IContainerRuntimeInternal,
} from "@fluidframework/container-runtime-definitions/internal";
import type {
FluidObject,
IFluidHandle,
IFluidLoadable,
} from "@fluidframework/core-interfaces";
import { assert } from "@fluidframework/core-utils/internal";
import type { IChannelFactory } from "@fluidframework/datastore-definitions/internal";
import type { IDirectory } from "@fluidframework/map/internal";
import type {
IFluidDataStoreRegistry,
MinimumVersionForCollab,
} from "@fluidframework/runtime-definitions/internal";
import type { SharedObjectKind } from "@fluidframework/shared-object-base/internal";
import { defaultRuntimeOptionsForMinVersion } from "./compatibilityConfiguration.js";
import type {
// eslint-disable-next-line import-x/no-deprecated
CompatibilityMode,
ContainerSchema,
IRootDataObject,
IStaticEntryPoint,
LoadableObjectKind,
LoadableObjectKindRecord,
LoadableObjectRecord,
} from "./types.js";
import {
createDataObject,
createSharedObject,
isDataObjectKind,
isSharedObjectKind,
makeFluidObject,
parseDataObjectsFromSharedObjects,
resolveCompatibilityModeToMinVersionForCollab,
} from "./utils.js";
/**
* Input props for {@link RootDataObject.initializingFirstTime}.
*/
interface RootDataObjectProps {
/**
* Initial object structure with which the {@link RootDataObject} will be first-time initialized.
*
* @see {@link RootDataObject.initializingFirstTime}
*/
readonly initialObjects: LoadableObjectKindRecord;
}
interface IProvideRootDataObject {
readonly RootDataObject: RootDataObject;
}
/**
* The entry-point/root collaborative object of the {@link IFluidContainer | Fluid Container}.
* Abstracts the dynamic code required to build a Fluid Container into a static representation for end customers.
*/
class RootDataObject
extends DataObject<{ InitialState: RootDataObjectProps }>
implements IRootDataObject, IProvideRootDataObject
{
private readonly initialObjectsDirKey = "initial-objects-key";
private readonly _initialObjects: LoadableObjectRecord = {};
public get RootDataObject(): RootDataObject {
return this;
}
private get initialObjectsDir(): IDirectory {
const dir = this.root.getSubDirectory(this.initialObjectsDirKey);
if (dir === undefined) {
throw new Error("InitialObjects sub-directory was not initialized");
}
return dir;
}
/**
* The first time this object is initialized, creates each object identified in
* {@link RootDataObjectProps.initialObjects} and stores them as unique values in the root directory.
*
* @see {@link @fluidframework/aqueduct#PureDataObject.initializingFirstTime}
*/
protected async initializingFirstTime(props: RootDataObjectProps): Promise<void> {
this.root.createSubDirectory(this.initialObjectsDirKey);
// Create initial objects provided by the developer
const initialObjectsP: Promise<void>[] = [];
for (const [id, objectClass] of Object.entries(props.initialObjects)) {
const createObject = async (): Promise<void> => {
const obj = await this.create<IFluidLoadable>(
objectClass as SharedObjectKind<IFluidLoadable>,
);
this.initialObjectsDir.set(id, obj.handle);
};
initialObjectsP.push(createObject());
}
await Promise.all(initialObjectsP);
}
/**
* Every time an instance is initialized, loads all of the initial objects in the root directory so they can be
* accessed immediately.
*
* @see {@link @fluidframework/aqueduct#PureDataObject.hasInitialized}
*/
protected async hasInitialized(): Promise<void> {
// We will always load the initial objects so they are available to the developer
const loadInitialObjectsP: Promise<void>[] = [];
for (const [key, value] of this.initialObjectsDir.entries()) {
const loadDir = async (): Promise<void> => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
const obj: unknown = await value.get();
Object.assign(this._initialObjects, { [key]: obj });
};
loadInitialObjectsP.push(loadDir());
}
await Promise.all(loadInitialObjectsP);
}
public get initialObjects(): LoadableObjectRecord {
if (Object.keys(this._initialObjects).length === 0) {
throw new Error("Initial Objects were not correctly initialized");
}
return this._initialObjects;
}
public async create<T>(objectClass: SharedObjectKind<T>): Promise<T> {
const internal = objectClass as unknown as LoadableObjectKind<T & IFluidLoadable>;
if (isDataObjectKind(internal)) {
return createDataObject(internal, this.context);
} else if (isSharedObjectKind(internal)) {
return createSharedObject(internal, this.runtime);
}
throw new Error("Could not create new Fluid object because an unknown object was passed");
}
public async uploadBlob(blob: ArrayBufferLike): Promise<IFluidHandle<ArrayBufferLike>> {
return this.runtime.uploadBlob(blob);
}
}
const rootDataStoreId = "rootDOId";
const rootDataObjectType = "rootDO";
async function provideEntryPoint(
containerRuntime: IContainerRuntime,
): Promise<IStaticEntryPoint> {
const entryPoint = await containerRuntime.getAliasedDataStoreEntryPoint(rootDataStoreId);
if (entryPoint === undefined) {
throw new Error(`default dataStore [${rootDataStoreId}] must exist`);
}
const rootDataObject = ((await entryPoint.get()) as FluidObject<RootDataObject>)
.RootDataObject;
assert(rootDataObject !== undefined, 0xb9f /* entryPoint must be of type RootDataObject */);
return makeFluidObject<IStaticEntryPoint>(
{
rootDataObject,
extensionStore: containerRuntime as IContainerRuntimeInternal,
},
"IStaticEntryPoint",
);
}
/**
* Creates an {@link @fluidframework/aqueduct#IRuntimeFactory} which constructs containers
* with an entry point containing single directory-based root data object.
*
* @remarks
* The entry point is opaque to caller.
* The root data object's registry and initial objects are configured based on the provided
* schema (and optionally, data store registry).
*
* @internal
*/
export function createDOProviderContainerRuntimeFactory(props: {
/**
* The schema for the container.
*/
schema: ContainerSchema;
/**
* Minimum Fluid Framework version required for collaboration. Accepts a
* {@link @fluidframework/runtime-definitions#MinimumVersionForCollab} semver string;
* the legacy {@link CompatibilityMode} values `"1"` and `"2"` are **deprecated**
* equivalents of `"1.0.0"` and `"2.0.0"`.
*/
// eslint-disable-next-line import-x/no-deprecated
compatibilityMode: MinimumVersionForCollab | CompatibilityMode;
/**
* Optional registry of data stores to pass to the DataObject factory.
* If not provided, one will be created based on the schema.
*/
rootDataStoreRegistry?: IFluidDataStoreRegistry;
/**
* Optional overrides for the container runtime options.
* If not provided, only the default options for the given compatibilityMode will be used.
*/
runtimeOptionOverrides?: Partial<IContainerRuntimeOptions>;
/**
* Optional override for minimum version for collab.
* If not provided, the default for the given compatibilityMode will be used.
* @remarks
* This is useful when runtime options are overridden and change the minimum version for collab.
*
* @deprecated Pass a {@link @fluidframework/runtime-definitions#MinimumVersionForCollab}
* semver string directly via `compatibilityMode` instead.
*/
minVersionForCollabOverride?: MinimumVersionForCollab;
}): IRuntimeFactory {
const {
minVersionForCollabOverride,
rootDataStoreRegistry,
runtimeOptionOverrides,
schema,
} = props;
const minVersionForCollab = resolveCompatibilityModeToMinVersionForCollab(
props.compatibilityMode,
);
const [registryEntries, sharedObjects] = parseDataObjectsFromSharedObjects(schema);
const registry = rootDataStoreRegistry ?? new FluidDataStoreRegistry(registryEntries);
return new DOProviderContainerRuntimeFactory(
schema,
new RootDataObjectFactory(sharedObjects, registry),
{
runtimeOptions: runtimeOptionOverrides,
minVersionForCollab: minVersionForCollabOverride ?? minVersionForCollab,
},
);
}
/**
* Factory for Container Runtime instances that provide a {@link IStaticEntryPoint}
* (containing single {@link IRootDataObject}) as their entry point.
*/
class DOProviderContainerRuntimeFactory extends BaseContainerRuntimeFactory {
private readonly rootDataObjectFactory: DataObjectFactory<
RootDataObject,
{
InitialState: RootDataObjectProps;
}
>;
private readonly initialObjects: LoadableObjectKindRecord;
/**
* Create a new instance of a container runtime factory.
* @remarks
* The caller is responsible for making sure that the provided root data object factory is configured
* appropriately based on the schema of the container (e.g. its registry entries contain all the
* DataStore/DDS types that the schema says can be constructed).
*
* Most scenarios probably want to use {@link createDOProviderContainerRuntimeFactory} instead,
* since it can take care of constructing the root data object factory based on the schema.
*
* @param schema - The schema for the container
* @param rootDataObjectFactory - A factory that can construct the root data object.
* @param config - Resolved minimum version for collab (required) and optional runtime option overrides.
*/
public constructor(
schema: ContainerSchema,
rootDataObjectFactory: DataObjectFactory<
RootDataObject,
{ InitialState: RootDataObjectProps }
>,
config: {
minVersionForCollab: MinimumVersionForCollab;
runtimeOptions?: Partial<IContainerRuntimeOptions>;
},
) {
super({
registryEntries: [rootDataObjectFactory.registryEntry],
runtimeOptions: {
...defaultRuntimeOptionsForMinVersion(config.minVersionForCollab),
...config.runtimeOptions,
},
provideEntryPoint,
minVersionForCollab: config.minVersionForCollab,
});
this.rootDataObjectFactory = rootDataObjectFactory;
this.initialObjects = schema.initialObjects;
}
protected async containerInitializingFirstTime(runtime: IContainerRuntime): Promise<void> {
// The first time we create the container we create the RootDataObject
await this.rootDataObjectFactory.createRootInstance(rootDataStoreId, runtime, {
initialObjects: this.initialObjects,
});
}
}
/**
* Factory that creates instances of a root data object.
*/
class RootDataObjectFactory extends DataObjectFactory<
RootDataObject,
{ InitialState: RootDataObjectProps }
> {
public constructor(
sharedObjects: readonly IChannelFactory[] = [],
private readonly dataStoreRegistry: IFluidDataStoreRegistry,
) {
// Note: we're passing `undefined` registry entries to the base class so it won't create a registry itself,
// and instead we override the necessary methods in this class to use the registry received in the constructor.
super({
type: rootDataObjectType,
ctor: RootDataObject,
sharedObjects,
});
}
public get IFluidDataStoreRegistry(): IFluidDataStoreRegistry {
return this.dataStoreRegistry;
}
}