Skip to content

Commit caa6ab6

Browse files
committed
feat: add scoped clients to server sdks
1 parent 0b32cb0 commit caa6ab6

13 files changed

Lines changed: 1176 additions & 5 deletions

File tree

packages/sdk/react/__tests__/server/createLDServerSession.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,55 @@ it('allFlagsState() forwards options to base client', async () => {
150150
expect(client.allFlagsState).toHaveBeenCalledWith(context, options);
151151
});
152152

153+
describe('given a client with forContext', () => {
154+
function makeMockScopedBaseClient() {
155+
const base = makeMockBaseClient();
156+
const forContext = jest.fn((ctx: LDContext) => ({
157+
currentContext: () => ctx,
158+
boolVariation: (key: string, def: boolean) => base.boolVariation(key, ctx, def),
159+
numberVariation: (key: string, def: number) => base.numberVariation(key, ctx, def),
160+
stringVariation: (key: string, def: string) => base.stringVariation(key, ctx, def),
161+
jsonVariation: (key: string, def: unknown) => base.jsonVariation(key, ctx, def),
162+
boolVariationDetail: (key: string, def: boolean) => base.boolVariationDetail(key, ctx, def),
163+
numberVariationDetail: (key: string, def: number) =>
164+
base.numberVariationDetail(key, ctx, def),
165+
stringVariationDetail: (key: string, def: string) =>
166+
base.stringVariationDetail(key, ctx, def),
167+
jsonVariationDetail: (key: string, def: unknown) => base.jsonVariationDetail(key, ctx, def),
168+
allFlagsState: (options?: LDFlagsStateOptions) => base.allFlagsState(ctx, options),
169+
}));
170+
return { ...base, forContext } as any;
171+
}
172+
173+
it('uses forContext when available', () => {
174+
const client = makeMockScopedBaseClient();
175+
createLDServerSession(client, context);
176+
expect(client.forContext).toHaveBeenCalledWith(context, { wrapperName: 'react-server-sdk' });
177+
});
178+
179+
it('delegates boolVariation through scoped client', async () => {
180+
const client = makeMockScopedBaseClient();
181+
client.boolVariation.mockResolvedValue(true);
182+
const session = createLDServerSession(client, context);
183+
const result = await session.boolVariation('my-flag', false);
184+
expect(result).toBe(true);
185+
expect(client.boolVariation).toHaveBeenCalledWith('my-flag', context, false);
186+
});
187+
188+
it('getContext() returns the scoped client currentContext', () => {
189+
const client = makeMockScopedBaseClient();
190+
const session = createLDServerSession(client, context);
191+
expect(session.getContext()).toEqual(context);
192+
});
193+
194+
it('initialized() delegates to the base client', () => {
195+
const client = makeMockScopedBaseClient();
196+
client.initialized.mockReturnValue(false);
197+
const session = createLDServerSession(client, context);
198+
expect(session.initialized()).toBe(false);
199+
});
200+
});
201+
153202
describe('given a browser environment (window defined)', () => {
154203
let originalWindow: typeof globalThis.window;
155204

packages/sdk/react/src/server/LDServerBaseClient.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {
33
LDEvaluationDetailTyped,
44
LDFlagsState,
55
LDFlagsStateOptions,
6+
LDScopedClient,
7+
LDScopedClientOptions,
68
} from '@launchdarkly/js-server-sdk-common';
79

810
/**
@@ -88,4 +90,14 @@ export interface LDServerBaseClient {
8890
* Builds an object encapsulating the state of all feature flags for a given context.
8991
*/
9092
allFlagsState(context: LDContext, options?: LDFlagsStateOptions): Promise<LDFlagsState>;
93+
94+
/**
95+
* Creates a scoped client bound to the given evaluation context.
96+
*
97+
* @remarks
98+
* When present, {@link createLDServerSession} will delegate to the scoped client
99+
* instead of manually wrapping each method. Clients that do not implement this
100+
* method (e.g., edge SDKs) fall back to the manual wrapping behavior.
101+
*/
102+
forContext?(context: LDContext, options?: LDScopedClientOptions): LDScopedClient;
91103
}

packages/sdk/react/src/server/LDServerSession.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,24 @@ export function createLDServerWrapper(
5353
);
5454
}
5555

56+
if (client.forContext) {
57+
const scoped = client.forContext(context, { wrapperName: 'react-server-sdk' });
58+
return {
59+
initialized: () => client.initialized(),
60+
getContext: () => scoped.currentContext(),
61+
boolVariation: scoped.boolVariation,
62+
numberVariation: scoped.numberVariation,
63+
stringVariation: scoped.stringVariation,
64+
jsonVariation: scoped.jsonVariation,
65+
boolVariationDetail: scoped.boolVariationDetail,
66+
numberVariationDetail: scoped.numberVariationDetail,
67+
stringVariationDetail: scoped.stringVariationDetail,
68+
jsonVariationDetail: scoped.jsonVariationDetail,
69+
allFlagsState: scoped.allFlagsState,
70+
};
71+
}
72+
73+
// Fallback for clients without forContext (e.g., edge SDKs)
5674
return {
5775
initialized: () => client.initialized(),
5876
getContext: () => context,

0 commit comments

Comments
 (0)