@fluidframework/container-runtime 2.41.0 → 2.43.0-343119
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/CHANGELOG.md +4 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/channelCollection.d.ts +1 -1
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +4 -4
- package/dist/channelCollection.js.map +1 -1
- package/dist/compatUtils.d.ts +24 -1
- package/dist/compatUtils.d.ts.map +1 -1
- package/dist/compatUtils.js +109 -7
- package/dist/compatUtils.js.map +1 -1
- package/dist/containerRuntime.d.ts +36 -15
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +186 -71
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +5 -0
- package/dist/dataStore.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +2 -0
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +1 -1
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/messageTypes.d.ts +5 -4
- package/dist/messageTypes.d.ts.map +1 -1
- package/dist/messageTypes.js.map +1 -1
- package/dist/metadata.d.ts +1 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js.map +1 -1
- package/dist/opLifecycle/definitions.d.ts +6 -5
- package/dist/opLifecycle/definitions.d.ts.map +1 -1
- package/dist/opLifecycle/definitions.js.map +1 -1
- package/dist/opLifecycle/index.d.ts +1 -1
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts +9 -0
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +6 -4
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/opSerialization.d.ts +2 -1
- package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
- package/dist/opLifecycle/opSerialization.js.map +1 -1
- 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/pendingStateManager.d.ts +18 -5
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +20 -13
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/summary/documentSchema.d.ts +79 -16
- package/dist/summary/documentSchema.d.ts.map +1 -1
- package/dist/summary/documentSchema.js +119 -53
- package/dist/summary/documentSchema.js.map +1 -1
- package/dist/summary/index.d.ts +1 -1
- package/dist/summary/index.d.ts.map +1 -1
- package/dist/summary/index.js.map +1 -1
- package/lib/channelCollection.d.ts +1 -1
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +4 -4
- package/lib/channelCollection.js.map +1 -1
- package/lib/compatUtils.d.ts +24 -1
- package/lib/compatUtils.d.ts.map +1 -1
- package/lib/compatUtils.js +102 -3
- package/lib/compatUtils.js.map +1 -1
- package/lib/containerRuntime.d.ts +36 -15
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +188 -73
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +5 -0
- package/lib/dataStore.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +2 -0
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +1 -1
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/messageTypes.d.ts +5 -4
- package/lib/messageTypes.d.ts.map +1 -1
- package/lib/messageTypes.js.map +1 -1
- package/lib/metadata.d.ts +1 -1
- package/lib/metadata.d.ts.map +1 -1
- package/lib/metadata.js.map +1 -1
- package/lib/opLifecycle/definitions.d.ts +6 -5
- package/lib/opLifecycle/definitions.d.ts.map +1 -1
- package/lib/opLifecycle/definitions.js.map +1 -1
- package/lib/opLifecycle/index.d.ts +1 -1
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts +9 -0
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +6 -4
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/opSerialization.d.ts +2 -1
- package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
- package/lib/opLifecycle/opSerialization.js.map +1 -1
- 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/pendingStateManager.d.ts +18 -5
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +20 -13
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/summary/documentSchema.d.ts +79 -16
- package/lib/summary/documentSchema.d.ts.map +1 -1
- package/lib/summary/documentSchema.js +119 -53
- package/lib/summary/documentSchema.js.map +1 -1
- package/lib/summary/index.d.ts +1 -1
- package/lib/summary/index.d.ts.map +1 -1
- package/lib/summary/index.js.map +1 -1
- package/package.json +18 -18
- package/src/channelCollection.ts +4 -4
- package/src/compatUtils.ts +147 -10
- package/src/containerRuntime.ts +242 -85
- package/src/dataStore.ts +7 -0
- package/src/gc/garbageCollection.ts +2 -0
- package/src/gc/gcDefinitions.ts +1 -1
- package/src/index.ts +4 -2
- package/src/messageTypes.ts +12 -5
- package/src/metadata.ts +1 -1
- package/src/opLifecycle/definitions.ts +7 -3
- package/src/opLifecycle/index.ts +1 -0
- package/src/opLifecycle/opGroupingManager.ts +17 -4
- package/src/opLifecycle/opSerialization.ts +6 -1
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +49 -22
- package/src/summary/documentSchema.ts +228 -83
- package/src/summary/index.ts +3 -1
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { assert } from "@fluidframework/core-utils/internal";
|
|
7
|
+
import type { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
|
|
7
8
|
import { DataProcessingError } from "@fluidframework/telemetry-utils/internal";
|
|
9
|
+
import { gt, lt, parse } from "semver-ts";
|
|
8
10
|
|
|
11
|
+
import type { SemanticVersion } from "../compatUtils.js";
|
|
9
12
|
import { pkgVersion } from "../packageVersion.js";
|
|
10
13
|
|
|
11
14
|
/**
|
|
@@ -59,15 +62,56 @@ export type IdCompressorMode = "on" | "delayed" | undefined;
|
|
|
59
62
|
* @internal
|
|
60
63
|
*/
|
|
61
64
|
export interface IDocumentSchema {
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
+
// Note: Incoming schemas from other clients may have additional root-level properties (i.e. IDocumentSchema.app)
|
|
66
|
+
// that this client does not understand. The runtime will ignore these properties, unless they are within the
|
|
67
|
+
// "runtime" sub-tree, in which case it will fail if it is unable to understand any runtime properties.
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Describes how data needed to understand the schema is stored in this structure.
|
|
71
|
+
* If runtime sees a version it does not understand, it should immediately fail and not
|
|
72
|
+
* attempt to interpret any further data.
|
|
73
|
+
*/
|
|
65
74
|
version: number;
|
|
66
75
|
|
|
67
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Sequence number when this schema became active.
|
|
78
|
+
*/
|
|
68
79
|
refSeq: number;
|
|
69
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Runtime configurations that affect the document schema. Other clients must understand these
|
|
83
|
+
* properties to be able to open the document.
|
|
84
|
+
*/
|
|
70
85
|
runtime: Record<string, DocumentSchemaValueType>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Info about this document that can be updated via Document Schema change op, but isn't required
|
|
89
|
+
* to be understood by all clients (unlike the rest of IDocumentSchema properties). Because of this,
|
|
90
|
+
* some older documents may not have this property, so it's an optional property.
|
|
91
|
+
*/
|
|
92
|
+
info?: IDocumentSchemaInfo;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Informational properties of the document that are not subject to strict schema enforcement.
|
|
97
|
+
*
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
export interface IDocumentSchemaInfo {
|
|
101
|
+
/**
|
|
102
|
+
* The minimum version of the FF runtime that should be used to load this document.
|
|
103
|
+
* Will likely be advanced over time as applications pick up later FF versions.
|
|
104
|
+
*
|
|
105
|
+
* We use this to issue telemetry warning events if a client tries to open a document
|
|
106
|
+
* with a runtime version lower than this.
|
|
107
|
+
*
|
|
108
|
+
* See {@link @fluidframework/container-runtime#LoadContainerRuntimeParams} for additional details on `minVersionForCollab`.
|
|
109
|
+
*
|
|
110
|
+
* @remarks
|
|
111
|
+
* We use `SemanticVersion` instead of `MinimumVersionForCollab` since we may open future documents that with a
|
|
112
|
+
* minVersionForCollab version that `MinimumVersionForCollab` does not support.
|
|
113
|
+
*/
|
|
114
|
+
minVersionForCollab: SemanticVersion;
|
|
71
115
|
}
|
|
72
116
|
|
|
73
117
|
/**
|
|
@@ -75,10 +119,17 @@ export interface IDocumentSchema {
|
|
|
75
119
|
* The meaning of refSeq field is different in such messages (compared to other usages of IDocumentSchemaCurrent)
|
|
76
120
|
* ContainerMessageType.DocumentSchemaChange messages use CAS (Compare-and-swap) semantics, and convey
|
|
77
121
|
* regSeq of last known schema change (known to a client proposing schema change).
|
|
78
|
-
* @see
|
|
122
|
+
* @see InboundContainerRuntimeDocumentSchemaMessage
|
|
123
|
+
* @internal
|
|
124
|
+
*/
|
|
125
|
+
export type IDocumentSchemaChangeMessageIncoming = IDocumentSchema;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Similar to {@link IDocumentSchemaChangeMessageIncoming}, but used for outgoing schema messages.
|
|
129
|
+
* @see OutboundContainerRuntimeDocumentSchemaMessage
|
|
79
130
|
* @internal
|
|
80
131
|
*/
|
|
81
|
-
export type
|
|
132
|
+
export type IDocumentSchemaChangeMessageOutgoing = IDocumentSchemaCurrent;
|
|
82
133
|
|
|
83
134
|
/**
|
|
84
135
|
* Settings that this session would like to have, based on options and feature gates.
|
|
@@ -113,42 +164,61 @@ export interface IDocumentSchemaFeatures {
|
|
|
113
164
|
|
|
114
165
|
/**
|
|
115
166
|
* Current version known properties that define document schema
|
|
116
|
-
* This must be bumped whenever the format of document schema or protocol for changing the current document schema changes
|
|
117
|
-
*
|
|
118
|
-
* Ex:
|
|
167
|
+
* This must be bumped whenever the format of document schema or protocol for changing the current document schema changes
|
|
168
|
+
* in a way that all old/new clients are required to understand.
|
|
169
|
+
* Ex: Adding a new configuration property (under IDocumentSchema.runtime) does not require changing this version since there is logic
|
|
170
|
+
* in old clients for handling new/unknown properties.
|
|
171
|
+
* Ex: Adding a new property to IDocumentSchema.info does not require changing this version, since info properties are not required to be
|
|
172
|
+
* understood by all clients.
|
|
173
|
+
* Ex: Changing the 'document schema acceptance' mechanism from convert-and-swap to one requiring consensus does require changing this version
|
|
174
|
+
* since all clients need to understand the new protocol.
|
|
119
175
|
* @internal
|
|
120
176
|
*/
|
|
121
177
|
export const currentDocumentVersionSchema = 1;
|
|
122
178
|
|
|
123
179
|
/**
|
|
124
180
|
* Current document schema.
|
|
181
|
+
* This interface represents the schema that we currently understand and know the
|
|
182
|
+
* structure of (which properties will be present).
|
|
183
|
+
*
|
|
125
184
|
* @internal
|
|
126
185
|
*/
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
version:
|
|
130
|
-
|
|
131
|
-
|
|
186
|
+
export interface IDocumentSchemaCurrent extends Required<IDocumentSchema> {
|
|
187
|
+
// This is the version of the schema that we currently understand.
|
|
188
|
+
version: typeof currentDocumentVersionSchema;
|
|
189
|
+
// This narrows the runtime property to only include the properties in IDocumentSchemaFeatures (all as optional)
|
|
132
190
|
runtime: {
|
|
133
191
|
[P in keyof IDocumentSchemaFeatures]?: IDocumentSchemaFeatures[P] extends boolean
|
|
134
192
|
? true
|
|
135
193
|
: IDocumentSchemaFeatures[P];
|
|
136
194
|
};
|
|
137
|
-
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Document schema that is incoming from another client but validated to be "current".
|
|
199
|
+
*
|
|
200
|
+
* This interface represents when we have validated that an incoming IDocumentSchema object
|
|
201
|
+
* is compatible with the current runtime (by calling `checkRuntimeCompatibility()`).
|
|
202
|
+
* However, the `info` property is optional because some older documents may not have this property, but
|
|
203
|
+
* `info` is not required to be understood by all clients to be compatible.
|
|
204
|
+
*/
|
|
205
|
+
interface IDocumentSchemaCurrentIncoming extends Omit<IDocumentSchemaCurrent, "info"> {
|
|
206
|
+
info?: IDocumentSchemaInfo;
|
|
207
|
+
}
|
|
138
208
|
|
|
139
209
|
interface IProperty<T = unknown> {
|
|
140
|
-
and: (
|
|
141
|
-
or: (
|
|
210
|
+
and: (persistedSchema: T, providedSchema: T) => T;
|
|
211
|
+
or: (persistedSchema: T, providedSchema: T) => T;
|
|
142
212
|
validate(t: unknown): boolean;
|
|
143
213
|
}
|
|
144
214
|
|
|
145
215
|
class TrueOrUndefined implements IProperty<true | undefined> {
|
|
146
|
-
public and(
|
|
147
|
-
return
|
|
216
|
+
public and(persistedSchema?: true, providedSchema?: true): true | undefined {
|
|
217
|
+
return persistedSchema === true && providedSchema === true ? true : undefined;
|
|
148
218
|
}
|
|
149
219
|
|
|
150
|
-
public or(
|
|
151
|
-
return
|
|
220
|
+
public or(persistedSchema?: true, providedSchema?: true): true | undefined {
|
|
221
|
+
return persistedSchema === true || providedSchema === true ? true : undefined;
|
|
152
222
|
}
|
|
153
223
|
|
|
154
224
|
public validate(t: unknown): t is true | undefined {
|
|
@@ -157,32 +227,32 @@ class TrueOrUndefined implements IProperty<true | undefined> {
|
|
|
157
227
|
}
|
|
158
228
|
|
|
159
229
|
class TrueOrUndefinedMax extends TrueOrUndefined {
|
|
160
|
-
public and(
|
|
161
|
-
return this.or(
|
|
230
|
+
public and(persistedSchema?: true, providedSchema?: true): true | undefined {
|
|
231
|
+
return this.or(persistedSchema, providedSchema);
|
|
162
232
|
}
|
|
163
233
|
}
|
|
164
234
|
|
|
165
235
|
class MultiChoice implements IProperty<string | undefined> {
|
|
166
236
|
constructor(private readonly choices: string[]) {}
|
|
167
237
|
|
|
168
|
-
public and(
|
|
169
|
-
if (
|
|
238
|
+
public and(persistedSchema?: string, providedSchema?: string): string | undefined {
|
|
239
|
+
if (persistedSchema === undefined || providedSchema === undefined) {
|
|
170
240
|
return undefined;
|
|
171
241
|
}
|
|
172
242
|
return this.choices[
|
|
173
|
-
Math.min(this.choices.indexOf(
|
|
243
|
+
Math.min(this.choices.indexOf(persistedSchema), this.choices.indexOf(providedSchema))
|
|
174
244
|
];
|
|
175
245
|
}
|
|
176
246
|
|
|
177
|
-
public or(
|
|
178
|
-
if (
|
|
179
|
-
return
|
|
247
|
+
public or(persistedSchema?: string, providedSchema?: string): string | undefined {
|
|
248
|
+
if (persistedSchema === undefined) {
|
|
249
|
+
return providedSchema;
|
|
180
250
|
}
|
|
181
|
-
if (
|
|
182
|
-
return
|
|
251
|
+
if (providedSchema === undefined) {
|
|
252
|
+
return persistedSchema;
|
|
183
253
|
}
|
|
184
254
|
return this.choices[
|
|
185
|
-
Math.max(this.choices.indexOf(
|
|
255
|
+
Math.max(this.choices.indexOf(persistedSchema), this.choices.indexOf(providedSchema))
|
|
186
256
|
];
|
|
187
257
|
}
|
|
188
258
|
|
|
@@ -193,26 +263,26 @@ class MultiChoice implements IProperty<string | undefined> {
|
|
|
193
263
|
|
|
194
264
|
class IdCompressorProperty extends MultiChoice {
|
|
195
265
|
// document schema always wins!
|
|
196
|
-
public and(
|
|
197
|
-
return
|
|
266
|
+
public and(persistedSchema?: string, providedSchema?: string): string | undefined {
|
|
267
|
+
return persistedSchema;
|
|
198
268
|
}
|
|
199
269
|
}
|
|
200
270
|
|
|
201
271
|
class CheckVersions implements IProperty<string[] | undefined> {
|
|
202
272
|
public or(
|
|
203
|
-
|
|
204
|
-
|
|
273
|
+
persistedSchema: string[] = [],
|
|
274
|
+
providedSchema: string[] = [],
|
|
205
275
|
): string[] | undefined {
|
|
206
|
-
const set = new Set<string>([...
|
|
276
|
+
const set = new Set<string>([...persistedSchema, ...providedSchema]);
|
|
207
277
|
return arrayToProp([...set.values()]);
|
|
208
278
|
}
|
|
209
279
|
|
|
210
280
|
// Once version is there, it stays there forever.
|
|
211
281
|
public and(
|
|
212
|
-
|
|
213
|
-
|
|
282
|
+
persistedSchema: string[] = [],
|
|
283
|
+
providedSchema: string[] = [],
|
|
214
284
|
): string[] | undefined {
|
|
215
|
-
return this.or(
|
|
285
|
+
return this.or(persistedSchema, providedSchema);
|
|
216
286
|
}
|
|
217
287
|
|
|
218
288
|
public validate(t: unknown): boolean {
|
|
@@ -240,7 +310,7 @@ const documentSchemaSupportedConfigs = {
|
|
|
240
310
|
function checkRuntimeCompatibility(
|
|
241
311
|
documentSchema: IDocumentSchema | undefined,
|
|
242
312
|
schemaName: string,
|
|
243
|
-
):
|
|
313
|
+
): asserts documentSchema is IDocumentSchemaCurrentIncoming {
|
|
244
314
|
// Back-compat - we can't do anything about legacy documents.
|
|
245
315
|
// There is no way to validate them, so we are taking a guess that safe deployment processes used by a given app
|
|
246
316
|
// do not run into compat problems.
|
|
@@ -298,59 +368,89 @@ function checkRuntimeCompatibility(
|
|
|
298
368
|
}
|
|
299
369
|
|
|
300
370
|
function and(
|
|
301
|
-
|
|
302
|
-
|
|
371
|
+
persistedSchema: IDocumentSchemaCurrentIncoming,
|
|
372
|
+
providedSchema: IDocumentSchemaCurrent,
|
|
303
373
|
): IDocumentSchemaCurrent {
|
|
304
374
|
const runtime = {};
|
|
305
375
|
for (const key of new Set([
|
|
306
|
-
...Object.keys(
|
|
307
|
-
...Object.keys(
|
|
376
|
+
...Object.keys(persistedSchema.runtime),
|
|
377
|
+
...Object.keys(providedSchema.runtime),
|
|
308
378
|
])) {
|
|
309
379
|
runtime[key] = (documentSchemaSupportedConfigs[key] as IProperty).and(
|
|
310
|
-
|
|
311
|
-
|
|
380
|
+
persistedSchema.runtime[key],
|
|
381
|
+
providedSchema.runtime[key],
|
|
312
382
|
);
|
|
313
383
|
}
|
|
384
|
+
|
|
385
|
+
// We keep the persisted minVersionForCollab if present, even if the provided minVersionForCollab
|
|
386
|
+
// is higher.
|
|
387
|
+
const minVersionForCollab =
|
|
388
|
+
persistedSchema.info?.minVersionForCollab ?? providedSchema.info.minVersionForCollab;
|
|
389
|
+
|
|
314
390
|
return {
|
|
315
391
|
version: currentDocumentVersionSchema,
|
|
316
|
-
refSeq:
|
|
392
|
+
refSeq: persistedSchema.refSeq,
|
|
393
|
+
info: { minVersionForCollab },
|
|
317
394
|
runtime,
|
|
318
|
-
}
|
|
395
|
+
};
|
|
319
396
|
}
|
|
320
397
|
|
|
321
398
|
function or(
|
|
322
|
-
|
|
323
|
-
|
|
399
|
+
persistedSchema: IDocumentSchemaCurrentIncoming,
|
|
400
|
+
providedSchema: IDocumentSchemaCurrent,
|
|
324
401
|
): IDocumentSchemaCurrent {
|
|
325
402
|
const runtime = {};
|
|
326
403
|
for (const key of new Set([
|
|
327
|
-
...Object.keys(
|
|
328
|
-
...Object.keys(
|
|
404
|
+
...Object.keys(persistedSchema.runtime),
|
|
405
|
+
...Object.keys(providedSchema.runtime),
|
|
329
406
|
])) {
|
|
330
407
|
runtime[key] = (documentSchemaSupportedConfigs[key] as IProperty).or(
|
|
331
|
-
|
|
332
|
-
|
|
408
|
+
persistedSchema.runtime[key],
|
|
409
|
+
providedSchema.runtime[key],
|
|
333
410
|
);
|
|
334
411
|
}
|
|
412
|
+
|
|
413
|
+
// We take the greater of the persisted/provided minVersionForCollab
|
|
414
|
+
const minVersionForCollab =
|
|
415
|
+
persistedSchema.info === undefined
|
|
416
|
+
? providedSchema.info.minVersionForCollab
|
|
417
|
+
: gt(persistedSchema.info.minVersionForCollab, providedSchema.info.minVersionForCollab)
|
|
418
|
+
? persistedSchema.info.minVersionForCollab
|
|
419
|
+
: providedSchema.info.minVersionForCollab;
|
|
420
|
+
|
|
335
421
|
return {
|
|
336
422
|
version: currentDocumentVersionSchema,
|
|
337
|
-
refSeq:
|
|
423
|
+
refSeq: persistedSchema.refSeq,
|
|
424
|
+
info: { minVersionForCollab },
|
|
338
425
|
runtime,
|
|
339
|
-
}
|
|
426
|
+
};
|
|
340
427
|
}
|
|
341
428
|
|
|
429
|
+
/**
|
|
430
|
+
* Determines if two schemas are the "same".
|
|
431
|
+
* Schemas are considered **not** the same if a schema change op is required to make
|
|
432
|
+
* the properties of `persistedSchema` match to the properties of `providedSchema`.
|
|
433
|
+
*/
|
|
342
434
|
function same(
|
|
343
|
-
|
|
344
|
-
|
|
435
|
+
persistedSchema: IDocumentSchemaCurrentIncoming,
|
|
436
|
+
providedSchema: IDocumentSchemaCurrent,
|
|
345
437
|
): boolean {
|
|
438
|
+
if (
|
|
439
|
+
persistedSchema.info === undefined ||
|
|
440
|
+
lt(persistedSchema.info.minVersionForCollab, providedSchema.info.minVersionForCollab)
|
|
441
|
+
) {
|
|
442
|
+
// If the persisted schema's minVersionForCollab is undefined or less than the provided schema's
|
|
443
|
+
// minVersionForCollab, then we should send a schema change op to update the minVersionForCollab.
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
346
446
|
for (const key of new Set([
|
|
347
|
-
...Object.keys(
|
|
348
|
-
...Object.keys(
|
|
447
|
+
...Object.keys(persistedSchema.runtime),
|
|
448
|
+
...Object.keys(providedSchema.runtime),
|
|
349
449
|
])) {
|
|
350
450
|
// If schemas differ only by type of behavior, then we should not send schema change ops!
|
|
351
451
|
if (
|
|
352
452
|
key !== "explicitSchemaControl" &&
|
|
353
|
-
|
|
453
|
+
persistedSchema.runtime[key] !== providedSchema.runtime[key]
|
|
354
454
|
) {
|
|
355
455
|
return false;
|
|
356
456
|
}
|
|
@@ -436,10 +536,15 @@ function arrayToProp(arr: string[]): string[] | undefined {
|
|
|
436
536
|
*/
|
|
437
537
|
export class DocumentsSchemaController {
|
|
438
538
|
private explicitSchemaControl: boolean;
|
|
439
|
-
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Have we generated a DocumentSchemaChange op and we're waiting for the ack?
|
|
542
|
+
* This is used to ensure that we do not generate multiple schema change ops - this client should only ever send one (if any).
|
|
543
|
+
*/
|
|
544
|
+
private opPending = false;
|
|
440
545
|
|
|
441
546
|
// schema coming from document metadata (snapshot we loaded from)
|
|
442
|
-
private documentSchema:
|
|
547
|
+
private documentSchema: IDocumentSchema;
|
|
443
548
|
|
|
444
549
|
// desired schema, based on feature gates / runtime options.
|
|
445
550
|
// This includes requests to enable to disable functionality
|
|
@@ -461,6 +566,8 @@ export class DocumentsSchemaController {
|
|
|
461
566
|
* @param documentMetadataSchema - current document's schema, if present.
|
|
462
567
|
* @param features - features of the document schema that current session wants to see enabled.
|
|
463
568
|
* @param onSchemaChange - callback that is called whenever schema is changed (not called on creation / load, only when processing document schema change ops)
|
|
569
|
+
* @param info - Informational properties of the document that are not subject to strict schema enforcement
|
|
570
|
+
* @param logger - telemetry logger from the runtime
|
|
464
571
|
*/
|
|
465
572
|
constructor(
|
|
466
573
|
existing: boolean,
|
|
@@ -468,6 +575,8 @@ export class DocumentsSchemaController {
|
|
|
468
575
|
documentMetadataSchema: IDocumentSchema | undefined,
|
|
469
576
|
features: IDocumentSchemaFeatures,
|
|
470
577
|
private readonly onSchemaChange: (schema: IDocumentSchemaCurrent) => void,
|
|
578
|
+
info: IDocumentSchemaInfo,
|
|
579
|
+
logger: ITelemetryLoggerExt,
|
|
471
580
|
) {
|
|
472
581
|
// For simplicity, let's only support new schema features for explicit schema control mode
|
|
473
582
|
assert(
|
|
@@ -475,10 +584,34 @@ export class DocumentsSchemaController {
|
|
|
475
584
|
0x949 /* not supported */,
|
|
476
585
|
);
|
|
477
586
|
|
|
587
|
+
// We check the document's metadata to see if there is a minVersionForCollab. If it's not an existing document or
|
|
588
|
+
// if the document is older, then it won't have one. If it does have a minVersionForCollab, we check if it's greater
|
|
589
|
+
// than this client's runtime version. If so, we log a telemetry event to warn the customer that the client is outdated.
|
|
590
|
+
// Note: We only send a warning because we will confirm via `checkRuntimeCompatibility` if this client **can** understand
|
|
591
|
+
// the existing document's schema. We still want to issue a warning regardless if this client can or cannot understand the
|
|
592
|
+
// schema since it may be a sign that the customer is not properly waiting for saturation before updating their
|
|
593
|
+
// `minVersionForCollab` value, which could cause disruptions to users in the future.
|
|
594
|
+
const existingMinVersionForCollab = documentMetadataSchema?.info?.minVersionForCollab;
|
|
595
|
+
if (
|
|
596
|
+
existingMinVersionForCollab !== undefined &&
|
|
597
|
+
gt(existingMinVersionForCollab, pkgVersion) &&
|
|
598
|
+
// We also want to avoid sending the telemetry warning for dev builds, since they currently are formatted as
|
|
599
|
+
// `0.0.0-#####-test`. This will cause the telemetry warning to constantly fire.
|
|
600
|
+
// TODO: This can be removed after ADO:41351
|
|
601
|
+
!isDevBuild(pkgVersion)
|
|
602
|
+
) {
|
|
603
|
+
const warnMsg = `WARNING: The version of Fluid Framework used by this client (${pkgVersion}) is not supported by this document! Please upgrade to version ${existingMinVersionForCollab} or later to ensure compatibility.`;
|
|
604
|
+
logger.sendTelemetryEvent({
|
|
605
|
+
eventName: "MinVersionForCollabWarning",
|
|
606
|
+
message: warnMsg,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
478
610
|
// Desired schema by this session - almost all props are coming from arguments
|
|
479
611
|
this.desiredSchema = {
|
|
480
612
|
version: currentDocumentVersionSchema,
|
|
481
613
|
refSeq: documentMetadataSchema?.refSeq ?? 0,
|
|
614
|
+
info,
|
|
482
615
|
runtime: {
|
|
483
616
|
explicitSchemaControl: boolToProp(features.explicitSchemaControl),
|
|
484
617
|
compressionLz4: boolToProp(features.compressionLz4),
|
|
@@ -491,13 +624,14 @@ export class DocumentsSchemaController {
|
|
|
491
624
|
|
|
492
625
|
// Schema coming from document metadata (snapshot we loaded from), or if no document exists
|
|
493
626
|
// (this is a new document) then this is the same as desiredSchema (same as session schema in such case).
|
|
494
|
-
// Latter is
|
|
627
|
+
// Latter is important sure that's what will go into summary.
|
|
495
628
|
this.documentSchema = existing
|
|
496
|
-
? (
|
|
629
|
+
? (documentMetadataSchema ??
|
|
497
630
|
({
|
|
498
631
|
version: currentDocumentVersionSchema,
|
|
499
632
|
// see comment in summarizeDocumentSchema() on why it has to stay zero
|
|
500
633
|
refSeq: 0,
|
|
634
|
+
info,
|
|
501
635
|
// If it's existing document and it has no schema, then it was written by legacy client.
|
|
502
636
|
// If it's a new document, then we define it's legacy-related behaviors.
|
|
503
637
|
runtime: {
|
|
@@ -542,14 +676,15 @@ export class DocumentsSchemaController {
|
|
|
542
676
|
checkRuntimeCompatibility(this.futureSchema, "future");
|
|
543
677
|
}
|
|
544
678
|
|
|
545
|
-
public summarizeDocumentSchema(
|
|
679
|
+
public summarizeDocumentSchema(
|
|
680
|
+
refSeq: number,
|
|
681
|
+
): IDocumentSchema | IDocumentSchemaCurrent | undefined {
|
|
546
682
|
// For legacy behavior, we could write nothing (return undefined).
|
|
547
683
|
// It does not buy us anything, as whatever written in summary does not actually impact clients operating in legacy mode.
|
|
548
684
|
// But writing current used config (and assuming most of the clients settle on same config over time) will help with transition
|
|
549
685
|
// out of legacy mode, as clients transitioning out of it would be able to use all the
|
|
550
686
|
// features that are mentioned in schema right away, without a need to go through schema transition (and thus for a session or
|
|
551
687
|
// two losing ability to use all the features)
|
|
552
|
-
|
|
553
688
|
const schema = this.explicitSchemaControl ? this.documentSchema : this.desiredSchema;
|
|
554
689
|
|
|
555
690
|
// It's important to keep refSeq at zero in legacy mode, such that transition out of it is simple and we do not have
|
|
@@ -567,20 +702,17 @@ export class DocumentsSchemaController {
|
|
|
567
702
|
/**
|
|
568
703
|
* Called by Container runtime whenever it is about to send some op.
|
|
569
704
|
* It gives opportunity for controller to issue its own ops - we do not want to send ops if there are no local changes in document.
|
|
570
|
-
* Please consider note above constructor about race conditions - current design is to
|
|
705
|
+
* Please consider note above constructor about race conditions - current design is to generate op only once in a session lifetime.
|
|
571
706
|
* @returns Optional message to send.
|
|
572
707
|
*/
|
|
573
|
-
public
|
|
574
|
-
if (this.
|
|
575
|
-
this.
|
|
708
|
+
public maybeGenerateSchemaMessage(): IDocumentSchemaChangeMessageOutgoing | undefined {
|
|
709
|
+
if (this.futureSchema !== undefined && !this.opPending) {
|
|
710
|
+
this.opPending = true;
|
|
576
711
|
assert(
|
|
577
712
|
this.explicitSchemaControl && this.futureSchema.runtime.explicitSchemaControl === true,
|
|
578
713
|
0x94e /* not legacy */,
|
|
579
714
|
);
|
|
580
|
-
return
|
|
581
|
-
...this.futureSchema,
|
|
582
|
-
refSeq: this.documentSchema.refSeq,
|
|
583
|
-
};
|
|
715
|
+
return this.futureSchema;
|
|
584
716
|
}
|
|
585
717
|
}
|
|
586
718
|
|
|
@@ -612,7 +744,7 @@ export class DocumentsSchemaController {
|
|
|
612
744
|
* @returns - true if schema was accepted, otherwise false (rejected due to failed CAS)
|
|
613
745
|
*/
|
|
614
746
|
public processDocumentSchemaMessages(
|
|
615
|
-
contents:
|
|
747
|
+
contents: IDocumentSchemaChangeMessageIncoming[],
|
|
616
748
|
local: boolean,
|
|
617
749
|
sequenceNumber: number,
|
|
618
750
|
): boolean {
|
|
@@ -639,10 +771,12 @@ export class DocumentsSchemaController {
|
|
|
639
771
|
|
|
640
772
|
// Changes are in effect. Immediately check that this client understands these changes
|
|
641
773
|
checkRuntimeCompatibility(content, "change");
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
774
|
+
const schema = {
|
|
775
|
+
...content,
|
|
776
|
+
refSeq: sequenceNumber,
|
|
777
|
+
} satisfies IDocumentSchemaCurrentIncoming;
|
|
778
|
+
this.documentSchema = schema;
|
|
779
|
+
this.sessionSchema = and(schema, this.desiredSchema);
|
|
646
780
|
assert(this.sessionSchema.refSeq === sequenceNumber, 0x97d /* seq# */);
|
|
647
781
|
|
|
648
782
|
// legacy behavior is automatically off for the document once someone sends a schema op -
|
|
@@ -663,9 +797,20 @@ export class DocumentsSchemaController {
|
|
|
663
797
|
return true;
|
|
664
798
|
}
|
|
665
799
|
|
|
666
|
-
|
|
667
|
-
|
|
800
|
+
/**
|
|
801
|
+
* Indicates the pending op was not ack'd and we may try to send it again if needed.
|
|
802
|
+
*/
|
|
803
|
+
public pendingOpNotAcked(): void {
|
|
804
|
+
this.opPending = false;
|
|
668
805
|
}
|
|
669
806
|
}
|
|
670
807
|
|
|
808
|
+
/**
|
|
809
|
+
* Determines if a given version is a dev-build (i.e. `0.0.0-#####-test`).
|
|
810
|
+
*/
|
|
811
|
+
function isDevBuild(version: string): boolean {
|
|
812
|
+
const parsed = parse(version);
|
|
813
|
+
return parsed !== null && parsed.prerelease.includes("test");
|
|
814
|
+
}
|
|
815
|
+
|
|
671
816
|
/* eslint-enable jsdoc/check-indentation */
|
package/src/summary/index.ts
CHANGED
|
@@ -103,10 +103,12 @@ export {
|
|
|
103
103
|
IdCompressorMode,
|
|
104
104
|
IDocumentSchemaCurrent,
|
|
105
105
|
IDocumentSchema,
|
|
106
|
+
IDocumentSchemaInfo,
|
|
106
107
|
currentDocumentVersionSchema,
|
|
107
108
|
DocumentSchemaValueType,
|
|
108
109
|
DocumentsSchemaController,
|
|
109
|
-
|
|
110
|
+
IDocumentSchemaChangeMessageIncoming,
|
|
111
|
+
IDocumentSchemaChangeMessageOutgoing,
|
|
110
112
|
IDocumentSchemaFeatures,
|
|
111
113
|
} from "./documentSchema.js";
|
|
112
114
|
export {
|