diff --git a/.github/workflows/package-ffi-engine.yml b/.github/workflows/package-ffi-engine.yml index c238f66b..937438ff 100644 --- a/.github/workflows/package-ffi-engine.yml +++ b/.github/workflows/package-ffi-engine.yml @@ -1,7 +1,5 @@ name: Package FFI Engine on: - pull_request: - branches: ["main"] push: tags: ["flipt-engine-ffi-v**"] workflow_dispatch: @@ -99,13 +97,6 @@ jobs: target/${{ matrix.platform.target }}/release/libfliptengine.* \ || true - - name: Upload Artifacts (Pull Request) - uses: actions/upload-artifact@v4 - if: github.event_name == 'pull_request' - with: - name: flipt-engine-ffi-${{ matrix.platform.name }} - path: flipt-engine-ffi-${{ matrix.platform.name }}.tar.gz - - name: Upload Release Assets (Tag) uses: softprops/action-gh-release@v2 if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/flipt-engine-ffi-v') diff --git a/flipt-client-browser/README.md b/flipt-client-browser/README.md index 7d25876b..9e27346e 100644 --- a/flipt-client-browser/README.md +++ b/flipt-client-browser/README.md @@ -1,6 +1,5 @@ # Flipt Client Browser -![Status: Hardening](https://img.shields.io/badge/status-hardening-orange) [![npm](https://img.shields.io/npm/v/@flipt-io/flipt-client-browser?label=%40flipt-io%2Fflipt-client-browser)](https://www.npmjs.com/package/@flipt-io/flipt-client-browser) The `flipt-client-browser` library contains the JavaScript/TypeScript source code for the Flipt [client-side evaluation](https://www.flipt.io/docs/integration/client) client for the browser. @@ -19,7 +18,7 @@ In your JavaScript/Typescript code you can import this client and use it as so: import { FliptEvaluationClient } from '@flipt-io/flipt-client-browser'; // namespace is the first positional argument and is optional here and will have a value of "default" if not specified. -// engine_opts is the second positional argument and is also optional, the structure is: +// options is the second positional argument and is also optional, the structure is: // { // "url": "http://localhost:8080", // "authentication": { @@ -46,7 +45,7 @@ console.log(variant); The `FliptEvaluationClient` constructor accepts two optional arguments: - `namespace`: The namespace to fetch flag state from. If not provided, the client will default to the `default` namespace. -- `engine_opts`: An instance of the `EngineOpts` type that supports several options for the client. The structure is: +- `options`: An instance of the `ClientOptions` type that supports several options for the client. The structure is: - `url`: The URL of the upstream Flipt instance. If not provided, the client will default to `http://localhost:8080`. - `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. @@ -80,7 +79,7 @@ The fetcher is a function that takes an optional [`IFetcherOpts`](https://github ## State Management -The `FliptEvaluationClient` class pulls flag state from the Flipt instance at the `url` provided in the `engine_opts` object on instantiation. +The `FliptEvaluationClient` class pulls flag state from the Flipt instance at the `url` provided in the `options` object on instantiation. To update the flag state, you can call the `refresh` method on the `FliptEvaluationClient` class. diff --git a/flipt-client-browser/__tests__/evaluation.test.js b/flipt-client-browser/__tests__/evaluation.test.js index 6b2a05b0..97bf46d5 100644 --- a/flipt-client-browser/__tests__/evaluation.test.js +++ b/flipt-client-browser/__tests__/evaluation.test.js @@ -1,4 +1,5 @@ const flipt = require('../dist/index.cjs'); + describe('FliptEvaluationClient', () => { let fliptUrl; let authToken; @@ -32,14 +33,11 @@ describe('FliptEvaluationClient', () => { fizz: 'buzz' }); - expect(variant.error_message).toBeUndefined(); - expect(variant.status).toEqual('success'); - expect(variant.result).toBeDefined(); - expect(variant.result?.flag_key).toEqual('flag1'); - expect(variant.result?.match).toEqual(true); - expect(variant.result?.reason).toEqual('MATCH_EVALUATION_REASON'); - expect(variant.result?.segment_keys).toContain('segment1'); - expect(variant.result?.variant_key).toContain('variant1'); + expect(variant.flag_key).toEqual('flag1'); + expect(variant.match).toEqual(true); + expect(variant.reason).toEqual('MATCH_EVALUATION_REASON'); + expect(variant.segment_keys).toContain('segment1'); + expect(variant.variant_key).toContain('variant1'); }); test('boolean', () => { @@ -47,34 +45,26 @@ describe('FliptEvaluationClient', () => { fizz: 'buzz' }); - expect(boolean.error_message).toBeUndefined(); - expect(boolean.status).toEqual('success'); - expect(boolean.result).toBeDefined(); - expect(boolean.result?.flag_key).toEqual('flag_boolean'); - expect(boolean.result?.enabled).toEqual(true); - expect(boolean.result?.reason).toEqual('MATCH_EVALUATION_REASON'); + expect(boolean.enabled).toEqual(true); + expect(boolean.reason).toEqual('MATCH_EVALUATION_REASON'); }); test('variant failure', () => { - const variant = client.evaluateVariant('nonexistent', 'someentity', { - fizz: 'buzz' - }); - - expect(variant.result).toBeUndefined(); - expect(variant.status).toEqual('failure'); - expect(variant.error_message).toEqual( + expect(() => { + client.evaluateVariant('nonexistent', 'someentity', { + fizz: 'buzz' + }); + }).toThrow( 'invalid request: failed to get flag information default/nonexistent' ); }); test('boolean failure', () => { - const boolean = client.evaluateVariant('nonexistent', 'someentity', { - fizz: 'buzz' - }); - - expect(boolean.result).toBeUndefined(); - expect(boolean.status).toEqual('failure'); - expect(boolean.error_message).toEqual( + expect(() => { + client.evaluateVariant('nonexistent', 'someentity', { + fizz: 'buzz' + }); + }).toThrow( 'invalid request: failed to get flag information default/nonexistent' ); }); @@ -86,14 +76,11 @@ describe('FliptEvaluationClient', () => { fizz: 'buzz' }); - expect(variant.error_message).toBeUndefined(); - expect(variant.status).toEqual('success'); - expect(variant.result).toBeDefined(); - expect(variant.result?.flag_key).toEqual('flag1'); - expect(variant.result?.match).toEqual(true); - expect(variant.result?.reason).toEqual('MATCH_EVALUATION_REASON'); - expect(variant.result?.segment_keys).toContain('segment1'); - expect(variant.result?.variant_key).toContain('variant1'); + expect(variant.flag_key).toEqual('flag1'); + expect(variant.match).toEqual(true); + expect(variant.reason).toEqual('MATCH_EVALUATION_REASON'); + expect(variant.segment_keys).toContain('segment1'); + expect(variant.variant_key).toContain('variant1'); }); test('batch', () => { @@ -121,12 +108,9 @@ describe('FliptEvaluationClient', () => { } ]); - expect(batch.error_message).toBeUndefined(); - expect(batch.status).toEqual('success'); - expect(batch.result).toBeDefined(); + expect(batch.responses).toHaveLength(3); + const variant = batch.responses[0]; - expect(batch.result?.responses).toHaveLength(3); - const variant = batch.result?.responses[0]; expect(variant?.type).toEqual('VARIANT_EVALUATION_RESPONSE_TYPE'); expect(variant?.variant_evaluation_response?.flag_key).toEqual('flag1'); expect(variant?.variant_evaluation_response?.match).toEqual(true); @@ -140,7 +124,7 @@ describe('FliptEvaluationClient', () => { 'variant1' ); - const boolean = batch.result?.responses[1]; + const boolean = batch.responses[1]; expect(boolean?.type).toEqual('BOOLEAN_EVALUATION_RESPONSE_TYPE'); expect(boolean?.boolean_evaluation_response?.flag_key).toEqual( 'flag_boolean' @@ -150,7 +134,7 @@ describe('FliptEvaluationClient', () => { 'MATCH_EVALUATION_REASON' ); - const error = batch.result?.responses[2]; + const error = batch.responses[2]; expect(error?.type).toEqual('ERROR_EVALUATION_RESPONSE_TYPE'); expect(error?.error_evaluation_response?.flag_key).toEqual('notfound'); expect(error?.error_evaluation_response?.namespace_key).toEqual('default'); diff --git a/flipt-client-browser/package-lock.json b/flipt-client-browser/package-lock.json index af6ab1a7..66ec95a7 100644 --- a/flipt-client-browser/package-lock.json +++ b/flipt-client-browser/package-lock.json @@ -1,12 +1,12 @@ { "name": "@flipt-io/flipt-client-browser", - "version": "0.0.20", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@flipt-io/flipt-client-browser", - "version": "0.0.20", + "version": "0.1.0", "license": "MIT", "devDependencies": { "@rollup/plugin-typescript": "^11.1.6", diff --git a/flipt-client-browser/package.json b/flipt-client-browser/package.json index ec49a0bf..4162dfa5 100644 --- a/flipt-client-browser/package.json +++ b/flipt-client-browser/package.json @@ -1,6 +1,6 @@ { "name": "@flipt-io/flipt-client-browser", - "version": "0.0.20", + "version": "0.1.0", "description": "Flipt Client Evaluation Browser SDK", "main": "dist/index.cjs", "module": "dist/index.mjs", diff --git a/flipt-client-browser/src/index.ts b/flipt-client-browser/src/index.ts index d0ba75c3..1e59a530 100644 --- a/flipt-client-browser/src/index.ts +++ b/flipt-client-browser/src/index.ts @@ -2,11 +2,14 @@ import init, { Engine } from '../dist/flipt_engine_wasm.js'; import wasm from '../dist/flipt_engine_wasm_bg.wasm'; import { + BatchEvaluationResponse, BatchResult, + BooleanEvaluationResponse, BooleanResult, - EngineOpts, + ClientOptions, EvaluationRequest, IFetcher, + VariantEvaluationResponse, VariantResult } from './models.js'; @@ -23,46 +26,46 @@ export class FliptEvaluationClient { /** * Initialize the client * @param namespace - optional namespace to evaluate flags - * @param engine_opts - optional engine options + * @param options - optional client options * @returns {Promise} */ static async init( namespace: string = 'default', - engine_opts: EngineOpts = { + options: ClientOptions = { url: 'http://localhost:8080', reference: '' } ): Promise { await init(await wasm()); - let url = engine_opts.url ?? 'http://localhost:8080'; + let url = options.url ?? 'http://localhost:8080'; // trim trailing slash url = url.replace(/\/$/, ''); url = `${url}/internal/v1/evaluation/snapshot/namespace/${namespace}`; - if (engine_opts.reference) { - url = `${url}?reference=${engine_opts.reference}`; + if (options.reference) { + url = `${url}?reference=${options.reference}`; } const headers = new Headers(); headers.append('Accept', 'application/json'); headers.append('x-flipt-accept-server-version', '1.47.0'); - if (engine_opts.authentication) { - if ('client_token' in engine_opts.authentication) { + if (options.authentication) { + if ('client_token' in options.authentication) { headers.append( 'Authorization', - `Bearer ${engine_opts.authentication.client_token}` + `Bearer ${options.authentication.client_token}` ); - } else if ('jwt_token' in engine_opts.authentication) { + } else if ('jwt_token' in options.authentication) { headers.append( 'Authorization', - `JWT ${engine_opts.authentication.jwt_token}` + `JWT ${options.authentication.jwt_token}` ); } } - let fetcher = engine_opts.fetcher; + let fetcher = options.fetcher; if (!fetcher) { fetcher = async (opts?: { etag?: string }) => { @@ -126,20 +129,28 @@ export class FliptEvaluationClient { * @param flag_key - flag key to evaluate * @param entity_id - entity id to evaluate * @param context - optional evaluation context - * @returns {VariantResult} + * @returns {VariantEvaluationResponse} */ public evaluateVariant( flag_key: string, entity_id: string, context: {} - ): VariantResult { + ): VariantEvaluationResponse { const evaluation_request: EvaluationRequest = { flag_key, entity_id, context }; - return this.engine.evaluate_variant(evaluation_request) as VariantResult; + const result = this.engine.evaluate_variant( + evaluation_request + ) as VariantResult; + + if (result.status === 'failure') { + throw new Error(result.error_message); + } + + return result.result as VariantEvaluationResponse; } /** @@ -147,28 +158,42 @@ export class FliptEvaluationClient { * @param flag_key - flag key to evaluate * @param entity_id - entity id to evaluate * @param context - optional evaluation context - * @returns {BooleanResult} + * @returns {BooleanEvaluationResponse} */ public evaluateBoolean( flag_key: string, entity_id: string, context: {} - ): BooleanResult { + ): BooleanEvaluationResponse { const evaluation_request: EvaluationRequest = { flag_key, entity_id, context }; - return this.engine.evaluate_boolean(evaluation_request) as BooleanResult; + const result = this.engine.evaluate_boolean( + evaluation_request + ) as BooleanResult; + + if (result.status === 'failure') { + throw new Error(result.error_message); + } + + return result.result as BooleanEvaluationResponse; } /** * Evaluate a batch of flag requests * @param requests evaluation requests - * @returns {BatchResult} + * @returns {BatchEvaluationResponse} */ - public evaluateBatch(requests: EvaluationRequest[]): BatchResult { - return this.engine.evaluate_batch(requests); + public evaluateBatch(requests: EvaluationRequest[]): BatchEvaluationResponse { + const result = this.engine.evaluate_batch(requests) as BatchResult; + + if (result.status === 'failure') { + throw new Error(result.error_message); + } + + return result.result as BatchEvaluationResponse; } } diff --git a/flipt-client-browser/src/models.ts b/flipt-client-browser/src/models.ts index 57ca9ff5..9b3346b3 100644 --- a/flipt-client-browser/src/models.ts +++ b/flipt-client-browser/src/models.ts @@ -1,9 +1,9 @@ -export interface IFetcherOpts { +export interface IFetcherOptions { etag?: string; } export interface IFetcher { - (opts?: IFetcherOpts): Promise; + (opts?: IFetcherOptions): Promise; } export interface AuthenticationStrategy {} @@ -16,7 +16,7 @@ export interface JWTAuthentication extends AuthenticationStrategy { jwt_token: string; } -export interface EngineOpts { +export interface ClientOptions { url?: string; authentication?: AuthenticationStrategy; reference?: string; @@ -34,8 +34,8 @@ export interface VariantEvaluationResponse { segment_keys: string[]; reason: string; flag_key: string; - variant_key: string; - variant_attachment: string; + variant_key?: string; + variant_attachment?: string; request_duration_millis: number; timestamp: string; }