Skip to content

Commit 63f5b25

Browse files
committed
Merge main into feat/node-sdk-flags-fallback-provider
2 parents be5dfac + 2d7c7f0 commit 63f5b25

17 files changed

Lines changed: 385 additions & 86 deletions

File tree

‎.changeset/fair-badgers-move.md‎

Lines changed: 0 additions & 4 deletions
This file was deleted.

‎.github/workflows/package-ci.yml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ permissions:
55
checks: write
66
contents: read
77

8-
on: [push]
8+
on:
9+
push:
10+
pull_request:
911

1012
jobs:
1113
build-and-test:

‎.github/workflows/publish.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
uses: changesets/action@v1
3333
with:
3434
version: yarn version-packages
35-
publish: yarn build && yarn release
35+
publish: yarn release-packages
3636
title: Version Packages
3737
commit: Version Packages
3838
env:

‎package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
"prettier:fix": "lerna run prettier -- --write",
2222
"lint": "lerna run lint --stream",
2323
"lint:ci": "lerna run lint:ci --stream",
24-
"version-packages": "changeset version",
24+
"version-packages": "changeset version && yarn install",
25+
"release-packages": "yarn build && yarn release",
2526
"release": "changeset publish",
2627
"docs": "./docs.sh"
2728
},

‎packages/node-sdk/CHANGELOG.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# @reflag/node-sdk
2+
3+
## 1.3.0
4+
5+
### Minor Changes
6+
7+
- e9920bc: improve flag override API for testing

‎packages/node-sdk/README.md‎

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -640,14 +640,32 @@ reflagClient.initialize().then(() => {
640640

641641
## Testing
642642

643-
When writing tests that cover code with flags, you can toggle flags on/off programmatically to test the different behavior.
643+
When writing tests that cover code with flags, you can toggle flags on/off programmatically to test different behavior. For tests, you will often want to run the client in offline mode and provide flag overrides directly through the client options.
644644

645645
`reflag.ts`:
646646

647647
```typescript
648648
import { ReflagClient } from "@reflag/node-sdk";
649649

650-
export const reflag = new ReflagClient();
650+
export const reflag = new ReflagClient({
651+
offline: true,
652+
});
653+
```
654+
655+
You can then set base overrides for a test run by passing `flagOverrides` in the constructor, replacing them later with `setFlagOverrides()`, or clearing them with `clearFlagOverrides()`:
656+
657+
```typescript
658+
// pass directly in the constructor
659+
const client = new ReflagClient({
660+
offline: true,
661+
flagOverrides: { myFlag: true },
662+
});
663+
664+
// or replace the base overrides at a later time
665+
client.setFlagOverrides({ myFlag: false });
666+
667+
// clear only the base overrides
668+
client.clearFlagOverrides();
651669
```
652670

653671
`app.test.ts`:
@@ -662,9 +680,9 @@ afterEach(() => {
662680

663681
describe("API Tests", () => {
664682
it("should return 200 for the root endpoint", async () => {
665-
reflag.flagOverrides = {
683+
reflag.setFlagOverrides({
666684
"show-todo": true,
667-
};
685+
});
668686

669687
const response = await request(app).get("/");
670688
expect(response.status).toBe(200);
@@ -673,11 +691,61 @@ describe("API Tests", () => {
673691
});
674692
```
675693

676-
See more on flag overrides in the section below.
694+
`pushFlagOverrides()` serves a different purpose: it adds a temporary layer on top of the base overrides and returns a remove function that removes only that layer. This is useful for nested tests:
695+
696+
```typescript
697+
export const flag = function (name: string, enabled: boolean): void {
698+
let remove: (() => void) | undefined;
699+
700+
beforeEach(function () {
701+
remove = reflagClient.pushFlagOverrides({ [name]: enabled });
702+
});
703+
704+
afterEach(function () {
705+
remove?.();
706+
remove = undefined;
707+
});
708+
};
709+
710+
describe("foo", () => {
711+
describe("with new search ranking enabled", () => {
712+
flag("search-ranking-v2", true);
713+
714+
describe("with summaries enabled", () => {
715+
flag("smart-summaries", true);
716+
717+
// ...
718+
});
719+
});
720+
});
721+
```
722+
723+
The precedence is:
724+
725+
1. Base overrides from the constructor or `setFlagOverrides()`
726+
2. Temporary layers added by `pushFlagOverrides()`
727+
728+
If the same flag is set in both places, the pushed override wins until its remove function is called.
729+
730+
`pushFlagOverrides()` also accepts a function if the temporary override depends on the evaluation context:
731+
732+
```typescript
733+
const remove = client.pushFlagOverrides((context) => ({
734+
"smart-summaries": context.user?.id === "qa-user",
735+
}));
736+
737+
// ...
738+
739+
remove();
740+
```
677741

678742
## Flag Overrides
679743

680-
Flag overrides allow you to override flags and their configurations locally. This is particularly useful for development and testing. You can specify overrides in three ways:
744+
Flag overrides allow you to override flags and their configurations locally. This is particularly useful when testing changes locally, for example when running your app and clicking around to verify behavior before deploying your changes.
745+
746+
For automated tests, see the [Testing](#testing) section above.
747+
748+
When testing locally during development, you also have these additional ways to provide overrides:
681749

682750
1. Through environment variables:
683751

@@ -705,20 +773,6 @@ REFLAG_FLAGS_DISABLED=flag3,flag4
705773
}
706774
```
707775

708-
1. Programmatically through the client options:
709-
710-
You can use a simple `Record<string, boolean>` and pass it either in the constructor or by setting `client.flagOverrides`:
711-
712-
```typescript
713-
// pass directly in the constructor
714-
const client = new ReflagClient({ flagOverrides: { myFlag: true } });
715-
// or set on the client at a later time
716-
client.flagOverrides = { myFlag: false };
717-
718-
// clear flag overrides. Same as setting to {}.
719-
client.clearFlagOverrides();
720-
```
721-
722776
To get dynamic overrides, use a function which takes a context and returns a boolean or an object with the shape of `{isEnabled, config}`:
723777

724778
```typescript
Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,32 @@
11
import request from "supertest";
22
import app, { todos } from "./app";
3-
import { beforeEach, describe, it, expect, beforeAll } from "vitest";
3+
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
44

55
import reflag from "./reflag";
66

7+
function flag(name: string, enabled: boolean): void {
8+
let remove: (() => void) | undefined;
9+
10+
beforeEach(() => {
11+
remove = reflag.pushFlagOverrides({ [name]: enabled });
12+
});
13+
14+
afterEach(() => {
15+
remove?.();
16+
remove = undefined;
17+
});
18+
}
19+
720
beforeAll(async () => await reflag.initialize());
21+
822
beforeEach(() => {
9-
reflag.featureOverrides = {
23+
reflag.setFlagOverrides({
1024
"show-todos": true,
11-
};
25+
});
26+
});
27+
28+
afterEach(() => {
29+
reflag.clearFlagOverrides();
1230
});
1331

1432
describe("API Tests", () => {
@@ -24,12 +42,13 @@ describe("API Tests", () => {
2442
expect(response.body).toEqual({ todos });
2543
});
2644

27-
it("should return no todos when list is disabled", async () => {
28-
reflag.featureOverrides = () => ({
29-
"show-todos": false,
45+
describe("with show-todos temporarily disabled", () => {
46+
flag("show-todos", false);
47+
48+
it("should return no todos", async () => {
49+
const response = await request(app).get("/todos");
50+
expect(response.status).toBe(200);
51+
expect(response.body).toEqual({ todos: [] });
3052
});
31-
const response = await request(app).get("/todos");
32-
expect(response.status).toBe(200);
33-
expect(response.body).toEqual({ todos: [] });
3453
});
3554
});

‎packages/node-sdk/examples/express/app.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import reflag from "./reflag";
22
import express from "express";
3-
import { BoundReflagClient } from "../src";
3+
import type { BoundReflagClient } from "../../src";
44

55
// Augment the Express types to include the `reflagUser` property on the `res.locals` object
66
// This will allow us to access the ReflagClient instance in our route handlers

‎packages/node-sdk/examples/express/bucket.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { ReflagClient, Context, FlagOverrides } from "../../";
1+
import { ReflagClient, Context, FlagOverrides } from "../../src";
22

33
type CreateConfigPayload = {
44
minimumLength: number;
55
};
66

77
// Extending the Flags interface to define the available features
8-
declare module "../../types" {
8+
declare module "../../src/types" {
99
interface Flags {
1010
"show-todos": boolean;
1111
"create-todos": {
@@ -18,7 +18,7 @@ declare module "../../types" {
1818
}
1919
}
2020

21-
let featureOverrides = (_: Context): FlagOverrides => {
21+
const flagOverrides = (_: Context): FlagOverrides => {
2222
return {
2323
"create-todos": {
2424
isEnabled: true,
@@ -39,5 +39,5 @@ let featureOverrides = (_: Context): FlagOverrides => {
3939
export default new ReflagClient({
4040
// Optional: Set a logger to log debug information, errors, etc.
4141
logger: console,
42-
featureOverrides, // Optional: Set feature overrides
42+
flagOverrides, // Optional: Set flag overrides
4343
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from "./bucket";

0 commit comments

Comments
 (0)