Repository: open-feature/js-sdk (ts)

Issue:
[FEATURE] Named provider metadata accessor
### Requirements

Add support for accessing provider metadata for named providers. If a name is invalid, the default provider metadata should be returned.

Pull request description:
feat: add named provider metadata accessor
## This PR

- adds an accessor for named provider metadata

### Related Issues

Fixes #714 

### Notes

The default metadata is returned if the name doesn't match a registered provider.

This is another example where provider namespace would work well. It doesn't make sense to call this "named client" since we are only interested in the client.




Patch:
diff --git a/packages/shared/src/open-feature.ts b/packages/shared/src/open-feature.ts
index f2d2b3e5..313e31fa 100644
--- a/packages/shared/src/open-feature.ts
+++ b/packages/shared/src/open-feature.ts
@@ -50,11 +50,21 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
   }
 
   /**
-   * Get metadata about registered provider.
+   * Get metadata about the default provider.
    * @returns {ProviderMetadata} Provider Metadata
    */
   get providerMetadata(): ProviderMetadata {
-    return this._defaultProvider.metadata;
+    return this.getProviderMetadata();
+  }
+
+  /**
+   * Get metadata about a registered provider using the client name.
+   * An unbound or empty client name will return metadata from the default provider.
+   * @param {string} [clientName] The name to identify the client
+   * @returns {ProviderMetadata} Provider Metadata
+   */
+  getProviderMetadata(clientName?: string): ProviderMetadata {
+    return this.getProviderForClient(clientName).metadata;
   }
 
   /**


Test patch:
diff --git a/packages/client/test/open-feature.spec.ts b/packages/client/test/open-feature.spec.ts
index 24cf03e4..e19d3e07 100644
--- a/packages/client/test/open-feature.spec.ts
+++ b/packages/client/test/open-feature.spec.ts
@@ -39,13 +39,13 @@ describe('OpenFeature', () => {
     describe('Requirement 1.1.2.1', () => {
       it('should throw because the provider is not intended for the client', () => {
         const provider = mockProvider({ runsOn: 'server' });
-        expect(() => OpenFeature.setProvider(provider)).toThrowError(
-          "Provider 'mock-events-success' is intended for use on the server."
+        expect(() => OpenFeature.setProvider(provider)).toThrow(
+          "Provider 'mock-events-success' is intended for use on the server.",
         );
       });
       it('should succeed because the provider is intended for the client', () => {
         const provider = mockProvider({ runsOn: 'client' });
-        expect(() => OpenFeature.setProvider(provider)).not.toThrowError();
+        expect(() => OpenFeature.setProvider(provider)).not.toThrow();
       });
     });
 
@@ -132,6 +132,22 @@ describe('OpenFeature', () => {
       OpenFeature.setProvider('client2', { ...provider1 });
       expect(provider1.onClose).toHaveBeenCalledTimes(1);
     });
+
+    it('should return the default provider metadata when passing an unregistered client name', async () => {
+      const mockProvider = { metadata: { name: 'test' } } as unknown as Provider;
+      OpenFeature.setProvider(mockProvider);
+      const metadata = OpenFeature.getProviderMetadata('unused');
+      expect(metadata.name === mockProvider.metadata.name).toBeTruthy();
+    });
+
+    it('should return the named provider metadata when passing a registered client name', async () => {
+      const mockProvider = { metadata: { name: 'mock' } } as unknown as Provider;
+      const mockNamedProvider = { metadata: { name: 'named-mock' } } as unknown as Provider;
+      OpenFeature.setProvider(mockProvider);
+      OpenFeature.setProvider('mocked', mockNamedProvider);
+      const metadata = OpenFeature.getProviderMetadata('mocked');
+      expect(metadata.name === mockNamedProvider.metadata.name).toBeTruthy();
+    });
   });
 
   describe('Requirement 1.1.4', () => {
diff --git a/packages/server/test/open-feature.spec.ts b/packages/server/test/open-feature.spec.ts
index 05dbb8e7..62414211 100644
--- a/packages/server/test/open-feature.spec.ts
+++ b/packages/server/test/open-feature.spec.ts
@@ -39,13 +39,13 @@ describe('OpenFeature', () => {
     describe('Requirement 1.1.2.1', () => {
       it('should throw because the provider is not intended for the server', () => {
         const provider = mockProvider({ runsOn: 'client' });
-        expect(() => OpenFeature.setProvider(provider)).toThrowError(
-          "Provider 'mock-events-success' is intended for use on the client."
+        expect(() => OpenFeature.setProvider(provider)).toThrow(
+          "Provider 'mock-events-success' is intended for use on the client.",
         );
       });
       it('should succeed because the provider is intended for the server', () => {
         const provider = mockProvider({ runsOn: 'server' });
-        expect(() => OpenFeature.setProvider(provider)).not.toThrowError();
+        expect(() => OpenFeature.setProvider(provider)).not.toThrow();
       });
     });
 
@@ -132,6 +132,22 @@ describe('OpenFeature', () => {
       OpenFeature.setProvider('client2', { ...provider1 });
       expect(provider1.onClose).toHaveBeenCalledTimes(1);
     });
+
+    it('should return the default provider metadata when passing an unregistered client name', async () => {
+      const mockProvider = { metadata: { name: 'test' } } as unknown as Provider;
+      OpenFeature.setProvider(mockProvider);
+      const metadata = OpenFeature.getProviderMetadata('unused');
+      expect(metadata.name === mockProvider.metadata.name).toBeTruthy();
+    });
+
+    it('should return the named provider metadata when passing a registered client name', async () => {
+      const mockProvider = { metadata: { name: 'mock' } } as unknown as Provider;
+      const mockNamedProvider = { metadata: { name: 'named-mock' } } as unknown as Provider;
+      OpenFeature.setProvider(mockProvider);
+      OpenFeature.setProvider('mocked', mockNamedProvider);
+      const metadata = OpenFeature.getProviderMetadata('mocked');
+      expect(metadata.name === mockNamedProvider.metadata.name).toBeTruthy();
+    });
   });
 
   describe('Requirement 1.1.4', () => {