Skip to content

Commit bfc7ab3

Browse files
committed
Merge branch 'main' into fix-ensure-timers-dont-keep-process-alive
2 parents c03057a + ee38109 commit bfc7ab3

File tree

11 files changed

+756
-120
lines changed

11 files changed

+756
-120
lines changed

packages/node-sdk/README.md

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,18 @@ const boundClient = bucketClient.bindClient({
7474

7575
// get the huddle feature using company, user and custom context to
7676
// evaluate the targeting.
77-
const { isEnabled, track } = boundClient.getFeature("huddle");
77+
const { isEnabled, track, config } = boundClient.getFeature("huddle");
7878

7979
if (isEnabled) {
8080
// this is your feature gated code ...
8181
// send an event when the feature is used:
8282
track();
8383

84+
if (config?.key === "zoom") {
85+
// this code will run if a given remote configuration
86+
// is set up.
87+
}
88+
8489
// CAUTION: if you plan to use the event for automated feedback surveys
8590
// call `flush` immediately after `track`. It can optionally be awaited
8691
// to guarantee the sent happened.
@@ -108,6 +113,34 @@ to `getFeatures()` (or through `bindClient(..).getFeatures()`). That means the
108113
`initialize()` has completed. `BucketClient` will continue to periodically
109114
download the targeting rules from the Bucket servers in the background.
110115

116+
### Remote config
117+
118+
Similar to `isEnabled`, each feature has a `config` property. This configuration is managed from within Bucket.
119+
It is managed similar to the way access to features is managed, but instead of the binary `isEnabled` you can have
120+
multiple configuration values which are given to different user/companies.
121+
122+
```ts
123+
const features = bucketClient.getFeatures();
124+
// {
125+
// huddle: {
126+
// isEnabled: true,
127+
// targetingVersion: 42,
128+
// config: {
129+
// key: "gpt-3.5",
130+
// payload: { maxTokens: 10000, model: "gpt-3.5-beta1" }
131+
// }
132+
// }
133+
// }
134+
```
135+
136+
The `key` is always present while the `payload` is a optional JSON value for arbitrary configuration needs.
137+
If feature has no configuration or, no configuration value was matched against the context, the `config` object
138+
will be empty, thus, `key` will be `undefined`. Make sure to check against this case when trying to use the
139+
configuration in your application.
140+
141+
Just as `isEnabled`, accessing `config` on the object returned by `getFeatures` does not automatically
142+
generate a `check` event, contrary to the `config` property on the object returned by `getFeature`.
143+
111144
## Configuring
112145

113146
The Bucket `Node.js` SDK can be configured through environment variables,
@@ -136,7 +169,13 @@ Note: BUCKET_FEATURES_ENABLED, BUCKET_FEATURES_DISABLED are comma separated list
136169
"apiBaseUrl": "https://proxy.slick-demo.com",
137170
"featureOverrides": {
138171
"huddles": true,
139-
"voiceChat": false
172+
"voiceChat": false,
173+
"aiAssist": {
174+
"key": "gpt-4.0",
175+
"payload": {
176+
"maxTokens": 50000
177+
}
178+
}
140179
}
141180
}
142181
```
@@ -162,8 +201,11 @@ import { BucketClient } from "@bucketco/node-sdk";
162201
declare module "@bucketco/node-sdk" {
163202
interface Features {
164203
"show-todos": boolean;
165-
"create-todos": boolean;
166-
"delete-todos": boolean;
204+
"create-todos": { isEnabled: boolean };
205+
"delete-todos": {
206+
isEnabled: boolean,
207+
config: any
208+
};
167209
}
168210
}
169211

@@ -173,7 +215,52 @@ bucketClient.initialize().then({
173215
console.log("Bucket initialized!")
174216
bucketClient.getFeature("invalid-feature") // feature doesn't exist
175217
})
218+
```
219+
220+
The following example show how to add strongly typed payloads when using remote configuration:
221+
222+
```typescript
223+
import { BucketClient } from "@bucketco/node-sdk";
224+
225+
type ConfirmationConfig = {
226+
shouldShowConfirmation: boolean;
227+
};
176228

229+
declare module "@bucketco/node-sdk" {
230+
interface Features {
231+
"delete-todos": {
232+
isEnabled: boolean;
233+
config: {
234+
key: string;
235+
payload: ConfirmationConfig;
236+
};
237+
};
238+
}
239+
}
240+
241+
export const bucketClient = new BucketClient();
242+
243+
function deleteTodo(todoId: string) {
244+
// get the feature information
245+
const {
246+
isEnabled,
247+
config: { payload: confirmationConfig },
248+
} = bucketClient.getFeature("delete-todos");
249+
250+
// check that feature is enabled for user
251+
if (!isEnabled) {
252+
return;
253+
}
254+
255+
// finally, check if we enabled the "confirmation" dialog for this user and only
256+
// show it in that case.
257+
// since we defined `ConfirmationConfig` as the only valid payload for `delete-todos`,
258+
// we have type-safety helping us with the payload value.
259+
if (confirmationConfig.shouldShowConfirmation) {
260+
showMessage("Are you really sure you want to delete this item?");
261+
// ... rest of the code
262+
}
263+
}
177264
```
178265
179266
![Type check failed](docs/type-check-failed.png "Type check failed")

packages/node-sdk/example/app.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,18 @@ app.post("/todos", (req, res) => {
6565
return res.status(400).json({ error: "Invalid todo" });
6666
}
6767

68-
const { track, isEnabled } = res.locals.bucketUser.getFeature("create-todos");
68+
const { track, isEnabled, config } =
69+
res.locals.bucketUser.getFeature("create-todos");
6970

7071
// Check if the user has the "create-todos" feature enabled
7172
if (isEnabled) {
73+
// Check if the todo is at least N characters long
74+
if (todo.length < config.payload.minimumLength) {
75+
return res
76+
.status(400)
77+
.json({ error: "Todo must be at least 5 characters long" });
78+
}
79+
7280
// Track the feature usage
7381
track();
7482
todos.push(todo);

packages/node-sdk/example/bucket.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,38 @@
11
import { BucketClient, Context } from "../src";
22
import { FeatureOverrides } from "../src/types";
33

4+
type CreateConfig = {
5+
minimumLength: number;
6+
};
7+
48
// Extending the Features interface to define the available features
59
declare module "../src/types" {
610
interface Features {
711
"show-todos": boolean;
8-
"create-todos": boolean;
12+
"create-todos": {
13+
isEnabled: boolean;
14+
config: {
15+
key: string;
16+
payload: CreateConfig;
17+
};
18+
};
919
"delete-todos": boolean;
20+
"some-else": {};
1021
}
1122
}
1223

13-
let featureOverrides = (context: Context): FeatureOverrides => {
14-
return { "delete-todos": true }; // feature keys checked at compile time
24+
let featureOverrides = (_: Context): FeatureOverrides => {
25+
return {
26+
"create-todos": {
27+
isEnabled: true,
28+
config: {
29+
key: "short",
30+
payload: {
31+
minimumLength: 10,
32+
},
33+
},
34+
},
35+
}; // feature keys checked at compile time
1536
};
1637

1738
let host = undefined;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"overrides": {
3-
"myFeature": true,
4-
"myFeatureFalse": false
3+
"show-todos": true,
4+
"create-todos": true
55
}
66
}

0 commit comments

Comments
 (0)