@fluidframework/container-runtime 2.60.0 → 2.61.0-355516

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 (70) hide show
  1. package/.mocharc.cjs +1 -2
  2. package/api-report/container-runtime.legacy.beta.api.md +2 -1
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/blobManager/blobManager.d.ts +33 -16
  5. package/dist/blobManager/blobManager.d.ts.map +1 -1
  6. package/dist/blobManager/blobManager.js +126 -106
  7. package/dist/blobManager/blobManager.js.map +1 -1
  8. package/dist/blobManager/blobManagerSnapSum.d.ts +4 -4
  9. package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  10. package/dist/blobManager/blobManagerSnapSum.js +30 -33
  11. package/dist/blobManager/blobManagerSnapSum.js.map +1 -1
  12. package/dist/channelCollection.d.ts +6 -2
  13. package/dist/channelCollection.d.ts.map +1 -1
  14. package/dist/channelCollection.js +1 -0
  15. package/dist/channelCollection.js.map +1 -1
  16. package/dist/containerCompatibility.d.ts +18 -0
  17. package/dist/containerCompatibility.d.ts.map +1 -1
  18. package/dist/containerCompatibility.js +23 -1
  19. package/dist/containerCompatibility.js.map +1 -1
  20. package/dist/containerRuntime.d.ts +15 -3
  21. package/dist/containerRuntime.d.ts.map +1 -1
  22. package/dist/containerRuntime.js +75 -52
  23. package/dist/containerRuntime.js.map +1 -1
  24. package/dist/dataStoreContext.d.ts +5 -1
  25. package/dist/dataStoreContext.d.ts.map +1 -1
  26. package/dist/dataStoreContext.js +1 -0
  27. package/dist/dataStoreContext.js.map +1 -1
  28. package/dist/legacy.d.ts +2 -1
  29. package/dist/packageVersion.d.ts +1 -1
  30. package/dist/packageVersion.d.ts.map +1 -1
  31. package/dist/packageVersion.js +1 -1
  32. package/dist/packageVersion.js.map +1 -1
  33. package/lib/blobManager/blobManager.d.ts +33 -16
  34. package/lib/blobManager/blobManager.d.ts.map +1 -1
  35. package/lib/blobManager/blobManager.js +126 -106
  36. package/lib/blobManager/blobManager.js.map +1 -1
  37. package/lib/blobManager/blobManagerSnapSum.d.ts +4 -4
  38. package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  39. package/lib/blobManager/blobManagerSnapSum.js +26 -29
  40. package/lib/blobManager/blobManagerSnapSum.js.map +1 -1
  41. package/lib/channelCollection.d.ts +6 -2
  42. package/lib/channelCollection.d.ts.map +1 -1
  43. package/lib/channelCollection.js +1 -0
  44. package/lib/channelCollection.js.map +1 -1
  45. package/lib/containerCompatibility.d.ts +18 -0
  46. package/lib/containerCompatibility.d.ts.map +1 -1
  47. package/lib/containerCompatibility.js +22 -0
  48. package/lib/containerCompatibility.js.map +1 -1
  49. package/lib/containerRuntime.d.ts +15 -3
  50. package/lib/containerRuntime.d.ts.map +1 -1
  51. package/lib/containerRuntime.js +26 -3
  52. package/lib/containerRuntime.js.map +1 -1
  53. package/lib/dataStoreContext.d.ts +5 -1
  54. package/lib/dataStoreContext.d.ts.map +1 -1
  55. package/lib/dataStoreContext.js +1 -0
  56. package/lib/dataStoreContext.js.map +1 -1
  57. package/lib/legacy.d.ts +2 -1
  58. package/lib/packageVersion.d.ts +1 -1
  59. package/lib/packageVersion.d.ts.map +1 -1
  60. package/lib/packageVersion.js +1 -1
  61. package/lib/packageVersion.js.map +1 -1
  62. package/lib/tsdoc-metadata.json +1 -1
  63. package/package.json +27 -27
  64. package/src/blobManager/blobManager.ts +138 -123
  65. package/src/blobManager/blobManagerSnapSum.ts +31 -53
  66. package/src/channelCollection.ts +9 -1
  67. package/src/containerCompatibility.ts +56 -0
  68. package/src/containerRuntime.ts +35 -4
  69. package/src/dataStoreContext.ts +7 -0
  70. package/src/packageVersion.ts +1 -1
@@ -3,11 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import {
7
- AttachState,
8
- type IContainerContext,
9
- } from "@fluidframework/container-definitions/internal";
10
- import { assert } from "@fluidframework/core-utils/internal";
6
+ import type { IContainerContext } from "@fluidframework/container-definitions/internal";
11
7
  import { readAndParse } from "@fluidframework/driver-utils/internal";
12
8
  import type { ISummaryTreeWithStats } from "@fluidframework/runtime-definitions/internal";
13
9
  import { SummaryTreeBuilder } from "@fluidframework/runtime-utils/internal";
@@ -60,72 +56,54 @@ const loadV1 = async (
60
56
  export const toRedirectTable = (
61
57
  blobManagerLoadInfo: IBlobManagerLoadInfo,
62
58
  logger: ITelemetryLoggerExt,
63
- attachState: AttachState,
64
- ): Map<string, string | undefined> => {
59
+ ): Map<string, string> => {
65
60
  logger.sendTelemetryEvent({
66
61
  eventName: "AttachmentBlobsLoaded",
67
62
  count: blobManagerLoadInfo.ids?.length ?? 0,
68
63
  redirectTable: blobManagerLoadInfo.redirectTable?.length,
69
64
  });
70
- const redirectTable = new Map<string, string | undefined>(blobManagerLoadInfo.redirectTable);
71
- const detached = attachState !== AttachState.Attached;
72
- if (blobManagerLoadInfo.ids) {
73
- // If we are detached, we don't have storage IDs yet, so set to undefined
74
- // Otherwise, set identity (id -> id) entries.
75
- for (const entry of blobManagerLoadInfo.ids) {
76
- redirectTable.set(entry, detached ? undefined : entry);
65
+ const redirectTable = new Map<string, string>(blobManagerLoadInfo.redirectTable);
66
+ if (blobManagerLoadInfo.ids !== undefined) {
67
+ for (const storageId of blobManagerLoadInfo.ids) {
68
+ // Older versions of the runtime used the storage ID directly in the handle,
69
+ // rather than routing through the redirectTable. To support old handles that
70
+ // were created in this way but unify handling through the redirectTable, we
71
+ // add identity mappings to the redirect table at load. These identity entries
72
+ // will be excluded during summarization.
73
+ redirectTable.set(storageId, storageId);
77
74
  }
78
75
  }
79
76
  return redirectTable;
80
77
  };
81
78
 
82
79
  export const summarizeBlobManagerState = (
83
- redirectTable: Map<string, string | undefined>,
84
- attachState: AttachState,
85
- ): ISummaryTreeWithStats => summarizeV1(redirectTable, attachState);
80
+ redirectTable: Map<string, string>,
81
+ ): ISummaryTreeWithStats => summarizeV1(redirectTable);
86
82
 
87
- const summarizeV1 = (
88
- redirectTable: Map<string, string | undefined>,
89
- attachState: AttachState,
90
- ): ISummaryTreeWithStats => {
91
- const storageIds = getStorageIds(redirectTable, attachState);
92
-
93
- // if storageIds is empty, it means we are detached and have only local IDs, or that there are no blobs attached
94
- const blobIds = storageIds.size > 0 ? [...storageIds] : [...redirectTable.keys()];
83
+ const summarizeV1 = (redirectTable: Map<string, string>): ISummaryTreeWithStats => {
95
84
  const builder = new SummaryTreeBuilder();
96
- for (const blobId of blobIds) {
97
- builder.addAttachment(blobId);
85
+ const storageIds = getStorageIds(redirectTable);
86
+ for (const storageId of storageIds) {
87
+ // The Attachment is inspectable by storage, which lets it detect that the blob is referenced
88
+ // and therefore should not be GC'd.
89
+ builder.addAttachment(storageId);
98
90
  }
99
91
 
100
- // Any non-identity entries in the table need to be saved in the summary
101
- if (redirectTable.size > blobIds.length) {
102
- builder.addBlob(
103
- redirectTableBlobName,
104
- // filter out identity entries
105
- JSON.stringify(
106
- [...redirectTable.entries()].filter(([localId, storageId]) => localId !== storageId),
107
- ),
108
- );
92
+ // Exclude identity mappings from the redirectTable summary. Note that
93
+ // the storageIds of the identity mappings are still included in the Attachments
94
+ // above, so we expect these identity mappings will be recreated at load
95
+ // time in toRedirectTable even if there is no non-identity mapping in
96
+ // the redirectTable.
97
+ const nonIdentityRedirectTableEntries = [...redirectTable.entries()].filter(
98
+ ([localId, storageId]) => localId !== storageId,
99
+ );
100
+ if (nonIdentityRedirectTableEntries.length > 0) {
101
+ builder.addBlob(redirectTableBlobName, JSON.stringify(nonIdentityRedirectTableEntries));
109
102
  }
110
103
 
111
104
  return builder.getSummaryTree();
112
105
  };
113
106
 
114
- export const getStorageIds = (
115
- redirectTable: Map<string, string | undefined>,
116
- attachState: AttachState,
117
- ): Set<string> => {
118
- const ids = new Set<string | undefined>(redirectTable.values());
119
-
120
- // If we are detached, we will not have storage IDs, only undefined
121
- const undefinedValueInTable = ids.delete(undefined);
122
-
123
- // For a detached container, entries are inserted into the redirect table with an undefined storage ID.
124
- // For an attached container, entries are inserted w/storage ID after the BlobAttach op round-trips.
125
- assert(
126
- !undefinedValueInTable || (attachState === AttachState.Detached && ids.size === 0),
127
- 0x382 /* 'redirectTable' must contain only undefined while detached / defined values while attached */,
128
- );
129
-
130
- return ids as Set<string>;
107
+ export const getStorageIds = (redirectTable: Map<string, string>): Set<string> => {
108
+ return new Set<string>(redirectTable.values());
131
109
  };
@@ -46,6 +46,7 @@ import {
46
46
  type IRuntimeMessagesContent,
47
47
  type InboundAttachMessage,
48
48
  type IRuntimeMessageCollection,
49
+ type MinimumVersionForCollab,
49
50
  } from "@fluidframework/runtime-definitions/internal";
50
51
  import {
51
52
  GCDataBuilder,
@@ -127,8 +128,14 @@ interface FluidDataStoreMessage {
127
128
  * being staged on IFluidParentContext can be added here as well, likely with optionality removed,
128
129
  * to ease interactions within this package.
129
130
  */
130
- export interface IFluidParentContextPrivate extends Omit<IFluidParentContext, "isReadOnly"> {
131
+ export interface IFluidParentContextPrivate
132
+ extends Omit<IFluidParentContext, "isReadOnly" | "minVersionForCollab"> {
131
133
  readonly isReadOnly: () => boolean;
134
+
135
+ /**
136
+ * {@inheritdoc IFluidParentContext.minVersionForCollab}
137
+ */
138
+ readonly minVersionForCollab: MinimumVersionForCollab;
132
139
  }
133
140
 
134
141
  /**
@@ -198,6 +205,7 @@ export function wrapContext(context: IFluidParentContextPrivate): IFluidParentCo
198
205
  setChannelDirty: (address: string) => {
199
206
  return context.setChannelDirty(address);
200
207
  },
208
+ minVersionForCollab: context.minVersionForCollab,
201
209
  };
202
210
  }
203
211
 
@@ -45,6 +45,49 @@ export type RuntimeOptionsAffectingDocSchema = Omit<
45
45
  | "summaryOptions"
46
46
  >;
47
47
 
48
+ /**
49
+ * Subset of {@link RuntimeOptionsAffectingDocSchema} which existed prior to the introduction of explicitSchemaControl.
50
+ *
51
+ * @remarks
52
+ * Runtime options that affect document schema should generally require explicitSchemaControl to be enabled.
53
+ * However, to prevent disruption to existing customers, options that existed prior to explicitSchemaControl
54
+ * do not explicity require explicitSchemaControl to be enabled. Do not add new options to this list.
55
+ */
56
+ type RuntimeOptionKeysPredatingExplicitSchemaControl = keyof Pick<
57
+ RuntimeOptionsAffectingDocSchema,
58
+ | "explicitSchemaControl"
59
+ | "compressionOptions"
60
+ | "enableRuntimeIdCompressor"
61
+ | "flushMode"
62
+ | "gcOptions"
63
+ | "enableGroupedBatching"
64
+ >;
65
+
66
+ /**
67
+ * List of keys of {@link RuntimeOptionsAffectingDocSchema} which existed prior to the introduction of explicitSchemaControl.
68
+ *
69
+ * @remarks
70
+ * Runtime options that affect document schema should generally require explicitSchemaControl to be enabled.
71
+ * However, to prevent disruption to existing customers, options that existed prior to explicitSchemaControl
72
+ * do not explicity require explicitSchemaControl to be enabled. Do not add new keys to this list.
73
+ */
74
+ const keysOfOptionsPredatingExplicitSchemaControl = new Set([
75
+ "explicitSchemaControl",
76
+ "compressionOptions",
77
+ "enableRuntimeIdCompressor",
78
+ "flushMode",
79
+ "gcOptions",
80
+ "enableGroupedBatching",
81
+ ]) satisfies Set<RuntimeOptionKeysPredatingExplicitSchemaControl>;
82
+
83
+ /**
84
+ * Subset of {@link RuntimeOptionsAffectingDocSchema} which require explicitSchemaControl to be enabled.
85
+ */
86
+ export type RuntimeOptionKeysThatRequireExplicitSchemaControl = keyof Omit<
87
+ RuntimeOptionsAffectingDocSchema,
88
+ RuntimeOptionKeysPredatingExplicitSchemaControl
89
+ >;
90
+
48
91
  /**
49
92
  * Mapping of RuntimeOptionsAffectingDocSchema to their compatibility related configs.
50
93
  *
@@ -113,6 +156,19 @@ const runtimeOptionsAffectingDocSchemaConfigMap = {
113
156
  },
114
157
  } as const satisfies ConfigMap<RuntimeOptionsAffectingDocSchema>;
115
158
 
159
+ /**
160
+ * Keys of {@link ContainerRuntimeOptionsInternal} that require explicitSchemaControl to be enabled.
161
+ */
162
+ export const runtimeOptionKeysThatRequireExplicitSchemaControl = (
163
+ Object.keys(
164
+ runtimeOptionsAffectingDocSchemaConfigMap,
165
+ ) as (keyof RuntimeOptionsAffectingDocSchema)[]
166
+ ).filter((key) => {
167
+ return !keysOfOptionsPredatingExplicitSchemaControl.has(
168
+ key as RuntimeOptionKeysPredatingExplicitSchemaControl,
169
+ );
170
+ }) as RuntimeOptionKeysThatRequireExplicitSchemaControl[];
171
+
116
172
  const runtimeOptionsAffectingDocSchemaConfigValidationMap = {
117
173
  enableGroupedBatching: configValueToMinVersionForCollab([
118
174
  [false, "1.0.0"],
@@ -130,7 +130,6 @@ import type {
130
130
  import {
131
131
  defaultMinVersionForCollab,
132
132
  isValidMinVersionForCollab,
133
- type SemanticVersion,
134
133
  } from "@fluidframework/runtime-utils/internal";
135
134
  import {
136
135
  GCDataBuilder,
@@ -144,6 +143,7 @@ import {
144
143
  exceptionToResponse,
145
144
  seqFromTree,
146
145
  } from "@fluidframework/runtime-utils/internal";
146
+ import { semanticVersionToMinimumVersionForCollab } from "@fluidframework/runtime-utils/internal";
147
147
  import type {
148
148
  IEventSampler,
149
149
  IFluidErrorBase,
@@ -195,6 +195,8 @@ import {
195
195
  getMinVersionForCollabDefaults,
196
196
  type RuntimeOptionsAffectingDocSchema,
197
197
  validateRuntimeOptions,
198
+ runtimeOptionKeysThatRequireExplicitSchemaControl,
199
+ type RuntimeOptionKeysThatRequireExplicitSchemaControl,
198
200
  } from "./containerCompatibility.js";
199
201
  import { ContainerFluidHandleContext } from "./containerHandleContext.js";
200
202
  import { channelToDataStore } from "./dataStore.js";
@@ -947,6 +949,19 @@ export class ContainerRuntime
947
949
  createBlobPayloadPending = defaultConfigs.createBlobPayloadPending,
948
950
  }: IContainerRuntimeOptionsInternal = runtimeOptions;
949
951
 
952
+ // If explicitSchemaControl is off, ensure that options which require explicitSchemaControl are not enabled.
953
+ if (!explicitSchemaControl) {
954
+ const disallowedKeys = Object.keys(runtimeOptions).filter(
955
+ (key) =>
956
+ runtimeOptionKeysThatRequireExplicitSchemaControl.includes(
957
+ key as RuntimeOptionKeysThatRequireExplicitSchemaControl,
958
+ ) && runtimeOptions[key] !== undefined,
959
+ );
960
+ if (disallowedKeys.length > 0) {
961
+ throw new UsageError(`explicitSchemaControl must be enabled to use ${disallowedKeys}`);
962
+ }
963
+ }
964
+
950
965
  // The logic for enableRuntimeIdCompressor is a bit different. Since `undefined` represents a logical state (off)
951
966
  // we need to check it's explicitly set in runtimeOptions. If so, we should use that value even if it's undefined.
952
967
  const enableRuntimeIdCompressor =
@@ -1181,7 +1196,7 @@ export class ContainerRuntime
1181
1196
  documentSchemaController,
1182
1197
  featureGatesForTelemetry,
1183
1198
  provideEntryPoint,
1184
- updatedMinVersionForCollab,
1199
+ semanticVersionToMinimumVersionForCollab(updatedMinVersionForCollab),
1185
1200
  requestHandler,
1186
1201
  undefined, // summaryConfiguration
1187
1202
  recentBatchInfo,
@@ -1503,7 +1518,7 @@ export class ContainerRuntime
1503
1518
  private readonly documentsSchemaController: DocumentsSchemaController,
1504
1519
  featureGatesForTelemetry: Record<string, boolean | number | undefined>,
1505
1520
  provideEntryPoint: (containerRuntime: IContainerRuntime) => Promise<FluidObject>,
1506
- private readonly minVersionForCollab: SemanticVersion,
1521
+ public readonly minVersionForCollab: MinimumVersionForCollab,
1507
1522
  private readonly requestHandler?: (
1508
1523
  request: IRequest,
1509
1524
  runtime: IContainerRuntime,
@@ -3708,7 +3723,7 @@ export class ContainerRuntime
3708
3723
  telemetryContext?: ITelemetryContext,
3709
3724
  ): ISummaryTree {
3710
3725
  if (blobRedirectTable) {
3711
- this.blobManager.setRedirectTable(blobRedirectTable);
3726
+ this.blobManager.patchRedirectTable(blobRedirectTable);
3712
3727
  }
3713
3728
 
3714
3729
  // We can finalize any allocated IDs since we're the only client
@@ -4520,6 +4535,22 @@ export class ContainerRuntime
4520
4535
  return this.blobManager.createBlob(blob, signal);
4521
4536
  }
4522
4537
 
4538
+ /**
4539
+ * Lookup the blob storage ID for a given local blob id.
4540
+ * @param localId - The local blob id. Likely coming from a handle.
4541
+ * @returns The storage ID if found and the blob is not pending, undefined otherwise.
4542
+ * @remarks
4543
+ * This method provides access to the BlobManager's storage ID lookup functionality.
4544
+ * For blobs with pending payloads (localId exists but upload hasn't finished), this returns undefined.
4545
+ * Consumers should use the observability APIs on the handle to understand/wait for storage ID availability.
4546
+ *
4547
+ * Warning: the returned blob URL may expire and does not support permalinks.
4548
+ * This API is intended for temporary integration scenarios only.
4549
+ */
4550
+ public lookupTemporaryBlobStorageId(localId: string): string | undefined {
4551
+ return this.blobManager.lookupTemporaryBlobStorageId(localId);
4552
+ }
4553
+
4523
4554
  private submitIdAllocationOpIfNeeded({
4524
4555
  resubmitOutstandingRanges = false,
4525
4556
  staged,
@@ -67,6 +67,7 @@ import {
67
67
  type IFluidDataStoreFactory,
68
68
  type PackagePath,
69
69
  type IRuntimeStorageService,
70
+ type MinimumVersionForCollab,
70
71
  } from "@fluidframework/runtime-definitions/internal";
71
72
  import {
72
73
  addBlobToSummary,
@@ -351,6 +352,11 @@ export abstract class FluidDataStoreContext
351
352
  return runtimeCompatDetailsForDataStore;
352
353
  }
353
354
 
355
+ /**
356
+ * {@inheritdoc IFluidDataStoreContext.minVersionForCollab}
357
+ */
358
+ public readonly minVersionForCollab: MinimumVersionForCollab;
359
+
354
360
  private baseSnapshotSequenceNumber: number | undefined;
355
361
 
356
362
  /**
@@ -460,6 +466,7 @@ export abstract class FluidDataStoreContext
460
466
 
461
467
  this._containerRuntime = props.parentContext.containerRuntime;
462
468
  this.parentContext = props.parentContext;
469
+ this.minVersionForCollab = props.parentContext.minVersionForCollab;
463
470
  this.id = props.id;
464
471
  this.storage = props.storage;
465
472
  this.scope = props.scope;
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.60.0";
9
+ export const pkgVersion = "2.61.0-355516";