From 5bca4c4c2e171dc67aceed2ad09a9e7459b8f48b Mon Sep 17 00:00:00 2001 From: Dominik Ferber Date: Fri, 24 Jan 2025 14:16:17 +0200 Subject: [PATCH] add precise types --- .../precompute/automatic/[code]/flags.tsx | 4 +- .../feature-flags-in-edge-middleware/flags.ts | 2 +- .../app/getting-started/overview/page.tsx | 2 +- examples/snippets/flags.ts | 2 +- .../lib/pages-router-precomputed/flags.ts | 2 +- examples/summer-sale/flags.ts | 2 +- packages/flags/README.md | 2 +- packages/flags/src/next/index.test.ts | 61 ++++++++++++------- packages/flags/src/next/precompute.test.ts | 28 +++++---- packages/flags/src/sveltekit/index.test.ts | 2 +- 10 files changed, 63 insertions(+), 44 deletions(-) diff --git a/examples/snippets/app/concepts/precompute/automatic/[code]/flags.tsx b/examples/snippets/app/concepts/precompute/automatic/[code]/flags.tsx index 5fffb9f..9f08dbe 100644 --- a/examples/snippets/app/concepts/precompute/automatic/[code]/flags.tsx +++ b/examples/snippets/app/concepts/precompute/automatic/[code]/flags.tsx @@ -1,11 +1,11 @@ import { flag } from '@vercel/flags/next'; -export const firstPrecomputedFlag = flag({ +export const firstPrecomputedFlag = flag({ key: 'first-precomputed-flag', decide: () => Math.random() > 0.5, }); -export const secondPrecomputedFlag = flag({ +export const secondPrecomputedFlag = flag({ key: 'second-precomputed-flag', decide: () => Date.now() % 2 === 0, }); diff --git a/examples/snippets/app/examples/feature-flags-in-edge-middleware/flags.ts b/examples/snippets/app/examples/feature-flags-in-edge-middleware/flags.ts index d4605ff..3dee36d 100644 --- a/examples/snippets/app/examples/feature-flags-in-edge-middleware/flags.ts +++ b/examples/snippets/app/examples/feature-flags-in-edge-middleware/flags.ts @@ -1,6 +1,6 @@ import { flag } from '@vercel/flags/next'; -export const basicEdgeMiddlewareFlag = flag({ +export const basicEdgeMiddlewareFlag = flag({ key: 'basic-edge-middleware-flag', decide({ cookies }) { return cookies.get('basic-edge-middleware-flag')?.value === '1'; diff --git a/examples/snippets/app/getting-started/overview/page.tsx b/examples/snippets/app/getting-started/overview/page.tsx index 8df55e5..dd588cd 100644 --- a/examples/snippets/app/getting-started/overview/page.tsx +++ b/examples/snippets/app/getting-started/overview/page.tsx @@ -3,7 +3,7 @@ import { DemoFlag } from '@/components/demo-flag'; import { ReloadButton } from './reload-button'; // declare a feature flag -const randomFlag = flag({ +const randomFlag = flag({ key: 'random-flag', decide() { // this flag will be on for 50% of visitors diff --git a/examples/snippets/flags.ts b/examples/snippets/flags.ts index b027b80..dbacde4 100644 --- a/examples/snippets/flags.ts +++ b/examples/snippets/flags.ts @@ -1,6 +1,6 @@ import { flag } from '@vercel/flags/next'; -export const exampleFlag = flag({ +export const exampleFlag = flag({ key: 'example-flag', decide() { return true; diff --git a/examples/snippets/lib/pages-router-precomputed/flags.ts b/examples/snippets/lib/pages-router-precomputed/flags.ts index cedfa3b..7550f96 100644 --- a/examples/snippets/lib/pages-router-precomputed/flags.ts +++ b/examples/snippets/lib/pages-router-precomputed/flags.ts @@ -1,6 +1,6 @@ import { flag } from '@vercel/flags/next'; -export const exampleFlag = flag({ +export const exampleFlag = flag({ key: 'pages-router-precomputed-example-flag', decide() { return true; diff --git a/examples/summer-sale/flags.ts b/examples/summer-sale/flags.ts index 734e22b..43e5f8b 100644 --- a/examples/summer-sale/flags.ts +++ b/examples/summer-sale/flags.ts @@ -37,7 +37,7 @@ export const showFreeDeliveryBannerFlag = flag({ ], }); -export const countryFlag = flag({ +export const countryFlag = flag({ key: 'country', async decide({ headers }) { return headers.get('accept-language') || 'no accept-lanugage header'; diff --git a/packages/flags/README.md b/packages/flags/README.md index e0723c6..79f67f4 100644 --- a/packages/flags/README.md +++ b/packages/flags/README.md @@ -40,7 +40,7 @@ Create a file called flags.ts in your project and declare your first feature fla // app/flags.tsx import { flag } from '@vercel/flags/next'; -export const exampleFlag = flag({ +export const exampleFlag = flag({ key: 'example-flag', decide() { return true; diff --git a/packages/flags/src/next/index.test.ts b/packages/flags/src/next/index.test.ts index 58ce273..9396746 100644 --- a/packages/flags/src/next/index.test.ts +++ b/packages/flags/src/next/index.test.ts @@ -50,7 +50,7 @@ describe('flag on app router', () => { it('allows declaring a flag', async () => { mocks.headers.mockReturnValueOnce(new Headers()); - const f = flag({ + const f = flag({ key: 'first-flag', decide: () => false, }); @@ -62,7 +62,7 @@ describe('flag on app router', () => { it('caches for the duration of a request', async () => { let i = 0; const decide = vi.fn(() => i++); - const f = flag({ key: 'first-flag', decide }); + const f = flag({ key: 'first-flag', decide }); // first request using the flag twice const headersOfFirstRequest = new Headers(); @@ -94,7 +94,7 @@ describe('flag on app router', () => { const mockDecide = vi.fn(() => promise); - const f = flag({ + const f = flag({ key: 'first-flag', decide: mockDecide, }); @@ -119,7 +119,7 @@ describe('flag on app router', () => { it('respects overrides', async () => { const decide = vi.fn(() => false); - const f = flag({ key: 'first-flag', decide }); + const f = flag({ key: 'first-flag', decide }); // first request using the flag twice const headersOfFirstRequest = new Headers(); @@ -139,7 +139,11 @@ describe('flag on app router', () => { it('uses precomputed values', async () => { const decide = vi.fn(() => true); - const f = flag({ key: 'first-flag', decide, options: [false, true] }); + const f = flag({ + key: 'first-flag', + decide, + options: [false, true], + }); const flagGroup = [f]; const code = await precompute(flagGroup); expect(decide).toHaveBeenCalledTimes(1); @@ -149,7 +153,7 @@ describe('flag on app router', () => { it('uses precomputed values even when options are inferred', async () => { const decide = vi.fn(() => true); - const f = flag({ key: 'first-flag', decide }); + const f = flag({ key: 'first-flag', decide }); const flagGroup = [f]; const code = await precompute(flagGroup); expect(decide).toHaveBeenCalledTimes(1); @@ -166,7 +170,7 @@ describe('flag on app router', () => { const mockDecide = vi.fn(() => promise); const catchFn = vi.fn(); - const f = flag({ + const f = flag({ key: 'first-flag', decide: mockDecide, defaultValue: false, @@ -187,16 +191,18 @@ describe('flag on app router', () => { }); it('falls back to the defaultValue when a decide function returns undefined', async () => { - const syncFlag = flag({ + const syncFlag = flag({ key: 'sync-flag', + // @ts-expect-error this is the case we are testing decide: () => undefined, defaultValue: true, }); await expect(syncFlag()).resolves.toEqual(true); - const asyncFlag = flag({ + const asyncFlag = flag({ key: 'async-flag', + // @ts-expect-error this is the case we are testing decide: async () => undefined, defaultValue: true, }); @@ -205,8 +211,9 @@ describe('flag on app router', () => { }); it('throws an error when the decide function returns undefined and no defaultValue is provided', async () => { - const syncFlag = flag({ + const syncFlag = flag({ key: 'sync-flag', + // @ts-expect-error this is the case we are testing decide: () => undefined, }); @@ -235,7 +242,7 @@ describe('flag on pages router', () => { it('allows declaring a flag', async () => { mocks.headers.mockReturnValueOnce(new Headers()); - const f = flag({ + const f = flag({ key: 'first-flag', decide: () => false, }); @@ -253,7 +260,7 @@ describe('flag on pages router', () => { it('caches for the duration of a request', async () => { let i = 0; const decide = vi.fn(() => i++); - const f = flag({ key: 'first-flag', decide }); + const f = flag({ key: 'first-flag', decide }); const [firstRequest, socket1] = createRequest(); const [secondRequest, socket2] = createRequest(); @@ -285,7 +292,7 @@ describe('flag on pages router', () => { const mockDecide = vi.fn(() => promise); - const f = flag({ + const f = flag({ key: 'first-flag', decide: mockDecide, }); @@ -311,7 +318,7 @@ describe('flag on pages router', () => { const mockDecide = vi.fn(() => { throw new Error('custom error'); }); - const f = flag({ + const f = flag({ key: 'first-flag', decide: mockDecide, }); @@ -325,16 +332,18 @@ describe('flag on pages router', () => { it('falls back to the defaultValue when a decide function returns undefined', async () => { const [firstRequest, socket1] = createRequest(); - const syncFlag = flag({ + const syncFlag = flag({ key: 'sync-flag', + // @ts-expect-error this is the case we are testing decide: () => undefined, defaultValue: true, }); await expect(syncFlag(firstRequest)).resolves.toEqual(true); - const asyncFlag = flag({ + const asyncFlag = flag({ key: 'async-flag', + // @ts-expect-error this is the case we are testing decide: async () => undefined, defaultValue: true, }); @@ -346,8 +355,9 @@ describe('flag on pages router', () => { it('throws an error when the decide function returns undefined and no defaultValue is provided', async () => { const [firstRequest, socket1] = createRequest(); - const syncFlag = flag({ + const syncFlag = flag({ key: 'sync-flag', + // @ts-expect-error this is the case we are testing decide: () => undefined, }); @@ -370,7 +380,7 @@ describe('flag on pages router', () => { it('respects overrides', async () => { const decide = vi.fn(() => false); - const f = flag({ key: 'first-flag', decide }); + const f = flag({ key: 'first-flag', decide }); const override = await encrypt({ 'first-flag': true }); const [firstRequest, socket1] = createRequest({ @@ -383,7 +393,11 @@ describe('flag on pages router', () => { it('uses precomputed values', async () => { const decide = vi.fn(() => true); - const f = flag({ key: 'first-flag', decide, options: [false, true] }); + const f = flag({ + key: 'first-flag', + decide, + options: [false, true], + }); const flagGroup = [f]; const code = await precompute(flagGroup); expect(decide).toHaveBeenCalledTimes(1); @@ -400,7 +414,7 @@ describe('flag on pages router', () => { const mockDecide = vi.fn(() => promise); const catchFn = vi.fn(); - const f = flag({ + const f = flag({ key: 'first-flag', decide: mockDecide, defaultValue: false, @@ -428,7 +442,7 @@ describe('dynamic io', () => { (error as any).digest = 'DYNAMIC_SERVER_USAGE;dynamic usage error'; throw error; }); - const f = flag({ + const f = flag({ key: 'first-flag', decide: mockDecide, defaultValue: false, @@ -456,7 +470,7 @@ describe('adapters', () => { mocks.headers.mockReturnValueOnce(new Headers()); - const f = flag({ + const f = flag({ key: 'adapter-flag', adapter: testAdapter(5), }); @@ -471,8 +485,9 @@ describe('adapters', () => { mocks.headers.mockReturnValueOnce(new Headers()); - const f = flag({ + const f = flag({ key: 'adapter-flag', + // @ts-expect-error this is the case we are testing adapter: testAdapter(undefined), }); diff --git a/packages/flags/src/next/precompute.test.ts b/packages/flags/src/next/precompute.test.ts index 7b602f1..7dbcdb6 100644 --- a/packages/flags/src/next/precompute.test.ts +++ b/packages/flags/src/next/precompute.test.ts @@ -35,7 +35,7 @@ describe('generatePermutations', () => { it('should infer boolean options', async () => { process.env.FLAGS_SECRET = crypto.randomBytes(32).toString('base64url'); - const flagA = flag({ key: 'a', decide: () => false }); + const flagA = flag({ key: 'a', decide: () => false }); await expectPermutations([flagA], [{ a: false }, { a: true }]); }); }); @@ -44,7 +44,11 @@ describe('generatePermutations', () => { it('should not infer any options', async () => { process.env.FLAGS_SECRET = crypto.randomBytes(32).toString('base64url'); - const flagA = flag({ key: 'a', decide: () => false, options: [] }); + const flagA = flag({ + key: 'a', + decide: () => false, + options: [], + }); await expectPermutations([flagA], []); }); }); @@ -89,12 +93,12 @@ describe('generatePermutations', () => { it('should generate permutations', async () => { process.env.FLAGS_SECRET = crypto.randomBytes(32).toString('base64url'); - const flagA = flag({ + const flagA = flag({ key: 'a', decide: () => false, }); - const flagB = flag({ + const flagB = flag({ key: 'b', decide: () => false, }); @@ -115,17 +119,17 @@ describe('generatePermutations', () => { it('should generate permutations', async () => { process.env.FLAGS_SECRET = crypto.randomBytes(32).toString('base64url'); - const flagA = flag({ + const flagA = flag({ key: 'a', decide: () => false, }); - const flagB = flag({ + const flagB = flag({ key: 'b', decide: () => false, }); - const flagC = flag({ + const flagC = flag({ key: 'c', decide: () => 'two', options: ['one', 'two', 'three'], @@ -157,17 +161,17 @@ describe('generatePermutations', () => { it('should generate permutations', async () => { process.env.FLAGS_SECRET = crypto.randomBytes(32).toString('base64url'); - const flagA = flag({ + const flagA = flag({ key: 'a', decide: () => false, }); - const flagB = flag({ + const flagB = flag({ key: 'b', decide: () => false, }); - const flagC = flag({ + const flagC = flag({ key: 'c', decide: () => 'two', options: ['one', 'two', 'three'], @@ -189,8 +193,8 @@ describe('getPrecomputed', () => { it('should return the precomputed value', async () => { process.env.FLAGS_SECRET = crypto.randomBytes(32).toString('base64url'); - const flagA = flag({ key: 'a', decide: () => true }); - const flagB = flag({ key: 'b', decide: () => false }); + const flagA = flag({ key: 'a', decide: () => true }); + const flagB = flag({ key: 'b', decide: () => false }); const group = [flagA, flagB]; const code = await serialize(group, [true, false]); diff --git a/packages/flags/src/sveltekit/index.test.ts b/packages/flags/src/sveltekit/index.test.ts index f1f8e09..0ae76b6 100644 --- a/packages/flags/src/sveltekit/index.test.ts +++ b/packages/flags/src/sveltekit/index.test.ts @@ -9,7 +9,7 @@ describe('getProviderData', () => { describe('flag', () => { it('defines a key', async () => { - const f = flag({ key: 'first-flag', decide: () => false }); + const f = flag({ key: 'first-flag', decide: () => false }); expect(f).toHaveProperty('key', 'first-flag'); }); });