Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat node server side #18

Draft
wants to merge 72 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
cbbd490
wip
Tim-53 May 31, 2023
83de4ff
switch to nodemon
Tim-53 May 31, 2023
070aa7f
make http service agnostic
Tim-53 May 31, 2023
8840ac8
add getFeatureFlagValue, add simple express example
Tim-53 May 31, 2023
f829b2e
add abTestMiddleware
Tim-53 Jun 1, 2023
dc08564
use unified IStorageService interface
Tim-53 Jun 1, 2023
6a27e82
Merge branch 'refac/Unify_StorageService_Interfaces' into feat_node_s…
Tim-53 Jun 1, 2023
66a1912
add storage service
Tim-53 Jun 1, 2023
c50e1c2
add cookieParser helper
Tim-53 Jun 1, 2023
a2fec41
fix ab test middle ware
Tim-53 Jun 1, 2023
a336452
add response context
Tim-53 Jun 1, 2023
ade1b65
fix responseContext
Tim-53 Jun 1, 2023
d6ceca7
add cookieMiddleware
Tim-53 Jun 1, 2023
9fc56f8
refac: move contexts to new folder
Tim-53 Jun 1, 2023
174c81e
docs: add documentation to create abby
Tim-53 Jun 1, 2023
911e2db
refac: cleanup unused things
Tim-53 Jun 1, 2023
316caa2
add test
Tim-53 Jun 1, 2023
87221b9
add await abby initialization
Tim-53 Jun 6, 2023
dc77bb0
Feat/core/add feature flag caching (#22)
Tim-53 Jun 6, 2023
bcd5d99
add cache config
Tim-53 Jun 6, 2023
cf61233
repair lockfile
Tim-53 Jun 7, 2023
078fe8e
upgrade pnpm version on workflow
Tim-53 Jun 7, 2023
3f2819a
revert pnpm version
Tim-53 Jun 7, 2023
16e564f
fix lockfile
Tim-53 Jun 7, 2023
7be093b
make fetch isomorphic
Tim-53 Jun 7, 2023
53f1bb8
merge main
Tim-53 Jun 9, 2023
f46c9cc
add: test WIP
Tim-53 Jun 9, 2023
d0a85ae
tests wip
Tim-53 Jun 13, 2023
b7baf02
tests wip
Tim-53 Jun 13, 2023
39086ad
downgrade msw version, revert httpService debug changes
Tim-53 Jun 13, 2023
06340c9
downgrade msw version, revert httpService debug changes
Tim-53 Jun 13, 2023
8e5d3c0
add tests for getABTestValue , getFeatureFlagValue
Tim-53 Jun 14, 2023
3b4f24a
add abbyExpressMiddleWareFactory
Tim-53 Jun 14, 2023
6f15e9b
fix tests
Tim-53 Jun 14, 2023
ec228f5
fix lockfile
Tim-53 Jun 14, 2023
69ae446
fix build
Tim-53 Jun 14, 2023
d9dd778
add express test middleware setting cookie
Tim-53 Jun 14, 2023
0191e54
chore: add prettier settings
Tim-53 Jun 14, 2023
02205d8
Merge branch 'chore/prettier_settings' into feat_node_serverSide
Tim-53 Jun 14, 2023
83a1bcd
add feature flag middleware tests
Tim-53 Jun 14, 2023
d4fc5ea
complete express middleware tests
Tim-53 Jun 15, 2023
498315f
disabled msw warnings for calls to express
Tim-53 Jun 15, 2023
3f400eb
add build script, tsup and exports
Tim-53 Jun 21, 2023
6a4841c
add gitignore
Tim-53 Jun 21, 2023
dc44eb6
add alll flags middleware
Tim-53 Jul 6, 2023
a5f7f94
merge multivariate feature Flags
Tim-53 Jul 6, 2023
1747955
fix package name
Tim-53 Jul 6, 2023
489ed85
add body parser
Tim-53 Jul 6, 2023
28e4633
remove unecassry awaits
Tim-53 Jul 7, 2023
9df8bcf
Fix build
Tim-53 Jul 7, 2023
e7b14ca
update package.json
Tim-53 Jul 7, 2023
0e24266
it works
Tim-53 Jul 11, 2023
f604535
it works
Tim-53 Jul 11, 2023
6f2c349
create fastify package
Tim-53 Jul 11, 2023
53b6a9a
make response and request context usable in express and fastify, add …
Tim-53 Jul 11, 2023
b5bf94b
update fastify factory
Tim-53 Jul 11, 2023
7b4d66b
repair lockfile
Tim-53 Jul 11, 2023
619d24f
fix tests
Tim-53 Jul 11, 2023
c784e8c
tests for fastify wip
Tim-53 Jul 11, 2023
0f93096
fix basic tests
Tim-53 Jul 11, 2023
2082531
fix build
Tim-53 Jul 11, 2023
85b55a0
remove console.log
Tim-53 Jul 11, 2023
c2c6c4d
enable setCookie for express AND fastify
Tim-53 Jul 12, 2023
f0f9438
finish tests for fastify
Tim-53 Jul 12, 2023
c3cbd53
working in browser for express and fastify , express test need to be …
Tim-53 Jul 12, 2023
815d794
add cookie parser
Tim-53 Jul 13, 2023
91fd26c
fix svelte imports
Tim-53 Jul 13, 2023
74cc9a3
add optional passing of response and request object
Tim-53 Jul 13, 2023
860b35b
fix tests and clean up
Tim-53 Jul 13, 2023
153e549
fix lockfile
Tim-53 Jul 13, 2023
76c2a88
add cookie parser types
Tim-53 Jul 13, 2023
a6b24a7
fix core tests
Tim-53 Jul 13, 2023
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
2 changes: 0 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,12 @@ on:
jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "16.19.0"

- uses: pnpm/action-setup@v2
name: Install pnpm
id: pnpm-install
Expand Down
7 changes: 5 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"[typescript][typescriptreact][typescriptangular]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"eslint.workingDirectories": ["apps", "packages"],
"eslint.workingDirectories": [
"apps",
"packages"
],
"prettier.enable": true
}
}
124 changes: 93 additions & 31 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export type ABConfig<T extends string = string> = {

type Settings<
FlagName extends string,
Flags extends Record<FlagName, FlagValueString> = Record<FlagName, FlagValueString>
Flags extends Record<FlagName, FlagValueString> = Record<FlagName, FlagValueString>,
> = {
flags?: {
defaultValues?: {
Expand All @@ -44,14 +44,19 @@ type LocalData<FlagName extends string = string, TestName extends string = strin
};

interface PersistentStorage {
get: (key: string) => string | null;
set: (key: string, value: string) => void;
get: (key: string, args?: unknown) => string | null;
set: (key: string, value: string, args?: unknown) => void;
}

type flagCacheConfig = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is done in another PR? maybe finish that one first? =)

refetchFlags: boolean;
timeToLive: number;
};

export type AbbyConfig<
FlagName extends string = string,
Tests extends Record<string, ABConfig> = Record<string, ABConfig>,
Flags extends Record<FlagName, FlagValueString> = Record<FlagName, FlagValueString>
Flags extends Record<FlagName, FlagValueString> = Record<FlagName, FlagValueString>,
> = {
projectId: string;
apiUrl?: string;
Expand All @@ -60,17 +65,25 @@ export type AbbyConfig<
flags?: Flags;
settings?: Settings<F.NoInfer<FlagName>, Flags>;
debug?: boolean;
flagCacheConfig?: flagCacheConfig;
};

export class Abby<
FlagName extends string,
TestName extends string,
Tests extends Record<string, ABConfig>,
Flags extends Record<FlagName, FlagValueString>
Flags extends Record<FlagName, FlagValueString>,
> {
private log = (...args: any[]) =>
this.config.debug ? console.log(`core.Abby`, ...args) : () => {};

private testDevtoolOverrides: Map<keyof Tests, Tests[keyof Tests]["variants"][number]> =
new Map();

private flagDevtoolOverrides: Map<FlagName, boolean> = new Map();

#flagTimeoutMap: Map<string, Date> = new Map();

#data: LocalData<FlagName, TestName> = {
tests: {} as any,
flags: {} as any,
Expand All @@ -92,13 +105,16 @@ export class Abby<
private persistantFlagStorage?: PersistentStorage
) {
this._cfg = config as AbbyConfig<FlagName, Tests, Flags>;
this.#data.flags = Object.keys(config.flags ?? {}).reduce((acc, flagName) => {
acc[flagName as FlagName] = this.getDefaultFlagValue(
flagName as FlagName,
config.flags as any
);
return acc;
}, {} as Record<FlagName, FlagValue>);
this.#data.flags = Object.keys(config.flags ?? {}).reduce(
(acc, flagName) => {
acc[flagName as FlagName] = this.getDefaultFlagValue(
flagName as FlagName,
config.flags as any
);
return acc;
},
{} as Record<FlagName, FlagValue>
);
this.#data.tests = config.tests ?? ({} as any);
}

Expand Down Expand Up @@ -139,22 +155,32 @@ export class Abby<
data: AbbyDataResponse
): LocalData<FlagName, TestName> {
return {
tests: data.tests.reduce((acc, { name, weights }) => {
if (!acc[name as keyof Tests]) {
tests: data.tests.reduce(
(acc, { name, weights }) => {
if (!acc[name as keyof Tests]) {
return acc;
}

// assigned the fetched weights to the initial config
acc[name as keyof Tests] = {
...acc[name as keyof Tests],
weights,
};
return acc;
}

// assigned the fetched weights to the initial config
acc[name as keyof Tests] = {
...acc[name as keyof Tests],
weights,
};
return acc;
}, (this.config.tests ?? {}) as any),
flags: data.flags.reduce((acc, { name, value }) => {
acc[name] = value;
return acc;
}, {} as Record<string, FlagValue>),
},
(this.config.tests ?? {}) as any
),
flags: data.flags.reduce(
(acc, { name, value }) => {
const validUntil = new Date(
new Date().getTime() + 1000 * 60 * (this.config.flagCacheConfig?.timeToLive ?? 1)
); // flagdefault timeout is 1 minute
this.#flagTimeoutMap.set(name, validUntil);
acc[name] = value;
return acc;
},
{} as Record<string, FlagValue>
),
};
}

Expand Down Expand Up @@ -201,6 +227,37 @@ export class Abby<
return this.getProjectData();
}

/**
* Helper function to retrieve the time a flag is valid
* @param key
* @returns
*/
getFeatureFlagTimeout<F extends FlagName>(key: F) {
return this.#flagTimeoutMap.get(key);
}

/**
* Helper function to check if a featureflag should be refetched
* @param key name of the featureflag
* @returns value of flag
*/
getValidFlag<F extends FlagName>(key: F) {
const flagTime = this.#flagTimeoutMap.get(key);
if (!flagTime) return this.#data.flags[key];
const now = new Date();
if (flagTime.getTime() <= now.getTime()) {
this.refetchFlags();
}
return this.#data.flags[key];
}

/**
* helper function to make testing easier
*/
refetchFlags() {
this.loadProjectData();
}

/**
* Function to get the value of a feature flag. This includes
* the overrides from the dev tools and the local overrides if in development mode
Expand All @@ -213,7 +270,9 @@ export class Abby<
): FlagValueStringToType<CurrentFlag> {
this.log(`getFeatureFlag()`, key);

const storedValue = this.#data.flags[key as unknown as FlagName];
const storedValue = this.config.flagCacheConfig?.refetchFlags
? this.getValidFlag(key as unknown as FlagName)
: this.#data.flags[key as unknown as FlagName];

const localOverride = this.flagOverrides?.get(key as unknown as FlagName);

Expand Down Expand Up @@ -254,7 +313,10 @@ export class Abby<
* @param key The name of the test
* @returns the value of the test variant
*/
getTestVariant<T extends keyof Tests>(key: T): Tests[T]["variants"][number] {
getTestVariant<T extends keyof Tests, T_ARGS = unknown>(
key: T,
args?: T_ARGS
): Tests[T]["variants"][number] {
this.log(`getTestVariant()`, key);

const { variants, weights } = (this.#data.tests as LocalData["tests"])[
Expand All @@ -267,7 +329,7 @@ export class Abby<
return override;
}

const persistedValue = this.persistantTestStorage?.get(key as string);
const persistedValue = this.persistantTestStorage?.get(key as string, args);

if (persistedValue != null) {
this.log(`getTestVariant() => persistedValue:`, persistedValue);
Expand All @@ -276,7 +338,7 @@ export class Abby<
}

const weightedVariant = getWeightedRandomVariant(variants, weights);
this.persistantTestStorage?.set(key as string, weightedVariant);
this.persistantTestStorage?.set(key as string, weightedVariant, args);

this.log(`getTestVariant() => weightedVariant:`, weightedVariant);

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/shared/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ export function getFFStorageKey(projectId: string, flagName: string): string {
}

export function assertUnreachable(x: never): never {
throw new Error("Reached unreachable code");
throw new Error(`Reached unreachable code: ${x}`);
}
2 changes: 2 additions & 0 deletions packages/core/src/shared/http.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ABBY_BASE_URL } from "./constants";
import type { AbbyEventType, AbbyEvent, AbbyDataResponse } from "./index";

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍

export abstract class HttpService {
static async getProjectData({
projectId,
Expand All @@ -21,6 +22,7 @@ export abstract class HttpService {
const data = (await res.json()) as AbbyDataResponse;
return data;
} catch (err) {
console.log(err);
console.error("[ABBY]: failed to load project data, falling back to defaults");
return null;
}
Expand Down
Loading