@fluid-experimental/oldest-client-observer 2.0.0-dev.2.3.0.115467 → 2.0.0-dev.4.1.0.148229

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 CHANGED
@@ -4,10 +4,8 @@
4
4
  */
5
5
 
6
6
  module.exports = {
7
- "extends": [
8
- require.resolve("@fluidframework/eslint-config-fluid"), "prettier"
9
- ],
10
- "parserOptions": {
11
- "project": "./tsconfig.json"
12
- },
13
- }
7
+ extends: [require.resolve("@fluidframework/eslint-config-fluid"), "prettier"],
8
+ parserOptions: {
9
+ project: "./tsconfig.json",
10
+ },
11
+ };
package/README.md CHANGED
@@ -1,9 +1,16 @@
1
1
  # @fluid-experimental/oldest-client-observer
2
2
 
3
3
  <!-- AUTO-GENERATED-CONTENT:START (README_TRADEMARK_SECTION:includeHeading=TRUE) -->
4
+
5
+ <!-- prettier-ignore-start -->
6
+ <!-- NOTE: This section is automatically generated using @fluid-tools/markdown-magic. Do not update these generated contents directly. -->
7
+
4
8
  ## Trademark
5
9
 
6
10
  This project may contain Microsoft trademarks or logos for Microsoft projects, products, or services.
7
11
  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
12
  Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
13
+
14
+ <!-- prettier-ignore-end -->
15
+
9
16
  <!-- AUTO-GENERATED-CONTENT:END -->
@@ -1,4 +1,4 @@
1
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"
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
4
  }
@@ -1 +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"}
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;IAC5D,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;IAC3C,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;CAC9C;AAED;;;;;GAKG;AACH,MAAM,WAAW,uBAAwB,SAAQ,cAAc,CAAC,6BAA6B,CAAC;IAC7F,SAAS,IAAI,cAAc,CAAC;IAM5B,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,MAAM,WAAW,2BAA4B,SAAQ,MAAM;IAC1D,CAAC,KAAK,EAAE,cAAc,GAAG,YAAY,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;CAC7D;AAED,MAAM,WAAW,qBAAsB,SAAQ,cAAc,CAAC,2BAA2B,CAAC;IACzF,QAAQ,IAAI,OAAO,CAAC;CACpB"}
@@ -1 +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"]}
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\t(event: \"connected\", listener: () => void);\n\t(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\tgetQuorum(): IQuorumClients;\n\t// Generic usage of attachState is a little unusual here. We will treat ourselves as \"the oldest client that\n\t// has information about this [container | data store]\", which in the case of detached data store may disagree\n\t// with whether we're the oldest client on the connected container. So in the data store case, it's only\n\t// safe use this as an indicator about rights to tasks performed against this specific data store, and not\n\t// more broadly.\n\tattachState: AttachState;\n\tconnected: boolean;\n\tclientId: string | undefined;\n}\n\nexport interface IOldestClientObserverEvents extends IEvent {\n\t(event: \"becameOldest\" | \"lostOldest\", listener: () => void);\n}\n\nexport interface IOldestClientObserver extends IEventProvider<IOldestClientObserverEvents> {\n\tisOldest(): boolean;\n}\n"]}
@@ -1 +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"}
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,EACN,uBAAuB,EACvB,2BAA2B,EAC3B,qBAAqB,EACrB,MAAM,cAAc,CAAC;AAEtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,qBAAa,oBACZ,SAAQ,iBAAiB,CAAC,2BAA2B,CACrD,YAAW,qBAAqB;IAIpB,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;CAkCvB"}
@@ -98,7 +98,10 @@ class OldestClientObserver extends common_utils_1.TypedEventEmitter {
98
98
  if (!this.observable.connected) {
99
99
  return false;
100
100
  }
101
- (0, common_utils_1.assert)(this.observable.clientId !== undefined, 0x1da /* "Client id should be set if connected" */);
101
+ // TODO: Clean up error code linter violations repo-wide.
102
+ (0, common_utils_1.assert)(this.observable.clientId !== undefined,
103
+ // eslint-disable-next-line unicorn/numeric-separators-style
104
+ 0x1da /* "Client id should be set if connected" */);
102
105
  const selfSequencedClient = this.quorum.getMember(this.observable.clientId);
103
106
  // When in readonly mode our clientId will not be present in the quorum.
104
107
  if (selfSequencedClient === undefined) {
@@ -1 +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"]}
1
+ {"version":3,"file":"oldestClientObserver.js","sourceRoot":"","sources":["../src/oldestClientObserver.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,+DAAyE;AACzE,iFAAoE;AAQpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,MAAa,oBACZ,SAAQ,gCAA8C;IAKtD,YAA6B,UAAmC;QAC/D,KAAK,EAAE,CAAC;QADoB,eAAU,GAAV,UAAU,CAAyB;QADxD,oBAAe,GAAY,KAAK,CAAC;QAexB,iBAAY,GAAG,GAAS,EAAE;YAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;YACtC,IAAI,IAAI,CAAC,eAAe,KAAK,MAAM,EAAE;gBACpC,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC;gBAC9B,IAAI,MAAM,EAAE;oBACX,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;iBAC1B;qBAAM;oBACN,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;iBACxB;aACD;QACF,CAAC,CAAC;QAtBD,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;IAClD,CAAC;IAEM,QAAQ;QACd,OAAO,IAAI,CAAC,eAAe,CAAC;IAC7B,CAAC;IAcO,eAAe;QACtB,uGAAuG;QACvG,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,KAAK,mCAAW,CAAC,QAAQ,EAAE;YACzD,OAAO,IAAI,CAAC;SACZ;QAED,kEAAkE;QAClE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE;YAC/B,OAAO,KAAK,CAAC;SACb;QAED,yDAAyD;QACzD,IAAA,qBAAM,EACL,IAAI,CAAC,UAAU,CAAC,QAAQ,KAAK,SAAS;QACtC,4DAA4D;QAC5D,KAAK,CAAC,4CAA4C,CAClD,CAAC;QAEF,MAAM,mBAAmB,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAC5E,wEAAwE;QACxE,IAAI,mBAAmB,KAAK,SAAS,EAAE;YACtC,OAAO,KAAK,CAAC;SACb;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QACzC,KAAK,MAAM,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE;YAC/C,IAAI,eAAe,CAAC,cAAc,GAAG,mBAAmB,CAAC,cAAc,EAAE;gBACxE,OAAO,KAAK,CAAC;aACb;SACD;QAED,oCAAoC;QACpC,OAAO,IAAI,CAAC;IACb,CAAC;CACD;AAlED,oDAkEC","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 {\n\tIOldestClientObservable,\n\tIOldestClientObserverEvents,\n\tIOldestClientObserver,\n} 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\n\textends TypedEventEmitter<IOldestClientObserverEvents>\n\timplements IOldestClientObserver\n{\n\tprivate readonly quorum: IQuorumClients;\n\tprivate currentIsOldest: boolean = false;\n\tconstructor(private readonly observable: IOldestClientObservable) {\n\t\tsuper();\n\t\tthis.quorum = this.observable.getQuorum();\n\t\tthis.currentIsOldest = this.computeIsOldest();\n\t\tthis.quorum.on(\"addMember\", this.updateOldest);\n\t\tthis.quorum.on(\"removeMember\", this.updateOldest);\n\t\tobservable.on(\"connected\", this.updateOldest);\n\t\tobservable.on(\"disconnected\", this.updateOldest);\n\t}\n\n\tpublic isOldest(): boolean {\n\t\treturn this.currentIsOldest;\n\t}\n\n\tprivate readonly updateOldest = (): void => {\n\t\tconst oldest = this.computeIsOldest();\n\t\tif (this.currentIsOldest !== oldest) {\n\t\t\tthis.currentIsOldest = oldest;\n\t\t\tif (oldest) {\n\t\t\t\tthis.emit(\"becameOldest\");\n\t\t\t} else {\n\t\t\t\tthis.emit(\"lostOldest\");\n\t\t\t}\n\t\t}\n\t};\n\n\tprivate computeIsOldest(): boolean {\n\t\t// If the container is detached, we are the only ones that know about it and are the oldest by default.\n\t\tif (this.observable.attachState === AttachState.Detached) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// If we're not connected we can't be the oldest connected client.\n\t\tif (!this.observable.connected) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// TODO: Clean up error code linter violations repo-wide.\n\t\tassert(\n\t\t\tthis.observable.clientId !== undefined,\n\t\t\t// eslint-disable-next-line unicorn/numeric-separators-style\n\t\t\t0x1da /* \"Client id should be set if connected\" */,\n\t\t);\n\n\t\tconst selfSequencedClient = this.quorum.getMember(this.observable.clientId);\n\t\t// When in readonly mode our clientId will not be present in the quorum.\n\t\tif (selfSequencedClient === undefined) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst members = this.quorum.getMembers();\n\t\tfor (const sequencedClient of members.values()) {\n\t\t\tif (sequencedClient.sequenceNumber < selfSequencedClient.sequenceNumber) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// No member of the quorum was older\n\t\treturn true;\n\t}\n}\n"]}
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluid-experimental/oldest-client-observer";
8
- export declare const pkgVersion = "2.0.0-dev.2.3.0.115467";
8
+ export declare const pkgVersion = "2.0.0-dev.4.1.0.148229";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -8,5 +8,5 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.pkgVersion = exports.pkgName = void 0;
10
10
  exports.pkgName = "@fluid-experimental/oldest-client-observer";
11
- exports.pkgVersion = "2.0.0-dev.2.3.0.115467";
11
+ exports.pkgVersion = "2.0.0-dev.4.1.0.148229";
12
12
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +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,wBAAwB,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-dev.2.3.0.115467\";\n"]}
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,wBAAwB,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-dev.4.1.0.148229\";\n"]}
@@ -1 +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"}
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;IAC5D,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;IAC3C,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;CAC9C;AAED;;;;;GAKG;AACH,MAAM,WAAW,uBAAwB,SAAQ,cAAc,CAAC,6BAA6B,CAAC;IAC7F,SAAS,IAAI,cAAc,CAAC;IAM5B,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,MAAM,WAAW,2BAA4B,SAAQ,MAAM;IAC1D,CAAC,KAAK,EAAE,cAAc,GAAG,YAAY,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAE;CAC7D;AAED,MAAM,WAAW,qBAAsB,SAAQ,cAAc,CAAC,2BAA2B,CAAC;IACzF,QAAQ,IAAI,OAAO,CAAC;CACpB"}
@@ -1 +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"]}
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\t(event: \"connected\", listener: () => void);\n\t(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\tgetQuorum(): IQuorumClients;\n\t// Generic usage of attachState is a little unusual here. We will treat ourselves as \"the oldest client that\n\t// has information about this [container | data store]\", which in the case of detached data store may disagree\n\t// with whether we're the oldest client on the connected container. So in the data store case, it's only\n\t// safe use this as an indicator about rights to tasks performed against this specific data store, and not\n\t// more broadly.\n\tattachState: AttachState;\n\tconnected: boolean;\n\tclientId: string | undefined;\n}\n\nexport interface IOldestClientObserverEvents extends IEvent {\n\t(event: \"becameOldest\" | \"lostOldest\", listener: () => void);\n}\n\nexport interface IOldestClientObserver extends IEventProvider<IOldestClientObserverEvents> {\n\tisOldest(): boolean;\n}\n"]}
@@ -1 +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"}
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,EACN,uBAAuB,EACvB,2BAA2B,EAC3B,qBAAqB,EACrB,MAAM,cAAc,CAAC;AAEtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,qBAAa,oBACZ,SAAQ,iBAAiB,CAAC,2BAA2B,CACrD,YAAW,qBAAqB;IAIpB,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;CAkCvB"}
@@ -95,7 +95,10 @@ export class OldestClientObserver extends TypedEventEmitter {
95
95
  if (!this.observable.connected) {
96
96
  return false;
97
97
  }
98
- assert(this.observable.clientId !== undefined, 0x1da /* "Client id should be set if connected" */);
98
+ // TODO: Clean up error code linter violations repo-wide.
99
+ assert(this.observable.clientId !== undefined,
100
+ // eslint-disable-next-line unicorn/numeric-separators-style
101
+ 0x1da /* "Client id should be set if connected" */);
99
102
  const selfSequencedClient = this.quorum.getMember(this.observable.clientId);
100
103
  // When in readonly mode our clientId will not be present in the quorum.
101
104
  if (selfSequencedClient === undefined) {
@@ -1 +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"]}
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;AAQpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,MAAM,OAAO,oBACZ,SAAQ,iBAA8C;IAKtD,YAA6B,UAAmC;QAC/D,KAAK,EAAE,CAAC;QADoB,eAAU,GAAV,UAAU,CAAyB;QADxD,oBAAe,GAAY,KAAK,CAAC;QAexB,iBAAY,GAAG,GAAS,EAAE;YAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;YACtC,IAAI,IAAI,CAAC,eAAe,KAAK,MAAM,EAAE;gBACpC,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC;gBAC9B,IAAI,MAAM,EAAE;oBACX,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;iBAC1B;qBAAM;oBACN,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;iBACxB;aACD;QACF,CAAC,CAAC;QAtBD,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;IAClD,CAAC;IAEM,QAAQ;QACd,OAAO,IAAI,CAAC,eAAe,CAAC;IAC7B,CAAC;IAcO,eAAe;QACtB,uGAAuG;QACvG,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,KAAK,WAAW,CAAC,QAAQ,EAAE;YACzD,OAAO,IAAI,CAAC;SACZ;QAED,kEAAkE;QAClE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE;YAC/B,OAAO,KAAK,CAAC;SACb;QAED,yDAAyD;QACzD,MAAM,CACL,IAAI,CAAC,UAAU,CAAC,QAAQ,KAAK,SAAS;QACtC,4DAA4D;QAC5D,KAAK,CAAC,4CAA4C,CAClD,CAAC;QAEF,MAAM,mBAAmB,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAC5E,wEAAwE;QACxE,IAAI,mBAAmB,KAAK,SAAS,EAAE;YACtC,OAAO,KAAK,CAAC;SACb;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QACzC,KAAK,MAAM,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE;YAC/C,IAAI,eAAe,CAAC,cAAc,GAAG,mBAAmB,CAAC,cAAc,EAAE;gBACxE,OAAO,KAAK,CAAC;aACb;SACD;QAED,oCAAoC;QACpC,OAAO,IAAI,CAAC;IACb,CAAC;CACD","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 {\n\tIOldestClientObservable,\n\tIOldestClientObserverEvents,\n\tIOldestClientObserver,\n} 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\n\textends TypedEventEmitter<IOldestClientObserverEvents>\n\timplements IOldestClientObserver\n{\n\tprivate readonly quorum: IQuorumClients;\n\tprivate currentIsOldest: boolean = false;\n\tconstructor(private readonly observable: IOldestClientObservable) {\n\t\tsuper();\n\t\tthis.quorum = this.observable.getQuorum();\n\t\tthis.currentIsOldest = this.computeIsOldest();\n\t\tthis.quorum.on(\"addMember\", this.updateOldest);\n\t\tthis.quorum.on(\"removeMember\", this.updateOldest);\n\t\tobservable.on(\"connected\", this.updateOldest);\n\t\tobservable.on(\"disconnected\", this.updateOldest);\n\t}\n\n\tpublic isOldest(): boolean {\n\t\treturn this.currentIsOldest;\n\t}\n\n\tprivate readonly updateOldest = (): void => {\n\t\tconst oldest = this.computeIsOldest();\n\t\tif (this.currentIsOldest !== oldest) {\n\t\t\tthis.currentIsOldest = oldest;\n\t\t\tif (oldest) {\n\t\t\t\tthis.emit(\"becameOldest\");\n\t\t\t} else {\n\t\t\t\tthis.emit(\"lostOldest\");\n\t\t\t}\n\t\t}\n\t};\n\n\tprivate computeIsOldest(): boolean {\n\t\t// If the container is detached, we are the only ones that know about it and are the oldest by default.\n\t\tif (this.observable.attachState === AttachState.Detached) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// If we're not connected we can't be the oldest connected client.\n\t\tif (!this.observable.connected) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// TODO: Clean up error code linter violations repo-wide.\n\t\tassert(\n\t\t\tthis.observable.clientId !== undefined,\n\t\t\t// eslint-disable-next-line unicorn/numeric-separators-style\n\t\t\t0x1da /* \"Client id should be set if connected\" */,\n\t\t);\n\n\t\tconst selfSequencedClient = this.quorum.getMember(this.observable.clientId);\n\t\t// When in readonly mode our clientId will not be present in the quorum.\n\t\tif (selfSequencedClient === undefined) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst members = this.quorum.getMembers();\n\t\tfor (const sequencedClient of members.values()) {\n\t\t\tif (sequencedClient.sequenceNumber < selfSequencedClient.sequenceNumber) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// No member of the quorum was older\n\t\treturn true;\n\t}\n}\n"]}
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluid-experimental/oldest-client-observer";
8
- export declare const pkgVersion = "2.0.0-dev.2.3.0.115467";
8
+ export declare const pkgVersion = "2.0.0-dev.4.1.0.148229";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export const pkgName = "@fluid-experimental/oldest-client-observer";
8
- export const pkgVersion = "2.0.0-dev.2.3.0.115467";
8
+ export const pkgVersion = "2.0.0-dev.4.1.0.148229";
9
9
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +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,wBAAwB,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-dev.2.3.0.115467\";\n"]}
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,wBAAwB,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-dev.4.1.0.148229\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluid-experimental/oldest-client-observer",
3
- "version": "2.0.0-dev.2.3.0.115467",
3
+ "version": "2.0.0-dev.4.1.0.148229",
4
4
  "description": "Data object to determine if the local client is the oldest amongst connected clients",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -14,28 +14,6 @@
14
14
  "main": "dist/index.js",
15
15
  "module": "lib/index.js",
16
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 typetests:gen && npm run tsc",
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
- "ci:build:docs": "api-extractor run --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/* ../../../_api-extractor-temp/",
27
- "clean": "rimraf dist lib *.tsbuildinfo *.build.log",
28
- "eslint": "eslint --format stylish src",
29
- "eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
30
- "format": "npm run prettier:fix",
31
- "lint": "npm run eslint",
32
- "lint:fix": "npm run eslint:fix",
33
- "prettier": "prettier --check . --ignore-path ../../../.prettierignore",
34
- "prettier:fix": "prettier --write . --ignore-path ../../../.prettierignore",
35
- "tsc": "tsc",
36
- "typetests:gen": "flub generate typetests --generate --dir .",
37
- "typetests:prepare": "flub generate typetests --prepare --dir . --pin"
38
- },
39
17
  "nyc": {
40
18
  "all": true,
41
19
  "cache-dir": "nyc/.cache",
@@ -58,36 +36,55 @@
58
36
  },
59
37
  "dependencies": {
60
38
  "@fluidframework/common-definitions": "^0.20.1",
61
- "@fluidframework/common-utils": "^1.0.0",
62
- "@fluidframework/container-definitions": ">=2.0.0-dev.2.3.0.115467 <2.0.0-dev.3.0.0",
39
+ "@fluidframework/common-utils": "^1.1.1",
40
+ "@fluidframework/container-definitions": "2.0.0-dev.4.1.0.148229",
63
41
  "@fluidframework/protocol-definitions": "^1.1.0"
64
42
  },
65
43
  "devDependencies": {
66
- "@fluid-internal/test-dds-utils": ">=2.0.0-dev.2.3.0.115467 <2.0.0-dev.3.0.0",
67
- "@fluid-tools/build-cli": "^0.7.0",
44
+ "@fluid-internal/test-dds-utils": "2.0.0-dev.4.1.0.148229",
45
+ "@fluid-tools/build-cli": "^0.13.1",
68
46
  "@fluidframework/build-common": "^1.1.0",
69
- "@fluidframework/build-tools": "^0.7.0",
70
- "@fluidframework/eslint-config-fluid": "^1.2.0",
71
- "@fluidframework/mocha-test-setup": ">=2.0.0-dev.2.3.0.115467 <2.0.0-dev.3.0.0",
72
- "@fluidframework/test-runtime-utils": ">=2.0.0-dev.2.3.0.115467 <2.0.0-dev.3.0.0",
73
- "@microsoft/api-extractor": "^7.22.2",
74
- "@rushstack/eslint-config": "^2.5.1",
47
+ "@fluidframework/build-tools": "^0.13.1",
48
+ "@fluidframework/eslint-config-fluid": "^2.0.0",
49
+ "@fluidframework/test-runtime-utils": "2.0.0-dev.4.1.0.148229",
50
+ "@microsoft/api-extractor": "^7.34.4",
75
51
  "@types/mocha": "^9.1.1",
76
- "@types/node": "^14.18.0",
77
- "concurrently": "^6.2.0",
52
+ "@types/node": "^14.18.38",
53
+ "concurrently": "^7.6.0",
78
54
  "copyfiles": "^2.4.1",
79
- "cross-env": "^7.0.2",
55
+ "cross-env": "^7.0.3",
80
56
  "eslint": "~8.6.0",
81
- "mocha": "^10.0.0",
57
+ "mocha": "^10.2.0",
82
58
  "mocha-junit-reporter": "^1.18.0",
83
- "nyc": "^15.0.0",
59
+ "nyc": "^15.1.0",
84
60
  "prettier": "~2.6.2",
85
- "rimraf": "^2.6.2",
61
+ "rimraf": "^4.4.0",
86
62
  "typescript": "~4.5.5"
87
63
  },
88
64
  "typeValidation": {
89
65
  "disabled": true,
90
- "version": "2.0.0-internal.2.1.0",
91
66
  "broken": {}
67
+ },
68
+ "scripts": {
69
+ "build": "npm run build:genver && concurrently npm:build:compile npm:lint && npm run build:docs",
70
+ "build:commonjs": "npm run typetests:gen && npm run tsc",
71
+ "build:compile": "concurrently npm:build:commonjs npm:build:esnext",
72
+ "build:docs": "api-extractor run --local --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/doc-models/* ../../../_api-extractor-temp/",
73
+ "build:esnext": "tsc --project ./tsconfig.esnext.json",
74
+ "build:full": "npm run build",
75
+ "build:full:compile": "npm run build:compile",
76
+ "build:genver": "gen-version",
77
+ "ci:build:docs": "api-extractor run --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/* ../../../_api-extractor-temp/",
78
+ "clean": "rimraf dist lib *.tsbuildinfo *.build.log",
79
+ "eslint": "eslint --format stylish src",
80
+ "eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
81
+ "format": "npm run prettier:fix",
82
+ "lint": "npm run prettier && npm run eslint",
83
+ "lint:fix": "npm run prettier:fix && npm run eslint:fix",
84
+ "prettier": "prettier --check . --ignore-path ../../../.prettierignore",
85
+ "prettier:fix": "prettier --write . --ignore-path ../../../.prettierignore",
86
+ "tsc": "tsc",
87
+ "typetests:gen": "fluid-type-test-generator",
88
+ "typetests:prepare": "flub generate typetests --prepare --dir . --pin"
92
89
  }
93
- }
90
+ }
@@ -4,5 +4,5 @@
4
4
  */
5
5
 
6
6
  module.exports = {
7
- ...require("@fluidframework/build-common/prettier.config.cjs"),
7
+ ...require("@fluidframework/build-common/prettier.config.cjs"),
8
8
  };
package/src/interfaces.ts CHANGED
@@ -8,11 +8,8 @@ import { AttachState } from "@fluidframework/container-definitions";
8
8
  import { IQuorumClients } from "@fluidframework/protocol-definitions";
9
9
 
10
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);
11
+ (event: "connected", listener: () => void);
12
+ (event: "disconnected", listener: () => void);
16
13
  }
17
14
 
18
15
  /**
@@ -22,21 +19,21 @@ export interface IOldestClientObservableEvents extends IEvent {
22
19
  * It's information about the connection, so the real source of truth is lower (at the connection layer).
23
20
  */
24
21
  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;
22
+ getQuorum(): IQuorumClients;
23
+ // Generic usage of attachState is a little unusual here. We will treat ourselves as "the oldest client that
24
+ // has information about this [container | data store]", which in the case of detached data store may disagree
25
+ // with whether we're the oldest client on the connected container. So in the data store case, it's only
26
+ // safe use this as an indicator about rights to tasks performed against this specific data store, and not
27
+ // more broadly.
28
+ attachState: AttachState;
29
+ connected: boolean;
30
+ clientId: string | undefined;
34
31
  }
35
32
 
36
33
  export interface IOldestClientObserverEvents extends IEvent {
37
- (event: "becameOldest" | "lostOldest", listener: () => void);
34
+ (event: "becameOldest" | "lostOldest", listener: () => void);
38
35
  }
39
36
 
40
37
  export interface IOldestClientObserver extends IEventProvider<IOldestClientObserverEvents> {
41
- isOldest(): boolean;
38
+ isOldest(): boolean;
42
39
  }
@@ -6,7 +6,11 @@
6
6
  import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
7
7
  import { AttachState } from "@fluidframework/container-definitions";
8
8
  import { IQuorumClients } from "@fluidframework/protocol-definitions";
9
- import { IOldestClientObservable, IOldestClientObserverEvents, IOldestClientObserver } from "./interfaces";
9
+ import {
10
+ IOldestClientObservable,
11
+ IOldestClientObserverEvents,
12
+ IOldestClientObserver,
13
+ } from "./interfaces";
10
14
 
11
15
  /**
12
16
  * The `OldestClientObserver` is a utility inspect if the local client is the oldest amongst connected clients (in
@@ -63,63 +67,70 @@ import { IOldestClientObservable, IOldestClientObserverEvents, IOldestClientObse
63
67
  * });
64
68
  * ```
65
69
  */
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
- }
70
+ export class OldestClientObserver
71
+ extends TypedEventEmitter<IOldestClientObserverEvents>
72
+ implements IOldestClientObserver
73
+ {
74
+ private readonly quorum: IQuorumClients;
75
+ private currentIsOldest: boolean = false;
76
+ constructor(private readonly observable: IOldestClientObservable) {
77
+ super();
78
+ this.quorum = this.observable.getQuorum();
79
+ this.currentIsOldest = this.computeIsOldest();
80
+ this.quorum.on("addMember", this.updateOldest);
81
+ this.quorum.on("removeMember", this.updateOldest);
82
+ observable.on("connected", this.updateOldest);
83
+ observable.on("disconnected", this.updateOldest);
84
+ }
79
85
 
80
- public isOldest(): boolean {
81
- return this.currentIsOldest;
82
- }
86
+ public isOldest(): boolean {
87
+ return this.currentIsOldest;
88
+ }
83
89
 
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
- };
90
+ private readonly updateOldest = (): void => {
91
+ const oldest = this.computeIsOldest();
92
+ if (this.currentIsOldest !== oldest) {
93
+ this.currentIsOldest = oldest;
94
+ if (oldest) {
95
+ this.emit("becameOldest");
96
+ } else {
97
+ this.emit("lostOldest");
98
+ }
99
+ }
100
+ };
95
101
 
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
- }
102
+ private computeIsOldest(): boolean {
103
+ // If the container is detached, we are the only ones that know about it and are the oldest by default.
104
+ if (this.observable.attachState === AttachState.Detached) {
105
+ return true;
106
+ }
101
107
 
102
- // If we're not connected we can't be the oldest connected client.
103
- if (!this.observable.connected) {
104
- return false;
105
- }
108
+ // If we're not connected we can't be the oldest connected client.
109
+ if (!this.observable.connected) {
110
+ return false;
111
+ }
106
112
 
107
- assert(this.observable.clientId !== undefined, 0x1da /* "Client id should be set if connected" */);
113
+ // TODO: Clean up error code linter violations repo-wide.
114
+ assert(
115
+ this.observable.clientId !== undefined,
116
+ // eslint-disable-next-line unicorn/numeric-separators-style
117
+ 0x1da /* "Client id should be set if connected" */,
118
+ );
108
119
 
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
- }
120
+ const selfSequencedClient = this.quorum.getMember(this.observable.clientId);
121
+ // When in readonly mode our clientId will not be present in the quorum.
122
+ if (selfSequencedClient === undefined) {
123
+ return false;
124
+ }
114
125
 
115
- const members = this.quorum.getMembers();
116
- for (const sequencedClient of members.values()) {
117
- if (sequencedClient.sequenceNumber < selfSequencedClient.sequenceNumber) {
118
- return false;
119
- }
120
- }
126
+ const members = this.quorum.getMembers();
127
+ for (const sequencedClient of members.values()) {
128
+ if (sequencedClient.sequenceNumber < selfSequencedClient.sequenceNumber) {
129
+ return false;
130
+ }
131
+ }
121
132
 
122
- // No member of the quorum was older
123
- return true;
124
- }
133
+ // No member of the quorum was older
134
+ return true;
135
+ }
125
136
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluid-experimental/oldest-client-observer";
9
- export const pkgVersion = "2.0.0-dev.2.3.0.115467";
9
+ export const pkgVersion = "2.0.0-dev.4.1.0.148229";
@@ -1,7 +1,7 @@
1
1
  {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./lib",
5
- "module": "esnext"
6
- },
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./lib",
5
+ "module": "esnext",
6
+ },
7
7
  }
package/tsconfig.json CHANGED
@@ -1,14 +1,10 @@
1
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
- ]
2
+ "extends": "@fluidframework/build-common/ts-common-config.json",
3
+ "exclude": ["src/test/**/*"],
4
+ "compilerOptions": {
5
+ "rootDir": "./src",
6
+ "outDir": "./dist",
7
+ "composite": true,
8
+ },
9
+ "include": ["src/**/*"],
14
10
  }