Skip to content

Commit

Permalink
Merge branch 'main' into typesafety-cli
Browse files Browse the repository at this point in the history
  • Loading branch information
roncohen committed Jan 30, 2025
2 parents e0b6735 + 81f5ddc commit 6c9e08b
Show file tree
Hide file tree
Showing 12 changed files with 748 additions and 119 deletions.
95 changes: 91 additions & 4 deletions packages/node-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,18 @@ const boundClient = bucketClient.bindClient({

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

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

if (config?.key === "zoom") {
// this code will run if a given remote configuration
// is set up.
}

// CAUTION: if you plan to use the event for automated feedback surveys
// call `flush` immediately after `track`. It can optionally be awaited
// to guarantee the sent happened.
Expand Down Expand Up @@ -108,6 +113,34 @@ to `getFeatures()` (or through `bindClient(..).getFeatures()`). That means the
`initialize()` has completed. `BucketClient` will continue to periodically
download the targeting rules from the Bucket servers in the background.

### Remote config

Similar to `isEnabled`, each feature has a `config` property. This configuration is managed from within Bucket.
It is managed similar to the way access to features is managed, but instead of the binary `isEnabled` you can have
multiple configuration values which are given to different user/companies.

```ts
const features = bucketClient.getFeatures();
// {
// huddle: {
// isEnabled: true,
// targetingVersion: 42,
// config: {
// key: "gpt-3.5",
// payload: { maxTokens: 10000, model: "gpt-3.5-beta1" }
// }
// }
// }
```

The `key` is always present while the `payload` is a optional JSON value for arbitrary configuration needs.
If feature has no configuration or, no configuration value was matched against the context, the `config` object
will be empty, thus, `key` will be `undefined`. Make sure to check against this case when trying to use the
configuration in your application.

Just as `isEnabled`, accessing `config` on the object returned by `getFeatures` does not automatically
generate a `check` event, contrary to the `config` property on the object returned by `getFeature`.

## Configuring

The Bucket `Node.js` SDK can be configured through environment variables,
Expand Down Expand Up @@ -136,7 +169,13 @@ Note: BUCKET_FEATURES_ENABLED, BUCKET_FEATURES_DISABLED are comma separated list
"apiBaseUrl": "https://proxy.slick-demo.com",
"featureOverrides": {
"huddles": true,
"voiceChat": false
"voiceChat": false,
"aiAssist": {
"key": "gpt-4.0",
"payload": {
"maxTokens": 50000
}
}
}
}
```
Expand All @@ -162,8 +201,11 @@ import { BucketClient } from "@bucketco/node-sdk";
declare module "@bucketco/node-sdk" {
interface Features {
"show-todos": boolean;
"create-todos": boolean;
"delete-todos": boolean;
"create-todos": { isEnabled: boolean };
"delete-todos": {
isEnabled: boolean,
config: any
};
}
}

Expand All @@ -173,7 +215,52 @@ bucketClient.initialize().then({
console.log("Bucket initialized!")
bucketClient.getFeature("invalid-feature") // feature doesn't exist
})
```
The following example show how to add strongly typed payloads when using remote configuration:
```typescript
import { BucketClient } from "@bucketco/node-sdk";

type ConfirmationConfig = {
shouldShowConfirmation: boolean;
};

declare module "@bucketco/node-sdk" {
interface Features {
"delete-todos": {
isEnabled: boolean;
config: {
key: string;
payload: ConfirmationConfig;
};
};
}
}

export const bucketClient = new BucketClient();

function deleteTodo(todoId: string) {
// get the feature information
const {
isEnabled,
config: { payload: confirmationConfig },
} = bucketClient.getFeature("delete-todos");

// check that feature is enabled for user
if (!isEnabled) {
return;
}

// finally, check if we enabled the "confirmation" dialog for this user and only
// show it in that case.
// since we defined `ConfirmationConfig` as the only valid payload for `delete-todos`,
// we have type-safety helping us with the payload value.
if (confirmationConfig.shouldShowConfirmation) {
showMessage("Are you really sure you want to delete this item?");
// ... rest of the code
}
}
```
![Type check failed](docs/type-check-failed.png "Type check failed")
Expand Down
10 changes: 9 additions & 1 deletion packages/node-sdk/example/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,18 @@ app.post("/todos", (req, res) => {
return res.status(400).json({ error: "Invalid todo" });
}

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

// Check if the user has the "create-todos" feature enabled
if (isEnabled) {
// Check if the todo is at least N characters long
if (todo.length < config.payload.minimumLength) {
return res
.status(400)
.json({ error: "Todo must be at least 5 characters long" });
}

// Track the feature usage
track();
todos.push(todo);
Expand Down
27 changes: 24 additions & 3 deletions packages/node-sdk/example/bucket.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
import { BucketClient, Context } from "../src";
import { FeatureOverrides } from "../src/types";

type CreateConfig = {
minimumLength: number;
};

// Extending the Features interface to define the available features
declare module "../src/types" {
interface Features {
"show-todos": boolean;
"create-todos": boolean;
"create-todos": {
isEnabled: boolean;
config: {
key: string;
payload: CreateConfig;
};
};
"delete-todos": boolean;
"some-else": {};
}
}

let featureOverrides = (context: Context): FeatureOverrides => {
return { "delete-todos": true }; // feature keys checked at compile time
let featureOverrides = (_: Context): FeatureOverrides => {
return {
"create-todos": {
isEnabled: true,
config: {
key: "short",
payload: {
minimumLength: 10,
},
},
},
}; // feature keys checked at compile time
};

let host = undefined;
Expand Down
4 changes: 2 additions & 2 deletions packages/node-sdk/example/bucketConfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"overrides": {
"myFeature": true,
"myFeatureFalse": false
"show-todos": true,
"create-todos": true
}
}
Loading

0 comments on commit 6c9e08b

Please sign in to comment.