Skip to content

Commit b827da6

Browse files
Merge pull request #242 from XavierGeerinck/issue_140
feat: Configuration API
2 parents 915260b + ac259ab commit b827da6

File tree

14 files changed

+534
-5
lines changed

14 files changed

+534
-5
lines changed

daprdocs/content/en/js-sdk-docs/js-server/_index.md

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ const serverHost = "127.0.0.1";
168168
const serverPort = "5051";
169169

170170
async function start() {
171-
const server = new DaprServer(serverHost, serverPort, daprHost, daprPort);;
171+
const server = new DaprServer(serverHost, serverPort, daprHost, daprPort);
172172

173173
const bindingName = "my-binding-name";
174174

@@ -185,6 +185,58 @@ start().catch((e) => {
185185

186186
> For a full guide on output bindings visit [How-To: Use bindings]({{< ref howto-bindings.md >}}).
187187
188+
### Configuration API
189+
190+
> 💡 The configuration API is currently only available through gRPC
191+
192+
#### Getting a configuration value
193+
194+
```javascript
195+
import { DaprServer } from "dapr-client";
196+
197+
const daprHost = "127.0.0.1";
198+
const daprPort = "3500";
199+
const serverHost = "127.0.0.1";
200+
const serverPort = "5051";
201+
202+
async function start() {
203+
const client = new DaprClient(daprHost, daprPort, CommunicationProtocolEnum.GRPC);
204+
const config = await client.configuration.get("config-redis", ["myconfigkey1", "myconfigkey2"]);
205+
}
206+
207+
start().catch((e) => {
208+
console.error(e);
209+
process.exit(1);
210+
});
211+
```
212+
213+
#### Subscribing to Key Changes
214+
215+
```javascript
216+
import { DaprServer } from "dapr-client";
217+
218+
const daprHost = "127.0.0.1";
219+
const daprPort = "3500";
220+
const serverHost = "127.0.0.1";
221+
const serverPort = "5051";
222+
223+
async function start() {
224+
const client = new DaprClient(daprHost, daprPort, CommunicationProtocolEnum.GRPC);
225+
const stream = await client.configuration.subscribeWithKeys("config-redis", ["myconfigkey1", "myconfigkey2"], () => {
226+
// Received a key update
227+
});
228+
229+
// When you are ready to stop listening, call the following
230+
await stream.close();
231+
}
232+
233+
start().catch((e) => {
234+
console.error(e);
235+
process.exit(1);
236+
});
237+
```
238+
239+
188240
## Related links
189241

190242
- [JavaScript SDK examples](https://github.com/dapr/js-sdk/tree/master/examples)

src/implementation/Client/DaprClient.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import IClientSecret from '../../interfaces/Client/IClientSecret';
1919
import IClientHealth from '../../interfaces/Client/IClientHealth';
2020
import IClientMetadata from '../../interfaces/Client/IClientMetadata';
2121
import IClientSidecar from '../../interfaces/Client/IClientSidecar';
22+
import IClientConfiguration from '../../interfaces/Client/IClientConfiguration';
2223
import IClientActorBuilder from '../../interfaces/Client/IClientActorBuilder';
2324
import IClient from '../../interfaces/Client/IClient';
2425

@@ -30,6 +31,7 @@ import GRPCClientSecret from './GRPCClient/secret';
3031
import GRPCClientHealth from './GRPCClient/health';
3132
import GRPCClientMetadata from './GRPCClient/metadata';
3233
import GRPCClientSidecar from './GRPCClient/sidecar';
34+
import GRPCClientConfiguration from './GRPCClient/configuration';
3335
import GRPCClientActor from './GRPCClient/actor';
3436
import GRPCClient from './GRPCClient/GRPCClient';
3537

@@ -41,6 +43,7 @@ import HTTPClientSecret from './HTTPClient/secret';
4143
import HTTPClientHealth from './HTTPClient/health';
4244
import HTTPClientMetadata from './HTTPClient/metadata';
4345
import HTTPClientSidecar from './HTTPClient/sidecar';
46+
import HTTPClientConfiguration from './HTTPClient/configuration';
4447
import HTTPClientActor from './HTTPClient/actor';
4548
import HTTPClient from './HTTPClient/HTTPClient';
4649

@@ -63,6 +66,7 @@ export default class DaprClient {
6366
readonly health: IClientHealth;
6467
readonly metadata: IClientMetadata;
6568
readonly sidecar: IClientSidecar;
69+
readonly configuration: IClientConfiguration;
6670
readonly actor: IClientActorBuilder;
6771

6872
constructor(
@@ -97,6 +101,7 @@ export default class DaprClient {
97101
this.health = new GRPCClientHealth(client);
98102
this.metadata = new GRPCClientMetadata(client);
99103
this.sidecar = new GRPCClientSidecar(client);
104+
this.configuration = new GRPCClientConfiguration(client);
100105
this.actor = new GRPCClientActor(client); // we use a abstractor here since we interface through a builder with the Actor Runtime
101106
break;
102107
}
@@ -113,6 +118,7 @@ export default class DaprClient {
113118
this.health = new HTTPClientHealth(client);
114119
this.metadata = new HTTPClientMetadata(client);
115120
this.sidecar = new HTTPClientSidecar(client);
121+
this.configuration = new HTTPClientConfiguration(client);
116122
this.actor = new HTTPClientActor(client); // we use a abstractor here since we interface through a builder with the Actor Runtime
117123
break;
118124
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
Copyright 2022 The Dapr Authors
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
import GRPCClient from './GRPCClient';
15+
import * as grpc from "@grpc/grpc-js";
16+
import { GetConfigurationRequest, GetConfigurationResponse, SubscribeConfigurationRequest, SubscribeConfigurationResponse, UnsubscribeConfigurationRequest, UnsubscribeConfigurationResponse } from '../../../proto/dapr/proto/runtime/v1/dapr_pb';
17+
import IClientConfiguration from '../../../interfaces/Client/IClientConfiguration';
18+
import { KeyValueType } from '../../../types/KeyValue.type';
19+
import { GetConfigurationResponse as GetConfigurationResponseResult } from '../../../types/configuration/GetConfigurationResponse';
20+
import { SubscribeConfigurationResponse as SubscribeConfigurationResponseResult } from '../../../types/configuration/SubscribeConfigurationResponse';
21+
import { SubscribeConfigurationCallback } from '../../../types/configuration/SubscribeConfigurationCallback';
22+
import { SubscribeConfigurationStream } from '../../../types/configuration/SubscribeConfigurationStream';
23+
24+
export default class GRPCClientConfiguration implements IClientConfiguration {
25+
client: GRPCClient;
26+
27+
constructor(client: GRPCClient) {
28+
this.client = client;
29+
}
30+
31+
async get(storeName: string, keys: string[], metadataObj?: KeyValueType): Promise<GetConfigurationResponseResult> {
32+
const metadata = new grpc.Metadata();
33+
34+
const msg = new GetConfigurationRequest();
35+
msg.setStoreName(storeName);
36+
37+
if (keys && keys.length > 0) {
38+
msg.setKeysList(keys.filter(i => i !== ""));
39+
}
40+
41+
if (metadataObj) {
42+
for (const [key, value] of Object.entries(metadataObj)) {
43+
metadata.add(key, value);
44+
}
45+
}
46+
47+
return new Promise((resolve, reject) => {
48+
const client = this.client.getClient();
49+
client.getConfigurationAlpha1(msg, metadata, (err, res: GetConfigurationResponse) => {
50+
if (err) {
51+
return reject(err);
52+
}
53+
54+
const wrapped: GetConfigurationResponseResult = {
55+
items: res.getItemsList().map((item) => ({
56+
key: item.getKey(),
57+
value: item.getValue(),
58+
version: item.getVersion(),
59+
metadata: item.getMetadataMap().toObject().reduce((result: object, [key, value]) => {
60+
// @ts-ignore
61+
result[key] = value;
62+
return result
63+
}, {}),
64+
}))
65+
}
66+
67+
return resolve(wrapped);
68+
});
69+
});
70+
}
71+
72+
async subscribe(storeName: string, cb: SubscribeConfigurationCallback): Promise<SubscribeConfigurationStream> {
73+
return this._subscribe(storeName, cb)
74+
}
75+
76+
async subscribeWithKeys(storeName: string, keys: string[], cb: SubscribeConfigurationCallback): Promise<SubscribeConfigurationStream> {
77+
return this._subscribe(storeName, cb, keys)
78+
}
79+
80+
async subscribeWithMetadata(storeName: string, keys: string[], metadata: KeyValueType, cb: SubscribeConfigurationCallback): Promise<SubscribeConfigurationStream> {
81+
return this._subscribe(storeName, cb, keys, metadata)
82+
}
83+
84+
async _subscribe(storeName: string, cb: SubscribeConfigurationCallback, keys?: string[], metadataObj?: KeyValueType): Promise<SubscribeConfigurationStream> {
85+
const metadata = new grpc.Metadata();
86+
87+
const msg = new SubscribeConfigurationRequest();
88+
msg.setStoreName(storeName);
89+
90+
if (keys && keys.length > 0) {
91+
msg.setKeysList(keys.filter(i => i !== ""));
92+
} else {
93+
msg.setKeysList([]);
94+
}
95+
96+
if (metadataObj) {
97+
for (const [key, value] of Object.entries(metadataObj)) {
98+
metadata.add(key, value);
99+
}
100+
}
101+
102+
const client = this.client.getClient();
103+
104+
// Open a stream. Note that this is a never-ending stream
105+
// and will stay open as long as the client is open
106+
// we will thus create a set with our listeners so we don't
107+
// break on multi listeners
108+
const stream = client.subscribeConfigurationAlpha1(msg, metadata);
109+
let streamId: string;
110+
111+
stream.on("data", async (data: SubscribeConfigurationResponse) => {
112+
streamId = data.getId();
113+
114+
const wrapped: SubscribeConfigurationResponseResult = {
115+
items: data.getItemsList().map((item) => ({
116+
key: item.getKey(),
117+
value: item.getValue(),
118+
version: item.getVersion(),
119+
metadata: item.getMetadataMap().toObject().reduce((result: object, [key, value]) => {
120+
// @ts-ignore
121+
result[key] = value;
122+
return result
123+
}, {}),
124+
}))
125+
}
126+
127+
await cb(wrapped);
128+
});
129+
130+
return {
131+
stop: async () => {
132+
return new Promise((resolve, reject) => {
133+
const req = new UnsubscribeConfigurationRequest();
134+
req.setStoreName(storeName);
135+
req.setId(streamId);
136+
137+
client.unsubscribeConfigurationAlpha1(req, (err, res: UnsubscribeConfigurationResponse) => {
138+
if (err || !res.getOk()) {
139+
return reject(res.getMessage());
140+
}
141+
142+
// Clean up the node.js event emitter
143+
stream.removeAllListeners();
144+
stream.destroy();
145+
146+
return resolve();
147+
});
148+
})
149+
}
150+
};
151+
}
152+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
Copyright 2022 The Dapr Authors
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
import IClientConfiguration from '../../../interfaces/Client/IClientConfiguration';
15+
import { KeyValueType } from '../../../types/KeyValue.type';
16+
import { GetConfigurationResponse as GetConfigurationResponseResult } from '../../../types/configuration/GetConfigurationResponse';
17+
import HTTPClient from './HTTPClient';
18+
import { SubscribeConfigurationCallback } from '../../../types/configuration/SubscribeConfigurationCallback';
19+
import { SubscribeConfigurationStream } from '../../../types/configuration/SubscribeConfigurationStream';
20+
21+
export default class HTTPClientConfiguration implements IClientConfiguration {
22+
client: HTTPClient;
23+
24+
constructor(client: HTTPClient) {
25+
this.client = client;
26+
}
27+
28+
async subscribe(_storeName: string, _cb: SubscribeConfigurationCallback): Promise<SubscribeConfigurationStream> {
29+
throw new Error('HTTP is currently not supported.');
30+
}
31+
32+
async subscribeWithKeys(_storeName: string, _keys: string[], _cb: SubscribeConfigurationCallback): Promise<SubscribeConfigurationStream> {
33+
throw new Error('HTTP is currently not supported.');
34+
}
35+
36+
async subscribeWithMetadata(_storeName: string, _keys: string[], _metadata: KeyValueType, _cb: SubscribeConfigurationCallback): Promise<SubscribeConfigurationStream> {
37+
throw new Error('HTTP is currently not supported.');
38+
}
39+
40+
async get(_storeName: string, _keys: string[], _metadata?: KeyValueType): Promise<GetConfigurationResponseResult> {
41+
throw new Error('HTTP is currently not supported.');
42+
}
43+
}

src/implementation/Server/GRPCServer/actor.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,18 @@ export default class GRPCServerActor implements IServerActor {
2525
}
2626

2727
deactivateActor(_actorType: string, _actorId: string): Promise<void> {
28-
throw new Error('Method not implemented.');
28+
throw new Error('GRPC is currently not supported.');
2929
}
3030

3131
init(): Promise<void> {
32-
throw new Error('Method not implemented.');
32+
throw new Error('GRPC is currently not supported.');
3333
}
3434

3535
getRegisteredActors(): Promise<string[]> {
36-
throw new Error('Method not implemented.');
36+
throw new Error('GRPC is currently not supported.');
3737
}
3838

3939
registerActor<T extends AbstractActor>(_cls: Class<T>): Promise<void> {
40-
throw new Error('Method not implemented.');
40+
throw new Error('GRPC is currently not supported.');
4141
}
4242
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
Copyright 2022 The Dapr Authors
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
import { GetConfigurationResponse } from "../../types/configuration/GetConfigurationResponse";
15+
import { SubscribeConfigurationCallback } from "../../types/configuration/SubscribeConfigurationCallback";
16+
import { SubscribeConfigurationStream } from "../../types/configuration/SubscribeConfigurationStream";
17+
import { KeyValueType } from "../../types/KeyValue.type";
18+
19+
export default interface IClientConfiguration {
20+
// https://github.com/dapr/dapr/blob/master/dapr/proto/runtime/v1/dapr.proto#L90
21+
get(storeName: string, keys?: string[], metadata?: KeyValueType): Promise<GetConfigurationResponse>;
22+
subscribe(storeName: string, cb: SubscribeConfigurationCallback): Promise<SubscribeConfigurationStream>;
23+
subscribeWithKeys(storeName: string, keys: string[], cb: SubscribeConfigurationCallback): Promise<SubscribeConfigurationStream>;
24+
subscribeWithMetadata(storeName: string, keys: string[], metadata: KeyValueType, cb: SubscribeConfigurationCallback): Promise<SubscribeConfigurationStream>;
25+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
Copyright 2022 The Dapr Authors
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
import { KeyValueType } from "../KeyValue.type";
15+
16+
export type ConfigurationItem = {
17+
key: string;
18+
value: string;
19+
version: string;
20+
metadata: KeyValueType;
21+
}

0 commit comments

Comments
 (0)