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: Alpha Release for react-universal #778

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use client';

import { useVariationDetail } from '@launchdarkly/react-universal-sdk/client';
import { useBoolVariationDetail } from '@launchdarkly/react-universal-sdk/client';

export default function HelloClientComponent() {
// You need to set evaluationReasons to true when initializing the LDProvider to useVariationDetail.
// Note: in the future evaluationReasons will be renamed withReasons.
const detail = useVariationDetail('my-boolean-flag-1');
const detail = useBoolVariationDetail('my-boolean-flag-1', false);

return (
<div className="border-2 border-white/20 p-4 ">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { getLDContext } from '@/app/utils';

import { useVariationRsc } from '@launchdarkly/react-universal-sdk/server';
import { useBoolVariationDetailRsc } from '@launchdarkly/react-universal-sdk/server';

export default async function HelloServerComponent() {
const flagValue = await useVariationRsc('my-boolean-flag-1', getLDContext());
const flagValue = await useBoolVariationDetailRsc('my-boolean-flag-1', getLDContext(), false);

return (
<div className="border-2 border-white/20 p-4">
Expand Down
3 changes: 2 additions & 1 deletion packages/sdk/react-universal/example/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export default async function RootLayout({
<LDProvider
clientSideID={clientSideID}
context={context}
options={{ bootstrap, evaluationReasons: true }}
bootstrap={bootstrap}
options={{ withReasons: true }}
>
{children}
</LDProvider>
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/react-universal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@
"typescript": "5.1.6"
},
"dependencies": {
"@launchdarkly/js-client-sdk": "^0.4.1",
"@launchdarkly/js-client-sdk-common": "^1.1.4",
"@launchdarkly/node-server-sdk": "^9.4.6",
"launchdarkly-js-client-sdk": "^3.4.0"
"@launchdarkly/node-server-sdk": "^9.4.6"
},
"peerDependencies": {
"react": "*"
Expand Down
10 changes: 7 additions & 3 deletions packages/sdk/react-universal/src/client/LDProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
'use client';

import { initialize, type LDOptions } from 'launchdarkly-js-client-sdk';
import { type PropsWithChildren, useEffect, useState } from 'react';
import React from 'react';

import { initialize, type LDOptions } from '@launchdarkly/js-client-sdk';
import type { LDContext, LDFlagSet } from '@launchdarkly/node-server-sdk';

import { isServer } from '../isServer';
Expand All @@ -14,6 +14,7 @@ import { setupListeners } from './setupListeners';
type LDProps = {
clientSideID: string;
context: LDContext;
bootstrap: LDFlagSet;
options?: LDOptions;
};

Expand All @@ -23,24 +24,27 @@ type LDProps = {
*
* @param clientSideID Your LaunchDarkly client side id.
* @param context The LDContext for evaluation.
* @param bootstrap LDFlagSet used to bootstrap
* @param options Configuration options for the js sdk. See {@link LDOptions}.
* @param children Your react application to be rendered.
*/
export const LDProvider = ({
clientSideID,
context,
bootstrap,
options,
children,
}: PropsWithChildren<LDProps>) => {
let jsSdk: JSSdk = undefined as any;
if (!isServer) {
jsSdk = initialize(clientSideID ?? '', context, options);
jsSdk = initialize(clientSideID ?? '', options);
jsSdk.identify(context, { bootstrap });
}

const [state, setState] = useState<ReactContext>({
jsSdk,
context,
bootstrap: options?.bootstrap as LDFlagSet,
bootstrap,
});

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ export const useTypedVariation = <T extends boolean | number | string | unknown>
const ldClient = useLDClient();

switch (typeof defaultValue) {
// case 'boolean':
// return ldClient.boolVariation(key, defaultValue as boolean) as T;
// case 'number':
// return ldClient.numberVariation(key, defaultValue as number) as T;
// case 'string':
// return ldClient.stringVariation(key, defaultValue as string) as T;
// case 'undefined':
// case 'object':
// return ldClient.jsonVariation(key, defaultValue) as T;
case 'boolean':
return ldClient.boolVariation(key, defaultValue as boolean) as T;
case 'number':
return ldClient.numberVariation(key, defaultValue as number) as T;
case 'string':
return ldClient.stringVariation(key, defaultValue as string) as T;
case 'undefined':
case 'object':
return ldClient.jsonVariation(key, defaultValue) as T;
default:
return ldClient.variation(key, defaultValue);
}
Expand All @@ -30,24 +30,24 @@ export const useTypedVariationDetail = <T extends boolean | number | string | un
const ldClient = useLDClient();

switch (typeof defaultValue) {
// case 'boolean':
// return ldClient.boolVariationDetail(
// key,
// defaultValue as boolean,
// ) as LDEvaluationDetailTyped<T>;
// case 'number':
// return ldClient.numberVariationDetail(
// key,
// defaultValue as number,
// ) as LDEvaluationDetailTyped<T>;
// case 'string':
// return ldClient.stringVariationDetail(
// key,
// defaultValue as string,
// ) as LDEvaluationDetailTyped<T>;
// case 'undefined':
// case 'object':
// return ldClient.jsonVariationDetail(key, defaultValue) as LDEvaluationDetailTyped<T>;
case 'boolean':
return ldClient.boolVariationDetail(
key,
defaultValue as boolean,
) as LDEvaluationDetailTyped<T>;
case 'number':
return ldClient.numberVariationDetail(
key,
defaultValue as number,
) as LDEvaluationDetailTyped<T>;
case 'string':
return ldClient.stringVariationDetail(
key,
defaultValue as string,
) as LDEvaluationDetailTyped<T>;
case 'undefined':
case 'object':
return ldClient.jsonVariationDetail(key, defaultValue) as LDEvaluationDetailTyped<T>;
default:
return ldClient.variationDetail(key, defaultValue) as LDEvaluationDetailTyped<T>;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { useTypedVariation, useTypedVariationDetail } from './useTypedVariation';

export const useBoolVariation = (key: string, defaultValue: boolean) =>
useTypedVariation<boolean>(key, defaultValue);

export const useStringVariation = (key: string, defaultValue: string) =>
useTypedVariation<string>(key, defaultValue);

export const useNumberVariation = (key: string, defaultValue: number) =>
useTypedVariation<number>(key, defaultValue);

export const useJsonVariation = (key: string, defaultValue?: undefined) =>
useTypedVariation<undefined>(key, defaultValue);

export const useVariation = (key: string, defaultValue?: boolean) =>
useTypedVariation<any>(key, defaultValue);

Expand All @@ -10,5 +22,17 @@ export const useVariation = (key: string, defaultValue?: boolean) =>
* @param key
* @param defaultValue
*/
export const useBoolVariationDetail = (key: string, defaultValue: boolean) =>
useTypedVariationDetail<boolean>(key, defaultValue);

export const useStringVariationDetail = (key: string, defaultValue: string) =>
useTypedVariationDetail<string>(key, defaultValue);

export const useNumberVariationDetail = (key: string, defaultValue: number) =>
useTypedVariationDetail<number>(key, defaultValue);

export const useJsonVariationDetail = (key: string, defaultValue?: undefined) =>
useTypedVariationDetail<undefined>(key, defaultValue);

export const useVariationDetail = (key: string, defaultValue?: boolean) =>
useTypedVariationDetail<any>(key, defaultValue);
3 changes: 3 additions & 0 deletions packages/sdk/react-universal/src/client/setupListeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ export const setupListeners = (setState: Dispatch<SetStateAction<ReactContext>>,
jsSdk.on('change', () => {
setState((prevState) => ({ ...prevState, jsSdk }));
});
jsSdk.on('ready', () => {
setState((prevState) => ({ ...prevState, jsSdk }));
});
};
73 changes: 71 additions & 2 deletions packages/sdk/react-universal/src/ldClientRsc.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LDEvaluationDetailTyped } from '@launchdarkly/js-client-sdk';
import type {
LDContext,
LDEvaluationDetail,
Expand All @@ -12,6 +13,12 @@ import type { JSSdk } from './types';
// Omit variationDetail because its return type is incompatible with js-core.
type PartialJSSdk = Omit<Partial<JSSdk>, 'variationDetail'>;

// Create a helper type for the variation methods
type VariationMethod<T> = (key: string, defaultValue: T) => T;

// Create a helper type for the variation detail methods
type VariationDetailMethod<T> = (key: string, defaultValue: T) => LDEvaluationDetailTyped<T>;

/**
* A partial ldClient suitable for RSC and server side rendering.
*/
Expand All @@ -29,17 +36,79 @@ export class LDClientRsc implements PartialJSSdk {
return this._ldContext;
}

private _createVariation<T>(
key: string,
defaultValue: T,
serverMethod: (key: string, context: LDContext, defaultValue: T) => Promise<T>,
): T {
serverMethod(key, this._ldContext, defaultValue).then(/* ignore */);

return this._bootstrap[key] ?? defaultValue;
}

private _createVariationDetail<T>(
key: string,
defaultValue: T,
serverMethod: (
key: string,
context: LDContext,
defaultValue: T,
) => Promise<LDEvaluationDetailTyped<T>>,
): LDEvaluationDetailTyped<T> {
serverMethod(key, this._ldContext, defaultValue).then(/* ignore */);
const { reason, variation: variationIndex } = this._bootstrap.$flagsState[key];
return { value: this._bootstrap[key], reason, variationIndex };
}

boolVariation: VariationMethod<boolean> = (key, defaultValue) =>
this._createVariation(key, defaultValue, global.nodeSdk.boolVariation.bind(global.nodeSdk));

stringVariation: VariationMethod<string> = (key, defaultValue) =>
this._createVariation(key, defaultValue, global.nodeSdk.stringVariation.bind(global.nodeSdk));

numberVariation: VariationMethod<number> = (key, defaultValue) =>
this._createVariation(key, defaultValue, global.nodeSdk.numberVariation.bind(global.nodeSdk));

jsonVariation: VariationMethod<unknown> = (key, defaultValue) =>
this._createVariation(key, defaultValue, global.nodeSdk.jsonVariation.bind(global.nodeSdk));

variation(key: string, defaultValue?: LDFlagValue): LDFlagValue {
if (isServer) {
// On the server during ssr, call variation for analytics purposes.
global.nodeSdk.variation(key, this._ldContext, defaultValue).then(/* ignore */);
}
return this._bootstrap[key] ?? defaultValue;
}

boolVariationDetail: VariationDetailMethod<boolean> = (key, defaultValue) =>
this._createVariationDetail(
key,
defaultValue,
global.nodeSdk.boolVariationDetail.bind(global.nodeSdk),
);

stringVariationDetail: VariationDetailMethod<string> = (key, defaultValue) =>
this._createVariationDetail(
key,
defaultValue,
global.nodeSdk.stringVariationDetail.bind(global.nodeSdk),
);

numberVariationDetail: VariationDetailMethod<number> = (key, defaultValue) =>
this._createVariationDetail(
key,
defaultValue,
global.nodeSdk.numberVariationDetail.bind(global.nodeSdk),
);

jsonVariationDetail: VariationDetailMethod<unknown> = (key, defaultValue) =>
this._createVariationDetail(
key,
defaultValue,
global.nodeSdk.jsonVariationDetail.bind(global.nodeSdk),
);

variationDetail(key: string, defaultValue?: LDFlagValue): LDEvaluationDetail {
if (isServer) {
// On the server during ssr, call variation for analytics purposes.
global.nodeSdk.variationDetail(key, this._ldContext, defaultValue).then(/* ignore */);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,55 @@ import type { LDContext, LDFlagValue } from '@launchdarkly/node-server-sdk';

import { useLDClientRsc } from '../useLDClientRsc';

export const useBoolVariationRsc = async (key: string, context: LDContext, def: boolean) => {
const ldc = await useLDClientRsc(context);
return ldc.boolVariation(key, def);
};

export const useStringVariationRsc = async (key: string, context: LDContext, def: string) => {
const ldc = await useLDClientRsc(context);
return ldc.stringVariation(key, def);
};

export const useNumberVariationRsc = async (key: string, context: LDContext, def: number) => {
const ldc = await useLDClientRsc(context);
return ldc.numberVariation(key, def);
};

export const useJsonVariationRsc = async (key: string, context: LDContext, def: undefined) => {
const ldc = await useLDClientRsc(context);
return ldc.jsonVariation(key, def);
};

export const useVariationRsc = async (key: string, context: LDContext, def?: LDFlagValue) => {
const ldc = await useLDClientRsc(context);
return ldc.variation(key, def);
};

export const useBoolVariationDetailRsc = async (key: string, context: LDContext, def: boolean) => {
const ldc = await useLDClientRsc(context);
return ldc.boolVariationDetail(key, def);
};

export const useStringVariationDetailRsc = async (key: string, context: LDContext, def: string) => {
const ldc = await useLDClientRsc(context);
return ldc.stringVariationDetail(key, def);
};

export const useNumberVariationDetailRsc = async (key: string, context: LDContext, def: number) => {
const ldc = await useLDClientRsc(context);
return ldc.numberVariationDetail(key, def);
};

export const useJsonVariationDetailRsc = async (
key: string,
context: LDContext,
def: undefined,
) => {
const ldc = await useLDClientRsc(context);
return ldc.jsonVariationDetail(key, def);
};

export const useVariationDetailRsc = async (key: string, context: LDContext, def?: LDFlagValue) => {
const ldc = await useLDClientRsc(context);
return ldc.variationDetail(key, def);
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/react-universal/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { LDClient as NodeSdk } from '@launchdarkly/node-server-sdk';

export type { LDClient as JSSdk } from 'launchdarkly-js-client-sdk';
export type { LDClient as JSSdk } from '@launchdarkly/js-client-sdk';

export type { NodeSdk };

Expand Down
3 changes: 3 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
"packages/sdk/browser": {
"bump-minor-pre-major": true
},
"packages/sdk/react-universal": {
"bump-minor-pre-major": true
},
"packages/sdk/server-ai": {
"bump-minor-pre-major": true,
"extra-files": [
Expand Down