Skip to content

Commit c005d80

Browse files
Merge pull request #149 from XavierGeerinck/master
Add Metadata & Health API
2 parents 0bd71f3 + 84572c1 commit c005d80

30 files changed

+462
-109
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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,26 @@ 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';
@@ -34,6 +40,8 @@ export default class DaprClient {
3440
readonly binding: IClientBinding;
3541
readonly invoker: IClientInvoker;
3642
readonly secret: IClientSecret;
43+
readonly health: IClientHealth;
44+
readonly metadata: IClientMetadata;
3745

3846
constructor(
3947
daprHost: string
@@ -64,6 +72,8 @@ export default class DaprClient {
6472
this.binding = new GRPCClientBinding(client);
6573
this.invoker = new GRPCClientInvoker(client);
6674
this.secret = new GRPCClientSecret(client);
75+
this.health = new GRPCClientHealth(client);
76+
this.metadata = new GRPCClientMetadata(client);
6777
break;
6878
}
6979
case CommunicationProtocolEnum.HTTP:
@@ -76,6 +86,8 @@ export default class DaprClient {
7686
this.binding = new HTTPClientBinding(client);
7787
this.invoker = new HTTPClientInvoker(client);
7888
this.secret = new HTTPClientSecret(client);
89+
this.health = new HTTPClientHealth(client);
90+
this.metadata = new HTTPClientMetadata(client);
7991
break;
8092
}
8193
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import GRPCClient from './GRPCClient';
2+
import IClientHealth from '../../../interfaces/Client/IClientHealth';
3+
import { GetMetadataResponse } from '../../../proto/dapr/proto/runtime/v1/dapr_pb';
4+
import { Empty } from "google-protobuf/google/protobuf/empty_pb";
5+
6+
// https://docs.dapr.io/reference/api/health_api/
7+
export default class GRPCClientHealth implements IClientHealth {
8+
client: GRPCClient;
9+
10+
constructor(client: GRPCClient) {
11+
this.client = client;
12+
}
13+
14+
// There is no gRPC implementation of /healthz, so we try to fetch the metadata
15+
async isHealthy(): Promise<boolean> {
16+
return new Promise((resolve, reject) => {
17+
const client = this.client.getClient();
18+
19+
try {
20+
client.getMetadata(new Empty(), (err, res: GetMetadataResponse) => {
21+
if (err) {
22+
return resolve(false);
23+
}
24+
25+
return resolve(true);
26+
});
27+
} catch (e) {
28+
return resolve(false);
29+
}
30+
});
31+
}
32+
}
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+
}

src/implementation/Client/GRPCClient/state.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import GRPCClient from './GRPCClient';
2-
import { DeleteStateRequest, ExecuteStateTransactionRequest, GetBulkStateRequest, GetBulkStateResponse, GetStateRequest, GetStateResponse, SaveStateRequest, TransactionalStateOperation } from '../../../proto/dapr/proto/runtime/v1/dapr_pb';
2+
import { DeleteStateRequest, ExecuteStateTransactionRequest, GetBulkStateRequest, GetBulkStateResponse, GetStateRequest, GetStateResponse, QueryStateRequest, QueryStateResponse, SaveStateRequest, TransactionalStateOperation } from '../../../proto/dapr/proto/runtime/v1/dapr_pb';
33
import { Etag, StateItem, StateOptions } from '../../../proto/dapr/proto/common/v1/common_pb';
44
import { KeyValuePairType } from '../../../types/KeyValuePair.type';
55
import { OperationType } from '../../../types/Operation.type';
66
import { IRequestMetadata } from '../../../types/RequestMetadata.type';
77
import IClientState from '../../../interfaces/Client/IClientState';
88
import { KeyValueType } from '../../../types/KeyValue.type';
99
import { merge } from '../../../utils/Map.util';
10+
import { StateQueryType } from '../../../types/StateQuery.type';
11+
import { StateQueryResponseType } from '../../../types/StateQueryResponse.type';
1012

1113
// https://docs.dapr.io/reference/api/state_api/
1214
export default class GRPCClientState implements IClientState {
@@ -25,7 +27,7 @@ export default class GRPCClientState implements IClientState {
2527
si.setValue(Buffer.from(typeof stateObject.value === "object" ? JSON.stringify(stateObject.value) : stateObject.value.toString(), "utf-8"));
2628
stateList.push(si);
2729
}
28-
30+
2931
const msgService = new SaveStateRequest();
3032
msgService.setStoreName(storeName);
3133
msgService.setStatesList(stateList);
@@ -48,7 +50,7 @@ export default class GRPCClientState implements IClientState {
4850
msgService.setStoreName(storeName);
4951
msgService.setKey(key)
5052

51-
53+
5254
// @todo: https://docs.dapr.io/reference/api/state_api/#optional-behaviors
5355
// msgService.setConsistency()
5456

@@ -96,7 +98,7 @@ export default class GRPCClientState implements IClientState {
9698
let data: string;
9799
try {
98100
data = JSON.parse(resDataStr);
99-
} catch(e) {
101+
} catch (e) {
100102
data = resDataStr;
101103
}
102104
return {
@@ -180,4 +182,33 @@ export default class GRPCClientState implements IClientState {
180182
});
181183
});
182184
}
185+
186+
async query(storeName: string, query: StateQueryType): Promise<StateQueryResponseType> {
187+
const msgService = new QueryStateRequest();
188+
msgService.setStoreName(storeName);
189+
msgService.setQuery(JSON.stringify(query))
190+
191+
return new Promise((resolve, reject) => {
192+
const client = this.client.getClient();
193+
client.queryStateAlpha1(msgService, (err, res: QueryStateResponse) => {
194+
if (err) {
195+
return reject(err);
196+
}
197+
198+
// https://docs.dapr.io/reference/api/state_api/#response-body
199+
// map the res from gRPC
200+
let resMapped: StateQueryResponseType = {
201+
results: res.getResultsList().map((i) => ({
202+
key: i.getKey(),
203+
data: i.getData(),
204+
etag: i.getEtag(),
205+
error: i.getError(),
206+
})),
207+
token: res.getToken()
208+
};
209+
210+
return resolve(resMapped);
211+
});
212+
});
213+
}
183214
}

src/implementation/Client/HTTPClient/HTTPClient.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ export default class HTTPClient implements IClient {
7373
this.httpsAgent.destroy();
7474
}
7575

76+
async executeWithApiVersion(apiVersion: string = "v1.0", url: string, params: any = {}): Promise<object | string> {
77+
const newClientUrl = this.clientUrl.replace("v1.0", apiVersion);
78+
return await this.execute(`${newClientUrl}${url}`, params);
79+
}
80+
7681
async execute(url: string, params: any = {}): Promise<object | string> {
7782
if (!params?.headers) {
7883
params.headers = {};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import HTTPClient from './HTTPClient';
2+
import IClientHealth from '../../../interfaces/Client/IClientHealth';
3+
4+
// https://docs.dapr.io/reference/api/health_api/
5+
export default class HTTPClientHealth implements IClientHealth {
6+
client: HTTPClient;
7+
8+
constructor(client: HTTPClient) {
9+
this.client = client;
10+
}
11+
12+
// Send an event to an external system
13+
async isHealthy(): Promise<boolean> {
14+
try {
15+
const result = await this.client.execute(`/healthz`, {
16+
method: 'GET',
17+
headers: {
18+
'Content-Type': 'application/json',
19+
}
20+
});
21+
22+
return true;
23+
} catch (e) {
24+
return false;
25+
}
26+
}
27+
}
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+
}

src/implementation/Client/HTTPClient/state.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { OperationType } from '../../../types/Operation.type';
44
import { IRequestMetadata } from '../../../types/RequestMetadata.type';
55
import IClientState from '../../../interfaces/Client/IClientState';
66
import { KeyValueType } from '../../../types/KeyValue.type';
7+
import { StateQueryType } from '../../../types/StateQuery.type';
8+
import { StateQueryResponseType } from '../../../types/StateQueryResponse.type';
79

810
// https://docs.dapr.io/reference/api/state_api/
911
export default class HTTPClientState implements IClientState {
@@ -61,4 +63,18 @@ export default class HTTPClientState implements IClientState {
6163
})
6264
});
6365
}
66+
67+
async query(storeName: string, query: StateQueryType): Promise<StateQueryResponseType> {
68+
const result = await this.client.executeWithApiVersion("v1.0-alpha1", `/state/${storeName}/query`, {
69+
method: 'POST',
70+
headers: {
71+
"Content-Type": "application/json"
72+
},
73+
body: JSON.stringify({
74+
query
75+
})
76+
});
77+
78+
return result as StateQueryResponseType;
79+
}
6480
}

0 commit comments

Comments
 (0)