Skip to content

Commit

Permalink
feat: consolidate browser and wasm engine (#226)
Browse files Browse the repository at this point in the history
* feat(wip): consolidate browser and wasm engine

* chore: architecture

* chore: fmt

* chore: dependabot, cleanup

* chore: move back seperate dir

* chore: dependabot

* chore: update release-please

* chore: fix build

* chore(wip): try to get tests working

* chore: blah tests

* chore: comment out broken test for now
  • Loading branch information
markphelps authored Apr 5, 2024
1 parent a183ee1 commit 9c219b7
Show file tree
Hide file tree
Showing 27 changed files with 1,808 additions and 1,015 deletions.
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ updates:
# kodiak `merge.automerge_label`
- "automerge"

- package-ecosystem: "cargo"
directory: "/flipt-engine-wasm"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
labels:
- "dependencies"
# kodiak `merge.automerge_label`
- "automerge"

- package-ecosystem: "cargo"
directory: "/flipt-evaluation"
schedule:
Expand Down
42 changes: 0 additions & 42 deletions .github/workflows/package-wasm-engine.yml

This file was deleted.

10 changes: 4 additions & 6 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,25 +68,23 @@ You can refer to the architecture diagram below:

#### WASM Engine

[`flipt-engine-wasm`](./flipt-engine-wasm) is a Rust library that compiles to WebAssembly and is designed to be embedded in the client-side SDKs that run in the browser.

The [`flipt-engine-wasm`](./flipt-engine-wasm) library is responsible for the following:
The [`flipt-engine-wasm`](./flipt-engine-wasm/) library is responsible for the following:

- Deserializing the evaluation state from JSON to memory.
- Calling the evaluation logic from the [`flipt-evaluation`](./flipt-evaluation) library to evaluate context against the evaluation state and returning the evaluation results.
- Serializing the evaluation results from memory to JSON.

#### Evaluation Library

The [`flipt-evaluation`](../flipt-evaluation) library is a Rust library responsible for the following:
The [`flipt-evaluation`](./flipt-evaluation) library is a Rust library responsible for the following:

- Evaluating context against the evaluation state and returning the evaluation results.

The evaluation logic is extracted into a separate library to allow for the evaluation logic to be reused by both the FFI and WASM engines and eventually by Flipt itself.

#### Client SDKs
#### Browser SDK

The client SDKs are responsible for the following:
The browser SDK is responsible for the following:

- Fetching the evaluation state from the Flipt server.
- Marshalling context to JSON.
Expand Down
12 changes: 1 addition & 11 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,9 @@ Note: In the future we can potentially automate the tagging/pushing of the SDKs

### WASM

#### Engine

The WASM engine is released as a NPM package and is published to the [flipt-engine-wasm](https://www.npmjs.com/package/@flipt-io/flipt-engine-wasm) package.

1. Create a new release in the [flipt-engine-wasm](./flipt-engine-wasm) package by merging a change to the `main` branch that would trigger a release via conventional commits.
2. Wait for the release-please workflow to complete. This will create a new release and tag for the engine.
3. The `package-wasm-engine` workflow will run, building the engine and publishing the NPM package.

#### SDKs

Releasing the `flipt-client-browser` SDK is a three to four-step process:

1. Update the `flipt-engine-wasm` dependency in `flipt-client-browser/package.json` to the new version (if necessary).
1. Update the `flipt-engine-wasm` dependency in `flipt-engine-wasm` (if necessary).
1. Update the `flipt-client-browser` version in `flipt-client-browser/package.json` and run `npm install`
1. Commit and tag the SDK with the new version using the naming convention `flipt-client-browser-v{version}` (i.e. `flipt-client-browser-v0.1.0`).
1. Push the tag to `origin`. This will trigger the appropriate GitHub Action to build, package, and publish the SDK.
58 changes: 15 additions & 43 deletions build/wasm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@ import (
)

var (
sdks string
push bool
tag string
engine bool
sdks string
push bool
tag string
)

func init() {
flag.BoolVar(&engine, "engine", false, "build the engine")
flag.StringVar(&sdks, "sdks", "", "comma separated list of which sdks(s) to run builds for")
flag.BoolVar(&push, "push", false, "push built artifacts to registry")
flag.StringVar(&tag, "tag", "", "tag to use for release")
Expand All @@ -39,9 +37,7 @@ type buildFn func(context.Context, *dagger.Client, *dagger.Directory) error
func run() error {
var build buildFn

if engine {
build = wasmBuild
} else if sdks == "browser" {
if sdks == "browser" {
build = browserBuild
} else {
return fmt.Errorf("no builds specified")
Expand All @@ -63,7 +59,7 @@ func run() error {
return build(ctx, client, dir)
}

func wasmBuild(ctx context.Context, client *dagger.Client, hostDirectory *dagger.Directory) error {
func browserBuild(ctx context.Context, client *dagger.Client, hostDirectory *dagger.Directory) error {
arch := "x86_64"
if strings.Contains(runtime.GOARCH, "arm64") || strings.Contains(runtime.GOARCH, "aarch64") {
arch = "arm64"
Expand All @@ -81,50 +77,26 @@ func wasmBuild(ctx context.Context, client *dagger.Client, hostDirectory *dagger
WithExec([]string{"apt-get", "-y", "install", "binaryen"})
}

rust = rust.
var err error

rust, err = rust.
WithExec([]string{"cargo", "install", "wasm-pack"}). // Install wasm-pack
WithWorkdir("/src/flipt-engine-wasm").
WithExec([]string{"wasm-pack", "build", "--scope", "flipt-io"}) // Build the wasm package

var err error
WithExec([]string{"wasm-pack", "build", "--target", "web"}).
Sync(ctx)

if !push {
_, err = rust.Sync(ctx)
if err != nil {
return err
}

container := client.Container().From("node:21.2-bookworm").
WithDirectory("/src", rust.Directory("/src/flipt-engine-wasm/pkg"), dagger.ContainerWithDirectoryOpts{
Exclude: []string{"./node_modules/"},
}).
WithWorkdir("/src").
WithExec([]string{"npm", "version", "--allow-same-version", tag}) // Update the version

if os.Getenv("NPM_API_KEY") == "" {
return fmt.Errorf("NPM_API_KEY is not set")
}

npmAPIKeySecret := client.SetSecret("npm-api-key", os.Getenv("NPM_API_KEY"))

_, err = container.WithSecretVariable("NPM_TOKEN", npmAPIKeySecret).
WithExec([]string{"npm", "config", "set", "--", "//registry.npmjs.org/:_authToken", "${NPM_TOKEN}"}).
WithExec([]string{"npm", "publish", "--access", "public"}).
Sync(ctx)

return err
}

func browserBuild(ctx context.Context, client *dagger.Client, hostDirectory *dagger.Directory) error {
container := client.Container().From("node:21.2-bookworm").
WithDirectory("/src", hostDirectory.Directory("flipt-client-browser"), dagger.ContainerWithDirectoryOpts{
WithDirectory("/src", hostDirectory.Directory("flipt-client-browser")).
WithDirectory("/src/pkg", rust.Directory("/src/flipt-engine-wasm/pkg"), dagger.ContainerWithDirectoryOpts{
Exclude: []string{"./node_modules/"},
}).
WithWorkdir("/src").
WithExec([]string{"npm", "install"}).
WithExec([]string{"npm", "run", "build"}).
WithExec([]string{"npm", "pack"})

var err error
WithExec([]string{"npm", "install"}). // Install dependencies
WithExec([]string{"npm", "run", "build"}) // Build the browser package

if !push {
_, err = container.Sync(ctx)
Expand Down
2 changes: 2 additions & 0 deletions flipt-client-browser/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,5 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

pkg
19 changes: 19 additions & 0 deletions flipt-client-browser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,22 @@ fliptEvaluationClient.refresh();
```

This allows you to update the flag state in a controlled manner, such as in a polling loop or when a user interacts with your application.

## Development

### Prerequisites

- [flipt-engine-wasm](../flipt-engine-wasm/README.md)

### Build

```bash
npm run build
```

### Test

```bash
npm install
npm test
```
7 changes: 0 additions & 7 deletions flipt-client-browser/jest.config.js

This file was deleted.

10 changes: 10 additions & 0 deletions flipt-client-browser/lib/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { BooleanResult, EngineOpts, VariantResult } from './models.js';
export declare class FliptEvaluationClient {
private engine;
private fetcher;
private constructor();
static init(namespace?: string, engine_opts?: EngineOpts): Promise<FliptEvaluationClient>;
refresh(): Promise<void>;
evaluateVariant(flag_key: string, entity_id: string, context: {}): VariantResult;
evaluateBoolean(flag_key: string, entity_id: string, context: {}): BooleanResult;
}
72 changes: 72 additions & 0 deletions flipt-client-browser/lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FliptEvaluationClient = void 0;
const pkg_1 = require("../pkg");
class FliptEvaluationClient {
engine;
fetcher;
constructor(engine, fetcher) {
this.engine = engine;
this.fetcher = fetcher;
}
static async init(namespace = 'default', engine_opts = {
url: 'http://localhost:8080',
reference: ''
}) {
await (0, pkg_1.default)();
let url = engine_opts.url ?? 'http://localhost:8080';
// trim trailing slash
url = url.replace(/\/$/, '');
url = `${url}/internal/v1/evaluation/snapshot/namespace/${namespace}`;
if (engine_opts.reference) {
url = `${url}?ref=${engine_opts.reference}`;
}
const headers = new Headers();
headers.append('Accept', 'application/json');
headers.append('Content-Type', 'application/json');
if (engine_opts.authentication) {
if ('client_token' in engine_opts.authentication) {
headers.append('Authorization', `Bearer ${engine_opts.authentication.client_token}`);
}
else if ('jwt_token' in engine_opts.authentication) {
headers.append('Authorization', `JWT ${engine_opts.authentication.jwt_token}`);
}
}
const fetcher = async () => {
const resp = await fetch(url, {
method: 'GET',
headers
});
if (!resp.ok) {
throw new Error('Failed to fetch data');
}
return resp;
};
const resp = await fetcher();
const data = await resp.json();
const engine = new pkg_1.Engine(namespace, data);
return new FliptEvaluationClient(engine, fetcher);
}
async refresh() {
const resp = await this.fetcher();
const data = await resp.json();
this.engine.snapshot(data);
}
evaluateVariant(flag_key, entity_id, context) {
const evaluation_request = {
flag_key,
entity_id,
context
};
return this.engine.evaluate_variant(evaluation_request);
}
evaluateBoolean(flag_key, entity_id, context) {
const evaluation_request = {
flag_key,
entity_id,
context
};
return this.engine.evaluate_boolean(evaluation_request);
}
}
exports.FliptEvaluationClient = FliptEvaluationClient;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Engine } from '@flipt-io/flipt-engine-wasm';
import { BooleanResult, EngineOpts, VariantResult } from './models';
import init, { Engine } from '../pkg';
import { BooleanResult, EngineOpts, VariantResult } from './models.js';

interface IEvaluationRequest {
flag_key: string;
Expand All @@ -23,6 +23,7 @@ export class FliptEvaluationClient {
reference: ''
}
) {
await init();
let url = engine_opts.url ?? 'http://localhost:8080';
// trim trailing slash
url = url.replace(/\/$/, '');
Expand Down
Loading

0 comments on commit 9c219b7

Please sign in to comment.