@fluidframework/container-runtime 2.41.0 → 2.42.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 (134) 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 +4 -4
  6. package/dist/channelCollection.js.map +1 -1
  7. package/dist/compatUtils.d.ts +22 -1
  8. package/dist/compatUtils.d.ts.map +1 -1
  9. package/dist/compatUtils.js +109 -7
  10. package/dist/compatUtils.js.map +1 -1
  11. package/dist/containerRuntime.d.ts +34 -13
  12. package/dist/containerRuntime.d.ts.map +1 -1
  13. package/dist/containerRuntime.js +158 -59
  14. package/dist/containerRuntime.js.map +1 -1
  15. package/dist/dataStore.d.ts.map +1 -1
  16. package/dist/dataStore.js +5 -0
  17. package/dist/dataStore.js.map +1 -1
  18. package/dist/gc/garbageCollection.d.ts.map +1 -1
  19. package/dist/gc/garbageCollection.js +2 -0
  20. package/dist/gc/garbageCollection.js.map +1 -1
  21. package/dist/gc/gcDefinitions.d.ts +1 -1
  22. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  23. package/dist/gc/gcDefinitions.js.map +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/messageTypes.d.ts +5 -4
  28. package/dist/messageTypes.d.ts.map +1 -1
  29. package/dist/messageTypes.js.map +1 -1
  30. package/dist/metadata.d.ts +1 -1
  31. package/dist/metadata.d.ts.map +1 -1
  32. package/dist/metadata.js.map +1 -1
  33. package/dist/opLifecycle/definitions.d.ts +6 -5
  34. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  35. package/dist/opLifecycle/definitions.js.map +1 -1
  36. package/dist/opLifecycle/index.d.ts +1 -1
  37. package/dist/opLifecycle/index.d.ts.map +1 -1
  38. package/dist/opLifecycle/index.js.map +1 -1
  39. package/dist/opLifecycle/opGroupingManager.d.ts +9 -0
  40. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  41. package/dist/opLifecycle/opGroupingManager.js +6 -4
  42. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  43. package/dist/opLifecycle/opSerialization.d.ts +2 -1
  44. package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
  45. package/dist/opLifecycle/opSerialization.js.map +1 -1
  46. package/dist/packageVersion.d.ts +1 -1
  47. package/dist/packageVersion.js +1 -1
  48. package/dist/packageVersion.js.map +1 -1
  49. package/dist/pendingStateManager.d.ts +18 -5
  50. package/dist/pendingStateManager.d.ts.map +1 -1
  51. package/dist/pendingStateManager.js +20 -13
  52. package/dist/pendingStateManager.js.map +1 -1
  53. package/dist/summary/documentSchema.d.ts +42 -18
  54. package/dist/summary/documentSchema.d.ts.map +1 -1
  55. package/dist/summary/documentSchema.js +62 -52
  56. package/dist/summary/documentSchema.js.map +1 -1
  57. package/dist/summary/index.d.ts +1 -1
  58. package/dist/summary/index.d.ts.map +1 -1
  59. package/dist/summary/index.js.map +1 -1
  60. package/lib/channelCollection.d.ts +1 -1
  61. package/lib/channelCollection.d.ts.map +1 -1
  62. package/lib/channelCollection.js +4 -4
  63. package/lib/channelCollection.js.map +1 -1
  64. package/lib/compatUtils.d.ts +22 -1
  65. package/lib/compatUtils.d.ts.map +1 -1
  66. package/lib/compatUtils.js +102 -3
  67. package/lib/compatUtils.js.map +1 -1
  68. package/lib/containerRuntime.d.ts +34 -13
  69. package/lib/containerRuntime.d.ts.map +1 -1
  70. package/lib/containerRuntime.js +160 -61
  71. package/lib/containerRuntime.js.map +1 -1
  72. package/lib/dataStore.d.ts.map +1 -1
  73. package/lib/dataStore.js +5 -0
  74. package/lib/dataStore.js.map +1 -1
  75. package/lib/gc/garbageCollection.d.ts.map +1 -1
  76. package/lib/gc/garbageCollection.js +2 -0
  77. package/lib/gc/garbageCollection.js.map +1 -1
  78. package/lib/gc/gcDefinitions.d.ts +1 -1
  79. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  80. package/lib/gc/gcDefinitions.js.map +1 -1
  81. package/lib/index.d.ts +1 -1
  82. package/lib/index.d.ts.map +1 -1
  83. package/lib/index.js.map +1 -1
  84. package/lib/messageTypes.d.ts +5 -4
  85. package/lib/messageTypes.d.ts.map +1 -1
  86. package/lib/messageTypes.js.map +1 -1
  87. package/lib/metadata.d.ts +1 -1
  88. package/lib/metadata.d.ts.map +1 -1
  89. package/lib/metadata.js.map +1 -1
  90. package/lib/opLifecycle/definitions.d.ts +6 -5
  91. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  92. package/lib/opLifecycle/definitions.js.map +1 -1
  93. package/lib/opLifecycle/index.d.ts +1 -1
  94. package/lib/opLifecycle/index.d.ts.map +1 -1
  95. package/lib/opLifecycle/index.js.map +1 -1
  96. package/lib/opLifecycle/opGroupingManager.d.ts +9 -0
  97. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  98. package/lib/opLifecycle/opGroupingManager.js +6 -4
  99. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  100. package/lib/opLifecycle/opSerialization.d.ts +2 -1
  101. package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
  102. package/lib/opLifecycle/opSerialization.js.map +1 -1
  103. package/lib/packageVersion.d.ts +1 -1
  104. package/lib/packageVersion.js +1 -1
  105. package/lib/packageVersion.js.map +1 -1
  106. package/lib/pendingStateManager.d.ts +18 -5
  107. package/lib/pendingStateManager.d.ts.map +1 -1
  108. package/lib/pendingStateManager.js +20 -13
  109. package/lib/pendingStateManager.js.map +1 -1
  110. package/lib/summary/documentSchema.d.ts +42 -18
  111. package/lib/summary/documentSchema.d.ts.map +1 -1
  112. package/lib/summary/documentSchema.js +62 -52
  113. package/lib/summary/documentSchema.js.map +1 -1
  114. package/lib/summary/index.d.ts +1 -1
  115. package/lib/summary/index.d.ts.map +1 -1
  116. package/lib/summary/index.js.map +1 -1
  117. package/package.json +18 -18
  118. package/src/channelCollection.ts +4 -4
  119. package/src/compatUtils.ts +145 -10
  120. package/src/containerRuntime.ts +209 -73
  121. package/src/dataStore.ts +7 -0
  122. package/src/gc/garbageCollection.ts +2 -0
  123. package/src/gc/gcDefinitions.ts +1 -1
  124. package/src/index.ts +2 -1
  125. package/src/messageTypes.ts +12 -5
  126. package/src/metadata.ts +1 -1
  127. package/src/opLifecycle/definitions.ts +7 -3
  128. package/src/opLifecycle/index.ts +1 -0
  129. package/src/opLifecycle/opGroupingManager.ts +17 -4
  130. package/src/opLifecycle/opSerialization.ts +6 -1
  131. package/src/packageVersion.ts +1 -1
  132. package/src/pendingStateManager.ts +49 -22
  133. package/src/summary/documentSchema.ts +111 -86
  134. package/src/summary/index.ts +2 -1
@@ -59,26 +59,42 @@ export type IdCompressorMode = "on" | "delayed" | undefined;
59
59
  * @internal
60
60
  */
61
61
  export interface IDocumentSchema {
62
- // version that describes how data is stored in this structure.
63
- // If runtime sees a version it does not understand, it should immediately fail and not
64
- // attempt to interpret any further data.
62
+ // Note: Incoming schemas from other clients may have additional root-level properties (i.e. IDocumentSchema.app)
63
+ // that this client does not understand. The runtime will ignore these properties, unless they are within the
64
+ // "runtime" sub-tree, in which case it will fail if it is unable to understand any runtime properties.
65
+
66
+ /**
67
+ * Describes how data needed to understand the schema is stored in this structure.
68
+ * If runtime sees a version it does not understand, it should immediately fail and not
69
+ * attempt to interpret any further data.
70
+ */
65
71
  version: number;
66
72
 
67
- // Sequence number when this schema became active.
73
+ /**
74
+ * Sequence number when this schema became active.
75
+ */
68
76
  refSeq: number;
69
77
 
78
+ /**
79
+ * Runtime configurations that affect the document schema. Other clients must understand these
80
+ * properties to be able to open the document.
81
+ */
70
82
  runtime: Record<string, DocumentSchemaValueType>;
71
83
  }
72
84
 
73
85
  /**
74
86
  * Content of the type=ContainerMessageType.DocumentSchemaChange ops.
75
- * The meaning of refSeq field is different in such messages (compared to other usages of IDocumentSchemaCurrent)
76
- * ContainerMessageType.DocumentSchemaChange messages use CAS (Compare-and-swap) semantics, and convey
77
- * regSeq of last known schema change (known to a client proposing schema change).
78
- * @see ContainerRuntimeDocumentSchemaMessage
87
+ * @see InboundContainerRuntimeDocumentSchemaMessage
79
88
  * @internal
80
89
  */
81
- export type IDocumentSchemaChangeMessage = IDocumentSchema;
90
+ export type IDocumentSchemaChangeMessageIncoming = IDocumentSchema;
91
+
92
+ /**
93
+ * Similar to {@link IDocumentSchemaChangeMessageIncoming}, but used for outgoing schema messages.
94
+ * @see OutboundContainerRuntimeDocumentSchemaMessage
95
+ * @internal
96
+ */
97
+ export type IDocumentSchemaChangeMessageOutgoing = IDocumentSchemaCurrent;
82
98
 
83
99
  /**
84
100
  * Settings that this session would like to have, based on options and feature gates.
@@ -113,9 +129,12 @@ export interface IDocumentSchemaFeatures {
113
129
 
114
130
  /**
115
131
  * 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
- * Ex: adding a new configuration property (under IDocumentSchema.runtime) does not require changing this version.
118
- * Ex: Changing the 'document schema acceptance' mechanism from convert-and-swap to one requiring consensus does require changing this version.
132
+ * This must be bumped whenever the format of document schema or protocol for changing the current document schema changes
133
+ * in a way that all old/new clients are required to understand.
134
+ * Ex: Adding a new configuration property (under IDocumentSchema.runtime) does not require changing this version since there is logic
135
+ * in old clients for handling new/unknown properties.
136
+ * Ex: Changing the 'document schema acceptance' mechanism from convert-and-swap to one requiring consensus does require changing this version
137
+ * since all clients need to understand the new protocol.
119
138
  * @internal
120
139
  */
121
140
  export const currentDocumentVersionSchema = 1;
@@ -124,31 +143,29 @@ export const currentDocumentVersionSchema = 1;
124
143
  * Current document schema.
125
144
  * @internal
126
145
  */
127
- // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
128
- export type IDocumentSchemaCurrent = {
129
- version: 1;
130
- refSeq: number;
131
-
146
+ export interface IDocumentSchemaCurrent extends Required<IDocumentSchema> {
147
+ // This is the version of the schema that we currently understand.
148
+ version: typeof currentDocumentVersionSchema;
132
149
  runtime: {
133
150
  [P in keyof IDocumentSchemaFeatures]?: IDocumentSchemaFeatures[P] extends boolean
134
151
  ? true
135
152
  : IDocumentSchemaFeatures[P];
136
153
  };
137
- };
154
+ }
138
155
 
139
156
  interface IProperty<T = unknown> {
140
- and: (currentDocSchema: T, desiredDocSchema: T) => T;
141
- or: (currentDocSchema: T, desiredDocSchema: T) => T;
157
+ and: (persistedSchema: T, providedSchema: T) => T;
158
+ or: (persistedSchema: T, providedSchema: T) => T;
142
159
  validate(t: unknown): boolean;
143
160
  }
144
161
 
145
162
  class TrueOrUndefined implements IProperty<true | undefined> {
146
- public and(currentDocSchema?: true, desiredDocSchema?: true): true | undefined {
147
- return currentDocSchema === true && desiredDocSchema === true ? true : undefined;
163
+ public and(persistedSchema?: true, providedSchema?: true): true | undefined {
164
+ return persistedSchema === true && providedSchema === true ? true : undefined;
148
165
  }
149
166
 
150
- public or(currentDocSchema?: true, desiredDocSchema?: true): true | undefined {
151
- return currentDocSchema === true || desiredDocSchema === true ? true : undefined;
167
+ public or(persistedSchema?: true, providedSchema?: true): true | undefined {
168
+ return persistedSchema === true || providedSchema === true ? true : undefined;
152
169
  }
153
170
 
154
171
  public validate(t: unknown): t is true | undefined {
@@ -157,32 +174,32 @@ class TrueOrUndefined implements IProperty<true | undefined> {
157
174
  }
158
175
 
159
176
  class TrueOrUndefinedMax extends TrueOrUndefined {
160
- public and(currentDocSchema?: true, desiredDocSchema?: true): true | undefined {
161
- return this.or(currentDocSchema, desiredDocSchema);
177
+ public and(persistedSchema?: true, providedSchema?: true): true | undefined {
178
+ return this.or(persistedSchema, providedSchema);
162
179
  }
163
180
  }
164
181
 
165
182
  class MultiChoice implements IProperty<string | undefined> {
166
183
  constructor(private readonly choices: string[]) {}
167
184
 
168
- public and(currentDocSchema?: string, desiredDocSchema?: string): string | undefined {
169
- if (currentDocSchema === undefined || desiredDocSchema === undefined) {
185
+ public and(persistedSchema?: string, providedSchema?: string): string | undefined {
186
+ if (persistedSchema === undefined || providedSchema === undefined) {
170
187
  return undefined;
171
188
  }
172
189
  return this.choices[
173
- Math.min(this.choices.indexOf(currentDocSchema), this.choices.indexOf(desiredDocSchema))
190
+ Math.min(this.choices.indexOf(persistedSchema), this.choices.indexOf(providedSchema))
174
191
  ];
175
192
  }
176
193
 
177
- public or(currentDocSchema?: string, desiredDocSchema?: string): string | undefined {
178
- if (currentDocSchema === undefined) {
179
- return desiredDocSchema;
194
+ public or(persistedSchema?: string, providedSchema?: string): string | undefined {
195
+ if (persistedSchema === undefined) {
196
+ return providedSchema;
180
197
  }
181
- if (desiredDocSchema === undefined) {
182
- return currentDocSchema;
198
+ if (providedSchema === undefined) {
199
+ return persistedSchema;
183
200
  }
184
201
  return this.choices[
185
- Math.max(this.choices.indexOf(currentDocSchema), this.choices.indexOf(desiredDocSchema))
202
+ Math.max(this.choices.indexOf(persistedSchema), this.choices.indexOf(providedSchema))
186
203
  ];
187
204
  }
188
205
 
@@ -193,26 +210,26 @@ class MultiChoice implements IProperty<string | undefined> {
193
210
 
194
211
  class IdCompressorProperty extends MultiChoice {
195
212
  // document schema always wins!
196
- public and(currentDocSchema?: string, desiredDocSchema?: string): string | undefined {
197
- return currentDocSchema;
213
+ public and(persistedSchema?: string, providedSchema?: string): string | undefined {
214
+ return persistedSchema;
198
215
  }
199
216
  }
200
217
 
201
218
  class CheckVersions implements IProperty<string[] | undefined> {
202
219
  public or(
203
- currentDocSchema: string[] = [],
204
- desiredDocSchema: string[] = [],
220
+ persistedSchema: string[] = [],
221
+ providedSchema: string[] = [],
205
222
  ): string[] | undefined {
206
- const set = new Set<string>([...currentDocSchema, ...desiredDocSchema]);
223
+ const set = new Set<string>([...persistedSchema, ...providedSchema]);
207
224
  return arrayToProp([...set.values()]);
208
225
  }
209
226
 
210
227
  // Once version is there, it stays there forever.
211
228
  public and(
212
- currentDocSchema: string[] = [],
213
- desiredDocSchema: string[] = [],
229
+ persistedSchema: string[] = [],
230
+ providedSchema: string[] = [],
214
231
  ): string[] | undefined {
215
- return this.or(currentDocSchema, desiredDocSchema);
232
+ return this.or(persistedSchema, providedSchema);
216
233
  }
217
234
 
218
235
  public validate(t: unknown): boolean {
@@ -240,7 +257,7 @@ const documentSchemaSupportedConfigs = {
240
257
  function checkRuntimeCompatibility(
241
258
  documentSchema: IDocumentSchema | undefined,
242
259
  schemaName: string,
243
- ): void {
260
+ ): asserts documentSchema is IDocumentSchemaCurrent {
244
261
  // Back-compat - we can't do anything about legacy documents.
245
262
  // There is no way to validate them, so we are taking a guess that safe deployment processes used by a given app
246
263
  // do not run into compat problems.
@@ -298,59 +315,59 @@ function checkRuntimeCompatibility(
298
315
  }
299
316
 
300
317
  function and(
301
- currentDocSchema: IDocumentSchemaCurrent,
302
- desiredDocSchema: IDocumentSchemaCurrent,
318
+ persistedSchema: IDocumentSchemaCurrent,
319
+ providedSchema: IDocumentSchemaCurrent,
303
320
  ): IDocumentSchemaCurrent {
304
321
  const runtime = {};
305
322
  for (const key of new Set([
306
- ...Object.keys(currentDocSchema.runtime),
307
- ...Object.keys(desiredDocSchema.runtime),
323
+ ...Object.keys(persistedSchema.runtime),
324
+ ...Object.keys(providedSchema.runtime),
308
325
  ])) {
309
326
  runtime[key] = (documentSchemaSupportedConfigs[key] as IProperty).and(
310
- currentDocSchema.runtime[key],
311
- desiredDocSchema.runtime[key],
327
+ persistedSchema.runtime[key],
328
+ providedSchema.runtime[key],
312
329
  );
313
330
  }
314
331
  return {
315
332
  version: currentDocumentVersionSchema,
316
- refSeq: currentDocSchema.refSeq,
333
+ refSeq: persistedSchema.refSeq,
317
334
  runtime,
318
- } as unknown as IDocumentSchemaCurrent;
335
+ };
319
336
  }
320
337
 
321
338
  function or(
322
- currentDocSchema: IDocumentSchemaCurrent,
323
- desiredDocSchema: IDocumentSchemaCurrent,
339
+ persistedSchema: IDocumentSchemaCurrent,
340
+ providedSchema: IDocumentSchemaCurrent,
324
341
  ): IDocumentSchemaCurrent {
325
342
  const runtime = {};
326
343
  for (const key of new Set([
327
- ...Object.keys(currentDocSchema.runtime),
328
- ...Object.keys(desiredDocSchema.runtime),
344
+ ...Object.keys(persistedSchema.runtime),
345
+ ...Object.keys(providedSchema.runtime),
329
346
  ])) {
330
347
  runtime[key] = (documentSchemaSupportedConfigs[key] as IProperty).or(
331
- currentDocSchema.runtime[key],
332
- desiredDocSchema.runtime[key],
348
+ persistedSchema.runtime[key],
349
+ providedSchema.runtime[key],
333
350
  );
334
351
  }
335
352
  return {
336
353
  version: currentDocumentVersionSchema,
337
- refSeq: currentDocSchema.refSeq,
354
+ refSeq: persistedSchema.refSeq,
338
355
  runtime,
339
- } as unknown as IDocumentSchemaCurrent;
356
+ };
340
357
  }
341
358
 
342
359
  function same(
343
- currentDocSchema: IDocumentSchemaCurrent,
344
- desiredDocSchema: IDocumentSchemaCurrent,
360
+ persistedSchema: IDocumentSchemaCurrent,
361
+ providedSchema: IDocumentSchemaCurrent,
345
362
  ): boolean {
346
363
  for (const key of new Set([
347
- ...Object.keys(currentDocSchema.runtime),
348
- ...Object.keys(desiredDocSchema.runtime),
364
+ ...Object.keys(persistedSchema.runtime),
365
+ ...Object.keys(providedSchema.runtime),
349
366
  ])) {
350
367
  // If schemas differ only by type of behavior, then we should not send schema change ops!
351
368
  if (
352
369
  key !== "explicitSchemaControl" &&
353
- currentDocSchema.runtime[key] !== desiredDocSchema.runtime[key]
370
+ persistedSchema.runtime[key] !== providedSchema.runtime[key]
354
371
  ) {
355
372
  return false;
356
373
  }
@@ -436,10 +453,15 @@ function arrayToProp(arr: string[]): string[] | undefined {
436
453
  */
437
454
  export class DocumentsSchemaController {
438
455
  private explicitSchemaControl: boolean;
439
- private sendOp = true;
456
+
457
+ /**
458
+ * Have we generated a DocumentSchemaChange op and we're waiting for the ack?
459
+ * This is used to ensure that we do not generate multiple schema change ops - this client should only ever send one (if any).
460
+ */
461
+ private opPending = false;
440
462
 
441
463
  // schema coming from document metadata (snapshot we loaded from)
442
- private documentSchema: IDocumentSchemaCurrent;
464
+ private documentSchema: IDocumentSchema;
443
465
 
444
466
  // desired schema, based on feature gates / runtime options.
445
467
  // This includes requests to enable to disable functionality
@@ -491,9 +513,9 @@ export class DocumentsSchemaController {
491
513
 
492
514
  // Schema coming from document metadata (snapshot we loaded from), or if no document exists
493
515
  // (this is a new document) then this is the same as desiredSchema (same as session schema in such case).
494
- // Latter is importnat sure that's what will go into summary.
516
+ // Latter is important sure that's what will go into summary.
495
517
  this.documentSchema = existing
496
- ? ((documentMetadataSchema as IDocumentSchemaCurrent) ??
518
+ ? (documentMetadataSchema ??
497
519
  ({
498
520
  version: currentDocumentVersionSchema,
499
521
  // see comment in summarizeDocumentSchema() on why it has to stay zero
@@ -542,14 +564,15 @@ export class DocumentsSchemaController {
542
564
  checkRuntimeCompatibility(this.futureSchema, "future");
543
565
  }
544
566
 
545
- public summarizeDocumentSchema(refSeq: number): IDocumentSchemaCurrent | undefined {
567
+ public summarizeDocumentSchema(
568
+ refSeq: number,
569
+ ): IDocumentSchema | IDocumentSchemaCurrent | undefined {
546
570
  // For legacy behavior, we could write nothing (return undefined).
547
571
  // It does not buy us anything, as whatever written in summary does not actually impact clients operating in legacy mode.
548
572
  // But writing current used config (and assuming most of the clients settle on same config over time) will help with transition
549
573
  // out of legacy mode, as clients transitioning out of it would be able to use all the
550
574
  // features that are mentioned in schema right away, without a need to go through schema transition (and thus for a session or
551
575
  // two losing ability to use all the features)
552
-
553
576
  const schema = this.explicitSchemaControl ? this.documentSchema : this.desiredSchema;
554
577
 
555
578
  // 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 +590,17 @@ export class DocumentsSchemaController {
567
590
  /**
568
591
  * Called by Container runtime whenever it is about to send some op.
569
592
  * 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 send op only once in a session lifetime.
593
+ * Please consider note above constructor about race conditions - current design is to generate op only once in a session lifetime.
571
594
  * @returns Optional message to send.
572
595
  */
573
- public maybeSendSchemaMessage(): IDocumentSchemaChangeMessage | undefined {
574
- if (this.sendOp && this.futureSchema !== undefined) {
575
- this.sendOp = false;
596
+ public maybeGenerateSchemaMessage(): IDocumentSchemaChangeMessageOutgoing | undefined {
597
+ if (this.futureSchema !== undefined && !this.opPending) {
598
+ this.opPending = true;
576
599
  assert(
577
600
  this.explicitSchemaControl && this.futureSchema.runtime.explicitSchemaControl === true,
578
601
  0x94e /* not legacy */,
579
602
  );
580
- return {
581
- ...this.futureSchema,
582
- refSeq: this.documentSchema.refSeq,
583
- };
603
+ return this.futureSchema;
584
604
  }
585
605
  }
586
606
 
@@ -612,7 +632,7 @@ export class DocumentsSchemaController {
612
632
  * @returns - true if schema was accepted, otherwise false (rejected due to failed CAS)
613
633
  */
614
634
  public processDocumentSchemaMessages(
615
- contents: IDocumentSchemaChangeMessage[],
635
+ contents: IDocumentSchemaChangeMessageIncoming[],
616
636
  local: boolean,
617
637
  sequenceNumber: number,
618
638
  ): boolean {
@@ -639,10 +659,12 @@ export class DocumentsSchemaController {
639
659
 
640
660
  // Changes are in effect. Immediately check that this client understands these changes
641
661
  checkRuntimeCompatibility(content, "change");
642
-
643
- const schema: IDocumentSchema = { ...content, refSeq: sequenceNumber };
644
- this.documentSchema = schema as IDocumentSchemaCurrent;
645
- this.sessionSchema = and(this.documentSchema, this.desiredSchema);
662
+ const schema = {
663
+ ...content,
664
+ refSeq: sequenceNumber,
665
+ } satisfies IDocumentSchemaCurrent;
666
+ this.documentSchema = schema;
667
+ this.sessionSchema = and(schema, this.desiredSchema);
646
668
  assert(this.sessionSchema.refSeq === sequenceNumber, 0x97d /* seq# */);
647
669
 
648
670
  // legacy behavior is automatically off for the document once someone sends a schema op -
@@ -663,8 +685,11 @@ export class DocumentsSchemaController {
663
685
  return true;
664
686
  }
665
687
 
666
- public onDisconnect(): void {
667
- this.sendOp = true;
688
+ /**
689
+ * Indicates the pending op was not ack'd and we may try to send it again if needed.
690
+ */
691
+ public pendingOpNotAcked(): void {
692
+ this.opPending = false;
668
693
  }
669
694
  }
670
695
 
@@ -106,7 +106,8 @@ export {
106
106
  currentDocumentVersionSchema,
107
107
  DocumentSchemaValueType,
108
108
  DocumentsSchemaController,
109
- IDocumentSchemaChangeMessage,
109
+ IDocumentSchemaChangeMessageIncoming,
110
+ IDocumentSchemaChangeMessageOutgoing,
110
111
  IDocumentSchemaFeatures,
111
112
  } from "./documentSchema.js";
112
113
  export {