@fluid-experimental/attributor 2.1.0 → 2.3.0-288113
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 +4 -0
- package/README.md +2 -2
- package/dist/attributorContracts.d.ts +49 -0
- package/dist/attributorContracts.d.ts.map +1 -0
- package/dist/attributorContracts.js +22 -0
- package/dist/attributorContracts.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/mixinAttributor.d.ts +8 -49
- package/dist/mixinAttributor.d.ts.map +1 -1
- package/dist/mixinAttributor.js +35 -140
- package/dist/mixinAttributor.js.map +1 -1
- package/dist/runtimeAttributor.d.ts +19 -0
- package/dist/runtimeAttributor.d.ts.map +1 -0
- package/dist/runtimeAttributor.js +71 -0
- package/dist/runtimeAttributor.js.map +1 -0
- package/dist/runtimeAttributorDataStoreChannel.d.ts +92 -0
- package/dist/runtimeAttributorDataStoreChannel.d.ts.map +1 -0
- package/dist/runtimeAttributorDataStoreChannel.js +177 -0
- package/dist/runtimeAttributorDataStoreChannel.js.map +1 -0
- package/dist/runtimeAttributorDataStoreFactory.d.ts +15 -0
- package/dist/runtimeAttributorDataStoreFactory.d.ts.map +1 -0
- package/dist/runtimeAttributorDataStoreFactory.js +39 -0
- package/dist/runtimeAttributorDataStoreFactory.js.map +1 -0
- package/lib/attributorContracts.d.ts +49 -0
- package/lib/attributorContracts.d.ts.map +1 -0
- package/lib/attributorContracts.js +19 -0
- package/lib/attributorContracts.js.map +1 -0
- 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/mixinAttributor.d.ts +8 -49
- package/lib/mixinAttributor.d.ts.map +1 -1
- package/lib/mixinAttributor.js +33 -138
- package/lib/mixinAttributor.js.map +1 -1
- package/lib/runtimeAttributor.d.ts +19 -0
- package/lib/runtimeAttributor.d.ts.map +1 -0
- package/lib/runtimeAttributor.js +67 -0
- package/lib/runtimeAttributor.js.map +1 -0
- package/lib/runtimeAttributorDataStoreChannel.d.ts +92 -0
- package/lib/runtimeAttributorDataStoreChannel.d.ts.map +1 -0
- package/lib/runtimeAttributorDataStoreChannel.js +173 -0
- package/lib/runtimeAttributorDataStoreChannel.js.map +1 -0
- package/lib/runtimeAttributorDataStoreFactory.d.ts +15 -0
- package/lib/runtimeAttributorDataStoreFactory.d.ts.map +1 -0
- package/lib/runtimeAttributorDataStoreFactory.js +35 -0
- package/lib/runtimeAttributorDataStoreFactory.js.map +1 -0
- package/package.json +20 -19
- package/src/attributorContracts.ts +61 -0
- package/src/index.ts +3 -3
- package/src/mixinAttributor.ts +45 -267
- package/src/runtimeAttributor.ts +111 -0
- package/src/runtimeAttributorDataStoreChannel.ts +261 -0
- package/src/runtimeAttributorDataStoreFactory.ts +59 -0
package/src/mixinAttributor.ts
CHANGED
|
@@ -3,11 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
type IDeltaManager,
|
|
9
|
-
type IContainerContext,
|
|
10
|
-
} from "@fluidframework/container-definitions/internal";
|
|
6
|
+
import { type IContainerContext } from "@fluidframework/container-definitions/internal";
|
|
11
7
|
import { ContainerRuntime } from "@fluidframework/container-runtime/internal";
|
|
12
8
|
import type { IContainerRuntimeOptions } from "@fluidframework/container-runtime/internal";
|
|
13
9
|
import { type IContainerRuntime } from "@fluidframework/container-runtime-definitions/internal";
|
|
@@ -16,102 +12,39 @@ import {
|
|
|
16
12
|
type IRequest,
|
|
17
13
|
type IResponse,
|
|
18
14
|
} from "@fluidframework/core-interfaces";
|
|
19
|
-
import { assert
|
|
20
|
-
import { type
|
|
21
|
-
import {
|
|
22
|
-
type IDocumentMessage,
|
|
23
|
-
type ISnapshotTree,
|
|
24
|
-
type ISequencedDocumentMessage,
|
|
25
|
-
} from "@fluidframework/driver-definitions/internal";
|
|
26
|
-
import {
|
|
27
|
-
type ISummaryTreeWithStats,
|
|
28
|
-
type ITelemetryContext,
|
|
29
|
-
type AttributionInfo,
|
|
30
|
-
type AttributionKey,
|
|
31
|
-
type NamedFluidDataStoreRegistryEntries,
|
|
32
|
-
} from "@fluidframework/runtime-definitions/internal";
|
|
33
|
-
import {
|
|
34
|
-
SummaryTreeBuilder,
|
|
35
|
-
addSummarizeResultToSummary,
|
|
36
|
-
} from "@fluidframework/runtime-utils/internal";
|
|
37
|
-
import {
|
|
38
|
-
PerformanceEvent,
|
|
39
|
-
UsageError,
|
|
40
|
-
createChildLogger,
|
|
41
|
-
loggerToMonitoringContext,
|
|
42
|
-
} from "@fluidframework/telemetry-utils/internal";
|
|
43
|
-
|
|
44
|
-
import { Attributor, type IAttributor, OpStreamAttributor } from "./attributor.js";
|
|
45
|
-
import { AttributorSerializer, type Encoder, chain, deltaEncoder } from "./encoders.js";
|
|
46
|
-
import { makeLZ4Encoder } from "./lz4Encoder.js";
|
|
47
|
-
|
|
48
|
-
// Summary tree keys
|
|
49
|
-
const attributorTreeName = ".attributor";
|
|
50
|
-
const opBlobName = "op";
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* @internal
|
|
54
|
-
*/
|
|
55
|
-
export const enableOnNewFileKey = "Fluid.Attribution.EnableOnNewFile";
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* @internal
|
|
59
|
-
*/
|
|
60
|
-
export const IRuntimeAttributor: keyof IProvideRuntimeAttributor = "IRuntimeAttributor";
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* @internal
|
|
64
|
-
*/
|
|
65
|
-
export interface IProvideRuntimeAttributor {
|
|
66
|
-
readonly IRuntimeAttributor: IRuntimeAttributor;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Provides access to attribution information stored on the container runtime.
|
|
71
|
-
*
|
|
72
|
-
* @remarks Attributors are only populated after the container runtime into which they are being injected has initialized.
|
|
73
|
-
*
|
|
74
|
-
* @sealed
|
|
75
|
-
* @internal
|
|
76
|
-
*/
|
|
77
|
-
export interface IRuntimeAttributor extends IProvideRuntimeAttributor {
|
|
78
|
-
/**
|
|
79
|
-
* @throws - If no AttributionInfo exists for this key.
|
|
80
|
-
*/
|
|
81
|
-
get(key: AttributionKey): AttributionInfo;
|
|
15
|
+
import { assert } from "@fluidframework/core-utils/internal";
|
|
16
|
+
import { type NamedFluidDataStoreRegistryEntries } from "@fluidframework/runtime-definitions/internal";
|
|
17
|
+
import { loggerToMonitoringContext } from "@fluidframework/telemetry-utils/internal";
|
|
82
18
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
* If enabled, the runtime attributor can be asked for the attribution info for different keys.
|
|
91
|
-
* See {@link mixinAttributor} for more details on when this happens.
|
|
92
|
-
*/
|
|
93
|
-
readonly isEnabled: boolean;
|
|
94
|
-
}
|
|
19
|
+
import {
|
|
20
|
+
attributorDataStoreAlias,
|
|
21
|
+
enableOnNewFileKey,
|
|
22
|
+
type IProvideRuntimeAttributor,
|
|
23
|
+
type IRuntimeAttributor,
|
|
24
|
+
} from "./attributorContracts.js";
|
|
25
|
+
import { RuntimeAttributorFactory } from "./runtimeAttributorDataStoreFactory.js";
|
|
95
26
|
|
|
96
27
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* @
|
|
100
|
-
*
|
|
28
|
+
* Utility function to get the runtime attributor from the container runtime.
|
|
29
|
+
* @param runtime - container runtime from which attributor is to be fetched.
|
|
30
|
+
* @returns IRuntimeAttributor if it exists, otherwise undefined.
|
|
101
31
|
* @internal
|
|
102
32
|
*/
|
|
103
|
-
export function
|
|
104
|
-
|
|
33
|
+
export async function getRuntimeAttributor(
|
|
34
|
+
runtime: IContainerRuntime,
|
|
35
|
+
): Promise<IRuntimeAttributor | undefined> {
|
|
36
|
+
const entryPoint = await runtime.getAliasedDataStoreEntryPoint(attributorDataStoreAlias);
|
|
37
|
+
const runtimeAttributor = (await entryPoint?.get()) as
|
|
38
|
+
| FluidObject<IProvideRuntimeAttributor>
|
|
39
|
+
| undefined;
|
|
40
|
+
return runtimeAttributor?.IRuntimeAttributor;
|
|
105
41
|
}
|
|
106
42
|
|
|
107
43
|
/**
|
|
108
44
|
* Mixes in logic to load and store runtime-based attribution functionality.
|
|
109
45
|
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* Existing documents without stored attributors will not start storing attribution information: if an
|
|
113
|
-
* IRuntimeAttributor is passed via scope to load a document that never previously had attribution information,
|
|
114
|
-
* that attributor's `has` method will always return `false`.
|
|
46
|
+
* Existing documents without stored attributor will not start storing attribution information. We only create the attributor
|
|
47
|
+
* if its tracking is enabled and we are creating a new document.
|
|
115
48
|
* @param Base - base class, inherits from FluidAttributorRuntime
|
|
116
49
|
* @internal
|
|
117
50
|
*/
|
|
@@ -143,29 +76,12 @@ export const mixinAttributor = (
|
|
|
143
76
|
containerRuntimeCtor = ContainerRuntimeWithAttributor as unknown as typeof ContainerRuntime,
|
|
144
77
|
} = params;
|
|
145
78
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const pendingRuntimeState = context.pendingLocalState as {
|
|
156
|
-
baseSnapshot?: ISnapshotTree;
|
|
157
|
-
};
|
|
158
|
-
const baseSnapshot: ISnapshotTree | undefined =
|
|
159
|
-
pendingRuntimeState?.baseSnapshot ?? context.baseSnapshot;
|
|
160
|
-
|
|
161
|
-
const { quorum, deltaManager, taggedLogger } = context;
|
|
162
|
-
assert(
|
|
163
|
-
quorum !== undefined,
|
|
164
|
-
0x968 /* quorum must exist when instantiating attribution-providing runtime */,
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
const mc = loggerToMonitoringContext(taggedLogger);
|
|
168
|
-
|
|
79
|
+
const mc = loggerToMonitoringContext(context.taggedLogger);
|
|
80
|
+
const factory = new RuntimeAttributorFactory();
|
|
81
|
+
const registryEntriesCopy: NamedFluidDataStoreRegistryEntries = [
|
|
82
|
+
...registryEntries,
|
|
83
|
+
[RuntimeAttributorFactory.type, Promise.resolve(factory)],
|
|
84
|
+
];
|
|
169
85
|
const shouldTrackAttribution = mc.config.getBoolean(enableOnNewFileKey) ?? false;
|
|
170
86
|
if (shouldTrackAttribution) {
|
|
171
87
|
const { options } = context;
|
|
@@ -176,170 +92,32 @@ export const mixinAttributor = (
|
|
|
176
92
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
177
93
|
const runtime = (await Base.loadRuntime({
|
|
178
94
|
context,
|
|
179
|
-
registryEntries,
|
|
95
|
+
registryEntries: registryEntriesCopy,
|
|
180
96
|
requestHandler,
|
|
181
97
|
provideEntryPoint,
|
|
182
|
-
// ! This prop is needed for back-compat. Can be removed in 2.0.0-internal.8.0.0
|
|
183
|
-
initializeEntryPoint: provideEntryPoint,
|
|
184
98
|
runtimeOptions,
|
|
185
99
|
containerScope,
|
|
186
100
|
existing,
|
|
187
101
|
containerRuntimeCtor,
|
|
188
102
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
189
103
|
} as any)) as ContainerRuntimeWithAttributor;
|
|
190
|
-
runtime.runtimeAttributor = runtimeAttributor as RuntimeAttributor;
|
|
191
|
-
|
|
192
|
-
const logger = createChildLogger({
|
|
193
|
-
logger: runtime.baseLogger,
|
|
194
|
-
namespace: "Attributor",
|
|
195
|
-
});
|
|
196
104
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
await runtime.runtimeAttributor?.initialize(
|
|
208
|
-
deltaManager,
|
|
209
|
-
quorum,
|
|
210
|
-
baseSnapshot,
|
|
211
|
-
async (id) => runtime.storage.readBlob(id),
|
|
212
|
-
shouldTrackAttribution,
|
|
105
|
+
let runtimeAttributor: IRuntimeAttributor | undefined;
|
|
106
|
+
if (shouldTrackAttribution) {
|
|
107
|
+
if (existing) {
|
|
108
|
+
runtimeAttributor = await getRuntimeAttributor(runtime);
|
|
109
|
+
} else {
|
|
110
|
+
const datastore = await runtime.createDataStore(RuntimeAttributorFactory.type);
|
|
111
|
+
const result = await datastore.trySetAlias(attributorDataStoreAlias);
|
|
112
|
+
assert(
|
|
113
|
+
result === "Success",
|
|
114
|
+
0xa1b /* Failed to set alias for attributor data store */,
|
|
213
115
|
);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
: false,
|
|
219
|
-
});
|
|
220
|
-
},
|
|
221
|
-
);
|
|
116
|
+
runtimeAttributor = (await datastore.entryPoint.get()) as IRuntimeAttributor;
|
|
117
|
+
assert(runtimeAttributor !== undefined, 0xa1c /* Attributor should be defined */);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
222
120
|
|
|
223
121
|
return runtime;
|
|
224
122
|
}
|
|
225
|
-
|
|
226
|
-
private runtimeAttributor: RuntimeAttributor | undefined;
|
|
227
|
-
|
|
228
|
-
protected addContainerStateToSummary(
|
|
229
|
-
summaryTree: ISummaryTreeWithStats,
|
|
230
|
-
fullTree: boolean,
|
|
231
|
-
trackState: boolean,
|
|
232
|
-
telemetryContext?: ITelemetryContext,
|
|
233
|
-
): void {
|
|
234
|
-
super.addContainerStateToSummary(summaryTree, fullTree, trackState, telemetryContext);
|
|
235
|
-
const attributorSummary = this.runtimeAttributor?.summarize();
|
|
236
|
-
if (attributorSummary) {
|
|
237
|
-
addSummarizeResultToSummary(summaryTree, attributorTreeName, attributorSummary);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
123
|
} as unknown as typeof ContainerRuntime;
|
|
241
|
-
|
|
242
|
-
class RuntimeAttributor implements IRuntimeAttributor {
|
|
243
|
-
public get IRuntimeAttributor(): IRuntimeAttributor {
|
|
244
|
-
return this;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
public get(key: AttributionKey): AttributionInfo {
|
|
248
|
-
assert(
|
|
249
|
-
this.opAttributor !== undefined,
|
|
250
|
-
0x509 /* RuntimeAttributor must be initialized before getAttributionInfo can be called */,
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
if (key.type === "detached") {
|
|
254
|
-
throw new Error("Attribution of detached keys is not yet supported.");
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (key.type === "local") {
|
|
258
|
-
// Note: we can *almost* orchestrate this correctly with internal-only changes by looking up the current
|
|
259
|
-
// client id in the audience. However, for read->write client transition, the container might have not yet
|
|
260
|
-
// received a client id. This is left as a TODO as it might be more easily solved once the detached case
|
|
261
|
-
// is settled (e.g. if it's reasonable for the host to know the current user information at container
|
|
262
|
-
// creation time, we could just use that here as well).
|
|
263
|
-
throw new Error("Attribution of local keys is not yet supported.");
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return this.opAttributor.getAttributionInfo(key.seq);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
public has(key: AttributionKey): boolean {
|
|
270
|
-
if (key.type === "detached") {
|
|
271
|
-
return false;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (key.type === "local") {
|
|
275
|
-
return false;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return this.opAttributor?.tryGetAttributionInfo(key.seq) !== undefined;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
private encoder: Encoder<IAttributor, string> = {
|
|
282
|
-
encode: unreachableCase,
|
|
283
|
-
decode: unreachableCase,
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
private opAttributor: IAttributor | undefined;
|
|
287
|
-
public isEnabled = false;
|
|
288
|
-
|
|
289
|
-
public async initialize(
|
|
290
|
-
deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
|
|
291
|
-
quorum: IQuorumClients,
|
|
292
|
-
baseSnapshot: ISnapshotTree | undefined,
|
|
293
|
-
readBlob: (id: string) => Promise<ArrayBufferLike>,
|
|
294
|
-
shouldAddAttributorOnNewFile: boolean,
|
|
295
|
-
): Promise<void> {
|
|
296
|
-
const attributorTree = baseSnapshot?.trees[attributorTreeName];
|
|
297
|
-
// Existing documents that don't already have a snapshot containing runtime attribution info shouldn't
|
|
298
|
-
// inject any for now--this causes some back-compat integration problems that aren't fully worked out.
|
|
299
|
-
const shouldExcludeAttributor =
|
|
300
|
-
(baseSnapshot !== undefined && attributorTree === undefined) ||
|
|
301
|
-
(baseSnapshot === undefined && !shouldAddAttributorOnNewFile);
|
|
302
|
-
if (shouldExcludeAttributor) {
|
|
303
|
-
// This gives a consistent error for calls to `get` on keys that don't exist.
|
|
304
|
-
this.opAttributor = new Attributor();
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
this.isEnabled = true;
|
|
309
|
-
this.encoder = chain(
|
|
310
|
-
new AttributorSerializer(
|
|
311
|
-
(entries) => new OpStreamAttributor(deltaManager, quorum, entries),
|
|
312
|
-
deltaEncoder,
|
|
313
|
-
),
|
|
314
|
-
makeLZ4Encoder(),
|
|
315
|
-
);
|
|
316
|
-
|
|
317
|
-
if (attributorTree === undefined) {
|
|
318
|
-
this.opAttributor = new OpStreamAttributor(deltaManager, quorum);
|
|
319
|
-
} else {
|
|
320
|
-
const id = attributorTree.blobs[opBlobName];
|
|
321
|
-
assert(
|
|
322
|
-
id !== undefined,
|
|
323
|
-
0x50a /* Attributor tree should have op attributor summary blob. */,
|
|
324
|
-
);
|
|
325
|
-
const blobContents = await readBlob(id);
|
|
326
|
-
const attributorSnapshot = bufferToString(blobContents, "utf8");
|
|
327
|
-
this.opAttributor = this.encoder.decode(attributorSnapshot);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
public summarize(): ISummaryTreeWithStats | undefined {
|
|
332
|
-
if (!this.isEnabled) {
|
|
333
|
-
// Loaded existing document without attributor data: avoid injecting any data.
|
|
334
|
-
return undefined;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
assert(
|
|
338
|
-
this.opAttributor !== undefined,
|
|
339
|
-
0x50b /* RuntimeAttributor should be initialized before summarization */,
|
|
340
|
-
);
|
|
341
|
-
const builder = new SummaryTreeBuilder();
|
|
342
|
-
builder.addBlob(opBlobName, this.encoder.encode(this.opAttributor));
|
|
343
|
-
return builder.getSummaryTree();
|
|
344
|
-
}
|
|
345
|
-
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { bufferToString } from "@fluid-internal/client-utils";
|
|
7
|
+
import { IDeltaManager } from "@fluidframework/container-definitions/internal";
|
|
8
|
+
import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
|
|
9
|
+
import {
|
|
10
|
+
IDocumentMessage,
|
|
11
|
+
type ISnapshotTree,
|
|
12
|
+
ISequencedDocumentMessage,
|
|
13
|
+
IQuorumClients,
|
|
14
|
+
} from "@fluidframework/driver-definitions/internal";
|
|
15
|
+
import {
|
|
16
|
+
type AttributionInfo,
|
|
17
|
+
type AttributionKey,
|
|
18
|
+
type ISummaryTreeWithStats,
|
|
19
|
+
} from "@fluidframework/runtime-definitions/internal";
|
|
20
|
+
import { SummaryTreeBuilder } from "@fluidframework/runtime-utils/internal";
|
|
21
|
+
|
|
22
|
+
import { OpStreamAttributor, type IAttributor } from "./attributor.js";
|
|
23
|
+
import { opBlobName, type IRuntimeAttributor } from "./attributorContracts.js";
|
|
24
|
+
import { AttributorSerializer, chain, deltaEncoder, type Encoder } from "./encoders.js";
|
|
25
|
+
import { makeLZ4Encoder } from "./lz4Encoder.js";
|
|
26
|
+
|
|
27
|
+
export class RuntimeAttributor implements IRuntimeAttributor {
|
|
28
|
+
public get IRuntimeAttributor(): IRuntimeAttributor {
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public get(key: AttributionKey): AttributionInfo {
|
|
33
|
+
assert(
|
|
34
|
+
this.opAttributor !== undefined,
|
|
35
|
+
0x509 /* RuntimeAttributor must be initialized before getAttributionInfo can be called */,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (key.type === "detached") {
|
|
39
|
+
throw new Error("Attribution of detached keys is not yet supported.");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (key.type === "local") {
|
|
43
|
+
// Note: we can *almost* orchestrate this correctly with internal-only changes by looking up the current
|
|
44
|
+
// client id in the audience. However, for read->write client transition, the container might have not yet
|
|
45
|
+
// received a client id. This is left as a TODO as it might be more easily solved once the detached case
|
|
46
|
+
// is settled (e.g. if it's reasonable for the host to know the current user information at container
|
|
47
|
+
// creation time, we could just use that here as well).
|
|
48
|
+
throw new Error("Attribution of local keys is not yet supported.");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return this.opAttributor.getAttributionInfo(key.seq);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public has(key: AttributionKey): boolean {
|
|
55
|
+
if (key.type === "detached") {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (key.type === "local") {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return this.opAttributor?.tryGetAttributionInfo(key.seq) !== undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private encoder: Encoder<IAttributor, string> = {
|
|
67
|
+
encode: unreachableCase,
|
|
68
|
+
decode: unreachableCase,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
private opAttributor: IAttributor | undefined;
|
|
72
|
+
public isEnabled = true;
|
|
73
|
+
|
|
74
|
+
public async initialize(
|
|
75
|
+
deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
|
|
76
|
+
quorum: IQuorumClients,
|
|
77
|
+
baseSnapshotForAttributorTree: ISnapshotTree | undefined,
|
|
78
|
+
readBlob: (id: string) => Promise<ArrayBufferLike>,
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
this.encoder = chain(
|
|
81
|
+
new AttributorSerializer(
|
|
82
|
+
(entries) => new OpStreamAttributor(deltaManager, quorum, entries),
|
|
83
|
+
deltaEncoder,
|
|
84
|
+
),
|
|
85
|
+
makeLZ4Encoder(),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
if (baseSnapshotForAttributorTree === undefined) {
|
|
89
|
+
this.opAttributor = new OpStreamAttributor(deltaManager, quorum);
|
|
90
|
+
} else {
|
|
91
|
+
const id = baseSnapshotForAttributorTree.blobs[opBlobName];
|
|
92
|
+
assert(
|
|
93
|
+
id !== undefined,
|
|
94
|
+
0x50a /* Attributor tree should have op attributor summary blob. */,
|
|
95
|
+
);
|
|
96
|
+
const blobContents = await readBlob(id);
|
|
97
|
+
const attributorSnapshot = bufferToString(blobContents, "utf8");
|
|
98
|
+
this.opAttributor = this.encoder.decode(attributorSnapshot);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public summarizeOpAttributor(): ISummaryTreeWithStats {
|
|
103
|
+
assert(
|
|
104
|
+
this.opAttributor !== undefined,
|
|
105
|
+
0xa1d /* RuntimeAttributor should be initialized before summarization */,
|
|
106
|
+
);
|
|
107
|
+
const builder = new SummaryTreeBuilder();
|
|
108
|
+
builder.addBlob(opBlobName, this.encoder.encode(this.opAttributor));
|
|
109
|
+
return builder.getSummaryTree();
|
|
110
|
+
}
|
|
111
|
+
}
|