@fluidframework/container-runtime 2.0.0-internal.6.3.2 → 2.0.0-internal.6.4.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 +8 -0
- package/dist/blobManager.d.ts +3 -2
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +29 -25
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +14 -69
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +155 -184
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +3 -1
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +1 -1
- package/dist/dataStores.js +3 -3
- package/dist/dataStores.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +6 -3
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +13 -12
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcConfigs.js +1 -1
- package/dist/gc/gcConfigs.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +17 -6
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js +19 -20
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/gc/gcHelpers.d.ts +0 -9
- package/dist/gc/gcHelpers.d.ts.map +1 -1
- package/dist/gc/gcHelpers.js +1 -13
- package/dist/gc/gcHelpers.js.map +1 -1
- package/dist/gc/gcTelemetry.d.ts.map +1 -1
- package/dist/gc/gcTelemetry.js +1 -4
- package/dist/gc/gcTelemetry.js.map +1 -1
- package/dist/gc/index.d.ts +2 -2
- package/dist/gc/index.d.ts.map +1 -1
- package/dist/gc/index.js +5 -6
- package/dist/gc/index.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/messageTypes.d.ts +134 -0
- package/dist/messageTypes.d.ts.map +1 -0
- package/dist/messageTypes.js +29 -0
- package/dist/messageTypes.js.map +1 -0
- package/dist/opLifecycle/definitions.d.ts +2 -1
- package/dist/opLifecycle/definitions.d.ts.map +1 -1
- package/dist/opLifecycle/definitions.js.map +1 -1
- package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opDecompressor.js +0 -4
- package/dist/opLifecycle/opDecompressor.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +4 -2
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/opSplitter.js +3 -3
- package/dist/opLifecycle/opSplitter.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +17 -3
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +38 -25
- 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 +2 -2
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +13 -6
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/summary/runningSummarizer.d.ts +1 -1
- package/dist/summary/runningSummarizer.d.ts.map +1 -1
- package/dist/summary/runningSummarizer.js +4 -2
- package/dist/summary/runningSummarizer.js.map +1 -1
- package/dist/summary/summarizer.d.ts +2 -2
- package/dist/summary/summarizer.js +2 -2
- package/dist/summary/summarizer.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.d.ts +1 -1
- package/dist/summary/summarizerNode/summarizerNode.js +1 -1
- package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/dist/summary/summarizerTypes.d.ts +2 -1
- package/dist/summary/summarizerTypes.d.ts.map +1 -1
- package/dist/summary/summarizerTypes.js.map +1 -1
- package/dist/summary/summaryFormat.d.ts +3 -0
- package/dist/summary/summaryFormat.d.ts.map +1 -1
- package/dist/summary/summaryFormat.js +3 -0
- package/dist/summary/summaryFormat.js.map +1 -1
- package/lib/blobManager.d.ts +3 -2
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +30 -26
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +14 -69
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +121 -150
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +3 -1
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +1 -1
- package/lib/dataStores.js +4 -4
- package/lib/dataStores.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +6 -3
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +13 -12
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcConfigs.js +2 -2
- package/lib/gc/gcConfigs.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +17 -6
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js +18 -19
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/gc/gcHelpers.d.ts +0 -9
- package/lib/gc/gcHelpers.d.ts.map +1 -1
- package/lib/gc/gcHelpers.js +0 -11
- package/lib/gc/gcHelpers.js.map +1 -1
- package/lib/gc/gcTelemetry.d.ts.map +1 -1
- package/lib/gc/gcTelemetry.js +1 -4
- package/lib/gc/gcTelemetry.js.map +1 -1
- package/lib/gc/index.d.ts +2 -2
- package/lib/gc/index.d.ts.map +1 -1
- package/lib/gc/index.js +2 -2
- package/lib/gc/index.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/index.js.map +1 -1
- package/lib/messageTypes.d.ts +134 -0
- package/lib/messageTypes.d.ts.map +1 -0
- package/lib/messageTypes.js +26 -0
- package/lib/messageTypes.js.map +1 -0
- package/lib/opLifecycle/definitions.d.ts +2 -1
- package/lib/opLifecycle/definitions.d.ts.map +1 -1
- package/lib/opLifecycle/definitions.js.map +1 -1
- package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opDecompressor.js +0 -4
- package/lib/opLifecycle/opDecompressor.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +4 -2
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/opSplitter.js +1 -1
- package/lib/opLifecycle/opSplitter.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +17 -3
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +37 -24
- 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 +2 -2
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +12 -5
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/summary/runningSummarizer.d.ts +1 -1
- package/lib/summary/runningSummarizer.d.ts.map +1 -1
- package/lib/summary/runningSummarizer.js +4 -2
- package/lib/summary/runningSummarizer.js.map +1 -1
- package/lib/summary/summarizer.d.ts +2 -2
- package/lib/summary/summarizer.js +2 -2
- package/lib/summary/summarizer.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.d.ts +1 -1
- package/lib/summary/summarizerNode/summarizerNode.js +1 -1
- package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/lib/summary/summarizerTypes.d.ts +2 -1
- package/lib/summary/summarizerTypes.d.ts.map +1 -1
- package/lib/summary/summarizerTypes.js.map +1 -1
- package/lib/summary/summaryFormat.d.ts +3 -0
- package/lib/summary/summaryFormat.d.ts.map +1 -1
- package/lib/summary/summaryFormat.js +3 -0
- package/lib/summary/summaryFormat.js.map +1 -1
- package/package.json +16 -16
- package/src/blobManager.ts +38 -28
- package/src/containerRuntime.ts +181 -245
- package/src/dataStoreContext.ts +3 -1
- package/src/dataStores.ts +4 -4
- package/src/gc/garbageCollection.md +53 -5
- package/src/gc/garbageCollection.ts +13 -12
- package/src/gc/gcConfigs.ts +3 -3
- package/src/gc/gcDefinitions.ts +18 -19
- package/src/gc/gcEarlyAdoption.md +145 -0
- package/src/gc/gcHelpers.ts +0 -12
- package/src/gc/gcTelemetry.ts +1 -4
- package/src/gc/index.ts +4 -5
- package/src/index.ts +7 -4
- package/src/messageTypes.ts +225 -0
- package/src/opLifecycle/README.md +40 -40
- package/src/opLifecycle/definitions.ts +2 -1
- package/src/opLifecycle/opDecompressor.ts +0 -8
- package/src/opLifecycle/opGroupingManager.ts +7 -6
- package/src/opLifecycle/opSplitter.ts +2 -2
- package/src/opLifecycle/remoteMessageProcessor.ts +54 -33
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +23 -6
- package/src/summary/runningSummarizer.ts +4 -2
- package/src/summary/summarizer.ts +2 -2
- package/src/summary/summarizerNode/summarizerNode.ts +1 -1
- package/src/summary/summarizerTypes.ts +2 -1
- package/src/summary/summaryFormat.ts +3 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
7
|
+
import {
|
|
8
|
+
IEnvelope,
|
|
9
|
+
InboundAttachMessage,
|
|
10
|
+
IAttachMessage,
|
|
11
|
+
IdCreationRangeWithStashedState,
|
|
12
|
+
IdCreationRange,
|
|
13
|
+
} from "@fluidframework/runtime-definitions";
|
|
14
|
+
import { IDataStoreAliasMessage } from "./dataStore";
|
|
15
|
+
import { IChunkedOp } from "./opLifecycle";
|
|
16
|
+
|
|
17
|
+
export enum ContainerMessageType {
|
|
18
|
+
// An op to be delivered to store
|
|
19
|
+
FluidDataStoreOp = "component",
|
|
20
|
+
|
|
21
|
+
// Creates a new store
|
|
22
|
+
Attach = "attach",
|
|
23
|
+
|
|
24
|
+
// Chunked operation.
|
|
25
|
+
ChunkedOp = "chunkedOp",
|
|
26
|
+
|
|
27
|
+
// Signifies that a blob has been attached and should not be garbage collected by storage
|
|
28
|
+
BlobAttach = "blobAttach",
|
|
29
|
+
|
|
30
|
+
// Ties our new clientId to our old one on reconnect
|
|
31
|
+
Rejoin = "rejoin",
|
|
32
|
+
|
|
33
|
+
// Sets the alias of a root data store
|
|
34
|
+
Alias = "alias",
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* An op containing an IdRange of Ids allocated using the runtime's IdCompressor since
|
|
38
|
+
* the last allocation op was sent.
|
|
39
|
+
* See the [IdCompressor README](./id-compressor/README.md) for more details.
|
|
40
|
+
*/
|
|
41
|
+
IdAllocation = "idAllocation",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* How should an older client handle an unrecognized remote op type?
|
|
46
|
+
*
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
export type CompatModeBehavior =
|
|
50
|
+
/** Ignore the op. It won't be persisted if this client summarizes */
|
|
51
|
+
| "Ignore"
|
|
52
|
+
/** Fail processing immediately. (The container will close) */
|
|
53
|
+
| "FailToProcess";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* All the info an older client would need to know how to handle an unrecognized remote op type
|
|
57
|
+
*
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
export interface IContainerRuntimeMessageCompatDetails {
|
|
61
|
+
/** How should an older client handle an unrecognized remote op type? */
|
|
62
|
+
behavior: CompatModeBehavior;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The unpacked runtime message / details to be handled or dispatched by the ContainerRuntime.
|
|
67
|
+
* Message type are differentiated via a `type` string and contain different contents depending on their type.
|
|
68
|
+
*
|
|
69
|
+
* IMPORTANT: when creating one to be serialized, set the properties in the order they appear here.
|
|
70
|
+
* This way stringified values can be compared.
|
|
71
|
+
*/
|
|
72
|
+
interface TypedContainerRuntimeMessage<TType extends ContainerMessageType, TContents> {
|
|
73
|
+
/** Type of the op, within the ContainerRuntime's domain */
|
|
74
|
+
type: TType;
|
|
75
|
+
/** Domain-specific contents, interpreted according to the type */
|
|
76
|
+
contents: TContents;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Additional details expected for any recently added message.
|
|
81
|
+
* @internal
|
|
82
|
+
*/
|
|
83
|
+
export interface RecentlyAddedContainerRuntimeMessageDetails {
|
|
84
|
+
/** Info describing how to handle this op in case the type is unrecognized (default: fail to process) */
|
|
85
|
+
compatDetails: IContainerRuntimeMessageCompatDetails;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type ContainerRuntimeDataStoreOpMessage = TypedContainerRuntimeMessage<
|
|
89
|
+
ContainerMessageType.FluidDataStoreOp,
|
|
90
|
+
IEnvelope
|
|
91
|
+
>;
|
|
92
|
+
export type InboundContainerRuntimeAttachMessage = TypedContainerRuntimeMessage<
|
|
93
|
+
ContainerMessageType.Attach,
|
|
94
|
+
InboundAttachMessage
|
|
95
|
+
>;
|
|
96
|
+
export type OutboundContainerRuntimeAttachMessage = TypedContainerRuntimeMessage<
|
|
97
|
+
ContainerMessageType.Attach,
|
|
98
|
+
IAttachMessage
|
|
99
|
+
>;
|
|
100
|
+
export type ContainerRuntimeChunkedOpMessage = TypedContainerRuntimeMessage<
|
|
101
|
+
ContainerMessageType.ChunkedOp,
|
|
102
|
+
IChunkedOp
|
|
103
|
+
>;
|
|
104
|
+
export type ContainerRuntimeBlobAttachMessage = TypedContainerRuntimeMessage<
|
|
105
|
+
ContainerMessageType.BlobAttach,
|
|
106
|
+
undefined
|
|
107
|
+
>;
|
|
108
|
+
export type ContainerRuntimeRejoinMessage = TypedContainerRuntimeMessage<
|
|
109
|
+
ContainerMessageType.Rejoin,
|
|
110
|
+
undefined
|
|
111
|
+
>;
|
|
112
|
+
export type ContainerRuntimeAliasMessage = TypedContainerRuntimeMessage<
|
|
113
|
+
ContainerMessageType.Alias,
|
|
114
|
+
IDataStoreAliasMessage
|
|
115
|
+
>;
|
|
116
|
+
export type LocalContainerRuntimeIdAllocationMessage = TypedContainerRuntimeMessage<
|
|
117
|
+
ContainerMessageType.IdAllocation,
|
|
118
|
+
IdCreationRangeWithStashedState
|
|
119
|
+
>;
|
|
120
|
+
export type ContainerRuntimeIdAllocationMessage = TypedContainerRuntimeMessage<
|
|
121
|
+
ContainerMessageType.IdAllocation,
|
|
122
|
+
IdCreationRange & { stashedState?: never }
|
|
123
|
+
>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Represents an unrecognized {@link TypedContainerRuntimeMessage}, e.g. a message from a future version of the container runtime.
|
|
127
|
+
* @internal
|
|
128
|
+
*/
|
|
129
|
+
export interface UnknownContainerRuntimeMessage
|
|
130
|
+
extends Partial<RecentlyAddedContainerRuntimeMessageDetails> {
|
|
131
|
+
/** Invalid type of the op, within the ContainerRuntime's domain. This value should never exist at runtime.
|
|
132
|
+
* This is useful for type narrowing but should never be used as an actual message type at runtime.
|
|
133
|
+
* Actual value will not be "__unknown...", but the type `Exclude<string, ContainerMessageType>` is not supported.
|
|
134
|
+
*/
|
|
135
|
+
type: "__unknown_container_message_type__never_use_as_value__";
|
|
136
|
+
|
|
137
|
+
/** Domain-specific contents, but not decipherable by an unknown op. */
|
|
138
|
+
contents: unknown;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* A {@link TypedContainerRuntimeMessage} that is received from the server and will be processed by the container runtime.
|
|
143
|
+
*/
|
|
144
|
+
export type InboundContainerRuntimeMessage =
|
|
145
|
+
| ContainerRuntimeDataStoreOpMessage
|
|
146
|
+
| InboundContainerRuntimeAttachMessage
|
|
147
|
+
| ContainerRuntimeChunkedOpMessage
|
|
148
|
+
| ContainerRuntimeBlobAttachMessage
|
|
149
|
+
| ContainerRuntimeRejoinMessage
|
|
150
|
+
| ContainerRuntimeAliasMessage
|
|
151
|
+
| ContainerRuntimeIdAllocationMessage
|
|
152
|
+
// Inbound messages may include unknown types from other clients, so we include that as a special case here
|
|
153
|
+
| UnknownContainerRuntimeMessage;
|
|
154
|
+
|
|
155
|
+
/** A {@link TypedContainerRuntimeMessage} that has been generated by the container runtime but is not yet being sent to the server. */
|
|
156
|
+
export type LocalContainerRuntimeMessage =
|
|
157
|
+
| ContainerRuntimeDataStoreOpMessage
|
|
158
|
+
| OutboundContainerRuntimeAttachMessage
|
|
159
|
+
| ContainerRuntimeChunkedOpMessage
|
|
160
|
+
| ContainerRuntimeBlobAttachMessage
|
|
161
|
+
| ContainerRuntimeRejoinMessage
|
|
162
|
+
| ContainerRuntimeAliasMessage
|
|
163
|
+
| LocalContainerRuntimeIdAllocationMessage
|
|
164
|
+
// In rare cases (e.g. related to stashed ops) we could have a local message of an unknown type
|
|
165
|
+
| UnknownContainerRuntimeMessage;
|
|
166
|
+
|
|
167
|
+
/** A {@link TypedContainerRuntimeMessage} that is being sent to the server from the container runtime. */
|
|
168
|
+
export type OutboundContainerRuntimeMessage =
|
|
169
|
+
| ContainerRuntimeDataStoreOpMessage
|
|
170
|
+
| OutboundContainerRuntimeAttachMessage
|
|
171
|
+
| ContainerRuntimeChunkedOpMessage
|
|
172
|
+
| ContainerRuntimeBlobAttachMessage
|
|
173
|
+
| ContainerRuntimeRejoinMessage
|
|
174
|
+
| ContainerRuntimeAliasMessage
|
|
175
|
+
| ContainerRuntimeIdAllocationMessage;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* An unpacked ISequencedDocumentMessage with the inner TypedContainerRuntimeMessage type/contents/etc
|
|
179
|
+
* promoted up to the outer object
|
|
180
|
+
*/
|
|
181
|
+
export type InboundSequencedContainerRuntimeMessage = Omit<
|
|
182
|
+
ISequencedDocumentMessage,
|
|
183
|
+
"type" | "contents"
|
|
184
|
+
> &
|
|
185
|
+
InboundContainerRuntimeMessage;
|
|
186
|
+
|
|
187
|
+
/** Essentially ISequencedDocumentMessage except that `type` is not `string` to enable narrowing
|
|
188
|
+
* as `Exclude<string, InboundContainerRuntimeMessage['type']>` is not supported.
|
|
189
|
+
* There should never be a runtime value of "__not_a_...".
|
|
190
|
+
* Currently additionally replaces `contents` type until protocol-definitions update is taken with `unknown` instead of `any`.
|
|
191
|
+
*/
|
|
192
|
+
type InboundSequencedNonContainerRuntimeMessage = Omit<
|
|
193
|
+
ISequencedDocumentMessage,
|
|
194
|
+
"type" | "contents"
|
|
195
|
+
> & { type: "__not_a_container_runtime_message_type__"; contents: unknown };
|
|
196
|
+
|
|
197
|
+
export type InboundSequencedContainerRuntimeMessageOrSystemMessage =
|
|
198
|
+
| InboundSequencedContainerRuntimeMessage
|
|
199
|
+
| InboundSequencedNonContainerRuntimeMessage;
|
|
200
|
+
|
|
201
|
+
/** A [loose] InboundSequencedContainerRuntimeMessage that is recent and may contain compat details.
|
|
202
|
+
* It exists solely to to provide access to those details.
|
|
203
|
+
*/
|
|
204
|
+
export type InboundSequencedRecentlyAddedContainerRuntimeMessage = ISequencedDocumentMessage &
|
|
205
|
+
Partial<RecentlyAddedContainerRuntimeMessageDetails>;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The unpacked runtime message / details to be handled or dispatched by the ContainerRuntime
|
|
209
|
+
*
|
|
210
|
+
* IMPORTANT: when creating one to be serialized, set the properties in the order they appear here.
|
|
211
|
+
* This way stringified values can be compared.
|
|
212
|
+
*
|
|
213
|
+
* @deprecated - this is an internal type which should not be used outside of the package.
|
|
214
|
+
* Internally, it is superseded by `TypedContainerRuntimeMessage`.
|
|
215
|
+
*
|
|
216
|
+
* @internal
|
|
217
|
+
*/
|
|
218
|
+
export interface ContainerRuntimeMessage {
|
|
219
|
+
/** Type of the op, within the ContainerRuntime's domain */
|
|
220
|
+
type: ContainerMessageType;
|
|
221
|
+
/** Domain-specific contents, interpreted according to the type */
|
|
222
|
+
contents: any;
|
|
223
|
+
/** Info describing how to handle this op in case the type is unrecognized (default: fail to process) */
|
|
224
|
+
compatDetails?: IContainerRuntimeMessageCompatDetails;
|
|
225
|
+
}
|
|
@@ -212,8 +212,6 @@ On the receiving end, the client will accumulate chunks 1 and 2 and keep them in
|
|
|
212
212
|
|
|
213
213
|
## How grouped batching works
|
|
214
214
|
|
|
215
|
-
**Note: There are plans to replace empty ops with something more efficient when doing grouped batching AB#4092**
|
|
216
|
-
|
|
217
215
|
Given the following baseline batch:
|
|
218
216
|
|
|
219
217
|
```
|
|
@@ -223,68 +221,70 @@ Given the following baseline batch:
|
|
|
223
221
|
+---------------+---------------+---------------+---------------+---------------+
|
|
224
222
|
```
|
|
225
223
|
|
|
226
|
-
|
|
224
|
+
Grouped batch:
|
|
227
225
|
|
|
228
226
|
```
|
|
229
|
-
|
|
230
|
-
| Op 1
|
|
231
|
-
|
|
|
232
|
-
|
|
|
233
|
-
|
|
227
|
+
+---------------------------------------------------------------------------------------------------------------------+
|
|
228
|
+
| Op 1 Contents: +----------------+---------------+---------------+---------------+---------------+ |
|
|
229
|
+
| Type: "groupedBatch" | Op 1 | Op 2 | Op 3 | Op 4 | Op 5 | |
|
|
230
|
+
| | Contents: "a" | Contents: "b" | Contents: "c" | Contents: "d" | Contents: "e" | |
|
|
231
|
+
| +----------------+---------------+---------------+---------------+---------------+ |
|
|
232
|
+
+---------------------------------------------------------------------------------------------------------------------+
|
|
234
233
|
```
|
|
235
234
|
|
|
236
|
-
|
|
235
|
+
Compressed batch:
|
|
237
236
|
|
|
238
237
|
```
|
|
239
|
-
|
|
240
|
-
| Op 1 Contents:
|
|
241
|
-
|
|
|
242
|
-
|
|
|
243
|
-
| |
|
|
244
|
-
|
|
|
245
|
-
|
|
238
|
+
+-------------------------------------------------------------------------------------------------------------------------+
|
|
239
|
+
| Op 1 Contents: +------------------------------------------------------------------------------------+ |
|
|
240
|
+
| Compression: 'lz4' | Type: "groupedBatch" | |
|
|
241
|
+
| | +----------------+---------------+---------------+---------------+---------------+ | |
|
|
242
|
+
| | | Op 1 | Op 2 | Op 3 | Op 4 | Op 5 | | |
|
|
243
|
+
| | | Contents: "a" | Contents: "b" | Contents: "c" | Contents: "d" | Contents: "e" | | |
|
|
244
|
+
| | +----------------+---------------+---------------+---------------+---------------+ | |
|
|
245
|
+
| +------------------------------------------------------------------------------------+ |
|
|
246
|
+
+-------------------------------------------------------------------------------------------------------------------------+
|
|
246
247
|
```
|
|
247
248
|
|
|
248
249
|
Can produce the following chunks:
|
|
249
250
|
|
|
250
251
|
```
|
|
251
|
-
|
|
252
|
-
| Chunk 1/2 Contents:
|
|
253
|
-
|
|
|
254
|
-
| |
|
|
255
|
-
| |
|
|
256
|
-
|
|
|
257
|
-
|
|
252
|
+
+------------------------------------------------+
|
|
253
|
+
| Chunk 1/2 Contents: +---------------------+ |
|
|
254
|
+
| | +-----------------+ | |
|
|
255
|
+
| | | Contents: "abc" | | |
|
|
256
|
+
| | +-----------------+ | |
|
|
257
|
+
| +---------------------+ |
|
|
258
|
+
+------------------------------------------------+
|
|
258
259
|
```
|
|
259
260
|
|
|
260
261
|
```
|
|
261
|
-
|
|
262
|
-
| Chunk 2/2 Contents:
|
|
263
|
-
|
|
|
264
|
-
| |
|
|
265
|
-
| |
|
|
266
|
-
|
|
|
267
|
-
|
|
262
|
+
+-----------------------------------------------+
|
|
263
|
+
| Chunk 2/2 Contents: +--------------------+ |
|
|
264
|
+
| | +----------------+ | |
|
|
265
|
+
| | | Contents: "de" | | |
|
|
266
|
+
| | +----------------+ | |
|
|
267
|
+
| +--------------------+ |
|
|
268
|
+
+-----------------------------------------------+
|
|
268
269
|
```
|
|
269
270
|
|
|
270
271
|
- Send to service
|
|
271
272
|
- Service acks ops sent
|
|
272
273
|
- Receive chunks from service
|
|
273
|
-
- Recompile to the
|
|
274
|
+
- Recompile to the compression step
|
|
274
275
|
|
|
275
|
-
|
|
276
|
+
Decompressed batch:
|
|
276
277
|
|
|
277
278
|
```
|
|
278
|
-
|
|
279
|
-
| Op 1
|
|
280
|
-
|
|
|
281
|
-
|
|
|
282
|
-
|
|
|
283
|
-
|
|
284
|
-
+--------------------+-----------------+-----------------+-----------------+-----------------+
|
|
279
|
+
+---------------------------------------------------------------------------------------------------------------------+
|
|
280
|
+
| Op 1 Contents: +----------------+---------------+---------------+---------------+---------------+ |
|
|
281
|
+
| SeqNum: 2 | Op 1 | Op 2 | Op 3 | Op 4 | Op 5 | |
|
|
282
|
+
| Type: "groupedBatch" | Contents: "a" | Contents: "b" | Contents: "c" | Contents: "d" | Contents: "e" | |
|
|
283
|
+
| +----------------+---------------+---------------+---------------+---------------+ |
|
|
284
|
+
+---------------------------------------------------------------------------------------------------------------------+
|
|
285
285
|
```
|
|
286
286
|
|
|
287
|
-
|
|
287
|
+
Ungrouped batch:
|
|
288
288
|
|
|
289
289
|
```
|
|
290
290
|
+-----------------+-----------------+-----------------+-----------------+-----------------+
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import { IBatchMessage } from "@fluidframework/container-definitions";
|
|
7
7
|
import { ISequencedDocumentMessage, MessageType } from "@fluidframework/protocol-definitions";
|
|
8
|
-
import { CompressionAlgorithms
|
|
8
|
+
import { CompressionAlgorithms } from "..";
|
|
9
|
+
import { ContainerMessageType } from "../messageTypes";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Batch message type used internally by the runtime
|
|
@@ -50,14 +50,6 @@ export class OpDecompressor {
|
|
|
50
50
|
) {
|
|
51
51
|
// Beginning of a compressed batch
|
|
52
52
|
assert(this.activeBatch === false, 0x4b8 /* shouldn't have multiple active batches */);
|
|
53
|
-
if (message.compression) {
|
|
54
|
-
// lz4 is the only supported compression algorithm for now
|
|
55
|
-
assert(
|
|
56
|
-
message.compression === CompressionAlgorithms.lz4,
|
|
57
|
-
0x4b9 /* lz4 is currently the only supported compression algorithm */,
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
53
|
this.activeBatch = true;
|
|
62
54
|
|
|
63
55
|
const contents = IsoBuffer.from(
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { assert } from "@fluidframework/core-utils";
|
|
7
7
|
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
8
|
-
import { ContainerMessageType } from "
|
|
8
|
+
import { ContainerMessageType } from "../messageTypes";
|
|
9
9
|
import { IBatch } from "./definitions";
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -22,6 +22,10 @@ interface IGroupedMessage {
|
|
|
22
22
|
compression?: string;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function isGroupContents(opContents: any): opContents is IGroupedBatchMessageContents {
|
|
26
|
+
return opContents?.type === OpGroupingManager.groupedBatchOp;
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
export class OpGroupingManager {
|
|
26
30
|
static readonly groupedBatchOp = "groupedBatch";
|
|
27
31
|
|
|
@@ -68,14 +72,11 @@ export class OpGroupingManager {
|
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
public ungroupOp(op: ISequencedDocumentMessage): ISequencedDocumentMessage[] {
|
|
71
|
-
if (
|
|
72
|
-
(op.contents as { type?: unknown } | undefined)?.type !==
|
|
73
|
-
OpGroupingManager.groupedBatchOp
|
|
74
|
-
) {
|
|
75
|
+
if (!isGroupContents(op.contents)) {
|
|
75
76
|
return [op];
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
const messages =
|
|
79
|
+
const messages = op.contents.contents;
|
|
79
80
|
let fakeCsn = 1;
|
|
80
81
|
return messages.map((subMessage) => ({
|
|
81
82
|
...op,
|
|
@@ -12,7 +12,7 @@ import { assert } from "@fluidframework/core-utils";
|
|
|
12
12
|
import { IBatchMessage } from "@fluidframework/container-definitions";
|
|
13
13
|
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
14
14
|
import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
|
|
15
|
-
import { ContainerMessageType,
|
|
15
|
+
import { ContainerMessageType, ContainerRuntimeChunkedOpMessage } from "../messageTypes";
|
|
16
16
|
import { estimateSocketSize } from "./batchManager";
|
|
17
17
|
import { BatchMessage, IBatch, IChunkedOp, IMessageProcessingResult } from "./definitions";
|
|
18
18
|
|
|
@@ -210,7 +210,7 @@ const chunkToBatchMessage = (
|
|
|
210
210
|
referenceSequenceNumber: number,
|
|
211
211
|
metadata: Record<string, unknown> | undefined = undefined,
|
|
212
212
|
): BatchMessage => {
|
|
213
|
-
const payload:
|
|
213
|
+
const payload: ContainerRuntimeChunkedOpMessage = {
|
|
214
214
|
type: ContainerMessageType.ChunkedOp,
|
|
215
215
|
contents: chunk,
|
|
216
216
|
};
|
|
@@ -6,13 +6,21 @@
|
|
|
6
6
|
import { ISequencedDocumentMessage, MessageType } from "@fluidframework/protocol-definitions";
|
|
7
7
|
import {
|
|
8
8
|
ContainerMessageType,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
type InboundContainerRuntimeMessage,
|
|
10
|
+
type InboundSequencedContainerRuntimeMessage,
|
|
11
|
+
type InboundSequencedContainerRuntimeMessageOrSystemMessage,
|
|
12
|
+
type InboundSequencedRecentlyAddedContainerRuntimeMessage,
|
|
13
|
+
} from "../messageTypes";
|
|
12
14
|
import { OpDecompressor } from "./opDecompressor";
|
|
13
15
|
import { OpGroupingManager } from "./opGroupingManager";
|
|
14
16
|
import { OpSplitter } from "./opSplitter";
|
|
15
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Stateful class for processing incoming remote messages as the virtualization measures are unwrapped,
|
|
20
|
+
* potentially across numerous inbound ops.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
16
24
|
export class RemoteMessageProcessor {
|
|
17
25
|
constructor(
|
|
18
26
|
private readonly opSplitter: OpSplitter,
|
|
@@ -30,14 +38,25 @@ export class RemoteMessageProcessor {
|
|
|
30
38
|
|
|
31
39
|
/**
|
|
32
40
|
* Ungroups and Unchunks the runtime ops encapsulated by the single remoteMessage received over the wire
|
|
33
|
-
* @param
|
|
34
|
-
*
|
|
41
|
+
* @param remoteMessageCopy - A shallow copy of a message from another client, possibly virtualized
|
|
42
|
+
* (grouped, compressed, and/or chunked).
|
|
43
|
+
* Being a shallow copy, it's considered mutable, meaning no other Container or other parallel procedure
|
|
44
|
+
* depends on this object instance.
|
|
45
|
+
* Note remoteMessageCopy.contents (and other object props) MUST not be modified,
|
|
46
|
+
* but may be overwritten (as is the case with contents).
|
|
47
|
+
* @returns the unchunked, decompressed, ungrouped, unpacked SequencedContainerRuntimeMessages encapsulated in the remote message.
|
|
48
|
+
* For ops that weren't virtualized (e.g. System ops that the ContainerRuntime will ultimately ignore),
|
|
49
|
+
* a singleton array [remoteMessageCopy] is returned
|
|
35
50
|
*/
|
|
36
|
-
public process(
|
|
37
|
-
|
|
51
|
+
public process(
|
|
52
|
+
remoteMessageCopy: ISequencedDocumentMessage,
|
|
53
|
+
): InboundSequencedContainerRuntimeMessageOrSystemMessage[] {
|
|
54
|
+
const result: InboundSequencedContainerRuntimeMessageOrSystemMessage[] = [];
|
|
55
|
+
|
|
56
|
+
ensureContentsDeserialized(remoteMessageCopy);
|
|
38
57
|
|
|
39
58
|
// Ungroup before and after decompression for back-compat (cleanup tracked by AB#4371)
|
|
40
|
-
for (const ungroupedMessage of this.opGroupingManager.ungroupOp(
|
|
59
|
+
for (const ungroupedMessage of this.opGroupingManager.ungroupOp(remoteMessageCopy)) {
|
|
41
60
|
const message = this.opDecompressor.processMessage(ungroupedMessage).message;
|
|
42
61
|
|
|
43
62
|
for (let ungroupedMessage2 of this.opGroupingManager.ungroupOp(message)) {
|
|
@@ -50,7 +69,9 @@ export class RemoteMessageProcessor {
|
|
|
50
69
|
if (chunkProcessingResult.state !== "Processed") {
|
|
51
70
|
// If the message is not chunked or if the splitter is still rebuilding the original message,
|
|
52
71
|
// there is no need to continue processing
|
|
53
|
-
result.push(
|
|
72
|
+
result.push(
|
|
73
|
+
ungroupedMessage2 as InboundSequencedContainerRuntimeMessageOrSystemMessage,
|
|
74
|
+
);
|
|
54
75
|
continue;
|
|
55
76
|
}
|
|
56
77
|
|
|
@@ -68,7 +89,9 @@ export class RemoteMessageProcessor {
|
|
|
68
89
|
if (decompressionAfterChunking.state === "Skipped") {
|
|
69
90
|
// After chunking, if the original message was not compressed,
|
|
70
91
|
// there is no need to continue processing
|
|
71
|
-
result.push(
|
|
92
|
+
result.push(
|
|
93
|
+
ungroupedMessageAfterChunking2 as InboundSequencedContainerRuntimeMessageOrSystemMessage,
|
|
94
|
+
);
|
|
72
95
|
continue;
|
|
73
96
|
}
|
|
74
97
|
|
|
@@ -84,40 +107,38 @@ export class RemoteMessageProcessor {
|
|
|
84
107
|
}
|
|
85
108
|
}
|
|
86
109
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// There might be multiple container instances receiving same message
|
|
90
|
-
// We do not need to make deep copy, as each layer will just replace message.content itself,
|
|
91
|
-
// but would not modify contents details
|
|
92
|
-
const message = { ...remoteMessage };
|
|
93
|
-
|
|
110
|
+
/** Takes an incoming message and if the contents is a string, JSON.parse's it in place */
|
|
111
|
+
function ensureContentsDeserialized(mutableMessage: ISequencedDocumentMessage): void {
|
|
94
112
|
// back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
|
|
95
113
|
// System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
|
|
96
114
|
// Old ops may contain empty string (I assume noops).
|
|
97
|
-
if (typeof
|
|
98
|
-
|
|
115
|
+
if (typeof mutableMessage.contents === "string" && mutableMessage.contents !== "") {
|
|
116
|
+
mutableMessage.contents = JSON.parse(mutableMessage.contents);
|
|
99
117
|
}
|
|
100
|
-
|
|
101
|
-
return message;
|
|
102
|
-
};
|
|
118
|
+
}
|
|
103
119
|
|
|
104
120
|
/**
|
|
105
|
-
* For a given message, it moves the nested
|
|
121
|
+
* For a given message, it moves the nested InboundContainerRuntimeMessage props one level up.
|
|
106
122
|
*
|
|
107
123
|
* The return type illustrates the assumption that the message param
|
|
108
|
-
* becomes a
|
|
109
|
-
* (but there is no runtime validation of the 'type' or 'compatDetails' values)
|
|
124
|
+
* becomes a InboundSequencedContainerRuntimeMessage by the time the function returns
|
|
125
|
+
* (but there is no runtime validation of the 'type' or 'compatDetails' values).
|
|
110
126
|
*/
|
|
111
127
|
function unpack(
|
|
112
128
|
message: ISequencedDocumentMessage,
|
|
113
|
-
): asserts message is
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
): asserts message is InboundSequencedContainerRuntimeMessage {
|
|
130
|
+
// We assume the contents is an InboundContainerRuntimeMessage (the message is "packed")
|
|
131
|
+
const contents = message.contents as InboundContainerRuntimeMessage;
|
|
132
|
+
|
|
133
|
+
// We're going to unpack message in-place (promoting those properties of contents up to message itself)
|
|
134
|
+
const messageUnpacked = message as InboundSequencedContainerRuntimeMessage;
|
|
135
|
+
|
|
136
|
+
messageUnpacked.type = contents.type;
|
|
137
|
+
messageUnpacked.contents = contents.contents;
|
|
138
|
+
if ("compatDetails" in contents) {
|
|
139
|
+
(messageUnpacked as InboundSequencedRecentlyAddedContainerRuntimeMessage).compatDetails =
|
|
140
|
+
contents.compatDetails;
|
|
141
|
+
}
|
|
121
142
|
}
|
|
122
143
|
|
|
123
144
|
/**
|
package/src/packageVersion.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { ICriticalContainerError } from "@fluidframework/container-definitions";
|
|
|
11
11
|
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
12
12
|
import { DataProcessingError, ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
|
|
13
13
|
|
|
14
|
-
import { ContainerMessageType,
|
|
14
|
+
import { ContainerMessageType, InboundSequencedContainerRuntimeMessage } from "./messageTypes";
|
|
15
15
|
import { pkgVersion } from "./packageVersion";
|
|
16
16
|
import { IBatchMetadata } from "./metadata";
|
|
17
17
|
|
|
@@ -69,6 +69,26 @@ export interface IRuntimeStateHandler {
|
|
|
69
69
|
isActiveConnection: () => boolean;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/** Union of keys of T */
|
|
73
|
+
type KeysOfUnion<T extends object> = T extends T ? keyof T : never;
|
|
74
|
+
/** *Partial* type all possible combinations of properties and values of union T.
|
|
75
|
+
* This loosens typing allowing access to all possible properties without
|
|
76
|
+
* narrowing.
|
|
77
|
+
*/
|
|
78
|
+
type AnyComboFromUnion<T extends object> = { [P in KeysOfUnion<T>]?: T[P] };
|
|
79
|
+
|
|
80
|
+
function buildPendingMessageContent(
|
|
81
|
+
// AnyComboFromUnion is needed need to gain access to compatDetails that
|
|
82
|
+
// is only defined for some cases.
|
|
83
|
+
message: AnyComboFromUnion<InboundSequencedContainerRuntimeMessage>,
|
|
84
|
+
): string {
|
|
85
|
+
// IMPORTANT: Order matters here, this must match the order of the properties used
|
|
86
|
+
// when submitting the message.
|
|
87
|
+
const { type, contents, compatDetails } = message;
|
|
88
|
+
// Any properties that are not defined, won't be emitted by stringify.
|
|
89
|
+
return JSON.stringify({ type, contents, compatDetails });
|
|
90
|
+
}
|
|
91
|
+
|
|
72
92
|
/**
|
|
73
93
|
* PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been
|
|
74
94
|
* acknowledged by the server. It also maintains the batch information for both automatically and manually flushed
|
|
@@ -242,7 +262,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
242
262
|
* the batch information was preserved for batch messages.
|
|
243
263
|
* @param message - The message that got ack'd and needs to be processed.
|
|
244
264
|
*/
|
|
245
|
-
public processPendingLocalMessage(message:
|
|
265
|
+
public processPendingLocalMessage(message: InboundSequencedContainerRuntimeMessage): unknown {
|
|
246
266
|
// Pre-processing part - This may be the start of a batch.
|
|
247
267
|
this.maybeProcessBatchBegin(message);
|
|
248
268
|
|
|
@@ -256,10 +276,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
256
276
|
|
|
257
277
|
this.pendingMessages.shift();
|
|
258
278
|
|
|
259
|
-
|
|
260
|
-
// when submitting the message.
|
|
261
|
-
const { type, contents, compatDetails } = message;
|
|
262
|
-
const messageContent = JSON.stringify({ type, contents, compatDetails });
|
|
279
|
+
const messageContent = buildPendingMessageContent(message);
|
|
263
280
|
|
|
264
281
|
// Stringified content should match
|
|
265
282
|
if (pendingMessage.content !== messageContent) {
|
|
@@ -308,7 +308,8 @@ export class RunningSummarizer extends TypedEventEmitter<ISummarizerEvents> impl
|
|
|
308
308
|
// latest version with which we will refresh the state. However in case of single commit
|
|
309
309
|
// summary, we might me missing a summary ack, so in that case we are still fine as the
|
|
310
310
|
// code in `submitSummary` function in container runtime, will refresh the latest state
|
|
311
|
-
// by calling `
|
|
311
|
+
// by calling `prefetchLatestSummaryThenClose`. We will load the next summarizer from the
|
|
312
|
+
// updated state and be fine.
|
|
312
313
|
const isIgnoredError =
|
|
313
314
|
isFluidError(error) &&
|
|
314
315
|
error.errorType === DriverErrorTypes.fileNotFoundOrAccessDeniedError;
|
|
@@ -407,6 +408,7 @@ export class RunningSummarizer extends TypedEventEmitter<ISummarizerEvents> impl
|
|
|
407
408
|
!this.heuristicRunnerMicroTaskExists
|
|
408
409
|
) {
|
|
409
410
|
this.heuristicRunnerMicroTaskExists = true;
|
|
411
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
410
412
|
Promise.resolve()
|
|
411
413
|
.then(() => {
|
|
412
414
|
this.heuristicRunner?.run();
|
|
@@ -519,7 +521,7 @@ export class RunningSummarizer extends TypedEventEmitter<ISummarizerEvents> impl
|
|
|
519
521
|
* @param before - set of instructions to run before running the action.
|
|
520
522
|
* @param action - action to perform.
|
|
521
523
|
* @param after - set of instructions to run after running the action.
|
|
522
|
-
* @returns
|
|
524
|
+
* @returns The result of the action.
|
|
523
525
|
*/
|
|
524
526
|
private async lockedSummaryAction<T>(
|
|
525
527
|
before: () => void,
|