Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Browser SDK for use in non-React web applications
## Node.js SDK

Node.js SDK for use on the server side.
Use this for Cloudflare Workers as well.

[Read the docs](packages/node-sdk/README.md)

Expand Down
77 changes: 51 additions & 26 deletions packages/node-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Bucket supports feature toggling, tracking feature usage, collecting feedback on

## Installation

Install using your favourite package manager:
Install using your favorite package manager:

{% tabs %}
{% tab title="npm" %}
Expand Down Expand Up @@ -207,6 +207,45 @@ const featureDefs = await client.getFeatureDefinitions();
// }]
```

## Edge-runtimes like Cloudflare Workers

To use the Bucket NodeSDK with Cloudflare workers, set the `node_compat` flag [in your wrangler file](https://developers.cloudflare.com/workers/runtime-apis/nodejs/#get-started).

Instead of using `BucketClient`, use `EdgeClient` and make sure you call `ctx.waitUntil(bucket.flush());` before returning from your worker function.

```typescript
import { EdgeClient } from "@bucketco/node-sdk";

// set the BUCKET_SECRET_KEY environment variable or pass the secret key in the constructor
const bucket = new EdgeClient();

export default {
async fetch(request, _env, ctx): Promise<Response> {
// initialize the client and wait for it to complete
// if the client was initialized on a previous invocation, this is a no-op.
await bucket.initialize();
const features = bucket.getFeatures({
user: { id: "userId" },
company: { id: "companyId" },
});

// ensure all events are flushed and any requests to refresh the feature cache
// have completed after the response is sent
ctx.waitUntil(bucket.flush());

return new Response(
`Features for user ${userId} and company ${companyId}: ${JSON.stringify(features, null, 2)}`,
);
},
};
```

See [examples/cloudflare-worker](examples/cloudflare-worker/src/index.ts) for a deployable example.

Bucket maintains a cached set of feature definitions in the memory of your worker which it uses to decide which features to turn on for which users/companies.

The SDK caches feature definitions in memory for fast performance. The first request to a new worker instance fetches definitions from Bucket's servers, while subsequent requests use the cache. When the cache expires, it's updated in the background. `ctx.waitUntil(bucket.flush())` ensures completion of the background work, so response times are not affected. This background work may increase wall-clock time for your worker, but it will not measurably increase billable CPU time on platforms like Cloudflare.

## Error Handling

The SDK is designed to fail gracefully and never throw exceptions to the caller. Instead, it logs errors and provides
Expand Down Expand Up @@ -307,14 +346,14 @@ 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](https://github.com/bucketco/bucket-javascript-sdk/tree/main/packages/browser-sdk/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 |
| 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 [examples/express/app.test.ts](https://github.com/bucketco/bucket-javascript-sdk/tree/main/packages/node-sdk/examples/express/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` and `BUCKET_FEATURES_DISABLED` are comma separated lists of features which will be enabled or disabled respectively.

Expand Down Expand Up @@ -616,7 +655,7 @@ app.get("/todos", async (_req, res) => {
}
```

See [example/app.ts](https://github.com/bucketco/bucket-javascript-sdk/tree/main/packages/node-sdk/example/app.ts) for a full example.
See [examples/express/app.ts](https://github.com/bucketco/bucket-javascript-sdk/tree/main/packages/node-sdk/example/express/app.ts) for a full example.

## Remote flag evaluation with stored context

Expand Down Expand Up @@ -687,11 +726,8 @@ these functions.

## Flushing

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.
BucketClient employs a batching technique to minimize the number of calls that are sent to
Bucket's servers.

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:
Expand All @@ -704,17 +740,6 @@ const client = new BucketClient({
});
```

> [!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 custom events and setting custom attributes

Tracking allows events and updating user/company attributes in Bucket.
Expand Down
2 changes: 1 addition & 1 deletion packages/node-sdk/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
const base = require("@bucketco/eslint-config");

module.exports = [...base, { ignores: ["dist/", "example/"] }];
module.exports = [...base, { ignores: ["dist/", "examples/"] }];
172 changes: 172 additions & 0 deletions packages/node-sdk/examples/cloudflare-worker/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Logs

logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)

report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# Runtime data

pids
_.pid
_.seed
\*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover

lib-cov

# Coverage directory used by tools like istanbul

coverage
\*.lcov

# nyc test coverage

.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)

.grunt

# Bower dependency directory (https://bower.io/)

bower_components

# node-waf configuration

.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)

build/Release

# Dependency directories

node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)

web_modules/

# TypeScript cache

\*.tsbuildinfo

# Optional npm cache directory

.npm

# Optional eslint cache

.eslintcache

# Optional stylelint cache

.stylelintcache

# Microbundle cache

.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history

.node_repl_history

# Output of 'npm pack'

\*.tgz

# Yarn Integrity file

.yarn-integrity

# dotenv environment variable files

.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)

.cache
.parcel-cache

# Next.js build output

.next
out

# Nuxt.js build / generate output

.nuxt
dist

# Gatsby files

.cache/

# Comment in the public line in if your project uses Gatsby and not Next.js

# https://nextjs.org/blog/next-9-1#public-directory-support

# public

# vuepress build output

.vuepress/dist

# vuepress v2.x temp and cache directory

.temp
.cache

# Docusaurus cache and generated files

.docusaurus

# Serverless directories

.serverless/

# FuseBox cache

.fusebox/

# DynamoDB Local files

.dynamodb/

# TernJS port file

.tern-port

# Stores VSCode versions used for testing VSCode extensions

.vscode-test

# yarn v2

.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*

# wrangler project

.dev.vars
.wrangler/
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
worker-configuration.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"files.associations": {
"wrangler.json": "jsonc"
}
}
10 changes: 10 additions & 0 deletions packages/node-sdk/examples/cloudflare-worker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# cloudflare-worker

This is a simple example of how to use the Bucket SDK in a Cloudflare Worker.
It demonstrates how to initialize the client and evaluate feature flags.
It also shows how to flush the client and wait for any in-flight requests to complete.

- Set the BUCKET_SECRET_KEY environment variable in wrangler.jsonc to get started.
- Run `yarn dev` in your terminal to start a development server
- Open a browser tab at http://localhost:8787/ to see your worker in action
- Run `yarn run deploy` to publish your worker
18 changes: 18 additions & 0 deletions packages/node-sdk/examples/cloudflare-worker/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "cloudflare-worker",
"version": "0.0.0",
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"test": "vitest",
"cf-typegen": "wrangler types"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.8.19",
"typescript": "^5.5.2",
"vitest": "~3.2.0",
"wrangler": "^4.20.5"
}
}
42 changes: 42 additions & 0 deletions packages/node-sdk/examples/cloudflare-worker/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* This is a simple example of how to use the Bucket SDK in a Cloudflare Worker.
* It demonstrates how to initialize the client and evaluate feature flags.
* It also shows how to flush the client and wait for any in-flight requests to complete.
*
* Set the BUCKET_SECRET_KEY environment variable in wrangler.jsonc to get started.
*
* - Run `yarn run dev` in your terminal to start a development server
* - Open a browser tab at http://localhost:8787/ to see your worker in action
* - Run `yarn run deploy` to publish your worker
*
*/

import { EdgeClient } from "../../../";

// set the BUCKET_SECRET_KEY environment variable or pass the secret key in the constructor
const bucket = new EdgeClient();

export default {
async fetch(request, _env, ctx): Promise<Response> {
// initialize the client and wait for it to complete
// this is not required for the edge client, but is included for completeness
await bucket.initialize();

const url = new URL(request.url);
const userId = url.searchParams.get("user.id");
const companyId = url.searchParams.get("company.id");

const f = bucket.getFeatures({
user: { id: userId ?? undefined },
company: { id: companyId ?? undefined },
});

// ensure all events are flushed and any requests to refresh the feature cache
// have completed after the response is sent
ctx.waitUntil(bucket.flush());

return new Response(
`Features for user ${userId} and company ${companyId}: ${JSON.stringify(f, null, 2)}`,
);
},
} satisfies ExportedHandler<Env>;
Loading
Loading