Skip to content

Commit e0af206

Browse files
Add Metadata API
Signed-off-by: Xavier Geerinck <[email protected]>
1 parent 310cfca commit e0af206

File tree

8 files changed

+186
-4
lines changed

8 files changed

+186
-4
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Version 2.0.0 brings a lot of changes to the Dapr JS SDK that were long due. Bel
99
* Actor Support has been added
1010
* Actor Proxy has been added for Actor Access
1111
* The HTTP Connection is now being reused to reduce the CONNRESET errors when intensively using the JS SDK
12+
* The [Metadata API](https://docs.dapr.io/reference/api/metadata_api/) is supported
13+
* The [Health API](https://docs.dapr.io/reference/api/health_api/) is supported
1214

1315
#### Breaking Changes
1416

@@ -18,5 +20,8 @@ Version 2.0.0 brings a lot of changes to the Dapr JS SDK that were long due. Bel
1820
#### Major Changes
1921

2022
* KeepAlive for HTTP has been added and a new method in the `DaprClient` has been added named `stop()` to stop the client and release the connections kept by the sockets.
23+
* `healthz` endpoint was implemented as `client.health.isHealthy()` for gRPC this checks the `getMetadata` function since it does not have a Health PROTO.
24+
* Server startup now ensures the Dapr Sidecar is healthy before starting
25+
* Add metadata API for gRPC and HTTP
2126

2227
## 1.x release

src/implementation/Client/DaprClient.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,30 @@ import IClientPubSub from '../../interfaces/Client/IClientPubSub';
33
import IClientState from '../../interfaces/Client/IClientState';
44
import IClientInvoker from '../../interfaces/Client/IClientInvoker';
55
import IClientSecret from '../../interfaces/Client/IClientSecret';
6+
import IClientHealth from '../../interfaces/Client/IClientHealth';
7+
import IClientMetadata from '../../interfaces/Client/IClientMetadata';
68
import IClient from '../../interfaces/Client/IClient';
79

810
import GRPCClientBinding from './GRPCClient/binding';
911
import GRPCClientPubSub from './GRPCClient/pubsub';
1012
import GRPCClientState from './GRPCClient/state';
1113
import GRPCClientInvoker from './GRPCClient/invoker';
1214
import GRPCClientSecret from './GRPCClient/secret';
15+
import GRPCClientHealth from './GRPCClient/health';
16+
import GRPCClientMetadata from './GRPCClient/metadata';
1317
import GRPCClient from './GRPCClient/GRPCClient';
1418

1519
import HTTPClientBinding from './HTTPClient/binding';
1620
import HTTPClientPubSub from './HTTPClient/pubsub';
1721
import HTTPClientState from './HTTPClient/state';
1822
import HTTPClientInvoker from './HTTPClient/invoker';
1923
import HTTPClientSecret from './HTTPClient/secret';
24+
import HTTPClientHealth from './HTTPClient/health';
25+
import HTTPClientMetadata from './HTTPClient/metadata';
2026
import HTTPClient from './HTTPClient/HTTPClient';
2127

2228
import CommunicationProtocolEnum from '../../enum/CommunicationProtocol.enum';
2329
import { DaprClientOptions } from '../../types/DaprClientOptions';
24-
import HTTPClientHealth from './HTTPClient/health';
25-
import IClientHealth from '../../interfaces/Client/IClientHealth';
26-
import GRPCClientHealth from './GRPCClient/health';
2730

2831
export default class DaprClient {
2932
readonly daprHost: string;
@@ -38,6 +41,7 @@ export default class DaprClient {
3841
readonly invoker: IClientInvoker;
3942
readonly secret: IClientSecret;
4043
readonly health: IClientHealth;
44+
readonly metadata: IClientMetadata;
4145

4246
constructor(
4347
daprHost: string
@@ -69,6 +73,7 @@ export default class DaprClient {
6973
this.invoker = new GRPCClientInvoker(client);
7074
this.secret = new GRPCClientSecret(client);
7175
this.health = new GRPCClientHealth(client);
76+
this.metadata = new GRPCClientMetadata(client);
7277
break;
7378
}
7479
case CommunicationProtocolEnum.HTTP:
@@ -82,6 +87,7 @@ export default class DaprClient {
8287
this.invoker = new HTTPClientInvoker(client);
8388
this.secret = new HTTPClientSecret(client);
8489
this.health = new HTTPClientHealth(client);
90+
this.metadata = new HTTPClientMetadata(client);
8591
break;
8692
}
8793
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import GRPCClient from './GRPCClient';
2+
import { GetMetadataResponse, SetMetadataRequest } from '../../../proto/dapr/proto/runtime/v1/dapr_pb';
3+
import { Empty } from "google-protobuf/google/protobuf/empty_pb";
4+
import IClientMetadata from '../../../interfaces/Client/IClientMetadata';
5+
import { GetMetadataResponse as GetMetadataResponseResult } from '../../../types/metadata/GetMetadataResponse';
6+
7+
// https://docs.dapr.io/reference/api/metadata_api
8+
export default class GRPCClientMetadata implements IClientMetadata {
9+
client: GRPCClient;
10+
11+
constructor(client: GRPCClient) {
12+
this.client = client;
13+
}
14+
15+
// There is no gRPC implementation of /healthz, so we try to fetch the metadata
16+
async get(): Promise<GetMetadataResponseResult> {
17+
return new Promise((resolve, reject) => {
18+
const client = this.client.getClient();
19+
client.getMetadata(new Empty(), (err, res: GetMetadataResponse) => {
20+
if (err) {
21+
return reject(err);
22+
}
23+
24+
const wrapped: GetMetadataResponseResult = {
25+
id: res.getId(),
26+
actors: res.getActiveActorsCountList().map(a => ({
27+
type: a.getType(),
28+
count: a.getCount()
29+
})),
30+
extended: res.getExtendedMetadataMap().toObject().reduce((result: object, [key, value]) => {
31+
// @ts-ignore
32+
result[key] = value;
33+
return result
34+
}, {}),
35+
components: res.getRegisteredComponentsList().map(c => ({
36+
name: c.getName(),
37+
type: c.getType(),
38+
version: c.getVersion()
39+
}))
40+
}
41+
42+
return resolve(wrapped);
43+
});
44+
});
45+
}
46+
47+
async set(key: string, value: string): Promise<boolean> {
48+
const msg = new SetMetadataRequest();
49+
msg.setKey(key);
50+
msg.setValue(value);
51+
52+
return new Promise((resolve, reject) => {
53+
const client = this.client.getClient();
54+
client.setMetadata(msg, (err, res: Empty) => {
55+
if (err) {
56+
return reject(false);
57+
}
58+
59+
return resolve(true);
60+
});
61+
});
62+
}
63+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import HTTPClient from './HTTPClient';
2+
import { GetMetadataResponse } from '../../../types/metadata/GetMetadataResponse';
3+
import IClientMetadata from '../../../interfaces/Client/IClientMetadata';
4+
5+
// https://docs.dapr.io/reference/api/metadata_api
6+
export default class HTTPClientMetadata implements IClientMetadata {
7+
client: HTTPClient;
8+
9+
constructor(client: HTTPClient) {
10+
this.client = client;
11+
}
12+
13+
async get(): Promise<GetMetadataResponse> {
14+
const result = await this.client.execute(`/metadata`);
15+
return result as GetMetadataResponse;
16+
}
17+
18+
async set(key: string, value: string): Promise<boolean> {
19+
const result = await this.client.execute(`/metadata/${key}`, {
20+
method: "PUT",
21+
headers: {
22+
"Content-Type": "text/plain"
23+
},
24+
body: value
25+
});
26+
27+
return true;
28+
}
29+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { GetMetadataResponse } from "../../types/metadata/GetMetadataResponse";
2+
3+
export default interface IClientMetadata {
4+
get(): Promise<GetMetadataResponse>;
5+
set(key: string, value: string): Promise<boolean>;
6+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { KeyValuePairType } from "../KeyValuePair.type";
2+
3+
export type GetMetadataResponse = {
4+
id: string;
5+
actors: MetadataRegisteredActor[]
6+
extended: KeyValueType;
7+
components: MetadataComponent[]
8+
}
9+
10+
type MetadataRegisteredActor = {
11+
type: string;
12+
count: number;
13+
}
14+
15+
type MetadataComponent = {
16+
name: string;
17+
type: string;
18+
version: string;
19+
}

test/e2e/main.grpc.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,35 @@ describe('grpc/main', () => {
3232
await server.stop();
3333
});
3434

35+
describe('metadata', () => {
36+
it('should be able to get the metadata of the Dapr sidecar', async () => {
37+
const res = await client.metadata.get();
38+
39+
// app id is not set in grpc?
40+
// expect(res.id.length).toBeGreaterThan(0);
41+
// expect(res.components.length).toBeGreaterThan(0);
42+
});
43+
44+
it('should be able to set a custom metadata value of the Dapr sidecar', async () => {
45+
await client.metadata.set("testKey", "Hello World");
46+
47+
const res = await client.metadata.get();
48+
49+
// app id is not set in grpc?
50+
// expect(res.id.length).toBeGreaterThan(0);
51+
// expect(res.id.length).toBeGreaterThan(0);
52+
// expect(res.components.length).toBeGreaterThan(0);
53+
expect(res.extended["testKey"]).toEqual("Hello World");
54+
});
55+
});
56+
57+
describe('health', () => {
58+
it('should return true if Dapr is ready', async () => {
59+
const res = await client.health.isHealthy();
60+
expect(res).toEqual(true);
61+
});
62+
});
63+
3564
describe('binding', () => {
3665
it('should be able to receive events', async () => {
3766
await client.binding.send('binding-mqtt', 'create', { hello: 'world' });

test/e2e/main.http.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,34 @@ describe('http/main', () => {
3737
await client.stop();
3838
});
3939

40+
describe('metadata', () => {
41+
it('should be able to get the metadata of the Dapr sidecar', async () => {
42+
const res = await client.metadata.get();
43+
44+
expect(res.id.length).toBeGreaterThan(0);
45+
expect(res.components.length).toBeGreaterThan(0);
46+
});
47+
48+
it('should be able to set a custom metadata value of the Dapr sidecar', async () => {
49+
await client.metadata.set("testKey", "Hello World");
50+
51+
const res = await client.metadata.get();
52+
53+
expect(res.id.length).toBeGreaterThan(0);
54+
expect(res.components.length).toBeGreaterThan(0);
55+
expect(res.extended["testKey"]).toEqual("Hello World");
56+
});
57+
});
58+
59+
describe('health', () => {
60+
it('should return true if Dapr is ready', async () => {
61+
const res = await client.health.isHealthy();
62+
expect(res).toEqual(true);
63+
});
64+
});
65+
4066
describe('binding', () => {
4167
it('should be able to receive events', async () => {
42-
console.log("STARTING TEST")
4368
await client.binding.send('binding-mqtt', 'create', { hello: 'world' });
4469

4570
// Delay a bit for event to arrive

0 commit comments

Comments
 (0)