@fluid-experimental/oldest-client-observer 2.0.0-internal.2.0.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.
Files changed (43) hide show
  1. package/.eslintrc.js +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +9 -0
  4. package/api-extractor.json +4 -0
  5. package/dist/index.d.ts +7 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +19 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/interfaces.d.ts +30 -0
  10. package/dist/interfaces.d.ts.map +1 -0
  11. package/dist/interfaces.js +7 -0
  12. package/dist/interfaces.js.map +1 -0
  13. package/dist/oldestClientObserver.d.ts +71 -0
  14. package/dist/oldestClientObserver.d.ts.map +1 -0
  15. package/dist/oldestClientObserver.js +118 -0
  16. package/dist/oldestClientObserver.js.map +1 -0
  17. package/dist/packageVersion.d.ts +9 -0
  18. package/dist/packageVersion.d.ts.map +1 -0
  19. package/dist/packageVersion.js +12 -0
  20. package/dist/packageVersion.js.map +1 -0
  21. package/lib/index.d.ts +7 -0
  22. package/lib/index.d.ts.map +1 -0
  23. package/lib/index.js +7 -0
  24. package/lib/index.js.map +1 -0
  25. package/lib/interfaces.d.ts +30 -0
  26. package/lib/interfaces.d.ts.map +1 -0
  27. package/lib/interfaces.js +6 -0
  28. package/lib/interfaces.js.map +1 -0
  29. package/lib/oldestClientObserver.d.ts +71 -0
  30. package/lib/oldestClientObserver.d.ts.map +1 -0
  31. package/lib/oldestClientObserver.js +114 -0
  32. package/lib/oldestClientObserver.js.map +1 -0
  33. package/lib/packageVersion.d.ts +9 -0
  34. package/lib/packageVersion.d.ts.map +1 -0
  35. package/lib/packageVersion.js +9 -0
  36. package/lib/packageVersion.js.map +1 -0
  37. package/package.json +90 -0
  38. package/src/index.ts +7 -0
  39. package/src/interfaces.ts +42 -0
  40. package/src/oldestClientObserver.ts +125 -0
  41. package/src/packageVersion.ts +9 -0
  42. package/tsconfig.esnext.json +7 -0
  43. package/tsconfig.json +14 -0
package/.eslintrc.js ADDED
@@ -0,0 +1,13 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ module.exports = {
7
+ "extends": [
8
+ require.resolve("@fluidframework/eslint-config-fluid")
9
+ ],
10
+ "parserOptions": {
11
+ "project": ["./tsconfig.json", "./src/test/tsconfig.json"]
12
+ },
13
+ }
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,9 @@
1
+ # @fluid-experimental/oldest-client-observer
2
+
3
+ <!-- AUTO-GENERATED-CONTENT:START (README_TRADEMARK_SECTION:includeHeading=TRUE) -->
4
+ ## Trademark
5
+
6
+ This project may contain Microsoft trademarks or logos for Microsoft projects, products, or services.
7
+ Use of these trademarks or logos must follow Microsoft's [Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
8
+ Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
9
+ <!-- AUTO-GENERATED-CONTENT:END -->
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
3
+ "extends": "@fluidframework/build-common/api-extractor-common-report.json"
4
+ }
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ export * from "./interfaces";
6
+ export * from "./oldestClientObserver";
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;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ /*!
3
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
+ * Licensed under the MIT License.
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./interfaces"), exports);
18
+ __exportStar(require("./oldestClientObserver"), exports);
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;;;;;;;AAEH,+CAA6B;AAC7B,yDAAuC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport * from \"./interfaces\";\nexport * from \"./oldestClientObserver\";\n"]}
@@ -0,0 +1,30 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { IEvent, IEventProvider } from "@fluidframework/common-definitions";
6
+ import { AttachState } from "@fluidframework/container-definitions";
7
+ import { IQuorumClients } from "@fluidframework/protocol-definitions";
8
+ export interface IOldestClientObservableEvents extends IEvent {
9
+ (event: "connected", listener: () => void): any;
10
+ (event: "disconnected", listener: () => void): any;
11
+ }
12
+ /**
13
+ * This is to make OldestClientObserver work with either a ContainerRuntime or an IFluidDataStoreRuntime
14
+ * (both expose the relevant API surface and eventing). However, really this info probably shouldn't live on either,
15
+ * since neither is really the source of truth (they are just the only currently-available plumbing options).
16
+ * It's information about the connection, so the real source of truth is lower (at the connection layer).
17
+ */
18
+ export interface IOldestClientObservable extends IEventProvider<IOldestClientObservableEvents> {
19
+ getQuorum(): IQuorumClients;
20
+ attachState: AttachState;
21
+ connected: boolean;
22
+ clientId: string | undefined;
23
+ }
24
+ export interface IOldestClientObserverEvents extends IEvent {
25
+ (event: "becameOldest" | "lostOldest", listener: () => void): any;
26
+ }
27
+ export interface IOldestClientObserver extends IEventProvider<IOldestClientObserverEvents> {
28
+ isOldest(): boolean;
29
+ }
30
+ //# sourceMappingURL=interfaces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAEtE,MAAM,WAAW,6BAA8B,SAAQ,MAAM;IACzD,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;IAI3C,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;CACjD;AAED;;;;;GAKG;AACH,MAAM,WAAW,uBAAwB,SAAQ,cAAc,CAAC,6BAA6B,CAAC;IAC1F,SAAS,IAAI,cAAc,CAAC;IAM5B,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,MAAM,WAAW,2BAA4B,SAAQ,MAAM;IACvD,CAAC,KAAK,EAAE,cAAc,GAAG,YAAY,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;CAChE;AAED,MAAM,WAAW,qBAAsB,SAAQ,cAAc,CAAC,2BAA2B,CAAC;IACtF,QAAQ,IAAI,OAAO,CAAC;CACvB"}
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /*!
3
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
+ * Licensed under the MIT License.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ //# sourceMappingURL=interfaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IEvent, IEventProvider } from \"@fluidframework/common-definitions\";\nimport { AttachState } from \"@fluidframework/container-definitions\";\nimport { IQuorumClients } from \"@fluidframework/protocol-definitions\";\n\nexport interface IOldestClientObservableEvents extends IEvent {\n (event: \"connected\", listener: () => void);\n // Typescript won't convert IFluidDataStoreRuntime and ContainerRuntime if we unify these,\n // I believe this is because the \"connected\" event has a clientId arg in the runtimes.\n // eslint-disable-next-line @typescript-eslint/unified-signatures\n (event: \"disconnected\", listener: () => void);\n}\n\n/**\n * This is to make OldestClientObserver work with either a ContainerRuntime or an IFluidDataStoreRuntime\n * (both expose the relevant API surface and eventing). However, really this info probably shouldn't live on either,\n * since neither is really the source of truth (they are just the only currently-available plumbing options).\n * It's information about the connection, so the real source of truth is lower (at the connection layer).\n */\nexport interface IOldestClientObservable extends IEventProvider<IOldestClientObservableEvents> {\n getQuorum(): IQuorumClients;\n // Generic usage of attachState is a little unusual here. We will treat ourselves as \"the oldest client that\n // has information about this [container | data store]\", which in the case of detached data store may disagree\n // with whether we're the oldest client on the connected container. So in the data store case, it's only\n // safe use this as an indicator about rights to tasks performed against this specific data store, and not\n // more broadly.\n attachState: AttachState;\n connected: boolean;\n clientId: string | undefined;\n}\n\nexport interface IOldestClientObserverEvents extends IEvent {\n (event: \"becameOldest\" | \"lostOldest\", listener: () => void);\n}\n\nexport interface IOldestClientObserver extends IEventProvider<IOldestClientObserverEvents> {\n isOldest(): boolean;\n}\n"]}
@@ -0,0 +1,71 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { TypedEventEmitter } from "@fluidframework/common-utils";
6
+ import { IOldestClientObservable, IOldestClientObserverEvents, IOldestClientObserver } from "./interfaces";
7
+ /**
8
+ * The `OldestClientObserver` is a utility inspect if the local client is the oldest amongst connected clients (in
9
+ * terms of when they connected) and watch for changes.
10
+ *
11
+ * It is still experimental and under development. Please do try it out, but expect breaking changes in the future.
12
+ *
13
+ * @remarks
14
+ * ### Creation
15
+ *
16
+ * The `OldestClientObserver` constructor takes an `IOldestClientObservable`. This is most easily satisfied with
17
+ * either an `IContainerRuntime` or an `IFluidDataStoreRuntime`:
18
+ *
19
+ * ```typescript
20
+ * // E.g. from within a BaseContainerRuntimeFactory:
21
+ * protected async containerHasInitialized(runtime: IContainerRuntime) {
22
+ * const oldestClientObserver = new OldestClientObserver(runtime);
23
+ * // ...
24
+ * }
25
+ * ```
26
+ *
27
+ * ```typescript
28
+ * // From within a DataObject
29
+ * protected async hasInitialized() {
30
+ * const oldestClientObserver = new OldestClientObserver(this.runtime);
31
+ * // ...
32
+ * }
33
+ * ```
34
+ *
35
+ * ### Usage
36
+ *
37
+ * To check if the local client is the oldest, use the `isOldest()` method.
38
+ *
39
+ * ```typescript
40
+ * if (oldestClientObserver.isOldest()) {
41
+ * console.log("I'm the oldest");
42
+ * } else {
43
+ * console.log("Someone else is older");
44
+ * }
45
+ * ```
46
+ *
47
+ * ### Eventing
48
+ *
49
+ * `OldestClientObserver` is an `EventEmitter`, and will emit events when the local client becomes the oldest and when
50
+ * it is no longer the oldest.
51
+ *
52
+ * ```typescript
53
+ * oldestClientObserver.on("becameOldest", () => {
54
+ * console.log("I'm the oldest now");
55
+ * });
56
+ *
57
+ * oldestClientObserver.on("lostOldest", () => {
58
+ * console.log("I'm not the oldest anymore");
59
+ * });
60
+ * ```
61
+ */
62
+ export declare class OldestClientObserver extends TypedEventEmitter<IOldestClientObserverEvents> implements IOldestClientObserver {
63
+ private readonly observable;
64
+ private readonly quorum;
65
+ private currentIsOldest;
66
+ constructor(observable: IOldestClientObservable);
67
+ isOldest(): boolean;
68
+ private readonly updateOldest;
69
+ private computeIsOldest;
70
+ }
71
+ //# sourceMappingURL=oldestClientObserver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oldestClientObserver.d.ts","sourceRoot":"","sources":["../src/oldestClientObserver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAU,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGzE,OAAO,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE3G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,qBAAa,oBAAqB,SAAQ,iBAAiB,CAAC,2BAA2B,CACnF,YAAW,qBAAqB;IAGpB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAFvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,eAAe,CAAkB;gBACZ,UAAU,EAAE,uBAAuB;IAUzD,QAAQ,IAAI,OAAO;IAI1B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAU3B;IAEF,OAAO,CAAC,eAAe;CA6B1B"}
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ /*!
3
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
+ * Licensed under the MIT License.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.OldestClientObserver = void 0;
8
+ const common_utils_1 = require("@fluidframework/common-utils");
9
+ const container_definitions_1 = require("@fluidframework/container-definitions");
10
+ /**
11
+ * The `OldestClientObserver` is a utility inspect if the local client is the oldest amongst connected clients (in
12
+ * terms of when they connected) and watch for changes.
13
+ *
14
+ * It is still experimental and under development. Please do try it out, but expect breaking changes in the future.
15
+ *
16
+ * @remarks
17
+ * ### Creation
18
+ *
19
+ * The `OldestClientObserver` constructor takes an `IOldestClientObservable`. This is most easily satisfied with
20
+ * either an `IContainerRuntime` or an `IFluidDataStoreRuntime`:
21
+ *
22
+ * ```typescript
23
+ * // E.g. from within a BaseContainerRuntimeFactory:
24
+ * protected async containerHasInitialized(runtime: IContainerRuntime) {
25
+ * const oldestClientObserver = new OldestClientObserver(runtime);
26
+ * // ...
27
+ * }
28
+ * ```
29
+ *
30
+ * ```typescript
31
+ * // From within a DataObject
32
+ * protected async hasInitialized() {
33
+ * const oldestClientObserver = new OldestClientObserver(this.runtime);
34
+ * // ...
35
+ * }
36
+ * ```
37
+ *
38
+ * ### Usage
39
+ *
40
+ * To check if the local client is the oldest, use the `isOldest()` method.
41
+ *
42
+ * ```typescript
43
+ * if (oldestClientObserver.isOldest()) {
44
+ * console.log("I'm the oldest");
45
+ * } else {
46
+ * console.log("Someone else is older");
47
+ * }
48
+ * ```
49
+ *
50
+ * ### Eventing
51
+ *
52
+ * `OldestClientObserver` is an `EventEmitter`, and will emit events when the local client becomes the oldest and when
53
+ * it is no longer the oldest.
54
+ *
55
+ * ```typescript
56
+ * oldestClientObserver.on("becameOldest", () => {
57
+ * console.log("I'm the oldest now");
58
+ * });
59
+ *
60
+ * oldestClientObserver.on("lostOldest", () => {
61
+ * console.log("I'm not the oldest anymore");
62
+ * });
63
+ * ```
64
+ */
65
+ class OldestClientObserver extends common_utils_1.TypedEventEmitter {
66
+ constructor(observable) {
67
+ super();
68
+ this.observable = observable;
69
+ this.currentIsOldest = false;
70
+ this.updateOldest = () => {
71
+ const oldest = this.computeIsOldest();
72
+ if (this.currentIsOldest !== oldest) {
73
+ this.currentIsOldest = oldest;
74
+ if (oldest) {
75
+ this.emit("becameOldest");
76
+ }
77
+ else {
78
+ this.emit("lostOldest");
79
+ }
80
+ }
81
+ };
82
+ this.quorum = this.observable.getQuorum();
83
+ this.currentIsOldest = this.computeIsOldest();
84
+ this.quorum.on("addMember", this.updateOldest);
85
+ this.quorum.on("removeMember", this.updateOldest);
86
+ observable.on("connected", this.updateOldest);
87
+ observable.on("disconnected", this.updateOldest);
88
+ }
89
+ isOldest() {
90
+ return this.currentIsOldest;
91
+ }
92
+ computeIsOldest() {
93
+ // If the container is detached, we are the only ones that know about it and are the oldest by default.
94
+ if (this.observable.attachState === container_definitions_1.AttachState.Detached) {
95
+ return true;
96
+ }
97
+ // If we're not connected we can't be the oldest connected client.
98
+ if (!this.observable.connected) {
99
+ return false;
100
+ }
101
+ (0, common_utils_1.assert)(this.observable.clientId !== undefined, 0x1da /* "Client id should be set if connected" */);
102
+ const selfSequencedClient = this.quorum.getMember(this.observable.clientId);
103
+ // When in readonly mode our clientId will not be present in the quorum.
104
+ if (selfSequencedClient === undefined) {
105
+ return false;
106
+ }
107
+ const members = this.quorum.getMembers();
108
+ for (const sequencedClient of members.values()) {
109
+ if (sequencedClient.sequenceNumber < selfSequencedClient.sequenceNumber) {
110
+ return false;
111
+ }
112
+ }
113
+ // No member of the quorum was older
114
+ return true;
115
+ }
116
+ }
117
+ exports.OldestClientObserver = OldestClientObserver;
118
+ //# sourceMappingURL=oldestClientObserver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oldestClientObserver.js","sourceRoot":"","sources":["../src/oldestClientObserver.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,+DAAyE;AACzE,iFAAoE;AAIpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,MAAa,oBAAqB,SAAQ,gCAA8C;IAIpF,YAA6B,UAAmC;QAC5D,KAAK,EAAE,CAAC;QADiB,eAAU,GAAV,UAAU,CAAyB;QADxD,oBAAe,GAAY,KAAK,CAAC;QAexB,iBAAY,GAAG,GAAG,EAAE;YACjC,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;YACtC,IAAI,IAAI,CAAC,eAAe,KAAK,MAAM,EAAE;gBACjC,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC;gBAC9B,IAAI,MAAM,EAAE;oBACR,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;iBAC7B;qBAAM;oBACH,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;iBAC3B;aACJ;QACL,CAAC,CAAC;QAtBE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;QAC1C,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QAC9C,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAC/C,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,cAAc,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAClD,UAAU,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAC9C,UAAU,CAAC,EAAE,CAAC,cAAc,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IACrD,CAAC;IAEM,QAAQ;QACX,OAAO,IAAI,CAAC,eAAe,CAAC;IAChC,CAAC;IAcO,eAAe;QACnB,uGAAuG;QACvG,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,KAAK,mCAAW,CAAC,QAAQ,EAAE;YACtD,OAAO,IAAI,CAAC;SACf;QAED,kEAAkE;QAClE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE;YAC5B,OAAO,KAAK,CAAC;SAChB;QAED,IAAA,qBAAM,EAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,KAAK,SAAS,EAAE,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAEnG,MAAM,mBAAmB,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAC5E,wEAAwE;QACxE,IAAI,mBAAmB,KAAK,SAAS,EAAE;YACnC,OAAO,KAAK,CAAC;SAChB;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QACzC,KAAK,MAAM,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE;YAC5C,IAAI,eAAe,CAAC,cAAc,GAAG,mBAAmB,CAAC,cAAc,EAAE;gBACrE,OAAO,KAAK,CAAC;aAChB;SACJ;QAED,oCAAoC;QACpC,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ;AA3DD,oDA2DC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert, TypedEventEmitter } from \"@fluidframework/common-utils\";\nimport { AttachState } from \"@fluidframework/container-definitions\";\nimport { IQuorumClients } from \"@fluidframework/protocol-definitions\";\nimport { IOldestClientObservable, IOldestClientObserverEvents, IOldestClientObserver } from \"./interfaces\";\n\n/**\n * The `OldestClientObserver` is a utility inspect if the local client is the oldest amongst connected clients (in\n * terms of when they connected) and watch for changes.\n *\n * It is still experimental and under development. Please do try it out, but expect breaking changes in the future.\n *\n * @remarks\n * ### Creation\n *\n * The `OldestClientObserver` constructor takes an `IOldestClientObservable`. This is most easily satisfied with\n * either an `IContainerRuntime` or an `IFluidDataStoreRuntime`:\n *\n * ```typescript\n * // E.g. from within a BaseContainerRuntimeFactory:\n * protected async containerHasInitialized(runtime: IContainerRuntime) {\n * const oldestClientObserver = new OldestClientObserver(runtime);\n * // ...\n * }\n * ```\n *\n * ```typescript\n * // From within a DataObject\n * protected async hasInitialized() {\n * const oldestClientObserver = new OldestClientObserver(this.runtime);\n * // ...\n * }\n * ```\n *\n * ### Usage\n *\n * To check if the local client is the oldest, use the `isOldest()` method.\n *\n * ```typescript\n * if (oldestClientObserver.isOldest()) {\n * console.log(\"I'm the oldest\");\n * } else {\n * console.log(\"Someone else is older\");\n * }\n * ```\n *\n * ### Eventing\n *\n * `OldestClientObserver` is an `EventEmitter`, and will emit events when the local client becomes the oldest and when\n * it is no longer the oldest.\n *\n * ```typescript\n * oldestClientObserver.on(\"becameOldest\", () => {\n * console.log(\"I'm the oldest now\");\n * });\n *\n * oldestClientObserver.on(\"lostOldest\", () => {\n * console.log(\"I'm not the oldest anymore\");\n * });\n * ```\n */\nexport class OldestClientObserver extends TypedEventEmitter<IOldestClientObserverEvents>\n implements IOldestClientObserver {\n private readonly quorum: IQuorumClients;\n private currentIsOldest: boolean = false;\n constructor(private readonly observable: IOldestClientObservable) {\n super();\n this.quorum = this.observable.getQuorum();\n this.currentIsOldest = this.computeIsOldest();\n this.quorum.on(\"addMember\", this.updateOldest);\n this.quorum.on(\"removeMember\", this.updateOldest);\n observable.on(\"connected\", this.updateOldest);\n observable.on(\"disconnected\", this.updateOldest);\n }\n\n public isOldest(): boolean {\n return this.currentIsOldest;\n }\n\n private readonly updateOldest = () => {\n const oldest = this.computeIsOldest();\n if (this.currentIsOldest !== oldest) {\n this.currentIsOldest = oldest;\n if (oldest) {\n this.emit(\"becameOldest\");\n } else {\n this.emit(\"lostOldest\");\n }\n }\n };\n\n private computeIsOldest(): boolean {\n // If the container is detached, we are the only ones that know about it and are the oldest by default.\n if (this.observable.attachState === AttachState.Detached) {\n return true;\n }\n\n // If we're not connected we can't be the oldest connected client.\n if (!this.observable.connected) {\n return false;\n }\n\n assert(this.observable.clientId !== undefined, 0x1da /* \"Client id should be set if connected\" */);\n\n const selfSequencedClient = this.quorum.getMember(this.observable.clientId);\n // When in readonly mode our clientId will not be present in the quorum.\n if (selfSequencedClient === undefined) {\n return false;\n }\n\n const members = this.quorum.getMembers();\n for (const sequencedClient of members.values()) {\n if (sequencedClient.sequenceNumber < selfSequencedClient.sequenceNumber) {\n return false;\n }\n }\n\n // No member of the quorum was older\n return true;\n }\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ *
5
+ * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
+ */
7
+ export declare const pkgName = "@fluid-experimental/oldest-client-observer";
8
+ export declare const pkgVersion = "2.0.0-internal.2.0.0";
9
+ //# sourceMappingURL=packageVersion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,+CAA+C,CAAC;AACpE,eAAO,MAAM,UAAU,yBAAyB,CAAC"}
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ /*!
3
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
+ * Licensed under the MIT License.
5
+ *
6
+ * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.pkgVersion = exports.pkgName = void 0;
10
+ exports.pkgName = "@fluid-experimental/oldest-client-observer";
11
+ exports.pkgVersion = "2.0.0-internal.2.0.0";
12
+ //# sourceMappingURL=packageVersion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,4CAA4C,CAAC;AACvD,QAAA,UAAU,GAAG,sBAAsB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluid-experimental/oldest-client-observer\";\nexport const pkgVersion = \"2.0.0-internal.2.0.0\";\n"]}
package/lib/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 * from "./interfaces";
6
+ export * from "./oldestClientObserver";
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;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC"}
package/lib/index.js 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 * from "./interfaces";
6
+ export * from "./oldestClientObserver";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport * from \"./interfaces\";\nexport * from \"./oldestClientObserver\";\n"]}
@@ -0,0 +1,30 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { IEvent, IEventProvider } from "@fluidframework/common-definitions";
6
+ import { AttachState } from "@fluidframework/container-definitions";
7
+ import { IQuorumClients } from "@fluidframework/protocol-definitions";
8
+ export interface IOldestClientObservableEvents extends IEvent {
9
+ (event: "connected", listener: () => void): any;
10
+ (event: "disconnected", listener: () => void): any;
11
+ }
12
+ /**
13
+ * This is to make OldestClientObserver work with either a ContainerRuntime or an IFluidDataStoreRuntime
14
+ * (both expose the relevant API surface and eventing). However, really this info probably shouldn't live on either,
15
+ * since neither is really the source of truth (they are just the only currently-available plumbing options).
16
+ * It's information about the connection, so the real source of truth is lower (at the connection layer).
17
+ */
18
+ export interface IOldestClientObservable extends IEventProvider<IOldestClientObservableEvents> {
19
+ getQuorum(): IQuorumClients;
20
+ attachState: AttachState;
21
+ connected: boolean;
22
+ clientId: string | undefined;
23
+ }
24
+ export interface IOldestClientObserverEvents extends IEvent {
25
+ (event: "becameOldest" | "lostOldest", listener: () => void): any;
26
+ }
27
+ export interface IOldestClientObserver extends IEventProvider<IOldestClientObserverEvents> {
28
+ isOldest(): boolean;
29
+ }
30
+ //# sourceMappingURL=interfaces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAEtE,MAAM,WAAW,6BAA8B,SAAQ,MAAM;IACzD,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;IAI3C,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;CACjD;AAED;;;;;GAKG;AACH,MAAM,WAAW,uBAAwB,SAAQ,cAAc,CAAC,6BAA6B,CAAC;IAC1F,SAAS,IAAI,cAAc,CAAC;IAM5B,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,MAAM,WAAW,2BAA4B,SAAQ,MAAM;IACvD,CAAC,KAAK,EAAE,cAAc,GAAG,YAAY,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;CAChE;AAED,MAAM,WAAW,qBAAsB,SAAQ,cAAc,CAAC,2BAA2B,CAAC;IACtF,QAAQ,IAAI,OAAO,CAAC;CACvB"}
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=interfaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IEvent, IEventProvider } from \"@fluidframework/common-definitions\";\nimport { AttachState } from \"@fluidframework/container-definitions\";\nimport { IQuorumClients } from \"@fluidframework/protocol-definitions\";\n\nexport interface IOldestClientObservableEvents extends IEvent {\n (event: \"connected\", listener: () => void);\n // Typescript won't convert IFluidDataStoreRuntime and ContainerRuntime if we unify these,\n // I believe this is because the \"connected\" event has a clientId arg in the runtimes.\n // eslint-disable-next-line @typescript-eslint/unified-signatures\n (event: \"disconnected\", listener: () => void);\n}\n\n/**\n * This is to make OldestClientObserver work with either a ContainerRuntime or an IFluidDataStoreRuntime\n * (both expose the relevant API surface and eventing). However, really this info probably shouldn't live on either,\n * since neither is really the source of truth (they are just the only currently-available plumbing options).\n * It's information about the connection, so the real source of truth is lower (at the connection layer).\n */\nexport interface IOldestClientObservable extends IEventProvider<IOldestClientObservableEvents> {\n getQuorum(): IQuorumClients;\n // Generic usage of attachState is a little unusual here. We will treat ourselves as \"the oldest client that\n // has information about this [container | data store]\", which in the case of detached data store may disagree\n // with whether we're the oldest client on the connected container. So in the data store case, it's only\n // safe use this as an indicator about rights to tasks performed against this specific data store, and not\n // more broadly.\n attachState: AttachState;\n connected: boolean;\n clientId: string | undefined;\n}\n\nexport interface IOldestClientObserverEvents extends IEvent {\n (event: \"becameOldest\" | \"lostOldest\", listener: () => void);\n}\n\nexport interface IOldestClientObserver extends IEventProvider<IOldestClientObserverEvents> {\n isOldest(): boolean;\n}\n"]}
@@ -0,0 +1,71 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { TypedEventEmitter } from "@fluidframework/common-utils";
6
+ import { IOldestClientObservable, IOldestClientObserverEvents, IOldestClientObserver } from "./interfaces";
7
+ /**
8
+ * The `OldestClientObserver` is a utility inspect if the local client is the oldest amongst connected clients (in
9
+ * terms of when they connected) and watch for changes.
10
+ *
11
+ * It is still experimental and under development. Please do try it out, but expect breaking changes in the future.
12
+ *
13
+ * @remarks
14
+ * ### Creation
15
+ *
16
+ * The `OldestClientObserver` constructor takes an `IOldestClientObservable`. This is most easily satisfied with
17
+ * either an `IContainerRuntime` or an `IFluidDataStoreRuntime`:
18
+ *
19
+ * ```typescript
20
+ * // E.g. from within a BaseContainerRuntimeFactory:
21
+ * protected async containerHasInitialized(runtime: IContainerRuntime) {
22
+ * const oldestClientObserver = new OldestClientObserver(runtime);
23
+ * // ...
24
+ * }
25
+ * ```
26
+ *
27
+ * ```typescript
28
+ * // From within a DataObject
29
+ * protected async hasInitialized() {
30
+ * const oldestClientObserver = new OldestClientObserver(this.runtime);
31
+ * // ...
32
+ * }
33
+ * ```
34
+ *
35
+ * ### Usage
36
+ *
37
+ * To check if the local client is the oldest, use the `isOldest()` method.
38
+ *
39
+ * ```typescript
40
+ * if (oldestClientObserver.isOldest()) {
41
+ * console.log("I'm the oldest");
42
+ * } else {
43
+ * console.log("Someone else is older");
44
+ * }
45
+ * ```
46
+ *
47
+ * ### Eventing
48
+ *
49
+ * `OldestClientObserver` is an `EventEmitter`, and will emit events when the local client becomes the oldest and when
50
+ * it is no longer the oldest.
51
+ *
52
+ * ```typescript
53
+ * oldestClientObserver.on("becameOldest", () => {
54
+ * console.log("I'm the oldest now");
55
+ * });
56
+ *
57
+ * oldestClientObserver.on("lostOldest", () => {
58
+ * console.log("I'm not the oldest anymore");
59
+ * });
60
+ * ```
61
+ */
62
+ export declare class OldestClientObserver extends TypedEventEmitter<IOldestClientObserverEvents> implements IOldestClientObserver {
63
+ private readonly observable;
64
+ private readonly quorum;
65
+ private currentIsOldest;
66
+ constructor(observable: IOldestClientObservable);
67
+ isOldest(): boolean;
68
+ private readonly updateOldest;
69
+ private computeIsOldest;
70
+ }
71
+ //# sourceMappingURL=oldestClientObserver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oldestClientObserver.d.ts","sourceRoot":"","sources":["../src/oldestClientObserver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAU,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGzE,OAAO,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE3G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,qBAAa,oBAAqB,SAAQ,iBAAiB,CAAC,2BAA2B,CACnF,YAAW,qBAAqB;IAGpB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAFvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,eAAe,CAAkB;gBACZ,UAAU,EAAE,uBAAuB;IAUzD,QAAQ,IAAI,OAAO;IAI1B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAU3B;IAEF,OAAO,CAAC,eAAe;CA6B1B"}
@@ -0,0 +1,114 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
6
+ import { AttachState } from "@fluidframework/container-definitions";
7
+ /**
8
+ * The `OldestClientObserver` is a utility inspect if the local client is the oldest amongst connected clients (in
9
+ * terms of when they connected) and watch for changes.
10
+ *
11
+ * It is still experimental and under development. Please do try it out, but expect breaking changes in the future.
12
+ *
13
+ * @remarks
14
+ * ### Creation
15
+ *
16
+ * The `OldestClientObserver` constructor takes an `IOldestClientObservable`. This is most easily satisfied with
17
+ * either an `IContainerRuntime` or an `IFluidDataStoreRuntime`:
18
+ *
19
+ * ```typescript
20
+ * // E.g. from within a BaseContainerRuntimeFactory:
21
+ * protected async containerHasInitialized(runtime: IContainerRuntime) {
22
+ * const oldestClientObserver = new OldestClientObserver(runtime);
23
+ * // ...
24
+ * }
25
+ * ```
26
+ *
27
+ * ```typescript
28
+ * // From within a DataObject
29
+ * protected async hasInitialized() {
30
+ * const oldestClientObserver = new OldestClientObserver(this.runtime);
31
+ * // ...
32
+ * }
33
+ * ```
34
+ *
35
+ * ### Usage
36
+ *
37
+ * To check if the local client is the oldest, use the `isOldest()` method.
38
+ *
39
+ * ```typescript
40
+ * if (oldestClientObserver.isOldest()) {
41
+ * console.log("I'm the oldest");
42
+ * } else {
43
+ * console.log("Someone else is older");
44
+ * }
45
+ * ```
46
+ *
47
+ * ### Eventing
48
+ *
49
+ * `OldestClientObserver` is an `EventEmitter`, and will emit events when the local client becomes the oldest and when
50
+ * it is no longer the oldest.
51
+ *
52
+ * ```typescript
53
+ * oldestClientObserver.on("becameOldest", () => {
54
+ * console.log("I'm the oldest now");
55
+ * });
56
+ *
57
+ * oldestClientObserver.on("lostOldest", () => {
58
+ * console.log("I'm not the oldest anymore");
59
+ * });
60
+ * ```
61
+ */
62
+ export class OldestClientObserver extends TypedEventEmitter {
63
+ constructor(observable) {
64
+ super();
65
+ this.observable = observable;
66
+ this.currentIsOldest = false;
67
+ this.updateOldest = () => {
68
+ const oldest = this.computeIsOldest();
69
+ if (this.currentIsOldest !== oldest) {
70
+ this.currentIsOldest = oldest;
71
+ if (oldest) {
72
+ this.emit("becameOldest");
73
+ }
74
+ else {
75
+ this.emit("lostOldest");
76
+ }
77
+ }
78
+ };
79
+ this.quorum = this.observable.getQuorum();
80
+ this.currentIsOldest = this.computeIsOldest();
81
+ this.quorum.on("addMember", this.updateOldest);
82
+ this.quorum.on("removeMember", this.updateOldest);
83
+ observable.on("connected", this.updateOldest);
84
+ observable.on("disconnected", this.updateOldest);
85
+ }
86
+ isOldest() {
87
+ return this.currentIsOldest;
88
+ }
89
+ computeIsOldest() {
90
+ // If the container is detached, we are the only ones that know about it and are the oldest by default.
91
+ if (this.observable.attachState === AttachState.Detached) {
92
+ return true;
93
+ }
94
+ // If we're not connected we can't be the oldest connected client.
95
+ if (!this.observable.connected) {
96
+ return false;
97
+ }
98
+ assert(this.observable.clientId !== undefined, 0x1da /* "Client id should be set if connected" */);
99
+ const selfSequencedClient = this.quorum.getMember(this.observable.clientId);
100
+ // When in readonly mode our clientId will not be present in the quorum.
101
+ if (selfSequencedClient === undefined) {
102
+ return false;
103
+ }
104
+ const members = this.quorum.getMembers();
105
+ for (const sequencedClient of members.values()) {
106
+ if (sequencedClient.sequenceNumber < selfSequencedClient.sequenceNumber) {
107
+ return false;
108
+ }
109
+ }
110
+ // No member of the quorum was older
111
+ return true;
112
+ }
113
+ }
114
+ //# sourceMappingURL=oldestClientObserver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oldestClientObserver.js","sourceRoot":"","sources":["../src/oldestClientObserver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AAIpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,MAAM,OAAO,oBAAqB,SAAQ,iBAA8C;IAIpF,YAA6B,UAAmC;QAC5D,KAAK,EAAE,CAAC;QADiB,eAAU,GAAV,UAAU,CAAyB;QADxD,oBAAe,GAAY,KAAK,CAAC;QAexB,iBAAY,GAAG,GAAG,EAAE;YACjC,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;YACtC,IAAI,IAAI,CAAC,eAAe,KAAK,MAAM,EAAE;gBACjC,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC;gBAC9B,IAAI,MAAM,EAAE;oBACR,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;iBAC7B;qBAAM;oBACH,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;iBAC3B;aACJ;QACL,CAAC,CAAC;QAtBE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;QAC1C,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QAC9C,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAC/C,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,cAAc,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAClD,UAAU,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAC9C,UAAU,CAAC,EAAE,CAAC,cAAc,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IACrD,CAAC;IAEM,QAAQ;QACX,OAAO,IAAI,CAAC,eAAe,CAAC;IAChC,CAAC;IAcO,eAAe;QACnB,uGAAuG;QACvG,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,KAAK,WAAW,CAAC,QAAQ,EAAE;YACtD,OAAO,IAAI,CAAC;SACf;QAED,kEAAkE;QAClE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE;YAC5B,OAAO,KAAK,CAAC;SAChB;QAED,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,KAAK,SAAS,EAAE,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAEnG,MAAM,mBAAmB,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAC5E,wEAAwE;QACxE,IAAI,mBAAmB,KAAK,SAAS,EAAE;YACnC,OAAO,KAAK,CAAC;SAChB;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QACzC,KAAK,MAAM,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE;YAC5C,IAAI,eAAe,CAAC,cAAc,GAAG,mBAAmB,CAAC,cAAc,EAAE;gBACrE,OAAO,KAAK,CAAC;aAChB;SACJ;QAED,oCAAoC;QACpC,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert, TypedEventEmitter } from \"@fluidframework/common-utils\";\nimport { AttachState } from \"@fluidframework/container-definitions\";\nimport { IQuorumClients } from \"@fluidframework/protocol-definitions\";\nimport { IOldestClientObservable, IOldestClientObserverEvents, IOldestClientObserver } from \"./interfaces\";\n\n/**\n * The `OldestClientObserver` is a utility inspect if the local client is the oldest amongst connected clients (in\n * terms of when they connected) and watch for changes.\n *\n * It is still experimental and under development. Please do try it out, but expect breaking changes in the future.\n *\n * @remarks\n * ### Creation\n *\n * The `OldestClientObserver` constructor takes an `IOldestClientObservable`. This is most easily satisfied with\n * either an `IContainerRuntime` or an `IFluidDataStoreRuntime`:\n *\n * ```typescript\n * // E.g. from within a BaseContainerRuntimeFactory:\n * protected async containerHasInitialized(runtime: IContainerRuntime) {\n * const oldestClientObserver = new OldestClientObserver(runtime);\n * // ...\n * }\n * ```\n *\n * ```typescript\n * // From within a DataObject\n * protected async hasInitialized() {\n * const oldestClientObserver = new OldestClientObserver(this.runtime);\n * // ...\n * }\n * ```\n *\n * ### Usage\n *\n * To check if the local client is the oldest, use the `isOldest()` method.\n *\n * ```typescript\n * if (oldestClientObserver.isOldest()) {\n * console.log(\"I'm the oldest\");\n * } else {\n * console.log(\"Someone else is older\");\n * }\n * ```\n *\n * ### Eventing\n *\n * `OldestClientObserver` is an `EventEmitter`, and will emit events when the local client becomes the oldest and when\n * it is no longer the oldest.\n *\n * ```typescript\n * oldestClientObserver.on(\"becameOldest\", () => {\n * console.log(\"I'm the oldest now\");\n * });\n *\n * oldestClientObserver.on(\"lostOldest\", () => {\n * console.log(\"I'm not the oldest anymore\");\n * });\n * ```\n */\nexport class OldestClientObserver extends TypedEventEmitter<IOldestClientObserverEvents>\n implements IOldestClientObserver {\n private readonly quorum: IQuorumClients;\n private currentIsOldest: boolean = false;\n constructor(private readonly observable: IOldestClientObservable) {\n super();\n this.quorum = this.observable.getQuorum();\n this.currentIsOldest = this.computeIsOldest();\n this.quorum.on(\"addMember\", this.updateOldest);\n this.quorum.on(\"removeMember\", this.updateOldest);\n observable.on(\"connected\", this.updateOldest);\n observable.on(\"disconnected\", this.updateOldest);\n }\n\n public isOldest(): boolean {\n return this.currentIsOldest;\n }\n\n private readonly updateOldest = () => {\n const oldest = this.computeIsOldest();\n if (this.currentIsOldest !== oldest) {\n this.currentIsOldest = oldest;\n if (oldest) {\n this.emit(\"becameOldest\");\n } else {\n this.emit(\"lostOldest\");\n }\n }\n };\n\n private computeIsOldest(): boolean {\n // If the container is detached, we are the only ones that know about it and are the oldest by default.\n if (this.observable.attachState === AttachState.Detached) {\n return true;\n }\n\n // If we're not connected we can't be the oldest connected client.\n if (!this.observable.connected) {\n return false;\n }\n\n assert(this.observable.clientId !== undefined, 0x1da /* \"Client id should be set if connected\" */);\n\n const selfSequencedClient = this.quorum.getMember(this.observable.clientId);\n // When in readonly mode our clientId will not be present in the quorum.\n if (selfSequencedClient === undefined) {\n return false;\n }\n\n const members = this.quorum.getMembers();\n for (const sequencedClient of members.values()) {\n if (sequencedClient.sequenceNumber < selfSequencedClient.sequenceNumber) {\n return false;\n }\n }\n\n // No member of the quorum was older\n return true;\n }\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ *
5
+ * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
+ */
7
+ export declare const pkgName = "@fluid-experimental/oldest-client-observer";
8
+ export declare const pkgVersion = "2.0.0-internal.2.0.0";
9
+ //# sourceMappingURL=packageVersion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,+CAA+C,CAAC;AACpE,eAAO,MAAM,UAAU,yBAAyB,CAAC"}
@@ -0,0 +1,9 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ *
5
+ * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
+ */
7
+ export const pkgName = "@fluid-experimental/oldest-client-observer";
8
+ export const pkgVersion = "2.0.0-internal.2.0.0";
9
+ //# sourceMappingURL=packageVersion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,4CAA4C,CAAC;AACpE,MAAM,CAAC,MAAM,UAAU,GAAG,sBAAsB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluid-experimental/oldest-client-observer\";\nexport const pkgVersion = \"2.0.0-internal.2.0.0\";\n"]}
package/package.json ADDED
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "@fluid-experimental/oldest-client-observer",
3
+ "version": "2.0.0-internal.2.0.0",
4
+ "description": "Data object to determine if the local client is the oldest amongst connected clients",
5
+ "homepage": "https://fluidframework.com",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/microsoft/FluidFramework.git",
9
+ "directory": "packages/framework/oldest-client-observer"
10
+ },
11
+ "license": "MIT",
12
+ "author": "Microsoft and contributors",
13
+ "sideEffects": false,
14
+ "main": "dist/index.js",
15
+ "module": "lib/index.js",
16
+ "types": "dist/index.d.ts",
17
+ "scripts": {
18
+ "build": "npm run build:genver && concurrently npm:build:compile npm:lint && npm run build:docs",
19
+ "build:commonjs": "npm run tsc && npm run typetests:gen && npm run build:test",
20
+ "build:compile": "concurrently npm:build:commonjs npm:build:esnext",
21
+ "build:docs": "api-extractor run --local --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/doc-models/* ../../../_api-extractor-temp/",
22
+ "build:esnext": "tsc --project ./tsconfig.esnext.json",
23
+ "build:full": "npm run build",
24
+ "build:full:compile": "npm run build:compile",
25
+ "build:genver": "gen-version",
26
+ "build:test": "tsc --project ./src/test/tsconfig.json",
27
+ "ci:build:docs": "api-extractor run --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/* ../../../_api-extractor-temp/",
28
+ "clean": "rimraf dist lib *.tsbuildinfo *.build.log",
29
+ "eslint": "eslint --format stylish src",
30
+ "eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
31
+ "lint": "npm run eslint",
32
+ "lint:fix": "npm run eslint:fix",
33
+ "tsc": "tsc",
34
+ "tsfmt": "tsfmt --verify",
35
+ "tsfmt:fix": "tsfmt --replace",
36
+ "typetests:gen": "fluid-type-validator -g -d ."
37
+ },
38
+ "nyc": {
39
+ "all": true,
40
+ "cache-dir": "nyc/.cache",
41
+ "exclude": [
42
+ "src/test/**/*.ts",
43
+ "dist/test/**/*.js"
44
+ ],
45
+ "exclude-after-remap": false,
46
+ "include": [
47
+ "src/**/*.ts",
48
+ "dist/**/*.js"
49
+ ],
50
+ "report-dir": "nyc/report",
51
+ "reporter": [
52
+ "cobertura",
53
+ "html",
54
+ "text"
55
+ ],
56
+ "temp-directory": "nyc/.nyc_output"
57
+ },
58
+ "dependencies": {
59
+ "@fluidframework/common-definitions": "^0.20.1",
60
+ "@fluidframework/common-utils": "^1.0.0",
61
+ "@fluidframework/container-definitions": "^2.0.0-internal.2.0.0",
62
+ "@fluidframework/protocol-definitions": "^1.1.0"
63
+ },
64
+ "devDependencies": {
65
+ "@fluid-internal/test-dds-utils": "^2.0.0-internal.2.0.0",
66
+ "@fluidframework/build-common": "^1.0.0",
67
+ "@fluidframework/build-tools": "^0.4.6000",
68
+ "@fluidframework/eslint-config-fluid": "^1.0.0",
69
+ "@fluidframework/mocha-test-setup": "^2.0.0-internal.2.0.0",
70
+ "@fluidframework/test-runtime-utils": "^2.0.0-internal.2.0.0",
71
+ "@microsoft/api-extractor": "^7.22.2",
72
+ "@rushstack/eslint-config": "^2.5.1",
73
+ "@types/mocha": "^9.1.1",
74
+ "@types/node": "^14.18.0",
75
+ "concurrently": "^6.2.0",
76
+ "copyfiles": "^2.4.1",
77
+ "cross-env": "^7.0.2",
78
+ "eslint": "~8.6.0",
79
+ "mocha": "^10.0.0",
80
+ "mocha-junit-reporter": "^1.18.0",
81
+ "nyc": "^15.0.0",
82
+ "rimraf": "^2.6.2",
83
+ "typescript": "~4.5.5",
84
+ "typescript-formatter": "7.1.0"
85
+ },
86
+ "typeValidation": {
87
+ "version": "2.0.0",
88
+ "broken": {}
89
+ }
90
+ }
package/src/index.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
+
6
+ export * from "./interfaces";
7
+ export * from "./oldestClientObserver";
@@ -0,0 +1,42 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { IEvent, IEventProvider } from "@fluidframework/common-definitions";
7
+ import { AttachState } from "@fluidframework/container-definitions";
8
+ import { IQuorumClients } from "@fluidframework/protocol-definitions";
9
+
10
+ export interface IOldestClientObservableEvents extends IEvent {
11
+ (event: "connected", listener: () => void);
12
+ // Typescript won't convert IFluidDataStoreRuntime and ContainerRuntime if we unify these,
13
+ // I believe this is because the "connected" event has a clientId arg in the runtimes.
14
+ // eslint-disable-next-line @typescript-eslint/unified-signatures
15
+ (event: "disconnected", listener: () => void);
16
+ }
17
+
18
+ /**
19
+ * This is to make OldestClientObserver work with either a ContainerRuntime or an IFluidDataStoreRuntime
20
+ * (both expose the relevant API surface and eventing). However, really this info probably shouldn't live on either,
21
+ * since neither is really the source of truth (they are just the only currently-available plumbing options).
22
+ * It's information about the connection, so the real source of truth is lower (at the connection layer).
23
+ */
24
+ export interface IOldestClientObservable extends IEventProvider<IOldestClientObservableEvents> {
25
+ getQuorum(): IQuorumClients;
26
+ // Generic usage of attachState is a little unusual here. We will treat ourselves as "the oldest client that
27
+ // has information about this [container | data store]", which in the case of detached data store may disagree
28
+ // with whether we're the oldest client on the connected container. So in the data store case, it's only
29
+ // safe use this as an indicator about rights to tasks performed against this specific data store, and not
30
+ // more broadly.
31
+ attachState: AttachState;
32
+ connected: boolean;
33
+ clientId: string | undefined;
34
+ }
35
+
36
+ export interface IOldestClientObserverEvents extends IEvent {
37
+ (event: "becameOldest" | "lostOldest", listener: () => void);
38
+ }
39
+
40
+ export interface IOldestClientObserver extends IEventProvider<IOldestClientObserverEvents> {
41
+ isOldest(): boolean;
42
+ }
@@ -0,0 +1,125 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
7
+ import { AttachState } from "@fluidframework/container-definitions";
8
+ import { IQuorumClients } from "@fluidframework/protocol-definitions";
9
+ import { IOldestClientObservable, IOldestClientObserverEvents, IOldestClientObserver } from "./interfaces";
10
+
11
+ /**
12
+ * The `OldestClientObserver` is a utility inspect if the local client is the oldest amongst connected clients (in
13
+ * terms of when they connected) and watch for changes.
14
+ *
15
+ * It is still experimental and under development. Please do try it out, but expect breaking changes in the future.
16
+ *
17
+ * @remarks
18
+ * ### Creation
19
+ *
20
+ * The `OldestClientObserver` constructor takes an `IOldestClientObservable`. This is most easily satisfied with
21
+ * either an `IContainerRuntime` or an `IFluidDataStoreRuntime`:
22
+ *
23
+ * ```typescript
24
+ * // E.g. from within a BaseContainerRuntimeFactory:
25
+ * protected async containerHasInitialized(runtime: IContainerRuntime) {
26
+ * const oldestClientObserver = new OldestClientObserver(runtime);
27
+ * // ...
28
+ * }
29
+ * ```
30
+ *
31
+ * ```typescript
32
+ * // From within a DataObject
33
+ * protected async hasInitialized() {
34
+ * const oldestClientObserver = new OldestClientObserver(this.runtime);
35
+ * // ...
36
+ * }
37
+ * ```
38
+ *
39
+ * ### Usage
40
+ *
41
+ * To check if the local client is the oldest, use the `isOldest()` method.
42
+ *
43
+ * ```typescript
44
+ * if (oldestClientObserver.isOldest()) {
45
+ * console.log("I'm the oldest");
46
+ * } else {
47
+ * console.log("Someone else is older");
48
+ * }
49
+ * ```
50
+ *
51
+ * ### Eventing
52
+ *
53
+ * `OldestClientObserver` is an `EventEmitter`, and will emit events when the local client becomes the oldest and when
54
+ * it is no longer the oldest.
55
+ *
56
+ * ```typescript
57
+ * oldestClientObserver.on("becameOldest", () => {
58
+ * console.log("I'm the oldest now");
59
+ * });
60
+ *
61
+ * oldestClientObserver.on("lostOldest", () => {
62
+ * console.log("I'm not the oldest anymore");
63
+ * });
64
+ * ```
65
+ */
66
+ export class OldestClientObserver extends TypedEventEmitter<IOldestClientObserverEvents>
67
+ implements IOldestClientObserver {
68
+ private readonly quorum: IQuorumClients;
69
+ private currentIsOldest: boolean = false;
70
+ constructor(private readonly observable: IOldestClientObservable) {
71
+ super();
72
+ this.quorum = this.observable.getQuorum();
73
+ this.currentIsOldest = this.computeIsOldest();
74
+ this.quorum.on("addMember", this.updateOldest);
75
+ this.quorum.on("removeMember", this.updateOldest);
76
+ observable.on("connected", this.updateOldest);
77
+ observable.on("disconnected", this.updateOldest);
78
+ }
79
+
80
+ public isOldest(): boolean {
81
+ return this.currentIsOldest;
82
+ }
83
+
84
+ private readonly updateOldest = () => {
85
+ const oldest = this.computeIsOldest();
86
+ if (this.currentIsOldest !== oldest) {
87
+ this.currentIsOldest = oldest;
88
+ if (oldest) {
89
+ this.emit("becameOldest");
90
+ } else {
91
+ this.emit("lostOldest");
92
+ }
93
+ }
94
+ };
95
+
96
+ private computeIsOldest(): boolean {
97
+ // If the container is detached, we are the only ones that know about it and are the oldest by default.
98
+ if (this.observable.attachState === AttachState.Detached) {
99
+ return true;
100
+ }
101
+
102
+ // If we're not connected we can't be the oldest connected client.
103
+ if (!this.observable.connected) {
104
+ return false;
105
+ }
106
+
107
+ assert(this.observable.clientId !== undefined, 0x1da /* "Client id should be set if connected" */);
108
+
109
+ const selfSequencedClient = this.quorum.getMember(this.observable.clientId);
110
+ // When in readonly mode our clientId will not be present in the quorum.
111
+ if (selfSequencedClient === undefined) {
112
+ return false;
113
+ }
114
+
115
+ const members = this.quorum.getMembers();
116
+ for (const sequencedClient of members.values()) {
117
+ if (sequencedClient.sequenceNumber < selfSequencedClient.sequenceNumber) {
118
+ return false;
119
+ }
120
+ }
121
+
122
+ // No member of the quorum was older
123
+ return true;
124
+ }
125
+ }
@@ -0,0 +1,9 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ *
5
+ * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
+ */
7
+
8
+ export const pkgName = "@fluid-experimental/oldest-client-observer";
9
+ export const pkgVersion = "2.0.0-internal.2.0.0";
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./lib",
5
+ "module": "esnext"
6
+ },
7
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "@fluidframework/build-common/ts-common-config.json",
3
+ "exclude": [
4
+ "src/test/**/*"
5
+ ],
6
+ "compilerOptions": {
7
+ "rootDir": "./src",
8
+ "outDir": "./dist",
9
+ "composite": true
10
+ },
11
+ "include": [
12
+ "src/**/*"
13
+ ]
14
+ }