Gathering detailed insights and metrics for @bucketco/node-sdk
Gathering detailed insights and metrics for @bucketco/node-sdk
Gathering detailed insights and metrics for @bucketco/node-sdk
Gathering detailed insights and metrics for @bucketco/node-sdk
npm install @bucketco/node-sdk
Typescript
Module System
Node Version
NPM Version
TypeScript (93.59%)
CSS (2.62%)
JavaScript (2.53%)
HTML (0.95%)
Shell (0.3%)
Total Downloads
43,856
Last Day
828
Last Week
3,860
Last Month
16,466
Last Year
43,856
MIT License
8 Stars
409 Commits
4 Forks
1 Watchers
42 Branches
8 Contributors
Updated on Mar 26, 2025
Minified
Minified + Gzipped
Latest Version
1.8.0
Package Id
@bucketco/node-sdk@1.8.0
Unpacked Size
202.99 kB
Size
46.06 kB
File Count
45
NPM Version
lerna/8.1.3/node@v20.15.1+x64 (linux)
Node Version
20.15.1
Published on
Mar 26, 2025
Cumulative downloads
Total Downloads
Last Day
32.7%
828
Compared to previous day
Last Week
8.2%
3,860
Compared to previous week
Last Month
48.9%
16,466
Compared to previous month
Last Year
0%
43,856
Compared to previous year
Node.js, JavaScript/TypeScript client for Bucket.co.
Bucket supports feature toggling, tracking feature usage, collecting feedback on features, and remotely configuring features.
Install using yarn
or npm
with:
yarn add -s @bucketco/node-sdk
ornpm install -s @bucketco/node-sdk
.
Other supported languages/frameworks are in the Supported languages documentation pages.
You can also use the HTTP API directly
To get started you need to obtain your secret key from the environment settings in Bucket.
[!CAUTION] Secret keys are meant for use in server side SDKs only. Secret keys offer the users the ability to obtain information that is often sensitive and thus should not be used in client-side applications.
Bucket will load settings through the various environment variables automatically (see Configuring below).
BUCKET_SECRET_KEY
in your .env
filebucket.ts
file containing the following:1import { BucketClient } from "@bucketco/node-sdk"; 2 3// Create a new instance of the client with the secret key. Additional options 4// are available, such as supplying a logger and other custom properties. 5// 6// We recommend that only one global instance of `client` should be created 7// to avoid multiple round-trips to our servers. 8export const bucketClient = new BucketClient(); 9 10// Initialize the client and begin fetching feature targeting definitions. 11// You must call this method prior to any calls to `getFeatures()`, 12// otherwise an empty object will be returned. 13bucketClient.initialize().then({ 14 console.log("Bucket initialized!") 15})
Once the client is initialized, you can obtain features along with the isEnabled
status to indicate whether the feature is targeted for this user/company:
[!IMPORTANT] If
user.id
orcompany.id
is not given, the wholeuser
orcompany
object is ignored.
1// configure the client 2const boundClient = bucketClient.bindClient({ 3 user: { 4 id: "john_doe", 5 name: "John Doe", 6 email: "john@acme.com", 7 avatar: "https://example.com/users/jdoe", 8 }, 9 company: { 10 id: "acme_inc", 11 name: "Acme, Inc.", 12 avatar: "https://example.com/companies/acme", 13 }, 14}); 15 16// get the huddle feature using company, user and custom context to 17// evaluate the targeting. 18const { isEnabled, track, config } = boundClient.getFeature("huddle"); 19 20if (isEnabled) { 21 // this is your feature gated code ... 22 // send an event when the feature is used: 23 track(); 24 25 if (config?.key === "zoom") { 26 // this code will run if a given remote configuration 27 // is set up. 28 } 29 30 // CAUTION: if you plan to use the event for automated feedback surveys 31 // call `flush` immediately after `track`. It can optionally be awaited 32 // to guarantee the sent happened. 33 boundClient.flush(); 34}
You can also use the getFeatures()
method which returns a map of all features:
1// get the current features (uses company, user and custom context to 2// evaluate the features). 3const features = boundClient.getFeatures(); 4const bothEnabled = 5 features.huddle?.isEnabled && features.voiceHuddle?.isEnabled;
The SDK contacts the Bucket servers when you call initialize()
and downloads the features with their targeting rules.
These rules are then matched against the user/company information you provide
to getFeatures()
(or through bindClient(..).getFeatures()
). That means the
getFeatures()
call does not need to contact the Bucket servers once
initialize()
has completed. BucketClient
will continue to periodically
download the targeting rules from the Bucket servers in the background.
The SDK automatically batches operations like user/company updates and feature tracking events to minimize API calls. The batch buffer is configurable through the client options:
1const client = new BucketClient({ 2 batchOptions: { 3 maxSize: 100, // Maximum number of events to batch 4 intervalMs: 1000, // Flush interval in milliseconds 5 }, 6});
You can manually flush the batch buffer at any time:
1await client.flush();
[!TIP] It's recommended to call
flush()
before your application shuts down to ensure all events are sent.
The SDK includes automatic rate limiting for feature events to prevent overwhelming the API. Rate limiting is applied per unique combination of feature key and context. The rate limiter window size is configurable:
1const client = new BucketClient({ 2 rateLimiterOptions: { 3 windowSizeMs: 60000, // Rate limiting window size in milliseconds 4 }, 5});
Feature definitions include the rules needed to determine which features should be enabled and which config values should be applied to any given user/company.
Feature definitions are automatically fetched when calling initialize()
.
They are then cached and refreshed in the background.
It's also possible to get the currently in use feature definitions:
1import fs from "fs"; 2 3const client = new BucketClient(); 4 5const featureDefs = await client.getFeatureDefinitions(); 6// [{ 7// key: "huddle", 8// description: "Live voice conversations with colleagues." 9// flag: { ... } 10// config: { ... } 11// }]
The SDK is designed to fail gracefully and never throw exceptions to the caller. Instead, it logs errors and provides fallback behavior:
Feature Evaluation Failures:
1const { isEnabled } = client.getFeature("my-feature"); 2// If feature evaluation fails, isEnabled will be false
Network Errors:
1// Network errors during tracking are logged but don't affect your application 2const { track } = client.getFeature("my-feature"); 3if (isEnabled) { 4 try { 5 await track(); 6 } catch (error) { 7 // The SDK already logged this error 8 // Your application can continue normally 9 } 10}
Missing Context:
1// The SDK tracks missing context fields but continues operation 2const features = client.getFeatures({ 3 user: { id: "user123" }, 4 // Missing company context will be logged but won't cause errors 5});
Offline Mode:
1// In offline mode, the SDK uses feature overrides 2const client = new BucketClient({ 3 offline: true, 4 featureOverrides: () => ({ 5 "my-feature": true, 6 }), 7});
The SDK logs all errors with appropriate severity levels. You can customize logging by providing your own logger:
1const client = new BucketClient({ 2 logger: { 3 debug: (msg) => console.debug(msg), 4 info: (msg) => console.info(msg), 5 warn: (msg) => console.warn(msg), 6 error: (msg, error) => { 7 console.error(msg, error); 8 // Send to your error tracking service 9 errorTracker.capture(error); 10 }, 11 }, 12});
Remote config is a dynamic and flexible approach to configuring feature behavior outside of your app – without needing to re-deploy it.
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.
1const features = bucketClient.getFeatures(); 2// { 3// huddle: { 4// isEnabled: true, 5// targetingVersion: 42, 6// config: { 7// key: "gpt-3.5", 8// payload: { maxTokens: 10000, model: "gpt-3.5-beta1" } 9// } 10// } 11// }
key
is mandatory for a config, but if a feature has no config or no config value was matched against the context, the key
will be undefined
. Make sure to check against this case when trying to use the configuration in your application. payload
is an optional JSON value for arbitrary configuration needs.
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
.
The Bucket Node.js
SDK can be configured through environment variables,
a configuration file on disk or by passing options to the BucketClient
constructor. By default, the SDK searches for bucketConfig.json
in the
current working directory.
Option | Type | Description | Env Var |
---|---|---|---|
secretKey | string | The secret key used for authentication with Bucket's servers. | BUCKET_SECRET_KEY |
logLevel | string | The log level for the SDK (e.g., "DEBUG" , "INFO" , "WARN" , "ERROR" ). Default: INFO | BUCKET_LOG_LEVEL |
offline | boolean | Operate in offline mode. Default: false , except in tests it will default to true based off of the TEST env. var. | BUCKET_OFFLINE |
apiBaseUrl | string | The base API URL for the Bucket servers. | BUCKET_API_BASE_URL |
featureOverrides | Record<string, boolean> | An object specifying feature overrides for testing or local development. See example/app.test.ts for how to use featureOverrides in tests. | BUCKET_FEATURES_ENABLED, BUCKET_FEATURES_DISABLED |
configFile | string | Load this config file from disk. Default: bucketConfig.json | BUCKET_CONFIG_FILE |
[!NOTE] >
BUCKET_FEATURES_ENABLED
andBUCKET_FEATURES_DISABLED
are comma separated lists of features which will be enabled or disabled respectively.
bucketConfig.json
example:
1{ 2 "secretKey": "...", 3 "logLevel": "warn", 4 "offline": true, 5 "apiBaseUrl": "https://proxy.slick-demo.com", 6 "featureOverrides": { 7 "huddles": true, 8 "voiceChat": { "isEnabled": false }, 9 "aiAssist": { 10 "isEnabled": true, 11 "config": { 12 "key": "gpt-4.0", 13 "payload": { 14 "maxTokens": 50000 15 } 16 } 17 } 18 } 19}
When using a bucketConfig.json
for local development, make sure you add it to your
.gitignore
file. You can also set these options directly in the BucketClient
constructor. The precedence for configuration options is as follows, listed in the
order of importance:
To get type checked feature flags, install the Bucket CLI:
npm i --save-dev @bucketco/cli
then generate the types:
npx bucket features types
This will generate a bucket.d.ts
containing all your features.
Any feature look ups will now be checked against the features that exist in Bucket.
Here's an example of a failed type check:
1import { BucketClient } from "@bucketco/node-sdk"; 2 3export const bucketClient = new BucketClient(); 4 5bucketClient.initialize().then(() => { 6 console.log("Bucket initialized!"); 7 8 // TypeScript will catch this error: "invalid-feature" doesn't exist 9 bucketClient.getFeature("invalid-feature"); 10 11 const { 12 isEnabled, 13 config: { payload }, 14 } = bucketClient.getFeature("create-todos"); 15});
This is an example of a failed config payload check:
1bucketClient.initialize().then(() => { 2 // TypeScript will catch this error as well: "minLength" is not part of the payload. 3 if (isEnabled && todo.length > config.payload.minLength) { 4 // ... 5 } 6});
Feature overrides allow you to override feature flags and their configurations locally. This is particularly useful for development and testing. You can specify overrides in three ways:
1BUCKET_FEATURES_ENABLED=feature1,feature2 2BUCKET_FEATURES_DISABLED=feature3,feature4
bucketConfig.json
:1{ 2 "featureOverrides": { 3 "delete-todos": { 4 "isEnabled": true, 5 "config": { 6 "key": "dev-config", 7 "payload": { 8 "requireConfirmation": true, 9 "maxDeletionsPerDay": 5 10 } 11 } 12 } 13 } 14}
1import { BucketClient, Context } from "@bucketco/node-sdk"; 2 3const featureOverrides = (context: Context) => ({ 4 "delete-todos": { 5 isEnabled: true, 6 config: { 7 key: "dev-config", 8 payload: { 9 requireConfirmation: true, 10 maxDeletionsPerDay: 5, 11 }, 12 }, 13 }, 14}); 15 16const client = new BucketClient({ 17 featureOverrides, 18});
In addition to local feature evaluation, Bucket supports remote evaluation using stored context. This is useful when you want to evaluate features using user/company attributes that were previously sent to Bucket:
1// First, update user and company attributes 2await client.updateUser("user123", { 3 attributes: { 4 role: "admin", 5 subscription: "premium", 6 }, 7}); 8 9await client.updateCompany("company456", { 10 attributes: { 11 tier: "enterprise", 12 employees: 1000, 13 }, 14}); 15 16// Later, evaluate features remotely using stored context 17const features = await client.getFeaturesRemote("company456", "user123"); 18// Or evaluate a single feature 19const feature = await client.getFeatureRemote( 20 "create-todos", 21 "company456", 22 "user123", 23); 24 25// You can also provide additional context 26const featuresWithContext = await client.getFeaturesRemote( 27 "company456", 28 "user123", 29 { 30 other: { 31 location: "US", 32 platform: "mobile", 33 }, 34 }, 35);
Remote evaluation is particularly useful when:
A popular way to integrate the Bucket Node.js SDK is through an express middleware.
1import bucket from "./bucket"; 2import express from "express"; 3import { BoundBucketClient } from "@bucketco/node-sdk"; 4 5// Augment the Express types to include a `boundBucketClient` property on the 6// `res.locals` object. 7// This will allow us to access the BucketClient instance in our route handlers 8// without having to pass it around manually 9declare global { 10 namespace Express { 11 interface Locals { 12 boundBucketClient: BoundBucketClient; 13 } 14 } 15} 16 17// Add express middleware 18app.use((req, res, next) => { 19 // Extract the user and company IDs from the request 20 // You'll want to use a proper authentication and identification 21 // mechanism in a real-world application 22 const user = { 23 id: req.user?.id, 24 name: req.user?.name 25 email: req.user?.email 26 } 27 28 const company = { 29 id: req.user?.companyId 30 name: req.user?.companyName 31 } 32 33 // Create a new BoundBucketClient instance by calling the `bindClient` 34 // method on a `BucketClient` instance 35 // This will create a new instance that is bound to the user/company given. 36 const boundBucketClient = bucket.bindClient({ user, company }); 37 38 // Store the BoundBucketClient instance in the `res.locals` object so we 39 // can access it in our route handlers 40 res.locals.boundBucketClient = boundBucketClient; 41 next(); 42}); 43 44// Now use res.locals.boundBucketClient in your handlers 45app.get("/todos", async (_req, res) => { 46 const { track, isEnabled } = res.locals.bucketUser.getFeature("show-todos"); 47 48 if (!isEnabled) { 49 res.status(403).send({"error": "feature inaccessible"}) 50 return 51 } 52 53 ... 54}
See example/app.ts for a full example.
If you don't want to provide context each time when evaluating feature flags but
rather you would like to utilize the attributes you sent to Bucket previously
(by calling updateCompany
and updateUser
) you can do so by calling getFeaturesRemote
(or getFeatureRemote
for a specific feature) with providing just userId
and companyId
.
These methods will call Bucket's servers and feature flags will be evaluated remotely
using the stored attributes.
1// Update user and company attributes 2client.updateUser("john_doe", { 3 attributes: { 4 name: "John O.", 5 role: "admin", 6 }, 7}); 8 9client.updateCompany("acme_inc", { 10 attributes: { 11 name: "Acme, Inc", 12 tier: "premium" 13 }, 14}); 15... 16 17// This will evaluate feature flags with respecting the attributes sent previously 18const features = await client.getFeaturesRemote("acme_inc", "john_doe");
[!IMPORTANT] User and company attribute updates are processed asynchronously, so there might be a small delay between when attributes are updated and when they are available for evaluation.
There are use cases in which you not want to be sending user
, company
and
track
events to Bucket.co. These are usually cases where you could be impersonating
another user in the system and do not want to interfere with the data being
collected by Bucket.
To disable tracking, bind the client using bindClient()
as follows:
1// binds the client to a given user and company and set `enableTracking` to `false`. 2const boundClient = client.bindClient({ user, company, enableTracking: false }); 3 4boundClient.track("some event"); // this will not actually send the event to Bucket. 5 6// the following code will not update the `user` nor `company` in Bucket and will 7// not send `track` events either. 8const { isEnabled, track } = boundClient.getFeature("user-menu"); 9if (isEnabled) { 10 track(); 11}
Another way way to disable tracking without employing a bound client is to call getFeature()
or getFeatures()
by supplying enableTracking: false
in the arguments passed to
these functions.
[!IMPORTANT] Note, however, that calling
track()
,updateCompany()
orupdateUser()
in theBucketClient
will still send tracking data. As such, it is always recommended to usebindClient()
when using this SDK.
It is highly recommended that users of this SDK manually call flush()
method on process shutdown. The SDK employs a batching technique to minimize
the number of calls that are sent to Bucket's servers. During process shutdown,
some messages could be waiting to be sent, and thus, would be discarded if the
buffer is not flushed.
By default, the SDK automatically subscribes to process exit signals and attempts to flush
any pending events. This behavior is controlled by the flushOnExit
option in the client configuration:
1const client = new BucketClient({ 2 batchOptions: { 3 flushOnExit: false, // disable automatic flushing on exit 4 }, 5});
[!NOTE] If you are creating multiple client instances in your application, it's recommended to disable
flushOnExit
to avoid potential conflicts during process shutdown. In such cases, you should implement your own flush handling.
When you bind a client to a user/company, this data is matched against the targeting rules. To get accurate targeting, you must ensure that the user/company information provided is sufficient to match against the targeting rules you've created. The user/company data is automatically transferred to Bucket. This ensures that you'll have up-to-date information about companies and users and accurate targeting information available in Bucket at all time.
Tracking allows events and updating user/company attributes in Bucket. For example, if a customer changes their plan, you'll want Bucket to know about it, in order to continue to provide up-do-date targeting information in the Bucket interface.
The following example shows how to register a new user, associate it with a company and finally update the plan they are on.
1// registers the user with Bucket using the provided unique ID, and 2// providing a set of custom attributes (can be anything) 3client.updateUser("user_id", { 4 attributes: { longTimeUser: true, payingCustomer: false }, 5}); 6client.updateCompany("company_id", { userId: "user_id" }); 7 8// the user started a voice huddle 9client.track("user_id", "huddle", { attributes: { voice: true } });
It's also possible to achieve the same through a bound client in the following manner:
1const boundClient = client.bindClient({ 2 user: { id: "user_id", longTimeUser: true, payingCustomer: false }, 3 company: { id: "company_id" }, 4}); 5 6boundClient.track("huddle", { attributes: { voice: true } });
Some attributes are used by Bucket to improve the UI, and are recommended to provide for easier navigation:
name
-- display name for user
/company
,email
-- the email of the user,avatar
-- the URL for user
/company
avatar image.Attributes cannot be nested (multiple levels) and must be either strings, integers or booleans.
Last seen
By default updateUser
/updateCompany
calls automatically update the given
user/company Last seen
property on Bucket servers.
You can control if Last seen
should be updated when the events are sent by setting
meta.active = false
. This is often useful if you
have a background job that goes through a set of companies just to update their
attributes but not their activity.
Example:
1client.updateUser("john_doe", { 2 attributes: { name: "John O." }, 3 meta: { active: true }, 4}); 5 6client.updateCompany("acme_inc", { 7 attributes: { name: "Acme, Inc" }, 8 meta: { active: false }, 9});
bindClient()
updates attributes on the Bucket servers but does not automatically
update Last seen
.
The Bucket SDK doesn't collect any metadata and HTTP IP addresses are not being stored. For tracking individual users, we recommend using something like database ID as userId, as it's unique and doesn't include any PII (personal identifiable information). If, however, you're using e.g. email address as userId, but prefer not to send any PII to Bucket, you can hash the sensitive data before sending it to Bucket:
1import { sha256 } from 'crypto-hash'; 2 3client.updateUser({ userId: await sha256("john_doe"), ... });
Types are bundled together with the library and exposed automatically when importing through a package manager.
MIT License Copyright (c) 2025 Bucket ApS
No vulnerabilities found.
No security vulnerabilities found.