@fluidframework/container-runtime 2.42.0 → 2.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/container-runtime.test-files.tar +0 -0
  3. package/dist/channelCollection.d.ts +1 -1
  4. package/dist/channelCollection.d.ts.map +1 -1
  5. package/dist/channelCollection.js +3 -1
  6. package/dist/channelCollection.js.map +1 -1
  7. package/dist/compatUtils.d.ts +2 -0
  8. package/dist/compatUtils.d.ts.map +1 -1
  9. package/dist/compatUtils.js.map +1 -1
  10. package/dist/containerRuntime.d.ts +3 -3
  11. package/dist/containerRuntime.d.ts.map +1 -1
  12. package/dist/containerRuntime.js +43 -26
  13. package/dist/containerRuntime.js.map +1 -1
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/opLifecycle/batchManager.d.ts +1 -0
  18. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  19. package/dist/opLifecycle/batchManager.js +3 -2
  20. package/dist/opLifecycle/batchManager.js.map +1 -1
  21. package/dist/opLifecycle/index.d.ts +1 -1
  22. package/dist/opLifecycle/index.d.ts.map +1 -1
  23. package/dist/opLifecycle/index.js +2 -1
  24. package/dist/opLifecycle/index.js.map +1 -1
  25. package/dist/opLifecycle/opGroupingManager.d.ts +1 -1
  26. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  27. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  28. package/dist/opLifecycle/outbox.js +1 -1
  29. package/dist/opLifecycle/outbox.js.map +1 -1
  30. package/dist/packageVersion.d.ts +1 -1
  31. package/dist/packageVersion.js +1 -1
  32. package/dist/packageVersion.js.map +1 -1
  33. package/dist/pendingStateManager.d.ts +7 -6
  34. package/dist/pendingStateManager.d.ts.map +1 -1
  35. package/dist/pendingStateManager.js +20 -15
  36. package/dist/pendingStateManager.js.map +1 -1
  37. package/dist/summary/documentSchema.d.ts +40 -1
  38. package/dist/summary/documentSchema.d.ts.map +1 -1
  39. package/dist/summary/documentSchema.js +57 -1
  40. package/dist/summary/documentSchema.js.map +1 -1
  41. package/dist/summary/index.d.ts +1 -1
  42. package/dist/summary/index.d.ts.map +1 -1
  43. package/dist/summary/index.js.map +1 -1
  44. package/lib/channelCollection.d.ts +1 -1
  45. package/lib/channelCollection.d.ts.map +1 -1
  46. package/lib/channelCollection.js +3 -1
  47. package/lib/channelCollection.js.map +1 -1
  48. package/lib/compatUtils.d.ts +2 -0
  49. package/lib/compatUtils.d.ts.map +1 -1
  50. package/lib/compatUtils.js.map +1 -1
  51. package/lib/containerRuntime.d.ts +3 -3
  52. package/lib/containerRuntime.d.ts.map +1 -1
  53. package/lib/containerRuntime.js +43 -26
  54. package/lib/containerRuntime.js.map +1 -1
  55. package/lib/index.d.ts +2 -2
  56. package/lib/index.d.ts.map +1 -1
  57. package/lib/index.js.map +1 -1
  58. package/lib/opLifecycle/batchManager.d.ts +1 -0
  59. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  60. package/lib/opLifecycle/batchManager.js +1 -1
  61. package/lib/opLifecycle/batchManager.js.map +1 -1
  62. package/lib/opLifecycle/index.d.ts +1 -1
  63. package/lib/opLifecycle/index.d.ts.map +1 -1
  64. package/lib/opLifecycle/index.js +1 -1
  65. package/lib/opLifecycle/index.js.map +1 -1
  66. package/lib/opLifecycle/opGroupingManager.d.ts +1 -1
  67. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  68. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  69. package/lib/opLifecycle/outbox.js +1 -1
  70. package/lib/opLifecycle/outbox.js.map +1 -1
  71. package/lib/packageVersion.d.ts +1 -1
  72. package/lib/packageVersion.js +1 -1
  73. package/lib/packageVersion.js.map +1 -1
  74. package/lib/pendingStateManager.d.ts +7 -6
  75. package/lib/pendingStateManager.d.ts.map +1 -1
  76. package/lib/pendingStateManager.js +20 -15
  77. package/lib/pendingStateManager.js.map +1 -1
  78. package/lib/summary/documentSchema.d.ts +40 -1
  79. package/lib/summary/documentSchema.d.ts.map +1 -1
  80. package/lib/summary/documentSchema.js +57 -1
  81. package/lib/summary/documentSchema.js.map +1 -1
  82. package/lib/summary/index.d.ts +1 -1
  83. package/lib/summary/index.d.ts.map +1 -1
  84. package/lib/summary/index.js.map +1 -1
  85. package/package.json +20 -20
  86. package/src/channelCollection.ts +3 -1
  87. package/src/compatUtils.ts +2 -0
  88. package/src/containerRuntime.ts +54 -35
  89. package/src/index.ts +2 -1
  90. package/src/opLifecycle/batchManager.ts +1 -1
  91. package/src/opLifecycle/index.ts +1 -0
  92. package/src/opLifecycle/opGroupingManager.ts +1 -1
  93. package/src/opLifecycle/outbox.ts +1 -1
  94. package/src/packageVersion.ts +1 -1
  95. package/src/pendingStateManager.ts +30 -22
  96. package/src/summary/documentSchema.ts +125 -5
  97. package/src/summary/index.ts +1 -0
@@ -48,9 +48,9 @@ export interface IPendingMessage {
48
48
  content: string;
49
49
  /**
50
50
  * The original runtime op that was submitted to the ContainerRuntime
51
- * Unless this pending message came from stashed content, in which case this was roundtripped through string
51
+ * Unless this pending message came from stashed content, in which case this is undefined at first and then deserialized from the contents string
52
52
  */
53
- runtimeOp?: LocalContainerRuntimeMessage | EmptyGroupedBatch | undefined; // Undefined for initial messages before parsing
53
+ runtimeOp: LocalContainerRuntimeMessage | EmptyGroupedBatch | undefined; // Undefined for initial messages before parsing
54
54
  /**
55
55
  * Local Op Metadata that was passed to the ContainerRuntime when the op was submitted.
56
56
  * This contains state needed when processing the ack, or to resubmit or rollback the op.
@@ -234,10 +234,11 @@ function toSerializableForm(
234
234
 
235
235
  interface ReplayPendingStateOptions {
236
236
  /**
237
- * If true, only replay staged batches. This is used when we are exiting staging mode and want to rebase and submit the staged batches.
237
+ * If true, only replay staged batches, clearing the "staged" flag.
238
+ * This is used when we are exiting staging mode and want to rebase and submit the staged batches without resubmitting pre-staged messages.
238
239
  * Default: false
239
240
  */
240
- onlyStagedBatches: boolean;
241
+ committingStagedBatches: boolean;
241
242
  /**
242
243
  * @param squash - If true, edits should be squashed when resubmitting.
243
244
  * Default: false
@@ -246,7 +247,7 @@ interface ReplayPendingStateOptions {
246
247
  }
247
248
 
248
249
  const defaultReplayPendingStatesOptions: ReplayPendingStateOptions = {
249
- onlyStagedBatches: false,
250
+ committingStagedBatches: false,
250
251
  squash: false,
251
252
  };
252
253
 
@@ -300,8 +301,8 @@ export class PendingStateManager implements IDisposable {
300
301
  for (let i = 0; i < this.pendingMessages.length; i++) {
301
302
  const element = this.pendingMessages.get(i);
302
303
  if (
303
- element?.runtimeOp !== undefined &&
304
- isNotEmptyGroupedBatch(element) &&
304
+ element !== undefined &&
305
+ hasTypicalRuntimeOp(element) && // Empty batches don't count towards user changes
305
306
  isContainerMessageDirtyable(element.runtimeOp)
306
307
  ) {
307
308
  return true;
@@ -742,16 +743,18 @@ export class PendingStateManager implements IDisposable {
742
743
  * states in its queue. This includes triggering resubmission of unacked ops.
743
744
  * ! Note: successfully resubmitting an op that has been successfully sequenced is not possible due to checks in the ConnectionStateHandler (Loader layer)
744
745
  */
745
- public replayPendingStates(optionsParam?: ReplayPendingStateOptions): void {
746
- const options = { ...defaultReplayPendingStatesOptions, ...optionsParam };
747
- const { onlyStagedBatches, squash } = options;
746
+ public replayPendingStates(options?: ReplayPendingStateOptions): void {
747
+ const { committingStagedBatches, squash } = {
748
+ ...defaultReplayPendingStatesOptions,
749
+ ...options,
750
+ };
748
751
  assert(
749
- this.stateHandler.connected() || onlyStagedBatches === true,
752
+ this.stateHandler.connected() || committingStagedBatches === true,
750
753
  0x172 /* "The connection state is not consistent with the runtime" */,
751
754
  );
752
755
 
753
756
  // Staged batches have not yet been submitted so check doesn't apply
754
- if (!onlyStagedBatches) {
757
+ if (!committingStagedBatches) {
755
758
  // This assert suggests we are about to send same ops twice, which will result in data loss.
756
759
  assert(
757
760
  this.clientIdFromLastReplay !== this.stateHandler.clientId(),
@@ -778,8 +781,8 @@ export class PendingStateManager implements IDisposable {
778
781
  let pendingMessage = this.pendingMessages.shift()!;
779
782
  remainingPendingMessagesCount--;
780
783
 
781
- // Re-queue pre-staging messages if we are only processing staged batches
782
- if (onlyStagedBatches) {
784
+ // Re-queue pre-staging messages - we are only to replay staged batches
785
+ if (committingStagedBatches) {
783
786
  if (!pendingMessage.batchInfo.staged) {
784
787
  assert(!seenStagedBatch, 0xb86 /* Staged batch was followed by non-staged batch */);
785
788
  this.pendingMessages.push(pendingMessage);
@@ -807,8 +810,8 @@ export class PendingStateManager implements IDisposable {
807
810
  }
808
811
 
809
812
  assert(
810
- pendingMessage.runtimeOp !== undefined && isNotEmptyGroupedBatch(pendingMessage),
811
- 0xb87 /* viableOp is only undefined for empty batches */,
813
+ hasTypicalRuntimeOp(pendingMessage),
814
+ 0xb87 /* runtimeOp is only undefined for empty batches */,
812
815
  );
813
816
 
814
817
  /**
@@ -843,8 +846,8 @@ export class PendingStateManager implements IDisposable {
843
846
  // check is >= because batch end may be last pending message
844
847
  while (remainingPendingMessagesCount >= 0) {
845
848
  assert(
846
- pendingMessage.runtimeOp !== undefined && isNotEmptyGroupedBatch(pendingMessage),
847
- 0xb88 /* viableOp is only undefined for empty batches */,
849
+ hasTypicalRuntimeOp(pendingMessage),
850
+ 0xb88 /* runtimeOp is only undefined for empty batches */,
848
851
  );
849
852
  batch.push({
850
853
  runtimeOp: pendingMessage.runtimeOp,
@@ -890,15 +893,17 @@ export class PendingStateManager implements IDisposable {
890
893
  */
891
894
  public popStagedBatches(
892
895
  callback: (
893
- stagedMessage: IPendingMessage & { runtimeOp?: LocalContainerRuntimeMessage }, // exclude empty grouped batches
896
+ // callback will only be given staged messages with a valid runtime op (i.e. not empty batch and not an initial message with only serialized content)
897
+ stagedMessage: IPendingMessage & { runtimeOp: LocalContainerRuntimeMessage },
894
898
  ) => void,
895
899
  ): void {
896
900
  while (!this.pendingMessages.isEmpty()) {
897
901
  const stagedMessage = this.pendingMessages.peekBack();
898
902
  if (stagedMessage?.batchInfo.staged === true) {
899
- if (isNotEmptyGroupedBatch(stagedMessage)) {
903
+ this.pendingMessages.pop();
904
+
905
+ if (hasTypicalRuntimeOp(stagedMessage)) {
900
906
  callback(stagedMessage);
901
- this.pendingMessages.pop();
902
907
  }
903
908
  } else {
904
909
  break; // no more staged messages
@@ -924,7 +929,10 @@ function patchbatchInfo(
924
929
  }
925
930
  }
926
931
 
927
- function isNotEmptyGroupedBatch(
932
+ /**
933
+ * This filters out messages that are not "typical" runtime ops, i.e. empty batches or initial messages (which only have serialized content).
934
+ */
935
+ function hasTypicalRuntimeOp(
928
936
  message: IPendingMessage,
929
937
  ): message is IPendingMessage & { runtimeOp: LocalContainerRuntimeMessage } {
930
938
  return message.runtimeOp !== undefined && message.runtimeOp.type !== "groupedBatch";
@@ -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
  /**
@@ -80,10 +83,42 @@ export interface IDocumentSchema {
80
83
  * properties to be able to open the document.
81
84
  */
82
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;
83
115
  }
84
116
 
85
117
  /**
86
118
  * Content of the type=ContainerMessageType.DocumentSchemaChange ops.
119
+ * The meaning of refSeq field is different in such messages (compared to other usages of IDocumentSchemaCurrent)
120
+ * ContainerMessageType.DocumentSchemaChange messages use CAS (Compare-and-swap) semantics, and convey
121
+ * regSeq of last known schema change (known to a client proposing schema change).
87
122
  * @see InboundContainerRuntimeDocumentSchemaMessage
88
123
  * @internal
89
124
  */
@@ -133,6 +168,8 @@ export interface IDocumentSchemaFeatures {
133
168
  * in a way that all old/new clients are required to understand.
134
169
  * Ex: Adding a new configuration property (under IDocumentSchema.runtime) does not require changing this version since there is logic
135
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.
136
173
  * Ex: Changing the 'document schema acceptance' mechanism from convert-and-swap to one requiring consensus does require changing this version
137
174
  * since all clients need to understand the new protocol.
138
175
  * @internal
@@ -141,11 +178,15 @@ export const currentDocumentVersionSchema = 1;
141
178
 
142
179
  /**
143
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
+ *
144
184
  * @internal
145
185
  */
146
186
  export interface IDocumentSchemaCurrent extends Required<IDocumentSchema> {
147
187
  // This is the version of the schema that we currently understand.
148
188
  version: typeof currentDocumentVersionSchema;
189
+ // This narrows the runtime property to only include the properties in IDocumentSchemaFeatures (all as optional)
149
190
  runtime: {
150
191
  [P in keyof IDocumentSchemaFeatures]?: IDocumentSchemaFeatures[P] extends boolean
151
192
  ? true
@@ -153,6 +194,18 @@ export interface IDocumentSchemaCurrent extends Required<IDocumentSchema> {
153
194
  };
154
195
  }
155
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
+ }
208
+
156
209
  interface IProperty<T = unknown> {
157
210
  and: (persistedSchema: T, providedSchema: T) => T;
158
211
  or: (persistedSchema: T, providedSchema: T) => T;
@@ -257,7 +310,7 @@ const documentSchemaSupportedConfigs = {
257
310
  function checkRuntimeCompatibility(
258
311
  documentSchema: IDocumentSchema | undefined,
259
312
  schemaName: string,
260
- ): asserts documentSchema is IDocumentSchemaCurrent {
313
+ ): asserts documentSchema is IDocumentSchemaCurrentIncoming {
261
314
  // Back-compat - we can't do anything about legacy documents.
262
315
  // There is no way to validate them, so we are taking a guess that safe deployment processes used by a given app
263
316
  // do not run into compat problems.
@@ -315,7 +368,7 @@ function checkRuntimeCompatibility(
315
368
  }
316
369
 
317
370
  function and(
318
- persistedSchema: IDocumentSchemaCurrent,
371
+ persistedSchema: IDocumentSchemaCurrentIncoming,
319
372
  providedSchema: IDocumentSchemaCurrent,
320
373
  ): IDocumentSchemaCurrent {
321
374
  const runtime = {};
@@ -328,15 +381,22 @@ function and(
328
381
  providedSchema.runtime[key],
329
382
  );
330
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
+
331
390
  return {
332
391
  version: currentDocumentVersionSchema,
333
392
  refSeq: persistedSchema.refSeq,
393
+ info: { minVersionForCollab },
334
394
  runtime,
335
395
  };
336
396
  }
337
397
 
338
398
  function or(
339
- persistedSchema: IDocumentSchemaCurrent,
399
+ persistedSchema: IDocumentSchemaCurrentIncoming,
340
400
  providedSchema: IDocumentSchemaCurrent,
341
401
  ): IDocumentSchemaCurrent {
342
402
  const runtime = {};
@@ -349,17 +409,40 @@ function or(
349
409
  providedSchema.runtime[key],
350
410
  );
351
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
+
352
421
  return {
353
422
  version: currentDocumentVersionSchema,
354
423
  refSeq: persistedSchema.refSeq,
424
+ info: { minVersionForCollab },
355
425
  runtime,
356
426
  };
357
427
  }
358
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
+ */
359
434
  function same(
360
- persistedSchema: IDocumentSchemaCurrent,
435
+ persistedSchema: IDocumentSchemaCurrentIncoming,
361
436
  providedSchema: IDocumentSchemaCurrent,
362
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
+ }
363
446
  for (const key of new Set([
364
447
  ...Object.keys(persistedSchema.runtime),
365
448
  ...Object.keys(providedSchema.runtime),
@@ -483,6 +566,8 @@ export class DocumentsSchemaController {
483
566
  * @param documentMetadataSchema - current document's schema, if present.
484
567
  * @param features - features of the document schema that current session wants to see enabled.
485
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
486
571
  */
487
572
  constructor(
488
573
  existing: boolean,
@@ -490,6 +575,8 @@ export class DocumentsSchemaController {
490
575
  documentMetadataSchema: IDocumentSchema | undefined,
491
576
  features: IDocumentSchemaFeatures,
492
577
  private readonly onSchemaChange: (schema: IDocumentSchemaCurrent) => void,
578
+ info: IDocumentSchemaInfo,
579
+ logger: ITelemetryLoggerExt,
493
580
  ) {
494
581
  // For simplicity, let's only support new schema features for explicit schema control mode
495
582
  assert(
@@ -497,10 +584,34 @@ export class DocumentsSchemaController {
497
584
  0x949 /* not supported */,
498
585
  );
499
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
+
500
610
  // Desired schema by this session - almost all props are coming from arguments
501
611
  this.desiredSchema = {
502
612
  version: currentDocumentVersionSchema,
503
613
  refSeq: documentMetadataSchema?.refSeq ?? 0,
614
+ info,
504
615
  runtime: {
505
616
  explicitSchemaControl: boolToProp(features.explicitSchemaControl),
506
617
  compressionLz4: boolToProp(features.compressionLz4),
@@ -520,6 +631,7 @@ export class DocumentsSchemaController {
520
631
  version: currentDocumentVersionSchema,
521
632
  // see comment in summarizeDocumentSchema() on why it has to stay zero
522
633
  refSeq: 0,
634
+ info,
523
635
  // If it's existing document and it has no schema, then it was written by legacy client.
524
636
  // If it's a new document, then we define it's legacy-related behaviors.
525
637
  runtime: {
@@ -662,7 +774,7 @@ export class DocumentsSchemaController {
662
774
  const schema = {
663
775
  ...content,
664
776
  refSeq: sequenceNumber,
665
- } satisfies IDocumentSchemaCurrent;
777
+ } satisfies IDocumentSchemaCurrentIncoming;
666
778
  this.documentSchema = schema;
667
779
  this.sessionSchema = and(schema, this.desiredSchema);
668
780
  assert(this.sessionSchema.refSeq === sequenceNumber, 0x97d /* seq# */);
@@ -693,4 +805,12 @@ export class DocumentsSchemaController {
693
805
  }
694
806
  }
695
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
+
696
816
  /* eslint-enable jsdoc/check-indentation */
@@ -103,6 +103,7 @@ export {
103
103
  IdCompressorMode,
104
104
  IDocumentSchemaCurrent,
105
105
  IDocumentSchema,
106
+ IDocumentSchemaInfo,
106
107
  currentDocumentVersionSchema,
107
108
  DocumentSchemaValueType,
108
109
  DocumentsSchemaController,