Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ffecd3d
feat(node-sdk): add push sync mode for flag updates
roncohen Mar 27, 2026
3855b44
prettier
roncohen Mar 29, 2026
681b4e2
feat(node-sdk): add push sync channels + workspace express example
roncohen Apr 2, 2026
65f1631
fix(node-sdk): use explicit push channel names
roncohen Apr 3, 2026
45a293d
Merge branch 'main' into feat/node-sdk-flags-sync-mode-push
roncohen Apr 3, 2026
63b6379
refactor(node-sdk): split flags cache from refreshers
roncohen Apr 3, 2026
b888124
docs(changeset): describe flags sync modes
roncohen Apr 3, 2026
83c1e3f
chore(node-sdk): clarify push channel naming
roncohen Apr 3, 2026
a50a771
docs(node-sdk): update sync mode descriptions
roncohen Apr 3, 2026
71b1c3c
chore(node-sdk-example): rename bucket example files
roncohen Apr 3, 2026
3889c52
refactor(node-sdk): rename sync controller and handle background refr…
roncohen Apr 3, 2026
20a67b0
Merge branch 'main' into feat/node-sdk-flags-sync-mode-push
roncohen Apr 5, 2026
f761be1
chore(node-sdk): increase push reconnect delay
roncohen Apr 5, 2026
5ad1409
docs(node-sdk): format README
roncohen Apr 6, 2026
0cae1fc
style(node-sdk): sort imports and exports
roncohen Apr 6, 2026
db2325e
refactor(node-sdk): simplify flags cache refresh coordination
roncohen Apr 6, 2026
29c5567
refactor(node-sdk): throttle flags cache refreshes
roncohen Apr 6, 2026
d5897f4
refactor(node-sdk): simplify throttled refresh scheduling
roncohen Apr 6, 2026
ef00764
fix(node-sdk): guard SSE timer unref
roncohen Apr 6, 2026
eeb75e9
Merge branch 'main' into feat/node-sdk-flags-sync-mode-push
roncohen Apr 7, 2026
c2cfc6a
fix(node-sdk): keep flagStateVersion backward compatible
roncohen Apr 7, 2026
23a09e1
Merge branch 'main' into feat/node-sdk-flags-sync-mode-push
roncohen Apr 7, 2026
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
8 changes: 8 additions & 0 deletions .changeset/dull-deserts-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@reflag/openfeature-node-provider": minor
"@reflag/node-sdk": minor
---

Add a new `flagsSyncMode` option to the Node SDK with three sync strategies: `polling`, `in-request`, and `push`.

`polling` keeps the existing periodic background refresh behavior, `in-request` refreshes stale flag definitions during request handling, and `push` subscribes to live flag updates over SSE. The new `push` mode lets applications receive flag definition updates immediately as they happen without relying on periodic polling.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"packages/management-sdk/examples/*",
"packages/react-sdk/dev/*",
"packages/react-native-sdk/dev/*",
"packages/openfeature-browser-provider/example"
"packages/openfeature-browser-provider/example",
"packages/node-sdk/examples/express"
],
"scripts": {
"changeset": "changeset",
Expand Down
28 changes: 17 additions & 11 deletions packages/node-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,10 @@ and downloads the flags with their targeting rules.
These rules are then matched against the user/company information you provide
to `getFlags()` (or through `bindClient(..).getFlags()`). That means the
`getFlags()` call does not need to contact the Reflag servers once
`initialize()` has completed. `ReflagClient` will continue to periodically
download the targeting rules from the Reflag servers in the background.
`initialize()` has completed. By default, `ReflagClient` will continue to
refresh the targeting rules from the Reflag servers in the background. You can
change this behavior with `flagsSyncMode` to use request-driven refreshes or
push-based updates instead.

### Batch Operations

Expand Down Expand Up @@ -459,6 +461,8 @@ Reflag maintains a cached set of flag definitions in the memory of your worker w

The SDK caches flag definitions in memory for fast performance. The first request to a new worker instance fetches definitions from Reflag's servers, while subsequent requests use the cache. When the cache expires, it's updated in the background. `ctx.waitUntil(reflag.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.

`EdgeClient` uses `flagsSyncMode: "in-request"`. Refresh fetch starts are throttled to at most once per second, and Cloudflare Workers cannot rely on delayed timer callbacks to run follow-up refreshes later. That means `refreshFlags()` calls made during the throttle window only mark a refresh as pending, so the call itself may resolve before the fetch runs. The queued refresh runs on the next request/access or `refreshFlags()` call after the throttle window expires.

## 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 @@ -557,15 +561,17 @@ a configuration file on disk or by passing options to the `ReflagClient`
constructor. By default, the SDK searches for `reflag.config.json` in the
current working directory.

| Option | Type | Description | Env Var |
| ----------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
| `secretKey` | string | The secret key used for authentication with Reflag's servers. | REFLAG_SECRET_KEY |
| `logLevel` | string | The log level for the SDK (e.g., `"DEBUG"`, `"INFO"`, `"WARN"`, `"ERROR"`). Default: `INFO` | REFLAG_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. In offline mode the SDK does not fetch from Reflag and does not use `flagsFallbackProvider`. | REFLAG_OFFLINE |
| `apiBaseUrl` | string | The base API URL for the Reflag servers. | REFLAG_API_BASE_URL |
| `flagOverrides` | Record<string, boolean> | An object specifying flag overrides for testing or local development. See [examples/express/app.test.ts](https://github.com/reflagcom/javascript/tree/main/packages/node-sdk/examples/express/app.test.ts) for how to use `flagOverrides` in tests. | REFLAG_FLAGS_ENABLED, REFLAG_FLAGS_DISABLED |
| `flagsFallbackProvider` | `FlagsFallbackProvider` | Optional provider used to load and save raw flag definitions for fallback startup when the initial live fetch fails. Available only through the constructor. Ignored in offline mode. | - |
| `configFile` | string | Load this config file from disk. Default: `reflag.config.json` | REFLAG_CONFIG_FILE |
| Option | Type | Description | Env Var |
| ----------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
| `secretKey` | string | The secret key used for authentication with Reflag's servers. | REFLAG_SECRET_KEY |
| `logLevel` | string | The log level for the SDK (e.g., `"DEBUG"`, `"INFO"`, `"WARN"`, `"ERROR"`). Default: `INFO` | REFLAG_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. In offline mode the SDK does not fetch from Reflag and does not use `flagsFallbackProvider`. | REFLAG_OFFLINE |
| `apiBaseUrl` | string | The base API URL for the Reflag servers. | REFLAG_API_BASE_URL |
| `flagOverrides` | Record<string, boolean> | An object specifying flag overrides for testing or local development. See [examples/express/app.test.ts](https://github.com/reflagcom/javascript/tree/main/packages/node-sdk/examples/express/app.test.ts) for how to use `flagOverrides` in tests. | REFLAG_FLAGS_ENABLED, REFLAG_FLAGS_DISABLED |
| `flagsFallbackProvider` | `FlagsFallbackProvider` | Optional provider used to load and save raw flag definitions for fallback startup when the initial live fetch fails. Available only through the constructor. Ignored in offline mode. | - |
| `flagsSyncMode` | `"polling" \| "in-request" \| "push"` | Flag-definition sync mode. `polling` uses periodic background refresh, `in-request` refreshes stale flags during request handling, and `push` subscribes to live updates. Default: `"polling"`. | - |
| `flagsPushUrl` | string | Push endpoint used when `flagsSyncMode: "push"`. Default: `https://pubsub.reflag.com/sse`. | - |
| `configFile` | string | Load this config file from disk. Default: `reflag.config.json` | REFLAG_CONFIG_FILE |

> [!NOTE]
>
Expand Down
2 changes: 1 addition & 1 deletion packages/node-sdk/examples/express/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ of the SDK:
```sh
yarn install

REFLAG_SECRET_KEY=<secretKey> yarn start
REFLAG_SECRET_KEY=<secretKey> yarn workspace @reflag/node-sdk-example-express start
```
2 changes: 1 addition & 1 deletion packages/node-sdk/examples/express/app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import reflag from "./reflag";
import express from "express";
import type { BoundReflagClient } from "../../src";
import type { BoundReflagClient } from "@reflag/node-sdk";

// Augment the Express types to include the `reflagUser` property on the `res.locals` object
// This will allow us to access the ReflagClient instance in our route handlers
Expand Down
43 changes: 0 additions & 43 deletions packages/node-sdk/examples/express/bucket.ts

This file was deleted.

6 changes: 0 additions & 6 deletions packages/node-sdk/examples/express/bucketConfig.json

This file was deleted.

4 changes: 3 additions & 1 deletion packages/node-sdk/examples/express/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "example",
"name": "@reflag/node-sdk-example-express",
"private": true,
"packageManager": "yarn@4.10.3",
"scripts": {
"start": "tsx serve.ts",
Expand All @@ -8,6 +9,7 @@
"type": "commonjs",
"main": "serve.ts",
"dependencies": {
"@reflag/node-sdk": "workspace:*",
"express": "^4.20.0",
"tsx": "^4.16.2",
"typescript": "^5.5.3",
Expand Down
45 changes: 44 additions & 1 deletion packages/node-sdk/examples/express/reflag.ts
Original file line number Diff line number Diff line change
@@ -1 +1,44 @@
export { default } from "./bucket";
import { ReflagClient, Context, FlagOverrides } from "@reflag/node-sdk";

type CreateConfigPayload = {
minimumLength: number;
};

// Extending the Flags interface to define the available features
declare module "@reflag/node-sdk" {
interface Flags {
"show-todos": boolean;
"create-todos": {
config: {
payload: CreateConfigPayload;
};
};
"delete-todos": boolean;
"some-else": {};
}
}

const flagOverrides = (_: Context): FlagOverrides => {
return {
"create-todos": {
isEnabled: true,
config: {
key: "short",
payload: {
minimumLength: 10,
},
},
},
}; // feature keys checked at compile time
};

// Create a new ReflagClient instance with the secret key and default features
// The default features will be used if the user does not have any features set
// Create a reflag.config.json file to configure the client or set environment variables
// like REFLAG_SECRET_KEY, REFLAG_FLAGS_ENABLED, REFLAG_FLAGS_DISABLED, etc.
export default new ReflagClient({
// Optional: Set a logger to log debug information, errors, etc.
// logger: console,
flagOverrides, // Optional: Set flag overrides
flagsSyncMode: "push",
});
Loading
Loading