Skip to content

Commit

Permalink
feat: added error strategy for node and browser clients (#745)
Browse files Browse the repository at this point in the history
  • Loading branch information
erka authored Feb 25, 2025
1 parent 1a4b371 commit 4e57bc1
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 25 deletions.
8 changes: 8 additions & 0 deletions flipt-client-browser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ The `FliptEvaluationClient` constructor accepts two optional arguments:
- `authentication`: The authentication strategy to use when communicating with the upstream Flipt instance. If not provided, the client will default to no authentication. See the [Authentication](#authentication) section for more information.
- `reference`: The [reference](https://docs.flipt.io/guides/user/using-references) to use when fetching flag state. If not provided, reference will not be used.
- `fetcher`: An implementation of a [fetcher](https://github.com/flipt-io/flipt-client-sdks/blob/4821cb227c6c8b10419b96674d44ad1d6668a647/flipt-client-browser/src/models.ts#L5) interface to use when requesting flag state. If not provided, a default fetcher using the browser's [`fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) will be used.
- `errorStrategy`: The error strategy to use when fetching flag state. If not provided, the client will default to fail. See the [Error Strategies](#error-strategies) section for more information.

### Authentication

Expand All @@ -59,6 +60,13 @@ The `FliptEvaluationClient` supports the following authentication strategies:
- [Client Token Authentication](https://docs.flipt.io/authentication/using-tokens)
- [JWT Authentication](https://docs.flipt.io/authentication/using-jwts)

### Error Strategies

The client `errorStrategy` option supports the following error strategies:

- `fail`: The client will throw an error if the flag state cannot be fetched. This is the default behavior.
- `fallback`: The client will maintain the last known good state and use that state for evaluation in case of an error.

### Custom Fetcher

The `FliptEvaluationClient` supports custom fetchers. This allows you to fetch flag state from a custom source or override HTTP headers.
Expand Down
35 changes: 23 additions & 12 deletions flipt-client-browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
EvaluationResponse,
Flag,
IFetcher,
VariantEvaluationResponse
VariantEvaluationResponse,
ErrorStrategy
} from './models';

import {
Expand All @@ -24,6 +25,7 @@ export class FliptEvaluationClient {
private engine: Engine;
private fetcher: IFetcher;
private etag?: string;
private errorStrategy?: ErrorStrategy;

private constructor(engine: Engine, fetcher: IFetcher) {
this.engine = engine;
Expand All @@ -40,7 +42,8 @@ export class FliptEvaluationClient {
namespace: string = 'default',
options: ClientOptions = {
url: 'http://localhost:8080',
reference: ''
reference: '',
errorStrategy: ErrorStrategy.Fail
}
): Promise<FliptEvaluationClient> {
await init(await wasm());
Expand Down Expand Up @@ -104,6 +107,7 @@ export class FliptEvaluationClient {
engine.snapshot(data);
const client = new FliptEvaluationClient(engine, fetcher);
client.storeEtag(resp);
client.errorStrategy = options.errorStrategy;
return client;
}

Expand All @@ -122,19 +126,26 @@ export class FliptEvaluationClient {
* @returns true if snapshot changed
*/
public async refresh(): Promise<boolean> {
const opts = { etag: this.etag };
const resp = await this.fetcher(opts);
try {
const opts = { etag: this.etag };
const resp = await this.fetcher(opts);

let etag = resp.headers.get('etag');
if (this.etag && this.etag === etag) {
return false;
}
let etag = resp.headers.get('etag');
if (this.etag && this.etag === etag) {
return false;
}

this.storeEtag(resp);
this.storeEtag(resp);

const data = await resp.json();
this.engine.snapshot(data);
return true;
const data = await resp.json();
this.engine.snapshot(data);
return true;
} catch (error) {
if (this.errorStrategy === ErrorStrategy.Fail) {
throw error; // Re-throw the error
}
}
return false;
}

/**
Expand Down
15 changes: 15 additions & 0 deletions flipt-client-browser/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ export interface ClientOptions {
*/
reference?: string;
fetcher?: IFetcher;

/**
* The strategy to use for errors during refresh snapshot calls. {@link ErrorStrategy}
*/
errorStrategy?: ErrorStrategy;
}

/**
Expand Down Expand Up @@ -191,3 +196,13 @@ export interface BatchEvaluationResponse {
/** Duration of the request in milliseconds. */
requestDurationMillis: number;
}

/**
* Defines the strategy to handle errors during the flags snapshot update calls.
*/
export enum ErrorStrategy {
/** The client will throw an error if the flag state cannot be fetched. This is the default behavior. */
Fail = 'fail',
/** The client will maintain the last known good state and use that state for evaluation in case of an error. */
Fallback = 'fallback'
}
8 changes: 8 additions & 0 deletions flipt-client-node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ The `FliptEvaluationClient` constructor accepts two optional arguments:
- `updateInterval`: The interval (in seconds) in which to fetch new flag state. If not provided, the client will default to 120 seconds.
- `authentication`: The authentication strategy to use when communicating with the upstream Flipt instance. If not provided, the client will default to no authentication. See the [Authentication](#authentication) section for more information.
- `reference`: The [reference](https://docs.flipt.io/guides/user/using-references) to use when fetching flag state. If not provided, reference will not be used.
- `errorStrategy`: The error strategy to use when fetching flag state. If not provided, the client will default to fail. See the [Error Strategies](#error- strategies) section for more information.

### Authentication

Expand All @@ -62,6 +63,13 @@ The `FliptEvaluationClient` supports the following authentication strategies:
- [Client Token Authentication](https://docs.flipt.io/authentication/using-tokens)
- [JWT Authentication](https://docs.flipt.io/authentication/using-jwts)

### Error Strategies

The client `errorStrategy` option supports the following error strategies:

- `fail`: The client will throw an error if the flag state cannot be fetched. This is the default behavior.
- `fallback`: The client will maintain the last known good state and use that state for evaluation in case of an error.

### Custom Fetcher

The `FliptEvaluationClient` supports custom fetchers. This allows you to fetch flag state from a custom source or override HTTP headers.
Expand Down
36 changes: 23 additions & 13 deletions flipt-client-node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
BatchEvaluationResponse,
ErrorEvaluationResponse,
EvaluationResponse,
Flag
Flag,
ErrorStrategy
} from './models';

import {
Expand All @@ -26,6 +27,7 @@ export class FliptEvaluationClient {
private fetcher: IFetcher;
private etag?: string;
private updateInterval?: NodeJS.Timeout;
private errorStrategy?: ErrorStrategy;

private constructor(engine: Engine, fetcher: IFetcher) {
this.engine = engine;
Expand All @@ -43,7 +45,8 @@ export class FliptEvaluationClient {
options: ClientOptions<AuthenticationStrategy> = {
url: 'http://localhost:8080',
reference: '',
updateInterval: 120
updateInterval: 120,
errorStrategy: ErrorStrategy.Fail
}
): Promise<FliptEvaluationClient> {
let url = options.url ?? 'http://localhost:8080';
Expand Down Expand Up @@ -111,7 +114,7 @@ export class FliptEvaluationClient {
if (options.updateInterval && options.updateInterval > 0) {
client.startAutoRefresh(options.updateInterval * 1000);
}

client.errorStrategy = options.errorStrategy;
return client;
}

Expand Down Expand Up @@ -153,19 +156,26 @@ export class FliptEvaluationClient {
* @returns {boolean} true if snapshot changed
*/
public async refresh(): Promise<boolean> {
const opts = { etag: this.etag };
const resp = await this.fetcher(opts);
try {
const opts = { etag: this.etag };
const resp = await this.fetcher(opts);

let etag = resp.headers.get('etag');
if (this.etag && this.etag === etag) {
return false;
}
let etag = resp.headers.get('etag');
if (this.etag && this.etag === etag) {
return false;
}

this.storeEtag(resp);
this.storeEtag(resp);

const data = await resp.json();
this.engine.snapshot(data);
return true;
const data = await resp.json();
this.engine.snapshot(data);
return true;
} catch (err) {
if (this.errorStrategy === ErrorStrategy.Fail) {
throw err; // Re-throw the error
}
}
return false;
}

/**
Expand Down
15 changes: 15 additions & 0 deletions flipt-client-node/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ export interface ClientOptions<T> {
*/
reference?: string;
fetcher?: IFetcher;

/**
* The strategy to use for errors during refresh snapshot calls. {@link ErrorStrategy}
*/
errorStrategy?: ErrorStrategy;
}

/**
Expand Down Expand Up @@ -191,3 +196,13 @@ export interface BatchEvaluationResponse {
/** Duration of the request in milliseconds. */
requestDurationMillis: number;
}

/**
* Defines the strategy to handle errors during the flags snapshot update calls.
*/
export enum ErrorStrategy {
/** The client will throw an error if the flag state cannot be fetched. This is the default behavior. */
Fail = 'fail',
/** The client will maintain the last known good state and use that state for evaluation in case of an error. */
Fallback = 'fallback'
}

0 comments on commit 4e57bc1

Please sign in to comment.