Skip to content

Commit 804845d

Browse files
Merge branch 'codex/refactor-react-get-types'
2 parents c17654f + ebd8625 commit 804845d

4 files changed

Lines changed: 165 additions & 39 deletions

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,16 @@ export function Root() {
2424
);
2525
}
2626
```
27+
28+
You can also pass a client directly when you want to read Barekey values before any provider exists:
29+
30+
```tsx
31+
import { PublicBarekeyClient } from "@barekey/sdk/public";
32+
import { useBarekey } from "@barekey/react";
33+
import barekeyConfig from "../barekey.json" with { type: "json" };
34+
35+
function Bootstrap() {
36+
const env = useBarekey(new PublicBarekeyClient({ json: barekeyConfig }));
37+
return <div>{env.get("PUBLIC_THEME")}</div>;
38+
}
39+
```

spec/index.ts

Lines changed: 101 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { PublicBarekeyClient } from "@barekey/sdk/public";
33
import { Component, createElement } from "react";
44
import { act, create, type ReactTestRenderer } from "react-test-renderer";
55

6-
import { BarekeyProvider, useBarekey } from "../src/index.js";
6+
import { BarekeyProvider, type BarekeyReactEnv, useBarekey } from "../src/index.js";
77
import { resetBarekeyReactCacheForTests } from "../src/cache.js";
88
import { resetLeakGuardForTests } from "../src/leak-guard.js";
99

@@ -280,7 +280,7 @@ describe("@barekey/react", () => {
280280
});
281281
});
282282

283-
test("supports reads through a caller-provided public client", async () => {
283+
test("supports reads through a caller-provided public client without a provider", async () => {
284284
(
285285
globalThis as typeof globalThis & {
286286
IS_REACT_ACT_ENVIRONMENT?: boolean;
@@ -308,7 +308,7 @@ describe("@barekey/react", () => {
308308
}) as typeof globalThis.fetch;
309309

310310
function Theme() {
311-
const env = useBarekey();
311+
const env = useBarekey(client);
312312
return createElement("span", null, env.get("PUBLIC_THEME"));
313313
}
314314

@@ -325,7 +325,6 @@ describe("@barekey/react", () => {
325325
await act(async () => {
326326
renderer = create(
327327
renderWithClient({
328-
client,
329328
children: createElement(Theme),
330329
}),
331330
);
@@ -415,7 +414,103 @@ describe("@barekey/react", () => {
415414
expect(testDocument.getOverlay()?.innerHTML).toContain("&lt;img src=x onerror=alert(&quot;xss&quot;)&gt;");
416415
});
417416

418-
test("throws a clear error when used outside BarekeyProvider", () => {
417+
test("prefers an explicit client over provider context", async () => {
418+
(
419+
globalThis as typeof globalThis & {
420+
IS_REACT_ACT_ENVIRONMENT?: boolean;
421+
}
422+
).IS_REACT_ACT_ENVIRONMENT = true;
423+
globalThis.fetch = (async (_input, init) => {
424+
expect(JSON.parse(String(init?.body ?? ""))).toEqual({
425+
orgSlug: "acme",
426+
projectSlug: "web-react-explicit",
427+
stageSlug: "production",
428+
names: ["PUBLIC_THEME"],
429+
});
430+
431+
return jsonResponse({
432+
definitions: [
433+
{
434+
name: "PUBLIC_THEME",
435+
kind: "secret",
436+
declaredType: "string",
437+
visibility: "public",
438+
value: "dark",
439+
},
440+
],
441+
});
442+
}) as typeof globalThis.fetch;
443+
444+
const providerClient = new PublicBarekeyClient({
445+
organization: "acme",
446+
project: "web-react-provider",
447+
environment: "production",
448+
baseUrl: "https://api.example.test",
449+
});
450+
const explicitClient = new PublicBarekeyClient({
451+
organization: "acme",
452+
project: "web-react-explicit",
453+
environment: "production",
454+
baseUrl: "https://api.example.test",
455+
});
456+
457+
function Theme() {
458+
const env = useBarekey(explicitClient);
459+
return createElement("span", null, env.get("PUBLIC_THEME"));
460+
}
461+
462+
let renderer!: ReactTestRenderer;
463+
await act(async () => {
464+
renderer = create(
465+
renderWithClient({
466+
client: providerClient,
467+
children: createElement(Theme),
468+
}),
469+
);
470+
await Promise.resolve();
471+
});
472+
473+
expect(renderer.toJSON()).toEqual({
474+
type: "span",
475+
props: {},
476+
children: ["dark"],
477+
});
478+
});
479+
480+
test("returns a stable env object when the resolved client stays the same", () => {
481+
(
482+
globalThis as typeof globalThis & {
483+
IS_REACT_ACT_ENVIRONMENT?: boolean;
484+
}
485+
).IS_REACT_ACT_ENVIRONMENT = true;
486+
487+
const client = new PublicBarekeyClient({
488+
organization: "acme",
489+
project: "web-react-stable-env",
490+
environment: "production",
491+
baseUrl: "https://api.example.test",
492+
});
493+
const seen: Array<BarekeyReactEnv> = [];
494+
495+
function StableEnvProbe() {
496+
const env = useBarekey(client);
497+
seen.push(env);
498+
return createElement("span", null, "ok");
499+
}
500+
501+
let renderer!: ReactTestRenderer;
502+
act(() => {
503+
renderer = create(createElement(StableEnvProbe));
504+
});
505+
act(() => {
506+
renderer.update(createElement(StableEnvProbe));
507+
});
508+
509+
expect(seen).toHaveLength(2);
510+
expect(seen[0]).toBe(seen[1]);
511+
});
512+
513+
test("throws a clear error when no client is provided and no provider exists", () => {
419514
(
420515
globalThis as typeof globalThis & {
421516
IS_REACT_ACT_ENVIRONMENT?: boolean;
@@ -440,7 +535,7 @@ describe("@barekey/react", () => {
440535
type: "span",
441536
props: {},
442537
children: [
443-
"[barekey/react] useBarekey() must be used within <BarekeyProvider client={new PublicBarekeyClient(...)} ...>.",
538+
"[barekey/react] useBarekey() requires a PublicBarekeyClient argument or a parent <BarekeyProvider client={new PublicBarekeyClient(...)} ...>.",
444539
],
445540
});
446541
});

src/index.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
createElement,
88
Suspense,
99
useContext,
10+
useMemo,
1011
type ReactElement,
1112
type ReactNode,
1213
} from "react";
@@ -54,25 +55,7 @@ export type BarekeyProviderProps = {
5455

5556
const BarekeyContext = createContext<BarekeyReactClient | null>(null);
5657

57-
export function BarekeyProvider(props: BarekeyProviderProps): ReactElement {
58-
return createElement(BarekeyContext.Provider, {
59-
value: props.client,
60-
children: createElement(Suspense, {
61-
fallback: props.fallback ?? null,
62-
children: props.children,
63-
}),
64-
});
65-
}
66-
67-
export function useBarekey() {
68-
const client = useContext(BarekeyContext);
69-
if (client === null) {
70-
throw new Error(
71-
"[barekey/react] useBarekey() must be used within <BarekeyProvider client={new PublicBarekeyClient(...)} ...>.",
72-
);
73-
}
74-
const runtimeClient = client;
75-
58+
function createBarekeyEnv(runtimeClient: BarekeyReactClient): BarekeyReactEnv {
7659
function get<TKey extends BarekeyReactAnyKey>(
7760
name: TKey,
7861
options?: BarekeyGetOptions,
@@ -93,3 +76,25 @@ export function useBarekey() {
9376
get,
9477
};
9578
}
79+
80+
export function BarekeyProvider(props: BarekeyProviderProps): ReactElement {
81+
return createElement(BarekeyContext.Provider, {
82+
value: props.client,
83+
children: createElement(Suspense, {
84+
fallback: props.fallback ?? null,
85+
children: props.children,
86+
}),
87+
});
88+
}
89+
90+
export function useBarekey(client?: BarekeyReactClient) {
91+
const contextClient = useContext(BarekeyContext);
92+
const resolvedClient = client ?? contextClient;
93+
if (resolvedClient === null) {
94+
throw new Error(
95+
"[barekey/react] useBarekey() requires a PublicBarekeyClient argument or a parent <BarekeyProvider client={new PublicBarekeyClient(...)} ...>.",
96+
);
97+
}
98+
const runtimeClient = resolvedClient;
99+
return useMemo(() => createBarekeyEnv(runtimeClient), [runtimeClient]);
100+
}

src/index.typecheck.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
import { PublicBarekeyClient, type Env, type Secret } from "@barekey/sdk/public";
1+
import { PublicBarekeyClient, type Env } from "@barekey/sdk/public";
22
import { createElement } from "react";
33

4-
import { BarekeyProvider, type BarekeyReactEnv } from "./index.js";
4+
import { BarekeyProvider, type BarekeyReactEnv, useBarekey } from "./index.js";
55

66
declare module "@barekey/sdk/public" {
77
interface BarekeyPublicGeneratedTypeMap {
88
PUBLIC_THEME: Env<{
9-
Mode: Secret;
9+
Type: "light" | "dark";
10+
Kind: "secret";
1011
Visibility: "public";
1112
Rollout: never;
12-
Type: "light" | "dark";
1313
}>;
1414
PUBLIC_TITLE: Env<{
15-
Mode: Secret;
15+
Type: string;
16+
Kind: "secret";
1617
Visibility: "public";
1718
Rollout: never;
18-
Type: string;
1919
}>;
2020
}
2121
}
@@ -39,34 +39,47 @@ createElement(BarekeyProvider, {
3939
client,
4040
fallback: null,
4141
});
42+
const envFromClient = useBarekey(client);
43+
const knownValueFromClient = envFromClient.get("PUBLIC_THEME");
4244

4345
type _knownValueStaysTyped = Assert<
4446
IsEqual<
4547
typeof knownValue,
4648
Env<{
47-
Mode: Secret;
49+
Type: "light" | "dark";
50+
Kind: "secret";
4851
Visibility: "public";
4952
Rollout: never;
50-
Type: "light" | "dark";
5153
}>
5254
>
5355
>;
5456
type _unknownValueFallsBackToUnknown = Assert<IsEqual<typeof unknownValue, unknown>>;
57+
type _knownValueFromClientStaysTyped = Assert<
58+
IsEqual<
59+
typeof knownValueFromClient,
60+
Env<{
61+
Type: "light" | "dark";
62+
Kind: "secret";
63+
Visibility: "public";
64+
Rollout: never;
65+
}>
66+
>
67+
>;
5568
type _tupleValueMapsKnownAndUnknownKeys = Assert<
5669
IsEqual<
5770
typeof tupleValue,
5871
readonly [
5972
Env<{
60-
Mode: Secret;
73+
Type: string;
74+
Kind: "secret";
6175
Visibility: "public";
6276
Rollout: never;
63-
Type: string;
6477
}>,
6578
Env<{
66-
Mode: Secret;
79+
Type: "light" | "dark";
80+
Kind: "secret";
6781
Visibility: "public";
6882
Rollout: never;
69-
Type: "light" | "dark";
7083
}>,
7184
unknown,
7285
]
@@ -77,10 +90,10 @@ type _arrayValueFallsBackToTupleInference = Assert<
7790
typeof arrayValue,
7891
readonly [
7992
Env<{
80-
Mode: Secret;
93+
Type: string;
94+
Kind: "secret";
8195
Visibility: "public";
8296
Rollout: never;
83-
Type: string;
8497
}>,
8598
unknown,
8699
]

0 commit comments

Comments
 (0)