@fluidframework/container-runtime 2.32.0 → 2.33.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 (189) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/api-report/container-runtime.legacy.alpha.api.md +71 -67
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/blobManager/blobManager.d.ts +7 -4
  5. package/dist/blobManager/blobManager.d.ts.map +1 -1
  6. package/dist/blobManager/blobManager.js +38 -12
  7. package/dist/blobManager/blobManager.js.map +1 -1
  8. package/dist/channelCollection.d.ts +4 -0
  9. package/dist/channelCollection.d.ts.map +1 -1
  10. package/dist/channelCollection.js +24 -0
  11. package/dist/channelCollection.js.map +1 -1
  12. package/dist/compatUtils.d.ts +74 -0
  13. package/dist/compatUtils.d.ts.map +1 -0
  14. package/dist/compatUtils.js +151 -0
  15. package/dist/compatUtils.js.map +1 -0
  16. package/dist/compressionDefinitions.d.ts +39 -0
  17. package/dist/compressionDefinitions.d.ts.map +1 -0
  18. package/dist/compressionDefinitions.js +30 -0
  19. package/dist/compressionDefinitions.js.map +1 -0
  20. package/dist/containerRuntime.d.ts +78 -52
  21. package/dist/containerRuntime.d.ts.map +1 -1
  22. package/dist/containerRuntime.js +141 -54
  23. package/dist/containerRuntime.js.map +1 -1
  24. package/dist/dataStoreContext.d.ts +3 -0
  25. package/dist/dataStoreContext.d.ts.map +1 -1
  26. package/dist/dataStoreContext.js +122 -66
  27. package/dist/dataStoreContext.js.map +1 -1
  28. package/dist/deltaManagerProxies.d.ts +55 -12
  29. package/dist/deltaManagerProxies.d.ts.map +1 -1
  30. package/dist/deltaManagerProxies.js +63 -55
  31. package/dist/deltaManagerProxies.js.map +1 -1
  32. package/dist/gc/gcDefinitions.d.ts +2 -0
  33. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  34. package/dist/gc/gcDefinitions.js.map +1 -1
  35. package/dist/index.d.ts +4 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +3 -2
  38. package/dist/index.js.map +1 -1
  39. package/dist/legacy.d.ts +1 -0
  40. package/dist/opLifecycle/batchManager.d.ts +3 -15
  41. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  42. package/dist/opLifecycle/batchManager.js +5 -39
  43. package/dist/opLifecycle/batchManager.js.map +1 -1
  44. package/dist/opLifecycle/definitions.d.ts +44 -11
  45. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  46. package/dist/opLifecycle/definitions.js.map +1 -1
  47. package/dist/opLifecycle/index.d.ts +3 -3
  48. package/dist/opLifecycle/index.d.ts.map +1 -1
  49. package/dist/opLifecycle/index.js +3 -2
  50. package/dist/opLifecycle/index.js.map +1 -1
  51. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  52. package/dist/opLifecycle/opCompressor.js +4 -4
  53. package/dist/opLifecycle/opCompressor.js.map +1 -1
  54. package/dist/opLifecycle/opDecompressor.js +3 -3
  55. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  56. package/dist/opLifecycle/opGroupingManager.d.ts +2 -2
  57. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  58. package/dist/opLifecycle/opGroupingManager.js +1 -2
  59. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  60. package/dist/opLifecycle/opSerialization.d.ts +3 -1
  61. package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
  62. package/dist/opLifecycle/opSerialization.js +4 -2
  63. package/dist/opLifecycle/opSerialization.js.map +1 -1
  64. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  65. package/dist/opLifecycle/opSplitter.js +2 -2
  66. package/dist/opLifecycle/opSplitter.js.map +1 -1
  67. package/dist/opLifecycle/outbox.d.ts +25 -3
  68. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  69. package/dist/opLifecycle/outbox.js +112 -61
  70. package/dist/opLifecycle/outbox.js.map +1 -1
  71. package/dist/packageVersion.d.ts +1 -1
  72. package/dist/packageVersion.js +1 -1
  73. package/dist/packageVersion.js.map +1 -1
  74. package/dist/pendingStateManager.d.ts +36 -7
  75. package/dist/pendingStateManager.d.ts.map +1 -1
  76. package/dist/pendingStateManager.js +83 -16
  77. package/dist/pendingStateManager.js.map +1 -1
  78. package/dist/runtimeLayerCompatState.d.ts.map +1 -1
  79. package/dist/runtimeLayerCompatState.js +1 -1
  80. package/dist/runtimeLayerCompatState.js.map +1 -1
  81. package/dist/summary/documentSchema.d.ts +1 -0
  82. package/dist/summary/documentSchema.d.ts.map +1 -1
  83. package/dist/summary/documentSchema.js +2 -0
  84. package/dist/summary/documentSchema.js.map +1 -1
  85. package/lib/blobManager/blobManager.d.ts +7 -4
  86. package/lib/blobManager/blobManager.d.ts.map +1 -1
  87. package/lib/blobManager/blobManager.js +38 -12
  88. package/lib/blobManager/blobManager.js.map +1 -1
  89. package/lib/channelCollection.d.ts +4 -0
  90. package/lib/channelCollection.d.ts.map +1 -1
  91. package/lib/channelCollection.js +24 -0
  92. package/lib/channelCollection.js.map +1 -1
  93. package/lib/compatUtils.d.ts +74 -0
  94. package/lib/compatUtils.d.ts.map +1 -0
  95. package/lib/compatUtils.js +142 -0
  96. package/lib/compatUtils.js.map +1 -0
  97. package/lib/compressionDefinitions.d.ts +39 -0
  98. package/lib/compressionDefinitions.d.ts.map +1 -0
  99. package/lib/compressionDefinitions.js +27 -0
  100. package/lib/compressionDefinitions.js.map +1 -0
  101. package/lib/containerRuntime.d.ts +78 -52
  102. package/lib/containerRuntime.d.ts.map +1 -1
  103. package/lib/containerRuntime.js +143 -56
  104. package/lib/containerRuntime.js.map +1 -1
  105. package/lib/dataStoreContext.d.ts +3 -0
  106. package/lib/dataStoreContext.d.ts.map +1 -1
  107. package/lib/dataStoreContext.js +57 -1
  108. package/lib/dataStoreContext.js.map +1 -1
  109. package/lib/deltaManagerProxies.d.ts +55 -12
  110. package/lib/deltaManagerProxies.d.ts.map +1 -1
  111. package/lib/deltaManagerProxies.js +63 -55
  112. package/lib/deltaManagerProxies.js.map +1 -1
  113. package/lib/gc/gcDefinitions.d.ts +2 -0
  114. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  115. package/lib/gc/gcDefinitions.js.map +1 -1
  116. package/lib/index.d.ts +4 -2
  117. package/lib/index.d.ts.map +1 -1
  118. package/lib/index.js +2 -1
  119. package/lib/index.js.map +1 -1
  120. package/lib/legacy.d.ts +1 -0
  121. package/lib/opLifecycle/batchManager.d.ts +3 -15
  122. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  123. package/lib/opLifecycle/batchManager.js +4 -37
  124. package/lib/opLifecycle/batchManager.js.map +1 -1
  125. package/lib/opLifecycle/definitions.d.ts +44 -11
  126. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  127. package/lib/opLifecycle/definitions.js.map +1 -1
  128. package/lib/opLifecycle/index.d.ts +3 -3
  129. package/lib/opLifecycle/index.d.ts.map +1 -1
  130. package/lib/opLifecycle/index.js +2 -2
  131. package/lib/opLifecycle/index.js.map +1 -1
  132. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  133. package/lib/opLifecycle/opCompressor.js +2 -2
  134. package/lib/opLifecycle/opCompressor.js.map +1 -1
  135. package/lib/opLifecycle/opDecompressor.js +1 -1
  136. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  137. package/lib/opLifecycle/opGroupingManager.d.ts +2 -2
  138. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  139. package/lib/opLifecycle/opGroupingManager.js +1 -2
  140. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  141. package/lib/opLifecycle/opSerialization.d.ts +3 -1
  142. package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
  143. package/lib/opLifecycle/opSerialization.js +4 -2
  144. package/lib/opLifecycle/opSerialization.js.map +1 -1
  145. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  146. package/lib/opLifecycle/opSplitter.js +1 -1
  147. package/lib/opLifecycle/opSplitter.js.map +1 -1
  148. package/lib/opLifecycle/outbox.d.ts +25 -3
  149. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  150. package/lib/opLifecycle/outbox.js +110 -61
  151. package/lib/opLifecycle/outbox.js.map +1 -1
  152. package/lib/packageVersion.d.ts +1 -1
  153. package/lib/packageVersion.js +1 -1
  154. package/lib/packageVersion.js.map +1 -1
  155. package/lib/pendingStateManager.d.ts +36 -7
  156. package/lib/pendingStateManager.d.ts.map +1 -1
  157. package/lib/pendingStateManager.js +84 -17
  158. package/lib/pendingStateManager.js.map +1 -1
  159. package/lib/runtimeLayerCompatState.d.ts.map +1 -1
  160. package/lib/runtimeLayerCompatState.js +2 -2
  161. package/lib/runtimeLayerCompatState.js.map +1 -1
  162. package/lib/summary/documentSchema.d.ts +1 -0
  163. package/lib/summary/documentSchema.d.ts.map +1 -1
  164. package/lib/summary/documentSchema.js +2 -0
  165. package/lib/summary/documentSchema.js.map +1 -1
  166. package/lib/tsdoc-metadata.json +1 -1
  167. package/package.json +21 -20
  168. package/src/blobManager/blobManager.ts +48 -15
  169. package/src/channelCollection.ts +27 -0
  170. package/src/compatUtils.ts +211 -0
  171. package/src/compressionDefinitions.ts +47 -0
  172. package/src/containerRuntime.ts +259 -108
  173. package/src/dataStoreContext.ts +82 -2
  174. package/src/deltaManagerProxies.ts +132 -70
  175. package/src/gc/gcDefinitions.ts +2 -0
  176. package/src/index.ts +5 -3
  177. package/src/opLifecycle/batchManager.ts +7 -52
  178. package/src/opLifecycle/definitions.ts +45 -11
  179. package/src/opLifecycle/index.ts +7 -2
  180. package/src/opLifecycle/opCompressor.ts +2 -2
  181. package/src/opLifecycle/opDecompressor.ts +1 -1
  182. package/src/opLifecycle/opGroupingManager.ts +7 -5
  183. package/src/opLifecycle/opSerialization.ts +6 -2
  184. package/src/opLifecycle/opSplitter.ts +1 -1
  185. package/src/opLifecycle/outbox.ts +154 -85
  186. package/src/packageVersion.ts +1 -1
  187. package/src/pendingStateManager.ts +135 -21
  188. package/src/runtimeLayerCompatState.ts +5 -2
  189. package/src/summary/documentSchema.ts +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-runtime",
3
- "version": "2.32.0",
3
+ "version": "2.33.0",
4
4
  "description": "Fluid container runtime",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -119,41 +119,43 @@
119
119
  "temp-directory": "nyc/.nyc_output"
120
120
  },
121
121
  "dependencies": {
122
- "@fluid-internal/client-utils": "~2.32.0",
123
- "@fluidframework/container-definitions": "~2.32.0",
124
- "@fluidframework/container-runtime-definitions": "~2.32.0",
125
- "@fluidframework/core-interfaces": "~2.32.0",
126
- "@fluidframework/core-utils": "~2.32.0",
127
- "@fluidframework/datastore": "~2.32.0",
128
- "@fluidframework/driver-definitions": "~2.32.0",
129
- "@fluidframework/driver-utils": "~2.32.0",
130
- "@fluidframework/id-compressor": "~2.32.0",
131
- "@fluidframework/runtime-definitions": "~2.32.0",
132
- "@fluidframework/runtime-utils": "~2.32.0",
133
- "@fluidframework/telemetry-utils": "~2.32.0",
122
+ "@fluid-internal/client-utils": "~2.33.0",
123
+ "@fluidframework/container-definitions": "~2.33.0",
124
+ "@fluidframework/container-runtime-definitions": "~2.33.0",
125
+ "@fluidframework/core-interfaces": "~2.33.0",
126
+ "@fluidframework/core-utils": "~2.33.0",
127
+ "@fluidframework/datastore": "~2.33.0",
128
+ "@fluidframework/driver-definitions": "~2.33.0",
129
+ "@fluidframework/driver-utils": "~2.33.0",
130
+ "@fluidframework/id-compressor": "~2.33.0",
131
+ "@fluidframework/runtime-definitions": "~2.33.0",
132
+ "@fluidframework/runtime-utils": "~2.33.0",
133
+ "@fluidframework/telemetry-utils": "~2.33.0",
134
134
  "@tylerbu/sorted-btree-es6": "^1.8.0",
135
135
  "double-ended-queue": "^2.1.0-0",
136
136
  "lz4js": "^0.2.0",
137
+ "semver": "^7.7.1",
137
138
  "uuid": "^9.0.0"
138
139
  },
139
140
  "devDependencies": {
140
141
  "@arethetypeswrong/cli": "^0.17.1",
141
142
  "@biomejs/biome": "~1.9.3",
142
- "@fluid-internal/mocha-test-setup": "~2.32.0",
143
- "@fluid-private/stochastic-test-utils": "~2.32.0",
144
- "@fluid-private/test-pairwise-generator": "~2.32.0",
143
+ "@fluid-internal/mocha-test-setup": "~2.33.0",
144
+ "@fluid-private/stochastic-test-utils": "~2.33.0",
145
+ "@fluid-private/test-pairwise-generator": "~2.33.0",
145
146
  "@fluid-tools/benchmark": "^0.50.0",
146
147
  "@fluid-tools/build-cli": "^0.55.0",
147
148
  "@fluidframework/build-common": "^2.0.3",
148
149
  "@fluidframework/build-tools": "^0.55.0",
149
- "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.31.0",
150
+ "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.32.0",
150
151
  "@fluidframework/eslint-config-fluid": "^5.7.3",
151
- "@fluidframework/test-runtime-utils": "~2.32.0",
152
- "@microsoft/api-extractor": "7.50.1",
152
+ "@fluidframework/test-runtime-utils": "~2.33.0",
153
+ "@microsoft/api-extractor": "7.52.5",
153
154
  "@types/double-ended-queue": "^2.1.0",
154
155
  "@types/lz4js": "^0.2.0",
155
156
  "@types/mocha": "^10.0.10",
156
157
  "@types/node": "^18.19.0",
158
+ "@types/semver": "^7.7.0",
157
159
  "@types/sinon": "^17.0.3",
158
160
  "@types/uuid": "^9.0.2",
159
161
  "c8": "^8.0.1",
@@ -163,7 +165,6 @@
163
165
  "eslint": "~8.55.0",
164
166
  "mocha": "^10.8.2",
165
167
  "mocha-multi-reporters": "^1.5.1",
166
- "moment": "^2.21.0",
167
168
  "rimraf": "^4.4.0",
168
169
  "sinon": "^18.0.1",
169
170
  "typescript": "~5.4.5"
@@ -18,6 +18,7 @@ import type {
18
18
  IEventProvider,
19
19
  IFluidHandleContext,
20
20
  IFluidHandleInternal,
21
+ IFluidHandleInternalPayloadPending,
21
22
  } from "@fluidframework/core-interfaces/internal";
22
23
  import { assert, Deferred } from "@fluidframework/core-utils/internal";
23
24
  import {
@@ -62,7 +63,10 @@ import {
62
63
  * DataObject.request() recognizes requests in the form of `/blobs/<id>`
63
64
  * and loads blob.
64
65
  */
65
- export class BlobHandle extends FluidHandleBase<ArrayBufferLike> {
66
+ export class BlobHandle
67
+ extends FluidHandleBase<ArrayBufferLike>
68
+ implements IFluidHandleInternalPayloadPending<ArrayBufferLike>
69
+ {
66
70
  private attached: boolean = false;
67
71
 
68
72
  public get isAttached(): boolean {
@@ -75,6 +79,7 @@ export class BlobHandle extends FluidHandleBase<ArrayBufferLike> {
75
79
  public readonly path: string,
76
80
  public readonly routeContext: IFluidHandleContext,
77
81
  public get: () => Promise<ArrayBufferLike>,
82
+ public readonly payloadPending: boolean,
78
83
  private readonly onAttachGraph?: () => void,
79
84
  ) {
80
85
  super();
@@ -133,7 +138,8 @@ export interface IBlobManagerEvents extends IEvent {
133
138
  }
134
139
 
135
140
  interface IBlobManagerInternalEvents extends IEvent {
136
- (event: "blobAttached", listener: (pending: PendingBlob) => void);
141
+ (event: "handleAttached", listener: (pending: PendingBlob) => void);
142
+ (event: "processedBlobAttach", listener: (localId: string, storageId: string) => void);
137
143
  }
138
144
 
139
145
  const stashedPendingBlobOverrides: Pick<
@@ -176,7 +182,7 @@ export class BlobManager {
176
182
  * we can resolve all pending blobs with the same storage ID even though they may have different local IDs. That's
177
183
  * because we know that the server will not delete the blob corresponding to that storage ID.
178
184
  */
179
- private readonly opsInFlight: Map<string, string[]> = new Map();
185
+ private readonly opsInFlight: Map<string, Set<string>> = new Map();
180
186
 
181
187
  private readonly sendBlobAttachOp: (localId: string, storageId?: string) => void;
182
188
  private stopAttaching: boolean = false;
@@ -195,7 +201,7 @@ export class BlobManager {
195
201
  new Map();
196
202
  public readonly stashedBlobsUploadP: Promise<(void | ICreateBlobResponse)[]>;
197
203
 
198
- constructor(props: {
204
+ public constructor(props: {
199
205
  readonly routeContext: IFluidHandleContext;
200
206
 
201
207
  blobManagerLoadInfo: IBlobManagerLoadInfo;
@@ -220,6 +226,7 @@ export class BlobManager {
220
226
  readonly runtime: IBlobManagerRuntime;
221
227
  stashedBlobs: IPendingBlobs | undefined;
222
228
  readonly localBlobIdGenerator?: (() => string) | undefined;
229
+ readonly createBlobPayloadPending: boolean;
223
230
  }) {
224
231
  const {
225
232
  routeContext,
@@ -351,7 +358,11 @@ export class BlobManager {
351
358
  return [...this.pendingBlobs.values()].some((e) => e.stashedUpload === true);
352
359
  }
353
360
 
354
- public async getBlob(blobId: string): Promise<ArrayBufferLike> {
361
+ public hasBlob(blobId: string): boolean {
362
+ return this.redirectTable.get(blobId) !== undefined;
363
+ }
364
+
365
+ public async getBlob(blobId: string, payloadPending: boolean): Promise<ArrayBufferLike> {
355
366
  // Verify that the blob is not deleted, i.e., it has not been garbage collected. If it is, this will throw
356
367
  // an error, failing the call.
357
368
  this.verifyBlobNotDeleted(blobId);
@@ -374,8 +385,24 @@ export class BlobManager {
374
385
  storageId = blobId;
375
386
  } else {
376
387
  const attachedStorageId = this.redirectTable.get(blobId);
377
- assert(!!attachedStorageId, 0x11f /* "requesting unknown blobs" */);
378
- storageId = attachedStorageId;
388
+ if (!payloadPending) {
389
+ assert(!!attachedStorageId, 0x11f /* "requesting unknown blobs" */);
390
+ }
391
+ // If we didn't find it in the redirectTable, assume the attach op is coming eventually and wait.
392
+ // We do this even if the local client doesn't have the blob payloadPending flag enabled, in case a
393
+ // remote client does have it enabled. This wait may be infinite if the uploading client failed
394
+ // the upload and doesn't exist anymore.
395
+ storageId =
396
+ attachedStorageId ??
397
+ (await new Promise<string>((resolve) => {
398
+ const onProcessBlobAttach = (localId: string, _storageId: string): void => {
399
+ if (localId === blobId) {
400
+ this.internalEvents.off("processedBlobAttach", onProcessBlobAttach);
401
+ resolve(_storageId);
402
+ }
403
+ };
404
+ this.internalEvents.on("processedBlobAttach", onProcessBlobAttach);
405
+ }));
379
406
  }
380
407
 
381
408
  return PerformanceEvent.timedExecAsync(
@@ -406,14 +433,15 @@ export class BlobManager {
406
433
  ? () => {
407
434
  pending.attached = true;
408
435
  // Notify listeners (e.g. serialization process) that blob has been attached
409
- this.internalEvents.emit("blobAttached", pending);
436
+ this.internalEvents.emit("handleAttached", pending);
410
437
  this.deletePendingBlobMaybe(localId);
411
438
  }
412
439
  : undefined;
413
440
  return new BlobHandle(
414
441
  getGCNodePathFromBlobId(localId),
415
442
  this.routeContext,
416
- async () => this.getBlob(localId),
443
+ async () => this.getBlob(localId, false),
444
+ false, // payloadPending
417
445
  callback,
418
446
  );
419
447
  }
@@ -597,10 +625,14 @@ export class BlobManager {
597
625
  // If there is already an op for this storage ID, append the local ID to the list. Once any op for
598
626
  // this storage ID is ack'd, all pending blobs for it can be resolved since the op will keep the
599
627
  // blob alive in storage.
600
- this.opsInFlight.set(response.id, [
601
- ...(this.opsInFlight.get(response.id) ?? []),
602
- localId,
603
- ]);
628
+ let setForRemoteId = this.opsInFlight.get(response.id);
629
+ if (setForRemoteId === undefined) {
630
+ setForRemoteId = new Set();
631
+ this.opsInFlight.set(response.id, setForRemoteId);
632
+ }
633
+ // seeing the same localId twice can happen if a blob is being reuploaded and stashed.
634
+ // TODO: review stashing logic and see if we can avoid this, as well in tests.
635
+ setForRemoteId.add(localId);
604
636
  }
605
637
  return response;
606
638
  }
@@ -677,6 +709,7 @@ export class BlobManager {
677
709
  this.deletePendingBlobMaybe(localId);
678
710
  }
679
711
  }
712
+ this.internalEvents.emit("processedBlobAttach", localId, blobId);
680
713
  }
681
714
 
682
715
  public summarize(telemetryContext?: ITelemetryContext): ISummaryTreeWithStats {
@@ -870,14 +903,14 @@ export class BlobManager {
870
903
  );
871
904
  const onBlobAttached = (attachedEntry: PendingBlob): void => {
872
905
  if (attachedEntry === entry) {
873
- this.internalEvents.off("blobAttached", onBlobAttached);
906
+ this.internalEvents.off("handleAttached", onBlobAttached);
874
907
  resolve();
875
908
  }
876
909
  };
877
910
  if (entry.attached) {
878
911
  resolve();
879
912
  } else {
880
- this.internalEvents.on("blobAttached", onBlobAttached);
913
+ this.internalEvents.on("handleAttached", onBlobAttached);
881
914
  }
882
915
  }),
883
916
  );
@@ -124,6 +124,7 @@ interface FluidDataStoreMessage {
124
124
  * Creates a shallow wrapper of {@link IFluidParentContext}. The wrapper can then have its methods overwritten as needed
125
125
  */
126
126
  export function wrapContext(context: IFluidParentContext): IFluidParentContext {
127
+ const isReadOnly = context.isReadOnly;
127
128
  return {
128
129
  get IFluidDataStoreRegistry() {
129
130
  return context.IFluidDataStoreRegistry;
@@ -149,6 +150,7 @@ export function wrapContext(context: IFluidParentContext): IFluidParentContext {
149
150
  get attachState() {
150
151
  return context.attachState;
151
152
  },
153
+ isReadOnly: isReadOnly === undefined ? undefined : () => isReadOnly(),
152
154
  containerRuntime: context.containerRuntime,
153
155
  scope: context.scope,
154
156
  gcThrowOnTombstoneUsage: context.gcThrowOnTombstoneUsage,
@@ -1116,6 +1118,31 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
1116
1118
  }
1117
1119
  }
1118
1120
 
1121
+ /**
1122
+ * Enumerates the contexts and calls notifyReadOnlyState on them.
1123
+ */
1124
+ public notifyReadOnlyState(readonly: boolean): void {
1125
+ for (const [fluidDataStoreId, context] of this.contexts) {
1126
+ try {
1127
+ context.notifyReadOnlyState(readonly);
1128
+ } catch (error) {
1129
+ this.mc.logger.sendErrorEvent(
1130
+ {
1131
+ eventName: "SetReadOnlyStateError",
1132
+ ...tagCodeArtifacts({
1133
+ fluidDataStoreId,
1134
+ }),
1135
+ details: {
1136
+ runtimeReadonly: this.parentContext.isReadOnly?.(),
1137
+ readonly,
1138
+ },
1139
+ },
1140
+ error,
1141
+ );
1142
+ }
1143
+ }
1144
+ }
1145
+
1119
1146
  public setAttachState(attachState: AttachState.Attaching | AttachState.Attached): void {
1120
1147
  for (const [, context] of this.contexts) {
1121
1148
  // Fire only for bounded stores.
@@ -0,0 +1,211 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { FlushMode } from "@fluidframework/runtime-definitions/internal";
7
+ // The semver package documents and encourages these imports for users that only need some of the semver functionality.
8
+ // eslint-disable-next-line import/no-internal-modules
9
+ import semverGte from "semver/functions/gte.js";
10
+ // eslint-disable-next-line import/no-internal-modules
11
+ import semverLte from "semver/functions/lte.js";
12
+ // eslint-disable-next-line import/no-internal-modules
13
+ import semverValid from "semver/functions/valid.js";
14
+
15
+ import {
16
+ disabledCompressionConfig,
17
+ enabledCompressionConfig,
18
+ } from "./compressionDefinitions.js";
19
+ import type { ContainerRuntimeOptionsInternal } from "./containerRuntime.js";
20
+ import { pkgVersion } from "./packageVersion.js";
21
+
22
+ /**
23
+ * Our policy is to support N/N-1 compatibility by default, where N is the most
24
+ * recent public major release of the runtime.
25
+ * Therefore, if the customer does not provide a compatibility mode, we will
26
+ * default to use N-1.
27
+ *
28
+ * However, this is not consistent with today's behavior. Some options (i.e.
29
+ * batching, compression) are enabled by default despite not being compatible
30
+ * with 1.x clients. Since the policy was introduced during 2.x's lifespan,
31
+ * N/N-1 compatibility by **default** will be in effect starting with 3.0.
32
+ * Importantly though, N/N-2 compatibility is still guaranteed with the proper
33
+ * configurations set.
34
+ *
35
+ * Further to distinguish unspecified `compatibilityVersion` from a specified
36
+ * version and allow `enableExplicitSchemaControl` to default to `true` for
37
+ * any 2.0.0+ version, we will use a special value of `2.0.0-defaults`, which
38
+ * is semantically less than 2.0.0.
39
+ */
40
+ export const defaultCompatibilityVersion = "2.0.0-defaults" as const;
41
+
42
+ /**
43
+ * String in a valid semver format specifying bottom of a minor version
44
+ * or special "defaults" prerelease of a major.
45
+ * @remarks Only 2.0.0-defaults is expected, but index signatures cannot be a
46
+ * literal; so, just allow any major -defaults prerelease.
47
+ */
48
+ export type MinimumMinorSemanticVersion = `${bigint}.${bigint}.0` | `${bigint}.0.0-defaults`;
49
+
50
+ /**
51
+ * String in a valid semver format of a specific version at least specifying minor.
52
+ */
53
+ export type SemanticVersion =
54
+ | `${bigint}.${bigint}.${bigint}`
55
+ | `${bigint}.${bigint}.${bigint}-${string}`;
56
+
57
+ /**
58
+ * Generic type for runtimeOptionsAffectingDocSchemaConfigMap
59
+ */
60
+ export type ConfigMap<T extends Record<string, unknown>> = {
61
+ [K in keyof T]-?: {
62
+ [version: MinimumMinorSemanticVersion]: T[K];
63
+ };
64
+ };
65
+
66
+ /**
67
+ * Subset of the {@link ContainerRuntimeOptionsInternal} properties which
68
+ * affect {@link IDocumentSchemaFeatures}.
69
+ *
70
+ * @remarks
71
+ * When a new option is added to {@link ContainerRuntimeOptionsInternal}, we
72
+ * must consider if it changes the DocumentSchema. If so, then a corresponding
73
+ * entry must be added to {@link runtimeOptionsAffectingDocSchemaConfigMap}
74
+ * below. If not, then it must be omitted from this type.
75
+ *
76
+ * Note: `Omit` is used instead of `Pick` to ensure that all new options are
77
+ * included in this type by default. If any new properties are added to
78
+ * {@link ContainerRuntimeOptionsInternal}, they will be included in this
79
+ * type unless explicitly omitted. This will prevent us from forgetting to
80
+ * account for any new properties in the future.
81
+ */
82
+ export type RuntimeOptionsAffectingDocSchema = Omit<
83
+ ContainerRuntimeOptionsInternal,
84
+ | "chunkSizeInBytes"
85
+ | "maxBatchSizeInBytes"
86
+ | "loadSequenceNumberVerification"
87
+ | "summaryOptions"
88
+ >;
89
+
90
+ /**
91
+ * Mapping of RuntimeOptionsAffectingDocSchema to their compatibility related configs.
92
+ *
93
+ * Each key in this map corresponds to a property in RuntimeOptionsAffectingDocSchema. The value is an object that maps SemanticVersions
94
+ * to the appropriate default value for that property to supporting that SemanticVersion. If clients running SemanticVersion X are able to understand
95
+ * the format changes introduced by the property, then the default value for that SemanticVersion will enable the feature associated with the property.
96
+ * Otherwise, the feature will be disabled.
97
+ *
98
+ * For example if the compatibilityVersion is a 1.x version (i.e. "1.5.0"), then the default value for `enableGroupedBatching` will be false since 1.x
99
+ * clients do not understand the document format when batching is enabled. If the compatibilityVersion is a 2.x client (i.e. "2.0.0" or later), then the
100
+ * default value for `enableGroupedBatching` will be true because clients running 2.0 or later will be able to understand the format changes associated
101
+ * with the batching feature.
102
+ */
103
+ const runtimeOptionsAffectingDocSchemaConfigMap = {
104
+ enableGroupedBatching: {
105
+ "1.0.0": false,
106
+ "2.0.0-defaults": true,
107
+ } as const,
108
+ compressionOptions: {
109
+ "1.0.0": disabledCompressionConfig,
110
+ "2.0.0-defaults": enabledCompressionConfig,
111
+ } as const,
112
+ enableRuntimeIdCompressor: {
113
+ // For IdCompressorMode, `undefined` represents a logical state (off).
114
+ // However, to satisfy the Required<> constraint while
115
+ // `exactOptionalPropertyTypes` is `false` (TODO: AB#8215), we need
116
+ // to have it defined, so we trick the type checker here.
117
+ "1.0.0": undefined,
118
+ // We do not yet want to enable idCompressor by default since it will
119
+ // increase bundle sizes, and not all customers will benefit from it.
120
+ // Therefore, we will require customers to explicitly enable it. We
121
+ // are keeping it as a DocSchema affecting option for now as this may
122
+ // change in the future.
123
+ } as const,
124
+ explicitSchemaControl: {
125
+ "1.0.0": false,
126
+ // This option's intention is to prevent 1.x clients from joining sessions
127
+ // when enabled. This is set to true when the compatibility version is set
128
+ // to >=2.0.0 (explicitly). This is different than other 2.0 defaults
129
+ // because it was not enabled by default prior to the implementation of
130
+ // `compatibilityVersion`.
131
+ // `defaultCompatibilityVersion` is set to "2.0.0-defaults" which "2.0.0"
132
+ // does not satisfy to avoiding enabling this option by default as of
133
+ // `compatibilityVersion` introduction, which could be unexpected.
134
+ // Only enable as a default when `compatibilityVersion` is specified at
135
+ // 2.0.0+.
136
+ "2.0.0": true,
137
+ } as const,
138
+ flushMode: {
139
+ // Note: 1.x clients are compatible with TurnBased flushing, but here we elect to remain on Immediate flush mode
140
+ // as a work-around for inability to send batches larger than 1Mb. Immediate flushing keeps batches smaller as
141
+ // fewer messages will be included per flush.
142
+ "1.0.0": FlushMode.Immediate,
143
+ "2.0.0-defaults": FlushMode.TurnBased,
144
+ } as const,
145
+ gcOptions: {
146
+ "1.0.0": {},
147
+ // Although sweep is supported in 2.x, it is disabled by default until compatibilityVersion>=3.0.0 to be extra safe.
148
+ "3.0.0": { enableGCSweep: true },
149
+ } as const,
150
+ createBlobPayloadPending: {
151
+ // This feature is new and disabled by default. In the future we will enable it by default, but we have not
152
+ // closed on the version where that will happen yet. Probably a .10 release since blob functionality is not
153
+ // exposed on the `@public` API surface.
154
+ "1.0.0": undefined,
155
+ } as const,
156
+ } as const satisfies ConfigMap<RuntimeOptionsAffectingDocSchema>;
157
+
158
+ /**
159
+ * Returns the default RuntimeOptionsAffectingDocSchema configuration for a given compatibility version.
160
+ */
161
+ export function getCompatibilityVersionDefaults(
162
+ compatibilityVersion: SemanticVersion,
163
+ ): RuntimeOptionsAffectingDocSchema {
164
+ return getConfigsForCompatMode(
165
+ compatibilityVersion,
166
+ runtimeOptionsAffectingDocSchemaConfigMap,
167
+ // This is a bad cast away from Partial that getConfigsForCompatMode provides.
168
+ // ConfigMap should be restructured to provide RuntimeOptionsAffectingDocSchema guarantee.
169
+ ) as RuntimeOptionsAffectingDocSchema;
170
+ }
171
+
172
+ /**
173
+ * Returns a default configuration given compatibility version and configuration version map.
174
+ */
175
+ export function getConfigsForCompatMode<T extends Record<SemanticVersion, unknown>>(
176
+ compatibilityVersion: SemanticVersion,
177
+ configMap: ConfigMap<T>,
178
+ ): Partial<T> {
179
+ const defaultConfigs: Partial<T> = {};
180
+ // Iterate over configMap to get default values for each option.
181
+ for (const key of Object.keys(configMap)) {
182
+ const config = configMap[key as keyof T];
183
+ // Sort the versions in ascending order so we can short circuit the loop.
184
+ const versions = Object.keys(config).sort((a, b) => (semverGte(b, a) ? -1 : 1));
185
+ // For each config, we iterate over the keys and check if compatibilityVersion is greater than or equal to the version.
186
+ // If so, we set it as the default value for the option. At the end of the loop we should have the most recent default
187
+ // value that is compatible with the version specified as the compatibilityVersion.
188
+ for (const version of versions) {
189
+ if (semverGte(compatibilityVersion, version)) {
190
+ defaultConfigs[key] = config[version as MinimumMinorSemanticVersion];
191
+ } else {
192
+ // If the compatibility mode is less than the version, we break out of the loop since we don't need to check
193
+ // any later versions.
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ return defaultConfigs;
199
+ }
200
+
201
+ /**
202
+ * Checks if the compatibility version is valid.
203
+ * A valid compatibility version is a string that is a valid semver version and is less than or equal to the current package version.
204
+ */
205
+ export function isValidCompatVersion(compatibilityVersion: SemanticVersion): boolean {
206
+ return (
207
+ compatibilityVersion !== undefined &&
208
+ semverValid(compatibilityVersion) !== null &&
209
+ semverLte(compatibilityVersion, pkgVersion)
210
+ );
211
+ }
@@ -0,0 +1,47 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /**
7
+ * Available compression algorithms for op compression.
8
+ * @legacy
9
+ * @alpha
10
+ */
11
+ export enum CompressionAlgorithms {
12
+ lz4 = "lz4",
13
+ }
14
+
15
+ /**
16
+ * Options for op compression.
17
+ * @legacy
18
+ * @alpha
19
+ */
20
+ export interface ICompressionRuntimeOptions {
21
+ /**
22
+ * The value the batch's content size must exceed for the batch to be compressed.
23
+ * By default the value is 600 * 1024 = 614400 bytes. If the value is set to `Infinity`, compression will be disabled.
24
+ */
25
+ readonly minimumBatchSizeInBytes: number;
26
+
27
+ /**
28
+ * The compression algorithm that will be used to compress the op.
29
+ * By default the value is `lz4` which is the only compression algorithm currently supported.
30
+ */
31
+ readonly compressionAlgorithm: CompressionAlgorithms;
32
+ }
33
+
34
+ /**
35
+ * @legacy
36
+ * @alpha
37
+ */
38
+ export const disabledCompressionConfig: ICompressionRuntimeOptions = {
39
+ minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
40
+ compressionAlgorithm: CompressionAlgorithms.lz4,
41
+ };
42
+
43
+ export const enabledCompressionConfig = {
44
+ // Batches with content size exceeding this value will be compressed
45
+ minimumBatchSizeInBytes: 614400,
46
+ compressionAlgorithm: CompressionAlgorithms.lz4,
47
+ } as const satisfies ICompressionRuntimeOptions;