@fluidframework/container-runtime 2.10.0 → 2.12.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.
- package/CHANGELOG.md +99 -0
- package/api-report/container-runtime.legacy.alpha.api.md +17 -15
- package/container-runtime.test-files.tar +0 -0
- package/dist/containerRuntime.d.ts +38 -5
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +40 -27
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +15 -2
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +39 -1
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStoreRegistry.d.ts +1 -0
- package/dist/dataStoreRegistry.d.ts.map +1 -1
- package/dist/dataStoreRegistry.js +10 -2
- package/dist/dataStoreRegistry.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +2 -0
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +40 -11
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcSummaryStateTracker.d.ts +0 -7
- package/dist/gc/gcSummaryStateTracker.d.ts.map +1 -1
- package/dist/gc/gcSummaryStateTracker.js +0 -12
- package/dist/gc/gcSummaryStateTracker.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/messageTypes.d.ts +3 -39
- package/dist/messageTypes.d.ts.map +1 -1
- package/dist/messageTypes.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +1 -5
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +2 -3
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/summary/runWhileConnectedCoordinator.d.ts +2 -1
- package/dist/summary/runWhileConnectedCoordinator.d.ts.map +1 -1
- package/dist/summary/runWhileConnectedCoordinator.js.map +1 -1
- package/dist/summary/runningSummarizer.d.ts +8 -1
- package/dist/summary/runningSummarizer.d.ts.map +1 -1
- package/dist/summary/runningSummarizer.js +70 -8
- package/dist/summary/runningSummarizer.js.map +1 -1
- package/dist/summary/summarizer.d.ts +5 -2
- package/dist/summary/summarizer.d.ts.map +1 -1
- package/dist/summary/summarizer.js +39 -6
- package/dist/summary/summarizer.js.map +1 -1
- package/dist/summary/summarizerTypes.d.ts +8 -4
- package/dist/summary/summarizerTypes.d.ts.map +1 -1
- package/dist/summary/summarizerTypes.js.map +1 -1
- package/dist/summary/summaryManager.d.ts +5 -2
- package/dist/summary/summaryManager.d.ts.map +1 -1
- package/dist/summary/summaryManager.js +23 -6
- package/dist/summary/summaryManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +38 -5
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +40 -27
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +15 -2
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +40 -2
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStoreRegistry.d.ts +1 -0
- package/lib/dataStoreRegistry.d.ts.map +1 -1
- package/lib/dataStoreRegistry.js +8 -0
- package/lib/dataStoreRegistry.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +2 -0
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +40 -11
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcSummaryStateTracker.d.ts +0 -7
- package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
- package/lib/gc/gcSummaryStateTracker.js +0 -12
- package/lib/gc/gcSummaryStateTracker.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/messageTypes.d.ts +3 -39
- package/lib/messageTypes.d.ts.map +1 -1
- package/lib/messageTypes.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +1 -5
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +2 -3
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/summary/runWhileConnectedCoordinator.d.ts +2 -1
- package/lib/summary/runWhileConnectedCoordinator.d.ts.map +1 -1
- package/lib/summary/runWhileConnectedCoordinator.js.map +1 -1
- package/lib/summary/runningSummarizer.d.ts +8 -1
- package/lib/summary/runningSummarizer.d.ts.map +1 -1
- package/lib/summary/runningSummarizer.js +70 -8
- package/lib/summary/runningSummarizer.js.map +1 -1
- package/lib/summary/summarizer.d.ts +5 -2
- package/lib/summary/summarizer.d.ts.map +1 -1
- package/lib/summary/summarizer.js +39 -6
- package/lib/summary/summarizer.js.map +1 -1
- package/lib/summary/summarizerTypes.d.ts +8 -4
- package/lib/summary/summarizerTypes.d.ts.map +1 -1
- package/lib/summary/summarizerTypes.js.map +1 -1
- package/lib/summary/summaryManager.d.ts +5 -2
- package/lib/summary/summaryManager.d.ts.map +1 -1
- package/lib/summary/summaryManager.js +23 -6
- package/lib/summary/summaryManager.js.map +1 -1
- package/package.json +21 -73
- package/src/containerRuntime.ts +94 -44
- package/src/dataStoreContext.ts +57 -1
- package/src/dataStoreRegistry.ts +10 -0
- package/src/gc/garbageCollection.ts +41 -11
- package/src/gc/gcSummaryStateTracker.ts +0 -13
- package/src/index.ts +1 -3
- package/src/messageTypes.ts +3 -50
- package/src/opLifecycle/remoteMessageProcessor.ts +1 -6
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +2 -3
- package/src/summary/runWhileConnectedCoordinator.ts +2 -5
- package/src/summary/runningSummarizer.ts +82 -11
- package/src/summary/summarizer.ts +49 -10
- package/src/summary/summarizerTypes.ts +11 -4
- package/src/summary/summaryManager.ts +30 -10
package/src/containerRuntime.ts
CHANGED
|
@@ -218,7 +218,6 @@ import {
|
|
|
218
218
|
ISubmitSummaryOptions,
|
|
219
219
|
ISummarizeResults,
|
|
220
220
|
ISummarizer,
|
|
221
|
-
ISummarizerEvents,
|
|
222
221
|
ISummarizerInternalsProvider,
|
|
223
222
|
ISummarizerRuntime,
|
|
224
223
|
ISummaryMetadataMessage,
|
|
@@ -475,6 +474,8 @@ export interface IContainerRuntimeOptions {
|
|
|
475
474
|
* send all operations to the driver layer, while in TurnBased the operations will be buffered
|
|
476
475
|
* and then sent them as a single batch at the end of the turn.
|
|
477
476
|
* By default, flush mode is TurnBased.
|
|
477
|
+
*
|
|
478
|
+
* @deprecated Only the default value TurnBased is supported. This option will be removed in the future.
|
|
478
479
|
*/
|
|
479
480
|
readonly flushMode?: FlushMode;
|
|
480
481
|
/**
|
|
@@ -516,9 +517,10 @@ export interface IContainerRuntimeOptions {
|
|
|
516
517
|
/**
|
|
517
518
|
* If enabled, the runtime will group messages within a batch into a single
|
|
518
519
|
* message to be sent to the service.
|
|
519
|
-
* The grouping
|
|
520
|
+
* The grouping and ungrouping of such messages is handled by the "OpGroupingManager".
|
|
520
521
|
*
|
|
521
522
|
* By default, the feature is enabled.
|
|
523
|
+
* @deprecated The ability to disable Grouped Batching is deprecated and will be removed in v2.20.0. This feature is required for the proper functioning of the Fluid Framework.
|
|
522
524
|
*/
|
|
523
525
|
readonly enableGroupedBatching?: boolean;
|
|
524
526
|
|
|
@@ -532,6 +534,31 @@ export interface IContainerRuntimeOptions {
|
|
|
532
534
|
readonly explicitSchemaControl?: boolean;
|
|
533
535
|
}
|
|
534
536
|
|
|
537
|
+
/**
|
|
538
|
+
* Internal extension of @see IContainerRuntimeOptions
|
|
539
|
+
*
|
|
540
|
+
* These options are not available to consumers when creating a new container runtime,
|
|
541
|
+
* but we do need to expose them for internal use, e.g. when configuring the container runtime
|
|
542
|
+
* to ensure compability with older versions.
|
|
543
|
+
*
|
|
544
|
+
* @internal
|
|
545
|
+
*/
|
|
546
|
+
export interface IContainerRuntimeOptionsInternal extends IContainerRuntimeOptions {
|
|
547
|
+
/**
|
|
548
|
+
* Sets the flush mode for the runtime. In Immediate flush mode the runtime will immediately
|
|
549
|
+
* send all operations to the driver layer, while in TurnBased the operations will be buffered
|
|
550
|
+
* and then sent them as a single batch at the end of the turn.
|
|
551
|
+
* By default, flush mode is TurnBased.
|
|
552
|
+
*/
|
|
553
|
+
readonly flushMode?: FlushMode;
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Allows Grouped Batching to be disabled by setting to false (default is true).
|
|
557
|
+
* In that case, batched messages will be sent individually (but still all at the same time).
|
|
558
|
+
*/
|
|
559
|
+
readonly enableGroupedBatching?: boolean;
|
|
560
|
+
}
|
|
561
|
+
|
|
535
562
|
/**
|
|
536
563
|
* Error responses when requesting a deleted object will have this header set to true
|
|
537
564
|
* @legacy
|
|
@@ -836,11 +863,15 @@ export async function loadContainerRuntime(
|
|
|
836
863
|
/**
|
|
837
864
|
* Represents the runtime of the container. Contains helper functions/state of the container.
|
|
838
865
|
* It will define the store level mappings.
|
|
866
|
+
*
|
|
867
|
+
* @deprecated To be removed from the Legacy-Alpha API in version 2.20.0.
|
|
868
|
+
* Use the loadContainerRuntime function and interfaces IContainerRuntime / IRuntime instead.
|
|
869
|
+
*
|
|
839
870
|
* @legacy
|
|
840
871
|
* @alpha
|
|
841
872
|
*/
|
|
842
873
|
export class ContainerRuntime
|
|
843
|
-
extends TypedEventEmitter<IContainerRuntimeEvents
|
|
874
|
+
extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
844
875
|
implements
|
|
845
876
|
IContainerRuntime,
|
|
846
877
|
IRuntime,
|
|
@@ -867,7 +898,7 @@ export class ContainerRuntime
|
|
|
867
898
|
context: IContainerContext;
|
|
868
899
|
registryEntries: NamedFluidDataStoreRegistryEntries;
|
|
869
900
|
existing: boolean;
|
|
870
|
-
runtimeOptions?: IContainerRuntimeOptions;
|
|
901
|
+
runtimeOptions?: IContainerRuntimeOptions; // May also include options from IContainerRuntimeOptionsInternal
|
|
871
902
|
containerScope?: FluidObject;
|
|
872
903
|
containerRuntimeCtor?: typeof ContainerRuntime;
|
|
873
904
|
/** @deprecated Will be removed once Loader LTS version is "2.0.0-internal.7.0.0". Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md */
|
|
@@ -914,7 +945,7 @@ export class ContainerRuntime
|
|
|
914
945
|
chunkSizeInBytes = defaultChunkSizeInBytes,
|
|
915
946
|
enableGroupedBatching = true,
|
|
916
947
|
explicitSchemaControl = false,
|
|
917
|
-
} = runtimeOptions;
|
|
948
|
+
}: IContainerRuntimeOptionsInternal = runtimeOptions;
|
|
918
949
|
|
|
919
950
|
const registry = new FluidDataStoreRegistry(registryEntries);
|
|
920
951
|
|
|
@@ -1093,6 +1124,21 @@ export class ContainerRuntime
|
|
|
1093
1124
|
|
|
1094
1125
|
const featureGatesForTelemetry: Record<string, boolean | number | undefined> = {};
|
|
1095
1126
|
|
|
1127
|
+
// Make sure we've got all the options including internal ones
|
|
1128
|
+
const internalRuntimeOptions: Readonly<Required<IContainerRuntimeOptionsInternal>> = {
|
|
1129
|
+
summaryOptions,
|
|
1130
|
+
gcOptions,
|
|
1131
|
+
loadSequenceNumberVerification,
|
|
1132
|
+
flushMode,
|
|
1133
|
+
compressionOptions,
|
|
1134
|
+
maxBatchSizeInBytes,
|
|
1135
|
+
chunkSizeInBytes,
|
|
1136
|
+
// Requires<> drops undefined from IdCompressorType
|
|
1137
|
+
enableRuntimeIdCompressor: enableRuntimeIdCompressor as "on" | "delayed",
|
|
1138
|
+
enableGroupedBatching,
|
|
1139
|
+
explicitSchemaControl,
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1096
1142
|
const runtime = new containerRuntimeCtor(
|
|
1097
1143
|
context,
|
|
1098
1144
|
registry,
|
|
@@ -1100,19 +1146,7 @@ export class ContainerRuntime
|
|
|
1100
1146
|
electedSummarizerData,
|
|
1101
1147
|
chunks ?? [],
|
|
1102
1148
|
aliases ?? [],
|
|
1103
|
-
|
|
1104
|
-
summaryOptions,
|
|
1105
|
-
gcOptions,
|
|
1106
|
-
loadSequenceNumberVerification,
|
|
1107
|
-
flushMode,
|
|
1108
|
-
compressionOptions,
|
|
1109
|
-
maxBatchSizeInBytes,
|
|
1110
|
-
chunkSizeInBytes,
|
|
1111
|
-
// Requires<> drops undefined from IdCompressorType
|
|
1112
|
-
enableRuntimeIdCompressor: enableRuntimeIdCompressor as "on" | "delayed",
|
|
1113
|
-
enableGroupedBatching,
|
|
1114
|
-
explicitSchemaControl,
|
|
1115
|
-
},
|
|
1149
|
+
internalRuntimeOptions,
|
|
1116
1150
|
containerScope,
|
|
1117
1151
|
logger,
|
|
1118
1152
|
existing,
|
|
@@ -1474,6 +1508,11 @@ export class ContainerRuntime
|
|
|
1474
1508
|
expiry: { policy: "absolute", durationMs: 60000 },
|
|
1475
1509
|
});
|
|
1476
1510
|
|
|
1511
|
+
/**
|
|
1512
|
+
* The options to apply to this ContainerRuntime instance (including internal options hidden from the public API)
|
|
1513
|
+
*/
|
|
1514
|
+
private readonly runtimeOptions: Readonly<Required<IContainerRuntimeOptionsInternal>>;
|
|
1515
|
+
|
|
1477
1516
|
/***/
|
|
1478
1517
|
protected constructor(
|
|
1479
1518
|
context: IContainerContext,
|
|
@@ -1482,7 +1521,10 @@ export class ContainerRuntime
|
|
|
1482
1521
|
electedSummarizerData: ISerializedElection | undefined,
|
|
1483
1522
|
chunks: [string, string[]][],
|
|
1484
1523
|
dataStoreAliasMap: [string, string][],
|
|
1485
|
-
|
|
1524
|
+
runtimeOptions: Readonly<
|
|
1525
|
+
Required<Omit<IContainerRuntimeOptions, "flushMode" | "enableGroupedBatching">> &
|
|
1526
|
+
IContainerRuntimeOptions // Let flushMode and enabledGroupedBatching be optional now since they're soon to be removed
|
|
1527
|
+
>,
|
|
1486
1528
|
private readonly containerScope: FluidObject,
|
|
1487
1529
|
// Create a custom ITelemetryBaseLogger to output telemetry events.
|
|
1488
1530
|
public readonly baseLogger: ITelemetryBaseLogger,
|
|
@@ -1527,6 +1569,12 @@ export class ContainerRuntime
|
|
|
1527
1569
|
snapshotWithContents,
|
|
1528
1570
|
} = context;
|
|
1529
1571
|
|
|
1572
|
+
// Backfill in defaults for the internal runtimeOptions, since they may not be present on the provided runtimeOptions object
|
|
1573
|
+
this.runtimeOptions = {
|
|
1574
|
+
flushMode: defaultFlushMode,
|
|
1575
|
+
enableGroupedBatching: true,
|
|
1576
|
+
...runtimeOptions,
|
|
1577
|
+
};
|
|
1530
1578
|
this.logger = createChildLogger({ logger: this.baseLogger });
|
|
1531
1579
|
this.mc = createChildMonitoringContext({
|
|
1532
1580
|
logger: this.logger,
|
|
@@ -1697,14 +1745,15 @@ export class ContainerRuntime
|
|
|
1697
1745
|
this.defaultMaxConsecutiveReconnects;
|
|
1698
1746
|
|
|
1699
1747
|
if (
|
|
1700
|
-
runtimeOptions.flushMode ===
|
|
1748
|
+
this.runtimeOptions.flushMode ===
|
|
1749
|
+
(FlushModeExperimental.Async as unknown as FlushMode) &&
|
|
1701
1750
|
supportedFeatures?.get("referenceSequenceNumbers") !== true
|
|
1702
1751
|
) {
|
|
1703
1752
|
// The loader does not support reference sequence numbers, falling back on FlushMode.TurnBased
|
|
1704
1753
|
this.mc.logger.sendErrorEvent({ eventName: "FlushModeFallback" });
|
|
1705
1754
|
this._flushMode = FlushMode.TurnBased;
|
|
1706
1755
|
} else {
|
|
1707
|
-
this._flushMode = runtimeOptions.flushMode;
|
|
1756
|
+
this._flushMode = this.runtimeOptions.flushMode;
|
|
1708
1757
|
}
|
|
1709
1758
|
this.offlineEnabled =
|
|
1710
1759
|
this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ?? false;
|
|
@@ -2015,9 +2064,19 @@ export class ContainerRuntime
|
|
|
2015
2064
|
initialDelayMs: this.initialSummarizerDelayMs,
|
|
2016
2065
|
},
|
|
2017
2066
|
);
|
|
2018
|
-
|
|
2019
|
-
|
|
2067
|
+
// Forward events from SummaryManager
|
|
2068
|
+
[
|
|
2069
|
+
"summarize",
|
|
2070
|
+
"summarizeAllAttemptsFailed",
|
|
2071
|
+
"summarizerStop",
|
|
2072
|
+
"summarizerStart",
|
|
2073
|
+
"summarizerStartupFailed",
|
|
2074
|
+
].forEach((eventName) => {
|
|
2075
|
+
this.summaryManager?.on(eventName, (...args: any[]) => {
|
|
2076
|
+
this.emit(eventName, ...args);
|
|
2077
|
+
});
|
|
2020
2078
|
});
|
|
2079
|
+
|
|
2021
2080
|
this.summaryManager.start();
|
|
2022
2081
|
}
|
|
2023
2082
|
}
|
|
@@ -2669,20 +2728,21 @@ export class ContainerRuntime
|
|
|
2669
2728
|
|
|
2670
2729
|
this._connected = connected;
|
|
2671
2730
|
|
|
2672
|
-
if (
|
|
2673
|
-
this._signalTracking.signalsLost = 0;
|
|
2674
|
-
this._signalTracking.signalsOutOfOrder = 0;
|
|
2675
|
-
this._signalTracking.signalTimestamp = 0;
|
|
2676
|
-
this._signalTracking.signalsSentSinceLastLatencyMeasurement = 0;
|
|
2677
|
-
this._signalTracking.totalSignalsSentInLatencyWindow = 0;
|
|
2678
|
-
this._signalTracking.roundTripSignalSequenceNumber = undefined;
|
|
2679
|
-
this._signalTracking.trackingSignalSequenceNumber = undefined;
|
|
2680
|
-
this._signalTracking.minimumTrackingSignalSequenceNumber = undefined;
|
|
2681
|
-
} else {
|
|
2731
|
+
if (connected) {
|
|
2682
2732
|
assert(
|
|
2683
2733
|
this.attachState === AttachState.Attached,
|
|
2684
2734
|
0x3cd /* Connection is possible only if container exists in storage */,
|
|
2685
2735
|
);
|
|
2736
|
+
if (changeOfState) {
|
|
2737
|
+
this._signalTracking.signalsLost = 0;
|
|
2738
|
+
this._signalTracking.signalsOutOfOrder = 0;
|
|
2739
|
+
this._signalTracking.signalTimestamp = 0;
|
|
2740
|
+
this._signalTracking.signalsSentSinceLastLatencyMeasurement = 0;
|
|
2741
|
+
this._signalTracking.totalSignalsSentInLatencyWindow = 0;
|
|
2742
|
+
this._signalTracking.roundTripSignalSequenceNumber = undefined;
|
|
2743
|
+
this._signalTracking.trackingSignalSequenceNumber = undefined;
|
|
2744
|
+
this._signalTracking.minimumTrackingSignalSequenceNumber = undefined;
|
|
2745
|
+
}
|
|
2686
2746
|
}
|
|
2687
2747
|
|
|
2688
2748
|
// Fail while disconnected
|
|
@@ -3227,16 +3287,7 @@ export class ContainerRuntime
|
|
|
3227
3287
|
};
|
|
3228
3288
|
|
|
3229
3289
|
// Only collect signal telemetry for broadcast messages sent by the current client.
|
|
3230
|
-
if (
|
|
3231
|
-
message.clientId === this.clientId &&
|
|
3232
|
-
// jason-ha: This `connected` check seems incorrect. Signals that come through
|
|
3233
|
-
// here must have been received while connected to service and there is no need
|
|
3234
|
-
// to avoid processing when connection has dropped. Because container runtime's
|
|
3235
|
-
// `connected` (and `_connected`) state also reflects some ops state, it may
|
|
3236
|
-
// easily be false when newly connected and signal tracking may very well
|
|
3237
|
-
// complain lost signals (that were simply skipped per this check).
|
|
3238
|
-
this.connected
|
|
3239
|
-
) {
|
|
3290
|
+
if (message.clientId === this.clientId) {
|
|
3240
3291
|
this.processSignalForTelemetry(envelope);
|
|
3241
3292
|
}
|
|
3242
3293
|
|
|
@@ -4621,7 +4672,6 @@ export class ContainerRuntime
|
|
|
4621
4672
|
this.channelCollection.rollback(type, contents, localOpMetadata);
|
|
4622
4673
|
break;
|
|
4623
4674
|
default:
|
|
4624
|
-
// Don't check message.compatDetails because this is for rolling back a local op so the type will be known
|
|
4625
4675
|
throw new Error(`Can't rollback ${type}`);
|
|
4626
4676
|
}
|
|
4627
4677
|
}
|
package/src/dataStoreContext.ts
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
IInboundSignalMessage,
|
|
56
56
|
type IPendingMessagesState,
|
|
57
57
|
type IRuntimeMessageCollection,
|
|
58
|
+
type IFluidDataStoreFactory,
|
|
58
59
|
} from "@fluidframework/runtime-definitions/internal";
|
|
59
60
|
import {
|
|
60
61
|
addBlobToSummary,
|
|
@@ -65,6 +66,7 @@ import {
|
|
|
65
66
|
LoggingError,
|
|
66
67
|
MonitoringContext,
|
|
67
68
|
ThresholdCounter,
|
|
69
|
+
UsageError,
|
|
68
70
|
createChildMonitoringContext,
|
|
69
71
|
extractSafePropertiesFromMessage,
|
|
70
72
|
generateStack,
|
|
@@ -496,7 +498,7 @@ export abstract class FluidDataStoreContext
|
|
|
496
498
|
this.rejectDeferredRealize("No registry for package", lastPkg, packages);
|
|
497
499
|
}
|
|
498
500
|
lastPkg = pkg;
|
|
499
|
-
entry = await registry.get(pkg);
|
|
501
|
+
entry = registry.getSync?.(pkg) ?? (await registry.get(pkg));
|
|
500
502
|
if (!entry) {
|
|
501
503
|
this.rejectDeferredRealize(
|
|
502
504
|
"Registry does not contain entry for the package",
|
|
@@ -517,6 +519,42 @@ export abstract class FluidDataStoreContext
|
|
|
517
519
|
return factory;
|
|
518
520
|
}
|
|
519
521
|
|
|
522
|
+
createChildDataStore<T extends IFluidDataStoreFactory>(
|
|
523
|
+
childFactory: T,
|
|
524
|
+
): ReturnType<Exclude<T["createDataStore"], undefined>> {
|
|
525
|
+
const maybe = this.registry?.getSync?.(childFactory.type);
|
|
526
|
+
|
|
527
|
+
const isUndefined = maybe === undefined;
|
|
528
|
+
const diffInstance = maybe?.IFluidDataStoreFactory !== childFactory;
|
|
529
|
+
|
|
530
|
+
if (isUndefined || diffInstance) {
|
|
531
|
+
throw new UsageError(
|
|
532
|
+
"The provided factory instance must be synchronously available as a child of this datastore",
|
|
533
|
+
{ isUndefined, diffInstance },
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
if (childFactory?.createDataStore === undefined) {
|
|
537
|
+
throw new UsageError("createDataStore must exist on the provided factory", {
|
|
538
|
+
noCreateDataStore: true,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const context = this._containerRuntime.createDetachedDataStore([
|
|
543
|
+
...this.packagePath,
|
|
544
|
+
childFactory.type,
|
|
545
|
+
]);
|
|
546
|
+
assert(
|
|
547
|
+
context instanceof LocalDetachedFluidDataStoreContext,
|
|
548
|
+
0xa89 /* must be a LocalDetachedFluidDataStoreContext */,
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
const created = childFactory.createDataStore(context) as ReturnType<
|
|
552
|
+
Exclude<T["createDataStore"], undefined>
|
|
553
|
+
>;
|
|
554
|
+
context.unsafe_AttachRuntimeSync(created.runtime);
|
|
555
|
+
return created;
|
|
556
|
+
}
|
|
557
|
+
|
|
520
558
|
private async realizeCore(existing: boolean) {
|
|
521
559
|
const details = await this.getInitialSnapshotDetails();
|
|
522
560
|
// Base snapshot is the baseline where pending ops are applied to.
|
|
@@ -1428,6 +1466,24 @@ export class LocalDetachedFluidDataStoreContext
|
|
|
1428
1466
|
return this.channelToDataStoreFn(await this.channelP);
|
|
1429
1467
|
}
|
|
1430
1468
|
|
|
1469
|
+
/**
|
|
1470
|
+
* This method provides a synchronous path for binding a runtime to the context.
|
|
1471
|
+
*
|
|
1472
|
+
* Due to its synchronous nature, it is unable to validate that the runtime
|
|
1473
|
+
* represents a datastore which is instantiable by remote clients. This could
|
|
1474
|
+
* happen if the runtime's package path does not return a factory when looked up
|
|
1475
|
+
* in the container runtime's registry, or if the runtime's entrypoint is not
|
|
1476
|
+
* properly initialized. As both of these validation's are asynchronous to preform.
|
|
1477
|
+
*
|
|
1478
|
+
* If used incorrectly, this function can result in permanent data corruption.
|
|
1479
|
+
*/
|
|
1480
|
+
public unsafe_AttachRuntimeSync(channel: IFluidDataStoreChannel) {
|
|
1481
|
+
this.channelP = Promise.resolve(channel);
|
|
1482
|
+
this.processPendingOps(channel);
|
|
1483
|
+
this.completeBindingRuntime(channel);
|
|
1484
|
+
return this.channelToDataStoreFn(channel);
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1431
1487
|
public async getInitialSnapshotDetails(): Promise<ISnapshotDetails> {
|
|
1432
1488
|
if (this.detachedRuntimeCreation) {
|
|
1433
1489
|
throw new Error(
|
package/src/dataStoreRegistry.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { isPromiseLike } from "@fluidframework/core-utils/internal";
|
|
6
7
|
import {
|
|
7
8
|
FluidDataStoreRegistryEntry,
|
|
8
9
|
IFluidDataStoreRegistry,
|
|
@@ -40,4 +41,13 @@ export class FluidDataStoreRegistry implements IFluidDataStoreRegistry {
|
|
|
40
41
|
|
|
41
42
|
return undefined;
|
|
42
43
|
}
|
|
44
|
+
|
|
45
|
+
public getSync(name: string): FluidDataStoreRegistryEntry | undefined {
|
|
46
|
+
const entry = this.map.get(name);
|
|
47
|
+
if (!isPromiseLike(entry)) {
|
|
48
|
+
return entry;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
43
53
|
}
|
|
@@ -338,6 +338,43 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
338
338
|
});
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
+
/** API for ensuring the correct auto-recovery mitigations */
|
|
342
|
+
private readonly autoRecovery = (() => {
|
|
343
|
+
// This uses a hidden state machine for forcing fullGC as part of autorecovery,
|
|
344
|
+
// to regenerate the GC data for each node.
|
|
345
|
+
//
|
|
346
|
+
// Once fullGC has been requested, we need to wait until GC has run and the summary has been acked before clearing the state.
|
|
347
|
+
//
|
|
348
|
+
// States:
|
|
349
|
+
// - undefined: No need to run fullGC now.
|
|
350
|
+
// - "requested": FullGC requested, but GC has not yet run. Keep using fullGC until back to undefined.
|
|
351
|
+
// - "ran": FullGC ran, but the following summary has not yet been acked. Keep using fullGC until back to undefined.
|
|
352
|
+
//
|
|
353
|
+
// Transitions:
|
|
354
|
+
// - autoRecovery.requestFullGCOnNextRun :: [anything] --> "requested"
|
|
355
|
+
// - autoRecovery.onCompletedGCRun :: "requested" --> "ran"
|
|
356
|
+
// - autoRecovery.onSummaryAck :: "ran" --> undefined
|
|
357
|
+
let state: "requested" | "ran" | undefined;
|
|
358
|
+
return {
|
|
359
|
+
requestFullGCOnNextRun: () => {
|
|
360
|
+
state = "requested";
|
|
361
|
+
},
|
|
362
|
+
onCompletedGCRun: () => {
|
|
363
|
+
if (state === "requested") {
|
|
364
|
+
state = "ran";
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
onSummaryAck: () => {
|
|
368
|
+
if (state === "ran") {
|
|
369
|
+
state = undefined;
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
useFullGC: () => {
|
|
373
|
+
return state !== undefined;
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
})();
|
|
377
|
+
|
|
341
378
|
/**
|
|
342
379
|
* Called during container initialization. Initializes the tombstone and deleted nodes state from the base snapshot.
|
|
343
380
|
* Also, initializes the GC state including unreferenced nodes tracking if a current reference timestamp exists.
|
|
@@ -460,9 +497,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
460
497
|
telemetryContext?: ITelemetryContext,
|
|
461
498
|
): Promise<IGCStats | undefined> {
|
|
462
499
|
const fullGC =
|
|
463
|
-
options.fullGC ??
|
|
464
|
-
(this.configs.runFullGC === true ||
|
|
465
|
-
this.summaryStateTracker.autoRecovery.fullGCRequested());
|
|
500
|
+
options.fullGC ?? (this.configs.runFullGC === true || this.autoRecovery.useFullGC());
|
|
466
501
|
|
|
467
502
|
// Add the options that are used to run GC to the telemetry context.
|
|
468
503
|
telemetryContext?.setMultiple("fluid_GC", "Options", {
|
|
@@ -521,6 +556,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
521
556
|
await this.telemetryTracker.logPendingEvents(logger);
|
|
522
557
|
// Update the state of summary state tracker from this run's stats.
|
|
523
558
|
this.summaryStateTracker.updateStateFromGCRunStats(gcStats);
|
|
559
|
+
this.autoRecovery.onCompletedGCRun();
|
|
524
560
|
this.newReferencesSinceLastRun.clear();
|
|
525
561
|
this.completedRuns++;
|
|
526
562
|
|
|
@@ -709,13 +745,9 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
709
745
|
deletedNodeIds: sweepReadyDSAndBlobs,
|
|
710
746
|
};
|
|
711
747
|
|
|
712
|
-
// Its fine for older clients to ignore this op because it doesn't have any functional impact. This op
|
|
713
|
-
// is an optimization to ensure that all clients are in sync when it comes to deleted nodes to prevent their
|
|
714
|
-
// accidental usage. The clients will sync without the delete op too but it may take longer.
|
|
715
748
|
const containerGCMessage: ContainerRuntimeGCMessage = {
|
|
716
749
|
type: ContainerMessageType.GC,
|
|
717
750
|
contents,
|
|
718
|
-
compatDetails: { behavior: "Ignore" }, // DEPRECATED: For temporary back compat only
|
|
719
751
|
};
|
|
720
752
|
this.submitMessage(containerGCMessage);
|
|
721
753
|
return;
|
|
@@ -857,6 +889,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
857
889
|
* Called to refresh the latest summary state. This happens when either a pending summary is acked.
|
|
858
890
|
*/
|
|
859
891
|
public async refreshLatestSummary(result: IRefreshSummaryResult): Promise<void> {
|
|
892
|
+
this.autoRecovery.onSummaryAck();
|
|
860
893
|
return this.summaryStateTracker.refreshLatestSummary(result);
|
|
861
894
|
}
|
|
862
895
|
|
|
@@ -891,7 +924,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
891
924
|
|
|
892
925
|
// In case the cause of the TombstoneLoaded event is incorrect GC Data (i.e. the object is actually reachable),
|
|
893
926
|
// do fullGC on the next run to get a chance to repair (in the likely case the bug is not deterministic)
|
|
894
|
-
this.
|
|
927
|
+
this.autoRecovery.requestFullGCOnNextRun();
|
|
895
928
|
break;
|
|
896
929
|
}
|
|
897
930
|
default:
|
|
@@ -1036,15 +1069,12 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1036
1069
|
return;
|
|
1037
1070
|
}
|
|
1038
1071
|
|
|
1039
|
-
// Use compat behavior "Ignore" since this is an optimization to opportunistically protect
|
|
1040
|
-
// objects from deletion, so it's fine for older clients to ignore this op.
|
|
1041
1072
|
const containerGCMessage: ContainerRuntimeGCMessage = {
|
|
1042
1073
|
type: ContainerMessageType.GC,
|
|
1043
1074
|
contents: {
|
|
1044
1075
|
type: GarbageCollectionMessageType.TombstoneLoaded,
|
|
1045
1076
|
nodePath,
|
|
1046
1077
|
},
|
|
1047
|
-
compatDetails: { behavior: "Ignore" }, // DEPRECATED: For temporary back compat only
|
|
1048
1078
|
};
|
|
1049
1079
|
this.submitMessage(containerGCMessage);
|
|
1050
1080
|
}
|
|
@@ -50,18 +50,6 @@ export class GCSummaryStateTracker {
|
|
|
50
50
|
// to unreferenced or vice-versa.
|
|
51
51
|
public updatedDSCountSinceLastSummary: number = 0;
|
|
52
52
|
|
|
53
|
-
/** API for ensuring the correct auto-recovery mitigations */
|
|
54
|
-
public autoRecovery = {
|
|
55
|
-
requestFullGCOnNextRun: () => {
|
|
56
|
-
this.fullGCModeForAutoRecovery = true;
|
|
57
|
-
},
|
|
58
|
-
fullGCRequested: () => {
|
|
59
|
-
return this.fullGCModeForAutoRecovery;
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
/** If true, the next GC run will do fullGC mode to regenerate the GC data for each node */
|
|
63
|
-
private fullGCModeForAutoRecovery: boolean = false;
|
|
64
|
-
|
|
65
53
|
constructor(
|
|
66
54
|
// Tells whether GC should run or not.
|
|
67
55
|
private readonly configs: Pick<
|
|
@@ -235,7 +223,6 @@ export class GCSummaryStateTracker {
|
|
|
235
223
|
this.latestSummaryData = this.pendingSummaryData;
|
|
236
224
|
this.pendingSummaryData = undefined;
|
|
237
225
|
this.updatedDSCountSinceLastSummary = 0;
|
|
238
|
-
this.fullGCModeForAutoRecovery = false;
|
|
239
226
|
}
|
|
240
227
|
|
|
241
228
|
/**
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export {
|
|
|
10
10
|
ISummaryConfigurationDisableSummarizer,
|
|
11
11
|
ISummaryConfigurationDisableHeuristics,
|
|
12
12
|
IContainerRuntimeOptions,
|
|
13
|
+
IContainerRuntimeOptionsInternal,
|
|
13
14
|
loadContainerRuntime,
|
|
14
15
|
LoadContainerRuntimeParams,
|
|
15
16
|
agentSchedulerId,
|
|
@@ -26,9 +27,6 @@ export {
|
|
|
26
27
|
} from "./containerRuntime.js";
|
|
27
28
|
export {
|
|
28
29
|
ContainerMessageType,
|
|
29
|
-
IContainerRuntimeMessageCompatDetails,
|
|
30
|
-
CompatModeBehavior,
|
|
31
|
-
RecentlyAddedContainerRuntimeMessageDetails,
|
|
32
30
|
UnknownContainerRuntimeMessage,
|
|
33
31
|
} from "./messageTypes.js";
|
|
34
32
|
export { IBlobManagerLoadInfo } from "./blobManager/index.js";
|
package/src/messageTypes.ts
CHANGED
|
@@ -58,29 +58,6 @@ export enum ContainerMessageType {
|
|
|
58
58
|
GC = "GC",
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/**
|
|
62
|
-
* How should an older client handle an unrecognized remote op type?
|
|
63
|
-
*
|
|
64
|
-
* @deprecated The utility of a mechanism to handle unknown messages is outweighed by the nuance required to get it right.
|
|
65
|
-
* @internal
|
|
66
|
-
*/
|
|
67
|
-
export type CompatModeBehavior =
|
|
68
|
-
/** Ignore the op. It won't be persisted if this client summarizes */
|
|
69
|
-
| "Ignore"
|
|
70
|
-
/** Fail processing immediately. (The container will close) */
|
|
71
|
-
| "FailToProcess";
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* All the info an older client would need to know how to handle an unrecognized remote op type
|
|
75
|
-
*
|
|
76
|
-
* @deprecated The utility of a mechanism to handle unknown messages is outweighed by the nuance required to get it right.
|
|
77
|
-
* @internal
|
|
78
|
-
*/
|
|
79
|
-
export interface IContainerRuntimeMessageCompatDetails {
|
|
80
|
-
/** How should an older client handle an unrecognized remote op type? */
|
|
81
|
-
behavior: CompatModeBehavior;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
61
|
/**
|
|
85
62
|
* The unpacked runtime message / details to be handled or dispatched by the ContainerRuntime.
|
|
86
63
|
* Message type are differentiated via a `type` string and contain different contents depending on their type.
|
|
@@ -88,27 +65,11 @@ export interface IContainerRuntimeMessageCompatDetails {
|
|
|
88
65
|
* IMPORTANT: when creating one to be serialized, set the properties in the order they appear here.
|
|
89
66
|
* This way stringified values can be compared.
|
|
90
67
|
*/
|
|
91
|
-
|
|
92
|
-
TType extends ContainerMessageType,
|
|
93
|
-
TContents,
|
|
94
|
-
TUSedCompatDetails extends boolean = false,
|
|
95
|
-
> = {
|
|
68
|
+
interface TypedContainerRuntimeMessage<TType extends ContainerMessageType, TContents> {
|
|
96
69
|
/** Type of the op, within the ContainerRuntime's domain */
|
|
97
70
|
type: TType;
|
|
98
71
|
/** Domain-specific contents, interpreted according to the type */
|
|
99
72
|
contents: TContents;
|
|
100
|
-
} & (TUSedCompatDetails extends true
|
|
101
|
-
? Partial<RecentlyAddedContainerRuntimeMessageDetails>
|
|
102
|
-
: { compatDetails?: undefined });
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Additional details expected for any recently added message.
|
|
106
|
-
* @deprecated The utility of a mechanism to handle unknown messages is outweighed by the nuance required to get it right.
|
|
107
|
-
* @internal
|
|
108
|
-
*/
|
|
109
|
-
export interface RecentlyAddedContainerRuntimeMessageDetails {
|
|
110
|
-
/** Info describing how to handle this op in case the type is unrecognized (default: fail to process) */
|
|
111
|
-
compatDetails: IContainerRuntimeMessageCompatDetails;
|
|
112
73
|
}
|
|
113
74
|
|
|
114
75
|
export type ContainerRuntimeDataStoreOpMessage = TypedContainerRuntimeMessage<
|
|
@@ -145,8 +106,7 @@ export type ContainerRuntimeIdAllocationMessage = TypedContainerRuntimeMessage<
|
|
|
145
106
|
>;
|
|
146
107
|
export type ContainerRuntimeGCMessage = TypedContainerRuntimeMessage<
|
|
147
108
|
ContainerMessageType.GC,
|
|
148
|
-
GarbageCollectionMessage
|
|
149
|
-
true // TUsedCompatDetails
|
|
109
|
+
GarbageCollectionMessage
|
|
150
110
|
>;
|
|
151
111
|
export type ContainerRuntimeDocumentSchemaMessage = TypedContainerRuntimeMessage<
|
|
152
112
|
ContainerMessageType.DocumentSchemaChange,
|
|
@@ -157,8 +117,7 @@ export type ContainerRuntimeDocumentSchemaMessage = TypedContainerRuntimeMessage
|
|
|
157
117
|
* Represents an unrecognized TypedContainerRuntimeMessage, e.g. a message from a future version of the container runtime.
|
|
158
118
|
* @internal
|
|
159
119
|
*/
|
|
160
|
-
export interface UnknownContainerRuntimeMessage
|
|
161
|
-
extends Partial<RecentlyAddedContainerRuntimeMessageDetails> {
|
|
120
|
+
export interface UnknownContainerRuntimeMessage {
|
|
162
121
|
/** Invalid type of the op, within the ContainerRuntime's domain. This value should never exist at runtime.
|
|
163
122
|
* This is useful for type narrowing but should never be used as an actual message type at runtime.
|
|
164
123
|
* Actual value will not be "__unknown...", but the type `Exclude<string, ContainerMessageType>` is not supported.
|
|
@@ -222,9 +181,3 @@ export type InboundSequencedContainerRuntimeMessage = Omit<
|
|
|
222
181
|
"type" | "contents"
|
|
223
182
|
> &
|
|
224
183
|
InboundContainerRuntimeMessage;
|
|
225
|
-
|
|
226
|
-
/** A [loose] InboundSequencedContainerRuntimeMessage that is recent and may contain compat details.
|
|
227
|
-
* It exists solely to to provide access to those details.
|
|
228
|
-
*/
|
|
229
|
-
export type InboundSequencedRecentlyAddedContainerRuntimeMessage = ISequencedDocumentMessage &
|
|
230
|
-
Partial<RecentlyAddedContainerRuntimeMessageDetails>;
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
ContainerMessageType,
|
|
14
14
|
type InboundContainerRuntimeMessage,
|
|
15
15
|
type InboundSequencedContainerRuntimeMessage,
|
|
16
|
-
type InboundSequencedRecentlyAddedContainerRuntimeMessage,
|
|
17
16
|
} from "../messageTypes.js";
|
|
18
17
|
import { asBatchMetadata } from "../metadata.js";
|
|
19
18
|
|
|
@@ -259,7 +258,7 @@ export function ensureContentsDeserialized(mutableMessage: ISequencedDocumentMes
|
|
|
259
258
|
*
|
|
260
259
|
* The return type illustrates the assumption that the message param
|
|
261
260
|
* becomes a InboundSequencedContainerRuntimeMessage by the time the function returns
|
|
262
|
-
* (but there is no runtime validation of the 'type'
|
|
261
|
+
* (but there is no runtime validation of the 'type').
|
|
263
262
|
*/
|
|
264
263
|
function unpack(message: ISequencedDocumentMessage): InboundSequencedContainerRuntimeMessage {
|
|
265
264
|
// We assume the contents is an InboundContainerRuntimeMessage (the message is "packed")
|
|
@@ -270,10 +269,6 @@ function unpack(message: ISequencedDocumentMessage): InboundSequencedContainerRu
|
|
|
270
269
|
|
|
271
270
|
messageUnpacked.type = contents.type;
|
|
272
271
|
messageUnpacked.contents = contents.contents;
|
|
273
|
-
if ("compatDetails" in contents) {
|
|
274
|
-
(messageUnpacked as InboundSequencedRecentlyAddedContainerRuntimeMessage).compatDetails =
|
|
275
|
-
contents.compatDetails;
|
|
276
|
-
}
|
|
277
272
|
return messageUnpacked;
|
|
278
273
|
}
|
|
279
274
|
|
package/src/packageVersion.ts
CHANGED
|
@@ -105,9 +105,9 @@ function isEmptyBatchPendingMessage(message: IPendingMessageFromStash): boolean
|
|
|
105
105
|
function buildPendingMessageContent(message: InboundSequencedContainerRuntimeMessage): string {
|
|
106
106
|
// IMPORTANT: Order matters here, this must match the order of the properties used
|
|
107
107
|
// when submitting the message.
|
|
108
|
-
const { type, contents
|
|
108
|
+
const { type, contents }: InboundContainerRuntimeMessage = message;
|
|
109
109
|
// Any properties that are not defined, won't be emitted by stringify.
|
|
110
|
-
return JSON.stringify({ type, contents
|
|
110
|
+
return JSON.stringify({ type, contents });
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
function typesOfKeys<T extends object>(obj: T): Record<keyof T, string> {
|
|
@@ -126,7 +126,6 @@ function scrubAndStringify(
|
|
|
126
126
|
// For these known/expected keys, we can either drill in (for contents)
|
|
127
127
|
// or just use the value as-is (since it's not personal info)
|
|
128
128
|
scrubbed.contents = message.contents && typesOfKeys(message.contents);
|
|
129
|
-
scrubbed.compatDetails = message.compatDetails;
|
|
130
129
|
scrubbed.type = message.type;
|
|
131
130
|
|
|
132
131
|
return JSON.stringify(scrubbed);
|