@fluid-experimental/attributor 2.0.0-internal.3.3.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/.eslintrc.js +20 -0
- package/.mocharc.js +12 -0
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/api-extractor.json +4 -0
- package/dist/attributor.d.ts +56 -0
- package/dist/attributor.d.ts.map +1 -0
- package/dist/attributor.js +67 -0
- package/dist/attributor.js.map +1 -0
- package/dist/encoders.d.ts +28 -0
- package/dist/encoders.d.ts.map +1 -0
- package/dist/encoders.js +80 -0
- package/dist/encoders.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/lz4Encoder.d.ts +7 -0
- package/dist/lz4Encoder.d.ts.map +1 -0
- package/dist/lz4Encoder.js +29 -0
- package/dist/lz4Encoder.js.map +1 -0
- package/dist/mixinAttributor.d.ts +60 -0
- package/dist/mixinAttributor.d.ts.map +1 -0
- package/dist/mixinAttributor.js +153 -0
- package/dist/mixinAttributor.js.map +1 -0
- package/dist/stringInterner.d.ts +55 -0
- package/dist/stringInterner.d.ts.map +1 -0
- package/dist/stringInterner.js +66 -0
- package/dist/stringInterner.js.map +1 -0
- package/lib/attributor.d.ts +56 -0
- package/lib/attributor.d.ts.map +1 -0
- package/lib/attributor.js +62 -0
- package/lib/attributor.js.map +1 -0
- package/lib/encoders.d.ts +28 -0
- package/lib/encoders.d.ts.map +1 -0
- package/lib/encoders.js +75 -0
- package/lib/encoders.js.map +1 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +7 -0
- package/lib/index.js.map +1 -0
- package/lib/lz4Encoder.d.ts +7 -0
- package/lib/lz4Encoder.d.ts.map +1 -0
- package/lib/lz4Encoder.js +25 -0
- package/lib/lz4Encoder.js.map +1 -0
- package/lib/mixinAttributor.d.ts +60 -0
- package/lib/mixinAttributor.d.ts.map +1 -0
- package/lib/mixinAttributor.js +148 -0
- package/lib/mixinAttributor.js.map +1 -0
- package/lib/stringInterner.d.ts +55 -0
- package/lib/stringInterner.d.ts.map +1 -0
- package/lib/stringInterner.js +62 -0
- package/lib/stringInterner.js.map +1 -0
- package/package.json +106 -0
- package/prettier.config.cjs +8 -0
- package/src/attributor.ts +105 -0
- package/src/encoders.ts +112 -0
- package/src/index.ts +12 -0
- package/src/lz4Encoder.ts +27 -0
- package/src/mixinAttributor.ts +283 -0
- package/src/stringInterner.ts +86 -0
- package/tsconfig.esnext.json +7 -0
- package/tsconfig.json +10 -0
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
extends: [require.resolve("@fluidframework/eslint-config-fluid/minimal"), "prettier"],
|
|
8
|
+
parserOptions: {
|
|
9
|
+
project: ["./tsconfig.json", "./src/test/tsconfig.json"],
|
|
10
|
+
},
|
|
11
|
+
overrides: [
|
|
12
|
+
{
|
|
13
|
+
// Rules only for test files
|
|
14
|
+
files: ["*.spec.ts", "src/test/**"],
|
|
15
|
+
rules: {
|
|
16
|
+
"import/no-nodejs-modules": ["error", { allow: ["assert", "fs", "path"] }],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
};
|
package/.mocharc.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
const getFluidTestMochaConfig = require("@fluidframework/mocha-test-setup/mocharc-common");
|
|
9
|
+
|
|
10
|
+
const packageDir = __dirname;
|
|
11
|
+
const config = getFluidTestMochaConfig(packageDir);
|
|
12
|
+
module.exports = config;
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# @fluid-experimental/attributor
|
|
2
|
+
|
|
3
|
+
This package contains definitions and implementations for framework-provided attribution functionality.
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
All attribution APIs (both in this package and elsewhere in `@fluidframework` packages) are marked as [alpha](https://api-extractor.com/pages/tsdoc/tag_alpha/) to enable fast iteration (as third-party use is not officially supported, breaking API changes can be made in minor versions).
|
|
8
|
+
|
|
9
|
+
Despite this, the APIs are generally ready for early adoption--feel free to play around with them in local setups and provide feedback on their shape, usability, or other factors!
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
To turn on op-stream based attribution in your container, use `mixinAttributor` to create a `ContainerRuntime` class which supports querying for attribution information.
|
|
14
|
+
When you instantiate your container runtime, pass a scope which implements `IProvideRuntimeAttributor`.
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { ContainerRuntime } from "@fluidframework/container-runtime";
|
|
18
|
+
import { mixinAttributor, createRuntimeAttributor } from "@fluid-experimental/attributor";
|
|
19
|
+
|
|
20
|
+
const ContainerRuntimeWithAttribution = mixinAttributor(ContainerRuntime);
|
|
21
|
+
|
|
22
|
+
// ...then, in your ContainerRuntime factory use this class:
|
|
23
|
+
class ContainerRuntimeFactory implements IRuntimeFactory {
|
|
24
|
+
public async instantiateRuntime(
|
|
25
|
+
context: IContainerContext,
|
|
26
|
+
existing?: boolean,
|
|
27
|
+
): Promise<IRuntime> {
|
|
28
|
+
const attributor = createRuntimeAttributor();
|
|
29
|
+
// ...make this attributor accessible to your application however you deem fit; e.g. by registering it on a DependencyContainer.
|
|
30
|
+
// To inject loading and storing of attribution data on your runtime, provide a scope implementing IProvideRuntimeAttributor:
|
|
31
|
+
const scope: FluidObject<IProvideRuntimeAttributor> = { IRuntimeAttributor: attributor };
|
|
32
|
+
const runtime = await ContainerRuntimeWithAttribution.load(
|
|
33
|
+
context,
|
|
34
|
+
dataStoreRegistry,
|
|
35
|
+
undefined,
|
|
36
|
+
undefined,
|
|
37
|
+
scope,
|
|
38
|
+
);
|
|
39
|
+
// do whatever setup is necessary with the runtime here
|
|
40
|
+
return runtime;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
This will cause your container runtime to load attribution data available on existing containers.
|
|
46
|
+
To additionally start storing attribution data on new documents, enable the config flag `"Fluid.Attribution.EnableOnNewFile"`.
|
|
47
|
+
Be sure to also [enable any necessary options at the DDS level](#dds-support).
|
|
48
|
+
For a more comprehensive list of backwards-compatability concerns which shed more light on these flags, see [integration](#integration).
|
|
49
|
+
|
|
50
|
+
Applications can recover this information using APIs on the DDSes they use. For example, the following code snippet illustrates how that works for `SharedString`:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
function getAttributionInfo(
|
|
54
|
+
attributor: IRuntimeAttributor,
|
|
55
|
+
sharedString: SharedString,
|
|
56
|
+
pos: number,
|
|
57
|
+
): AttributionInfo | undefined {
|
|
58
|
+
const { segment, offset } = sharedString.getContainingSegment(pos);
|
|
59
|
+
if (!segment || !offset) {
|
|
60
|
+
throw new UsageError("Invalid pos");
|
|
61
|
+
}
|
|
62
|
+
const attributionKey: AttributionKey = segment.attribution.getAtOffset(offset);
|
|
63
|
+
// BEWARE: DDSes may track attribution key with type "detached" and "local", which aren't yet
|
|
64
|
+
// supported out-of-the-box in IRuntimeAttributor. The application can recover AttributionInfo
|
|
65
|
+
// from these keys if it wants using user information about the creator of the document and the
|
|
66
|
+
// current active user, respectively.
|
|
67
|
+
if (attributor.has(attributionKey)) {
|
|
68
|
+
return attributor.get(attributionKey);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Get the user who inserted the text at position 0 in `sharedString` and the timestamp for when they did so.
|
|
73
|
+
const { user, timestamp } = getAttributionInfo(attributor, sharedString, 0);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Overview
|
|
77
|
+
|
|
78
|
+
Attribution is inherently a content-based operation--it answers questions about who created or changed a piece of content as well as when they did it.
|
|
79
|
+
Since applications typically want attribution at a relatively fine-grained level, DDSes are the initial entrypoint for attributing content.
|
|
80
|
+
A DDS may define its attribution API as it sees fit, but should somehow expose a way to retrieve attribution keys from its content.
|
|
81
|
+
These attribution keys can be exchanged for user and timestamp information using the container runtime.
|
|
82
|
+
|
|
83
|
+
### DDS Support
|
|
84
|
+
|
|
85
|
+
The following DDSes currently support attribution:
|
|
86
|
+
|
|
87
|
+
- [SharedString](../../dds/sequence/README.md#attribution)
|
|
88
|
+
|
|
89
|
+
### Op Stream Attribution
|
|
90
|
+
|
|
91
|
+
Framework-provided attribution tracks user and timestamp information for each op submitted.
|
|
92
|
+
Any more complex scenarios where attribution doesn't align with the direct submitter (such as attributing copy-pasted content to the original creators) will need to be handled by Fluid consumers using extensibility points.
|
|
93
|
+
The extensibility APIs are a work in progress; check back later for more details.
|
|
94
|
+
|
|
95
|
+
### Integration
|
|
96
|
+
|
|
97
|
+
Backwards-compatability for using this mixin with existing documents is a work in progress.
|
|
98
|
+
When an existing document is loaded that was created using a ContainerRuntimeFactory without a mixed in attributor,
|
|
99
|
+
that document will continue to operate as if no attribution has been mixed in.
|
|
100
|
+
Additionally, if a document that contains attribution is loaded using a container runtime without a mixed-in attributor,
|
|
101
|
+
any attribution information stored in that document may be lost.
|
|
102
|
+
|
|
103
|
+
The current design of the mixin's behavior is therefore motivated by the ability to roll out the feature in Fluid's collaborative environment.
|
|
104
|
+
The behavior of `"Fluid.Attribution.WriteOnNewFile"` supports the standard strategy of rolling out code that reads a new format and waiting for it to saturate before beginning to write that new format.
|
|
105
|
+
"reading the new format" corresponds to using a container runtime initialized with `mixinAttributor`, and "writing the new format" to enabling `"Fluid.Attribution.WriteOnNewFile"` in configuration.
|
|
106
|
+
During the "waiting to saturate" period, developers are free to experiment with turning the feature flag on locally and testing various compatability scenarios.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { IDocumentMessage, ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
2
|
+
import { AttributionInfo } from "@fluidframework/runtime-definitions";
|
|
3
|
+
import { IAudience, IDeltaManager } from "@fluidframework/container-definitions";
|
|
4
|
+
/**
|
|
5
|
+
* Provides lookup between attribution keys and their associated attribution information.
|
|
6
|
+
* @alpha
|
|
7
|
+
*/
|
|
8
|
+
export interface IAttributor {
|
|
9
|
+
/**
|
|
10
|
+
* Retrieves attribution information associated with a particular key.
|
|
11
|
+
* @param key - Attribution key to look up.
|
|
12
|
+
* @throws If no attribution information is recorded for that key.
|
|
13
|
+
*/
|
|
14
|
+
getAttributionInfo(key: number): AttributionInfo;
|
|
15
|
+
/**
|
|
16
|
+
* @param key - Attribution key to look up.
|
|
17
|
+
* @returns the attribution information associated with the provided key, or undefined if no information exists.
|
|
18
|
+
*/
|
|
19
|
+
tryGetAttributionInfo(key: number): AttributionInfo | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* @returns an iterable of (attribution key, attribution info) pairs for each stored key.
|
|
22
|
+
*/
|
|
23
|
+
entries(): IterableIterator<[number, AttributionInfo]>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* {@inheritdoc IAttributor}
|
|
27
|
+
* @alpha
|
|
28
|
+
*/
|
|
29
|
+
export declare class Attributor implements IAttributor {
|
|
30
|
+
protected readonly keyToInfo: Map<number, AttributionInfo>;
|
|
31
|
+
/**
|
|
32
|
+
* @param initialEntries - Any entries which should be populated on instantiation.
|
|
33
|
+
*/
|
|
34
|
+
constructor(initialEntries?: Iterable<[number, AttributionInfo]>);
|
|
35
|
+
/**
|
|
36
|
+
* {@inheritdoc IAttributor.getAttributionInfo}
|
|
37
|
+
*/
|
|
38
|
+
getAttributionInfo(key: number): AttributionInfo;
|
|
39
|
+
/**
|
|
40
|
+
* {@inheritdoc IAttributor.tryGetAttributionInfo}
|
|
41
|
+
*/
|
|
42
|
+
tryGetAttributionInfo(key: number): AttributionInfo | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* {@inheritdoc IAttributor.entries}
|
|
45
|
+
*/
|
|
46
|
+
entries(): IterableIterator<[number, AttributionInfo]>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Attributor which listens to an op stream and records entries for each op.
|
|
50
|
+
* Sequence numbers are used as attribution keys.
|
|
51
|
+
* @alpha
|
|
52
|
+
*/
|
|
53
|
+
export declare class OpStreamAttributor extends Attributor implements IAttributor {
|
|
54
|
+
constructor(deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>, audience: IAudience, initialEntries?: Iterable<[number, AttributionInfo]>);
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=attributor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attributor.d.ts","sourceRoot":"","sources":["../src/attributor.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AACnG,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAEtE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAEjF;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC3B;;;;OAIG;IACH,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IAEjD;;;OAGG;IACH,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAAC;IAEhE;;OAEG;IACH,OAAO,IAAI,gBAAgB,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;CAIvD;AAED;;;GAGG;AACH,qBAAa,UAAW,YAAW,WAAW;IAC7C,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAE3D;;OAEG;gBACS,cAAc,CAAC,EAAE,QAAQ,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAIhE;;OAEG;IACI,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe;IAQvD;;OAEG;IACI,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAItE;;OAEG;IACI,OAAO,IAAI,gBAAgB,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAG7D;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,UAAW,YAAW,WAAW;gBAEvE,YAAY,EAAE,aAAa,CAAC,yBAAyB,EAAE,gBAAgB,CAAC,EACxE,QAAQ,EAAE,SAAS,EACnB,cAAc,CAAC,EAAE,QAAQ,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAkBrD"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpStreamAttributor = exports.Attributor = void 0;
|
|
4
|
+
/*!
|
|
5
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
6
|
+
* Licensed under the MIT License.
|
|
7
|
+
*/
|
|
8
|
+
const common_utils_1 = require("@fluidframework/common-utils");
|
|
9
|
+
const container_utils_1 = require("@fluidframework/container-utils");
|
|
10
|
+
/**
|
|
11
|
+
* {@inheritdoc IAttributor}
|
|
12
|
+
* @alpha
|
|
13
|
+
*/
|
|
14
|
+
class Attributor {
|
|
15
|
+
/**
|
|
16
|
+
* @param initialEntries - Any entries which should be populated on instantiation.
|
|
17
|
+
*/
|
|
18
|
+
constructor(initialEntries) {
|
|
19
|
+
this.keyToInfo = new Map(initialEntries !== null && initialEntries !== void 0 ? initialEntries : []);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* {@inheritdoc IAttributor.getAttributionInfo}
|
|
23
|
+
*/
|
|
24
|
+
getAttributionInfo(key) {
|
|
25
|
+
const result = this.tryGetAttributionInfo(key);
|
|
26
|
+
if (!result) {
|
|
27
|
+
throw new container_utils_1.UsageError(`Requested attribution information for unstored key: ${key}.`);
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* {@inheritdoc IAttributor.tryGetAttributionInfo}
|
|
33
|
+
*/
|
|
34
|
+
tryGetAttributionInfo(key) {
|
|
35
|
+
return this.keyToInfo.get(key);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* {@inheritdoc IAttributor.entries}
|
|
39
|
+
*/
|
|
40
|
+
entries() {
|
|
41
|
+
return this.keyToInfo.entries();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.Attributor = Attributor;
|
|
45
|
+
/**
|
|
46
|
+
* Attributor which listens to an op stream and records entries for each op.
|
|
47
|
+
* Sequence numbers are used as attribution keys.
|
|
48
|
+
* @alpha
|
|
49
|
+
*/
|
|
50
|
+
class OpStreamAttributor extends Attributor {
|
|
51
|
+
constructor(deltaManager, audience, initialEntries) {
|
|
52
|
+
super(initialEntries);
|
|
53
|
+
deltaManager.on("op", (message) => {
|
|
54
|
+
const client = audience.getMember(message.clientId);
|
|
55
|
+
if (message.type === "op") {
|
|
56
|
+
// TODO: This case may be legitimate, and if so we need to figure out how to handle it.
|
|
57
|
+
(0, common_utils_1.assert)(client !== undefined, 0x4af /* Received message from user not in the audience */);
|
|
58
|
+
this.keyToInfo.set(message.sequenceNumber, {
|
|
59
|
+
user: client.user,
|
|
60
|
+
timestamp: message.timestamp,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.OpStreamAttributor = OpStreamAttributor;
|
|
67
|
+
//# sourceMappingURL=attributor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attributor.js","sourceRoot":"","sources":["../src/attributor.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,+DAAsD;AAGtD,qEAA6D;AA8B7D;;;GAGG;AACH,MAAa,UAAU;IAGtB;;OAEG;IACH,YAAY,cAAoD;QAC/D,IAAI,CAAC,SAAS,GAAG,IAAI,GAAG,CAAC,cAAc,aAAd,cAAc,cAAd,cAAc,GAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED;;OAEG;IACI,kBAAkB,CAAC,GAAW;QACpC,MAAM,MAAM,GAAG,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAC/C,IAAI,CAAC,MAAM,EAAE;YACZ,MAAM,IAAI,4BAAU,CAAC,uDAAuD,GAAG,GAAG,CAAC,CAAC;SACpF;QACD,OAAO,MAAM,CAAC;IACf,CAAC;IAED;;OAEG;IACI,qBAAqB,CAAC,GAAW;QACvC,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAED;;OAEG;IACI,OAAO;QACb,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;IACjC,CAAC;CACD;AAlCD,gCAkCC;AAED;;;;GAIG;AACH,MAAa,kBAAmB,SAAQ,UAAU;IACjD,YACC,YAAwE,EACxE,QAAmB,EACnB,cAAoD;QAEpD,KAAK,CAAC,cAAc,CAAC,CAAC;QACtB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,OAAkC,EAAE,EAAE;YAC5D,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YACpD,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE;gBAC1B,uFAAuF;gBACvF,IAAA,qBAAM,EACL,MAAM,KAAK,SAAS,EACpB,KAAK,CAAC,oDAAoD,CAC1D,CAAC;gBACF,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE;oBAC1C,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,SAAS,EAAE,OAAO,CAAC,SAAS;iBAC5B,CAAC,CAAC;aACH;QACF,CAAC,CAAC,CAAC;IACJ,CAAC;CACD;AAtBD,gDAsBC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\nimport { assert } from \"@fluidframework/common-utils\";\nimport { IDocumentMessage, ISequencedDocumentMessage } from \"@fluidframework/protocol-definitions\";\nimport { AttributionInfo } from \"@fluidframework/runtime-definitions\";\nimport { UsageError } from \"@fluidframework/container-utils\";\nimport { IAudience, IDeltaManager } from \"@fluidframework/container-definitions\";\n\n/**\n * Provides lookup between attribution keys and their associated attribution information.\n * @alpha\n */\nexport interface IAttributor {\n\t/**\n\t * Retrieves attribution information associated with a particular key.\n\t * @param key - Attribution key to look up.\n\t * @throws If no attribution information is recorded for that key.\n\t */\n\tgetAttributionInfo(key: number): AttributionInfo;\n\n\t/**\n\t * @param key - Attribution key to look up.\n\t * @returns the attribution information associated with the provided key, or undefined if no information exists.\n\t */\n\ttryGetAttributionInfo(key: number): AttributionInfo | undefined;\n\n\t/**\n\t * @returns an iterable of (attribution key, attribution info) pairs for each stored key.\n\t */\n\tentries(): IterableIterator<[number, AttributionInfo]>;\n\n\t// TODO:\n\t// - GC\n}\n\n/**\n * {@inheritdoc IAttributor}\n * @alpha\n */\nexport class Attributor implements IAttributor {\n\tprotected readonly keyToInfo: Map<number, AttributionInfo>;\n\n\t/**\n\t * @param initialEntries - Any entries which should be populated on instantiation.\n\t */\n\tconstructor(initialEntries?: Iterable<[number, AttributionInfo]>) {\n\t\tthis.keyToInfo = new Map(initialEntries ?? []);\n\t}\n\n\t/**\n\t * {@inheritdoc IAttributor.getAttributionInfo}\n\t */\n\tpublic getAttributionInfo(key: number): AttributionInfo {\n\t\tconst result = this.tryGetAttributionInfo(key);\n\t\tif (!result) {\n\t\t\tthrow new UsageError(`Requested attribution information for unstored key: ${key}.`);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * {@inheritdoc IAttributor.tryGetAttributionInfo}\n\t */\n\tpublic tryGetAttributionInfo(key: number): AttributionInfo | undefined {\n\t\treturn this.keyToInfo.get(key);\n\t}\n\n\t/**\n\t * {@inheritdoc IAttributor.entries}\n\t */\n\tpublic entries(): IterableIterator<[number, AttributionInfo]> {\n\t\treturn this.keyToInfo.entries();\n\t}\n}\n\n/**\n * Attributor which listens to an op stream and records entries for each op.\n * Sequence numbers are used as attribution keys.\n * @alpha\n */\nexport class OpStreamAttributor extends Attributor implements IAttributor {\n\tconstructor(\n\t\tdeltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,\n\t\taudience: IAudience,\n\t\tinitialEntries?: Iterable<[number, AttributionInfo]>,\n\t) {\n\t\tsuper(initialEntries);\n\t\tdeltaManager.on(\"op\", (message: ISequencedDocumentMessage) => {\n\t\t\tconst client = audience.getMember(message.clientId);\n\t\t\tif (message.type === \"op\") {\n\t\t\t\t// TODO: This case may be legitimate, and if so we need to figure out how to handle it.\n\t\t\t\tassert(\n\t\t\t\t\tclient !== undefined,\n\t\t\t\t\t0x4af /* Received message from user not in the audience */,\n\t\t\t\t);\n\t\t\t\tthis.keyToInfo.set(message.sequenceNumber, {\n\t\t\t\t\tuser: client.user,\n\t\t\t\t\ttimestamp: message.timestamp,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AttributionInfo } from "@fluidframework/runtime-definitions";
|
|
2
|
+
import { IAttributor } from "./attributor";
|
|
3
|
+
import { InternedStringId } from "./stringInterner";
|
|
4
|
+
export interface Encoder<TDecoded, TEncoded> {
|
|
5
|
+
encode(decoded: TDecoded): TEncoded;
|
|
6
|
+
decode(encoded: TEncoded): TDecoded;
|
|
7
|
+
}
|
|
8
|
+
export declare type TimestampEncoder = Encoder<number[], number[]>;
|
|
9
|
+
export declare const deltaEncoder: TimestampEncoder;
|
|
10
|
+
export declare type IAttributorSerializer = Encoder<IAttributor, SerializedAttributor>;
|
|
11
|
+
export interface SerializedAttributor {
|
|
12
|
+
interner: readonly string[];
|
|
13
|
+
seqs: number[];
|
|
14
|
+
timestamps: number[];
|
|
15
|
+
attributionRefs: InternedStringId[];
|
|
16
|
+
}
|
|
17
|
+
export declare class AttributorSerializer implements IAttributorSerializer {
|
|
18
|
+
private readonly makeAttributor;
|
|
19
|
+
private readonly timestampEncoder;
|
|
20
|
+
constructor(makeAttributor: (entries: Iterable<[number, AttributionInfo]>) => IAttributor, timestampEncoder: TimestampEncoder);
|
|
21
|
+
encode(attributor: IAttributor): SerializedAttributor;
|
|
22
|
+
decode(encoded: SerializedAttributor): IAttributor;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* @returns an encoder which composes `a` and `b`.
|
|
26
|
+
*/
|
|
27
|
+
export declare const chain: <T1, T2, T3>(a: Encoder<T1, T2>, b: Encoder<T2, T3>) => Encoder<T1, T3>;
|
|
28
|
+
//# sourceMappingURL=encoders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encoders.d.ts","sourceRoot":"","sources":["../src/encoders.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAyB,MAAM,kBAAkB,CAAC;AAE3E,MAAM,WAAW,OAAO,CAAC,QAAQ,EAAE,QAAQ;IAC1C,MAAM,CAAC,OAAO,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAEpC,MAAM,CAAC,OAAO,EAAE,QAAQ,GAAG,QAAQ,CAAC;CACpC;AAID,oBAAY,gBAAgB,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;AAE3D,eAAO,MAAM,YAAY,EAAE,gBAuB1B,CAAC;AAEF,oBAAY,qBAAqB,GAAG,OAAO,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC;AAE/E,MAAM,WAAW,oBAAoB;IACpC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAA+D;IAC1F,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,eAAe,EAAE,gBAAgB,EAAE,CAAC;CACpC;AAED,qBAAa,oBAAqB,YAAW,qBAAqB;IAEhE,OAAO,CAAC,QAAQ,CAAC,cAAc;IAG/B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBAHhB,cAAc,EAAE,CAChC,OAAO,EAAE,QAAQ,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,KACxC,WAAW,EACC,gBAAgB,EAAE,gBAAgB;IAG7C,MAAM,CAAC,UAAU,EAAE,WAAW,GAAG,oBAAoB;IAsBrD,MAAM,CAAC,OAAO,EAAE,oBAAoB,GAAG,WAAW;CAkBzD;AAED;;GAEG;AACH,eAAO,MAAM,KAAK,yEAGhB,CAAC"}
|
package/dist/encoders.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.chain = exports.AttributorSerializer = exports.deltaEncoder = void 0;
|
|
4
|
+
/*!
|
|
5
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
6
|
+
* Licensed under the MIT License.
|
|
7
|
+
*/
|
|
8
|
+
const common_utils_1 = require("@fluidframework/common-utils");
|
|
9
|
+
const stringInterner_1 = require("./stringInterner");
|
|
10
|
+
exports.deltaEncoder = {
|
|
11
|
+
encode: (timestamps) => {
|
|
12
|
+
const deltaTimestamps = new Array(timestamps.length);
|
|
13
|
+
let prev = 0;
|
|
14
|
+
for (let i = 0; i < timestamps.length; i++) {
|
|
15
|
+
deltaTimestamps[i] = timestamps[i] - prev;
|
|
16
|
+
prev = timestamps[i];
|
|
17
|
+
}
|
|
18
|
+
return deltaTimestamps;
|
|
19
|
+
},
|
|
20
|
+
decode: (encoded) => {
|
|
21
|
+
(0, common_utils_1.assert)(Array.isArray(encoded), 0x4b0 /* Encoded timestamps should be an array of nummbers */);
|
|
22
|
+
const timestamps = new Array(encoded.length);
|
|
23
|
+
let cumulativeSum = 0;
|
|
24
|
+
for (let i = 0; i < encoded.length; i++) {
|
|
25
|
+
cumulativeSum += encoded[i];
|
|
26
|
+
timestamps[i] = cumulativeSum;
|
|
27
|
+
}
|
|
28
|
+
return timestamps;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
class AttributorSerializer {
|
|
32
|
+
constructor(makeAttributor, timestampEncoder) {
|
|
33
|
+
this.makeAttributor = makeAttributor;
|
|
34
|
+
this.timestampEncoder = timestampEncoder;
|
|
35
|
+
}
|
|
36
|
+
encode(attributor) {
|
|
37
|
+
const interner = new stringInterner_1.MutableStringInterner();
|
|
38
|
+
const seqs = [];
|
|
39
|
+
const timestamps = [];
|
|
40
|
+
const attributionRefs = [];
|
|
41
|
+
for (const [seq, { user, timestamp }] of attributor.entries()) {
|
|
42
|
+
seqs.push(seq);
|
|
43
|
+
timestamps.push(timestamp);
|
|
44
|
+
const ref = interner.getOrCreateInternedId(JSON.stringify(user));
|
|
45
|
+
attributionRefs.push(ref);
|
|
46
|
+
}
|
|
47
|
+
const serialized = {
|
|
48
|
+
interner: interner.getSerializable(),
|
|
49
|
+
seqs,
|
|
50
|
+
timestamps: this.timestampEncoder.encode(timestamps),
|
|
51
|
+
attributionRefs,
|
|
52
|
+
};
|
|
53
|
+
return serialized;
|
|
54
|
+
}
|
|
55
|
+
decode(encoded) {
|
|
56
|
+
const interner = new stringInterner_1.MutableStringInterner(encoded.interner);
|
|
57
|
+
const { seqs, timestamps: encodedTimestamps, attributionRefs } = encoded;
|
|
58
|
+
const timestamps = this.timestampEncoder.decode(encodedTimestamps);
|
|
59
|
+
(0, common_utils_1.assert)(seqs.length === timestamps.length && timestamps.length === attributionRefs.length, 0x4b1 /* serialized attribution columns should have the same length */);
|
|
60
|
+
const entries = new Array(seqs.length);
|
|
61
|
+
for (let i = 0; i < seqs.length; i++) {
|
|
62
|
+
const key = seqs[i];
|
|
63
|
+
const timestamp = timestamps[i];
|
|
64
|
+
const ref = attributionRefs[i];
|
|
65
|
+
const user = JSON.parse(interner.getString(ref));
|
|
66
|
+
entries[i] = [key, { user, timestamp }];
|
|
67
|
+
}
|
|
68
|
+
return this.makeAttributor(entries);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.AttributorSerializer = AttributorSerializer;
|
|
72
|
+
/**
|
|
73
|
+
* @returns an encoder which composes `a` and `b`.
|
|
74
|
+
*/
|
|
75
|
+
const chain = (a, b) => ({
|
|
76
|
+
encode: (content) => b.encode(a.encode(content)),
|
|
77
|
+
decode: (content) => a.decode(b.decode(content)),
|
|
78
|
+
});
|
|
79
|
+
exports.chain = chain;
|
|
80
|
+
//# sourceMappingURL=encoders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encoders.js","sourceRoot":"","sources":["../src/encoders.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\nimport { assert } from \"@fluidframework/common-utils\";\nimport { Jsonable } from \"@fluidframework/datastore-definitions\";\nimport { IUser } from \"@fluidframework/protocol-definitions\";\nimport { AttributionInfo } from \"@fluidframework/runtime-definitions\";\nimport { IAttributor } from \"./attributor\";\nimport { InternedStringId, MutableStringInterner } from \"./stringInterner\";\n\nexport interface Encoder<TDecoded, TEncoded> {\n\tencode(decoded: TDecoded): TEncoded;\n\n\tdecode(encoded: TEncoded): TDecoded;\n}\n\n// Note: the encoded format doesn't matter as long as it's serializable;\n// these types could be weakened.\nexport type TimestampEncoder = Encoder<number[], number[]>;\n\nexport const deltaEncoder: TimestampEncoder = {\n\tencode: (timestamps: number[]) => {\n\t\tconst deltaTimestamps: number[] = new Array(timestamps.length);\n\t\tlet prev = 0;\n\t\tfor (let i = 0; i < timestamps.length; i++) {\n\t\t\tdeltaTimestamps[i] = timestamps[i] - prev;\n\t\t\tprev = timestamps[i];\n\t\t}\n\t\treturn deltaTimestamps;\n\t},\n\tdecode: (encoded: Jsonable) => {\n\t\tassert(\n\t\t\tArray.isArray(encoded),\n\t\t\t0x4b0 /* Encoded timestamps should be an array of nummbers */,\n\t\t);\n\t\tconst timestamps: number[] = new Array(encoded.length);\n\t\tlet cumulativeSum = 0;\n\t\tfor (let i = 0; i < encoded.length; i++) {\n\t\t\tcumulativeSum += encoded[i];\n\t\t\ttimestamps[i] = cumulativeSum;\n\t\t}\n\t\treturn timestamps;\n\t},\n};\n\nexport type IAttributorSerializer = Encoder<IAttributor, SerializedAttributor>;\n\nexport interface SerializedAttributor {\n\tinterner: readonly string[] /* result of calling getSerializable() on a StringInterner */;\n\tseqs: number[];\n\ttimestamps: number[];\n\tattributionRefs: InternedStringId[];\n}\n\nexport class AttributorSerializer implements IAttributorSerializer {\n\tconstructor(\n\t\tprivate readonly makeAttributor: (\n\t\t\tentries: Iterable<[number, AttributionInfo]>,\n\t\t) => IAttributor,\n\t\tprivate readonly timestampEncoder: TimestampEncoder,\n\t) {}\n\n\tpublic encode(attributor: IAttributor): SerializedAttributor {\n\t\tconst interner = new MutableStringInterner();\n\t\tconst seqs: number[] = [];\n\t\tconst timestamps: number[] = [];\n\t\tconst attributionRefs: InternedStringId[] = [];\n\t\tfor (const [seq, { user, timestamp }] of attributor.entries()) {\n\t\t\tseqs.push(seq);\n\t\t\ttimestamps.push(timestamp);\n\t\t\tconst ref = interner.getOrCreateInternedId(JSON.stringify(user));\n\t\t\tattributionRefs.push(ref);\n\t\t}\n\n\t\tconst serialized: SerializedAttributor = {\n\t\t\tinterner: interner.getSerializable(),\n\t\t\tseqs,\n\t\t\ttimestamps: this.timestampEncoder.encode(timestamps),\n\t\t\tattributionRefs,\n\t\t};\n\n\t\treturn serialized;\n\t}\n\n\tpublic decode(encoded: SerializedAttributor): IAttributor {\n\t\tconst interner = new MutableStringInterner(encoded.interner);\n\t\tconst { seqs, timestamps: encodedTimestamps, attributionRefs } = encoded;\n\t\tconst timestamps = this.timestampEncoder.decode(encodedTimestamps);\n\t\tassert(\n\t\t\tseqs.length === timestamps.length && timestamps.length === attributionRefs.length,\n\t\t\t0x4b1 /* serialized attribution columns should have the same length */,\n\t\t);\n\t\tconst entries = new Array(seqs.length);\n\t\tfor (let i = 0; i < seqs.length; i++) {\n\t\t\tconst key = seqs[i];\n\t\t\tconst timestamp = timestamps[i];\n\t\t\tconst ref = attributionRefs[i];\n\t\t\tconst user: IUser = JSON.parse(interner.getString(ref));\n\t\t\tentries[i] = [key, { user, timestamp }];\n\t\t}\n\t\treturn this.makeAttributor(entries);\n\t}\n}\n\n/**\n * @returns an encoder which composes `a` and `b`.\n */\nexport const chain = <T1, T2, T3>(a: Encoder<T1, T2>, b: Encoder<T2, T3>): Encoder<T1, T3> => ({\n\tencode: (content) => b.encode(a.encode(content)),\n\tdecode: (content) => a.decode(b.decode(content)),\n});\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
export { Attributor, OpStreamAttributor, IAttributor } from "./attributor";
|
|
6
|
+
export { createRuntimeAttributor, enableOnNewFileKey, IProvideRuntimeAttributor, IRuntimeAttributor, mixinAttributor, } from "./mixinAttributor";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3E,OAAO,EACN,uBAAuB,EACvB,kBAAkB,EAClB,yBAAyB,EACzB,kBAAkB,EAClB,eAAe,GACf,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mixinAttributor = exports.IRuntimeAttributor = exports.enableOnNewFileKey = exports.createRuntimeAttributor = exports.OpStreamAttributor = exports.Attributor = void 0;
|
|
4
|
+
/*!
|
|
5
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
6
|
+
* Licensed under the MIT License.
|
|
7
|
+
*/
|
|
8
|
+
var attributor_1 = require("./attributor");
|
|
9
|
+
Object.defineProperty(exports, "Attributor", { enumerable: true, get: function () { return attributor_1.Attributor; } });
|
|
10
|
+
Object.defineProperty(exports, "OpStreamAttributor", { enumerable: true, get: function () { return attributor_1.OpStreamAttributor; } });
|
|
11
|
+
var mixinAttributor_1 = require("./mixinAttributor");
|
|
12
|
+
Object.defineProperty(exports, "createRuntimeAttributor", { enumerable: true, get: function () { return mixinAttributor_1.createRuntimeAttributor; } });
|
|
13
|
+
Object.defineProperty(exports, "enableOnNewFileKey", { enumerable: true, get: function () { return mixinAttributor_1.enableOnNewFileKey; } });
|
|
14
|
+
Object.defineProperty(exports, "IRuntimeAttributor", { enumerable: true, get: function () { return mixinAttributor_1.IRuntimeAttributor; } });
|
|
15
|
+
Object.defineProperty(exports, "mixinAttributor", { enumerable: true, get: function () { return mixinAttributor_1.mixinAttributor; } });
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,2CAA2E;AAAlE,wGAAA,UAAU,OAAA;AAAE,gHAAA,kBAAkB,OAAA;AACvC,qDAM2B;AAL1B,0HAAA,uBAAuB,OAAA;AACvB,qHAAA,kBAAkB,OAAA;AAElB,qHAAA,kBAAkB,OAAA;AAClB,kHAAA,eAAe,OAAA","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\nexport { Attributor, OpStreamAttributor, IAttributor } from \"./attributor\";\nexport {\n\tcreateRuntimeAttributor,\n\tenableOnNewFileKey,\n\tIProvideRuntimeAttributor,\n\tIRuntimeAttributor,\n\tmixinAttributor,\n} from \"./mixinAttributor\";\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lz4Encoder.d.ts","sourceRoot":"","sources":["../src/lz4Encoder.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,QAAQ,EAAE,MAAM,uCAAuC,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErC;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAchE"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeLZ4Encoder = void 0;
|
|
4
|
+
/*!
|
|
5
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
6
|
+
* Licensed under the MIT License.
|
|
7
|
+
*/
|
|
8
|
+
const lz4js_1 = require("lz4js");
|
|
9
|
+
const common_utils_1 = require("@fluidframework/common-utils");
|
|
10
|
+
/**
|
|
11
|
+
* @alpha
|
|
12
|
+
*/
|
|
13
|
+
function makeLZ4Encoder() {
|
|
14
|
+
return {
|
|
15
|
+
encode: (decoded) => {
|
|
16
|
+
const uncompressed = new TextEncoder().encode(JSON.stringify(decoded));
|
|
17
|
+
const compressed = (0, lz4js_1.compress)(uncompressed);
|
|
18
|
+
return (0, common_utils_1.bufferToString)(compressed, "base64");
|
|
19
|
+
},
|
|
20
|
+
decode: (serializedSummary) => {
|
|
21
|
+
const compressed = new Uint8Array((0, common_utils_1.stringToBuffer)(serializedSummary, "base64"));
|
|
22
|
+
const uncompressed = (0, lz4js_1.decompress)(compressed);
|
|
23
|
+
const decoded = JSON.parse(new TextDecoder().decode(uncompressed));
|
|
24
|
+
return decoded;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
exports.makeLZ4Encoder = makeLZ4Encoder;
|
|
29
|
+
//# sourceMappingURL=lz4Encoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lz4Encoder.js","sourceRoot":"","sources":["../src/lz4Encoder.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,iCAA6C;AAC7C,+DAA8E;AAI9E;;GAEG;AACH,SAAgB,cAAc;IAC7B,OAAO;QACN,MAAM,EAAE,CAAC,OAAoB,EAAE,EAAE;YAChC,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;YACvE,MAAM,UAAU,GAAG,IAAA,gBAAQ,EAAC,YAAY,CAAC,CAAC;YAC1C,OAAO,IAAA,6BAAc,EAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC7C,CAAC;QACD,MAAM,EAAE,CAAC,iBAAyB,EAAe,EAAE;YAClD,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,IAAA,6BAAc,EAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC/E,MAAM,YAAY,GAAG,IAAA,kBAAU,EAAC,UAAU,CAAC,CAAC;YAC5C,MAAM,OAAO,GAAgB,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;YAChF,OAAO,OAAO,CAAC;QAChB,CAAC;KACD,CAAC;AACH,CAAC;AAdD,wCAcC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\nimport { compress, decompress } from \"lz4js\";\nimport { bufferToString, stringToBuffer } from \"@fluidframework/common-utils\";\nimport { Jsonable } from \"@fluidframework/datastore-definitions\";\nimport { Encoder } from \"./encoders\";\n\n/**\n * @alpha\n */\nexport function makeLZ4Encoder<T>(): Encoder<Jsonable<T>, string> {\n\treturn {\n\t\tencode: (decoded: Jsonable<T>) => {\n\t\t\tconst uncompressed = new TextEncoder().encode(JSON.stringify(decoded));\n\t\t\tconst compressed = compress(uncompressed);\n\t\t\treturn bufferToString(compressed, \"base64\");\n\t\t},\n\t\tdecode: (serializedSummary: string): Jsonable<T> => {\n\t\t\tconst compressed = new Uint8Array(stringToBuffer(serializedSummary, \"base64\"));\n\t\t\tconst uncompressed = decompress(compressed);\n\t\t\tconst decoded: Jsonable<T> = JSON.parse(new TextDecoder().decode(uncompressed));\n\t\t\treturn decoded;\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ContainerRuntime } from "@fluidframework/container-runtime";
|
|
2
|
+
import { AttributionInfo, AttributionKey } from "@fluidframework/runtime-definitions";
|
|
3
|
+
/**
|
|
4
|
+
* @alpha
|
|
5
|
+
* Feature Gate Key -
|
|
6
|
+
* Whether or not a container runtime instantiated using `mixinAttributor`'s load should generate an attributor on
|
|
7
|
+
* new files. See package README for more notes on integration.
|
|
8
|
+
*/
|
|
9
|
+
export declare const enableOnNewFileKey = "Fluid.Attribution.EnableOnNewFile";
|
|
10
|
+
/**
|
|
11
|
+
* @alpha
|
|
12
|
+
*/
|
|
13
|
+
export declare const IRuntimeAttributor: keyof IProvideRuntimeAttributor;
|
|
14
|
+
/**
|
|
15
|
+
* @alpha
|
|
16
|
+
*/
|
|
17
|
+
export interface IProvideRuntimeAttributor {
|
|
18
|
+
readonly IRuntimeAttributor: IRuntimeAttributor;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Provides access to attribution information stored on the container runtime.
|
|
22
|
+
*
|
|
23
|
+
* Attributors are only populated after the container runtime they are injected into has initialized.
|
|
24
|
+
* @sealed
|
|
25
|
+
* @alpha
|
|
26
|
+
*/
|
|
27
|
+
export interface IRuntimeAttributor extends IProvideRuntimeAttributor {
|
|
28
|
+
/**
|
|
29
|
+
* @throws - If no AttributionInfo exists for this key.
|
|
30
|
+
*/
|
|
31
|
+
get(key: AttributionKey): AttributionInfo;
|
|
32
|
+
/**
|
|
33
|
+
* @returns - Whether any AttributionInfo exists for the provided key.
|
|
34
|
+
*/
|
|
35
|
+
has(key: AttributionKey): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* @returns - Whether the runtime is currently tracking attribution information for the loaded container.
|
|
38
|
+
* See {@link mixinAttributor} for more details on when this happens.
|
|
39
|
+
*/
|
|
40
|
+
readonly isEnabled: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* @returns an IRuntimeAttributor for usage with `mixinAttributor`. The attributor will only be populated with data
|
|
44
|
+
* once it's passed via scope to a container runtime load flow. See {@link mixinAttributor}.
|
|
45
|
+
* @alpha
|
|
46
|
+
*/
|
|
47
|
+
export declare function createRuntimeAttributor(): IRuntimeAttributor;
|
|
48
|
+
/**
|
|
49
|
+
* Mixes in logic to load and store runtime-based attribution functionality.
|
|
50
|
+
*
|
|
51
|
+
* The `scope` passed to `load` should implement `IProvideRuntimeAttributor`.
|
|
52
|
+
*
|
|
53
|
+
* Existing documents without stored attributors will not start storing attribution information: if an
|
|
54
|
+
* IRuntimeAttributor is passed via scope to load a document that never previously had attribution information,
|
|
55
|
+
* that attributor's `has` method will always return `false`.
|
|
56
|
+
* @param Base - base class, inherits from FluidAttributorRuntime
|
|
57
|
+
* @alpha
|
|
58
|
+
*/
|
|
59
|
+
export declare const mixinAttributor: (Base?: typeof ContainerRuntime) => typeof ContainerRuntime;
|
|
60
|
+
//# sourceMappingURL=mixinAttributor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mixinAttributor.d.ts","sourceRoot":"","sources":["../src/mixinAttributor.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAErE,OAAO,EACN,eAAe,EACf,cAAc,EAId,MAAM,qCAAqC,CAAC;AAe7C;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,sCAAsC,CAAC;AAEtE;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,yBAAgD,CAAC;AAExF;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;CAChD;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAmB,SAAQ,yBAAyB;IACpE;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,cAAc,GAAG,eAAe,CAAC;IAE1C;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC;IAElC;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,kBAAkB,CAE5D;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,UAAU,uBAAuB,4BA+ErB,CAAC"}
|