Skip to content

Commit

Permalink
Add support for Next 15
Browse files Browse the repository at this point in the history
  • Loading branch information
marcospassos committed Jan 15, 2025
1 parent 69a44ce commit 203220a
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 69 deletions.
30 changes: 15 additions & 15 deletions src/config/context.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type {UnsafeUnwrappedCookies} from 'next/headers';
import type {cookies} from 'next/headers';
import {Token} from '@croct/sdk/token';
import {getRequestContext, RequestContext, resolvePreferredLocale, resolveRequestContext} from '@/config/context';
import {Header} from '@/config/http';
import {getUserTokenCookieOptions} from '@/config/cookie';
import {getCookies, getHeaders, PartialRequest, PartialResponse, RouteContext} from '@/headers';

type ReadonlyCookies = UnsafeUnwrappedCookies;
type ReadonlyCookies = Awaited<ReturnType<typeof cookies>>;

function createCookieJar(cookies: Record<string, string> = {}): ReadonlyCookies {
const jar: Record<string, string> = {...cookies};
Expand Down Expand Up @@ -167,7 +167,7 @@ describe('getRequestContext', () => {
});

describe('resolveRequestContext', () => {
it('should return the request context', () => {
it('should return the request context', async () => {
const headers = new Headers();
const cookies = createCookieJar();

Expand All @@ -194,10 +194,10 @@ describe('resolveRequestContext', () => {
headers.set(Header.PREVIEW_TOKEN, request.previewToken);
headers.set(Header.PREFERRED_LOCALE, request.preferredLocale);

jest.mocked(getHeaders).mockReturnValue(headers);
jest.mocked(getCookies).mockReturnValue(cookies);
jest.mocked(getHeaders).mockResolvedValue(headers);
jest.mocked(getCookies).mockResolvedValue(cookies);

expect(resolveRequestContext(route)).toEqual(request);
await expect(resolveRequestContext(route)).resolves.toEqual(request);

expect(getHeaders).toHaveBeenCalledWith(route);
expect(getCookies).toHaveBeenCalledWith(route);
Expand All @@ -210,7 +210,7 @@ describe('resolvePreferredLocale', () => {
jest.clearAllMocks();
});

it('should return the preferred locale from the headers', () => {
it('should return the preferred locale from the headers', async () => {
const headers = new Headers();

headers.set(Header.PREFERRED_LOCALE, 'en');
Expand All @@ -220,23 +220,23 @@ describe('resolvePreferredLocale', () => {
res: {} as PartialResponse,
};

jest.mocked(getHeaders).mockReturnValue(headers);
jest.mocked(getHeaders).mockResolvedValue(headers);

expect(resolvePreferredLocale(route)).toEqual('en');
await expect(resolvePreferredLocale(route)).resolves.toEqual('en');
expect(getHeaders).toHaveBeenCalledWith(route);
});

it('should return the preferred locale from the environment', () => {
it('should return the preferred locale from the environment', async () => {
process.env.NEXT_PUBLIC_CROCT_DEFAULT_PREFERRED_LOCALE = 'en';

jest.mocked(getHeaders).mockReturnValue(new Headers());
jest.mocked(getHeaders).mockResolvedValue(new Headers());

expect(resolvePreferredLocale()).toEqual('en');
await expect(resolvePreferredLocale()).resolves.toEqual('en');
});

it('should return null when the preferred locale is missing', () => {
jest.mocked(getHeaders).mockReturnValue(new Headers());
it('should return null when the preferred locale is missing', async () => {
jest.mocked(getHeaders).mockResolvedValue(new Headers());

expect(resolvePreferredLocale()).toBeNull();
await expect(resolvePreferredLocale()).resolves.toBeNull();
});
});
8 changes: 4 additions & 4 deletions src/config/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export type RequestContext = {
preferredLocale?: string,
};

export function resolveRequestContext(route?: RouteContext): RequestContext {
return getRequestContext(getHeaders(route), getCookies(route));
export async function resolveRequestContext(route?: RouteContext): Promise<RequestContext> {
return getRequestContext(await getHeaders(route), await getCookies(route));
}

export function getRequestContext(headers: HeaderReader, cookies: CookieReader): RequestContext {
Expand Down Expand Up @@ -78,8 +78,8 @@ export function getRequestContext(headers: HeaderReader, cookies: CookieReader):
return context;
}

export function resolvePreferredLocale(route?: RouteContext): string|null {
return getPreferredLocale(getHeaders(route));
export async function resolvePreferredLocale(route?: RouteContext): Promise<string|null> {
return getPreferredLocale(await getHeaders(route));
}

function getPreferredLocale(headers: HeaderReader): string|null {
Expand Down
28 changes: 14 additions & 14 deletions src/headers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ describe('getHeaders', () => {
mockHeaders.mockReset();
});

it('should use next/headers if available', () => {
it('should use next/headers if available', async () => {
const get = jest.fn(() => 'test');

mockHeaders.mockReturnValue({
get: get,
});

const header = getHeaders();
const header = await getHeaders();

expect(header.get('test')).toBe('test');

Expand Down Expand Up @@ -105,14 +105,14 @@ describe('getHeaders', () => {
response: response,
};
})(),
])('should use the $name if specified', scenario => {
])('should use the $name if specified', async scenario => {
const {request, response} = scenario;

mockHeaders.mockImplementation(() => {
throw new Error('next/headers requires app router');
});

const headers = getHeaders({
const headers = await getHeaders({
req: request,
res: response,
});
Expand All @@ -128,7 +128,7 @@ describe('getCookies', () => {
mockCookies.mockReset();
});

it('should use next/headers if available', () => {
it('should use next/headers if available', async () => {
const get = jest.fn(() => ({value: 'foo'}));
const set = jest.fn();

Expand All @@ -137,7 +137,7 @@ describe('getCookies', () => {
set: set,
});

const cookies = getCookies();
const cookies = await getCookies();

expect(cookies.get('test')).toEqual({value: 'foo'});

Expand Down Expand Up @@ -303,14 +303,14 @@ describe('getCookies', () => {
response: response,
};
})(),
])('should use the $name if specified', scenario => {
])('should use the $name if specified', async scenario => {
const {request, response} = scenario;

mockCookies.mockImplementation(() => {
throw new Error('next/headers requires app router');
});

const cookies = getCookies({
const cookies = await getCookies({
req: request,
res: response,
});
Expand All @@ -319,7 +319,7 @@ describe('getCookies', () => {
expect(cookies.get('test')).toBeUndefined();
});

it('should set a cookie to NextResponse', () => {
it('should set a cookie to NextResponse', async () => {
mockCookies.mockImplementation(() => {
throw new Error('next/headers requires app router');
});
Expand All @@ -336,7 +336,7 @@ describe('getCookies', () => {
} as Partial<NextResponse['cookies']> as NextResponse['cookies'],
} satisfies PartialResponse;

const cookies = getCookies({
const cookies = await getCookies({
req: request,
res: response,
});
Expand All @@ -350,7 +350,7 @@ describe('getCookies', () => {
expect(response.cookies.set).toHaveBeenCalledWith('test', 'value', options);
});

it('should set a cookie to NextApiResponse/ServerResponse', () => {
it('should set a cookie to NextApiResponse/ServerResponse', async () => {
mockCookies.mockImplementation(() => {
throw new Error('next/headers requires app router');
});
Expand All @@ -365,7 +365,7 @@ describe('getCookies', () => {
setHeader: jest.fn(),
} satisfies PartialResponse;

const cookies = getCookies({
const cookies = await getCookies({
req: request,
res: response,
});
Expand All @@ -379,7 +379,7 @@ describe('getCookies', () => {
expect(response.setHeader).toHaveBeenCalledWith('Set-Cookie', ['test=value; Domain=example.com']);
});

it('should preserve existing cookies when setting a new cookie', () => {
it('should preserve existing cookies when setting a new cookie', async () => {
mockCookies.mockImplementation(() => {
throw new Error('next/headers requires app router');
});
Expand All @@ -404,7 +404,7 @@ describe('getCookies', () => {
setHeader: jest.fn(),
} satisfies PartialResponse;

const cookies = getCookies({
const cookies = await getCookies({
req: request,
res: response,
});
Expand Down
22 changes: 11 additions & 11 deletions src/headers.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type {ServerResponse} from 'http';
import type {NextRequest, NextResponse} from 'next/server';
import type {GetServerSidePropsContext, NextApiRequest, NextApiResponse} from 'next';
import type {UnsafeUnwrappedHeaders, UnsafeUnwrappedCookies} from 'next/headers';
import type {cookies, headers} from 'next/headers';
import cookie from 'cookie';

export type HeaderReader = Pick<UnsafeUnwrappedHeaders, 'get'>;
export type HeaderReader = Pick<Awaited<ReturnType<typeof headers>>, 'get'>;

export type CookieReader = {
get: (name: string) => {value: string}|undefined,
};

export type CookieOptions = NonNullable<Parameters<UnsafeUnwrappedCookies['set']>[2]>;
export type CookieOptions = NonNullable<Parameters<Awaited<ReturnType<typeof cookies>>['set']>[2]>;

export type CookieAccessor = CookieReader & {
set: (name: string, value: string, options?: CookieOptions) => void,
Expand All @@ -31,18 +31,18 @@ export type RouteContext = {
res: PartialResponse,
};

export function getHeaders(route?: RouteContext): HeaderReader {
export function getHeaders(route?: RouteContext): Promise<HeaderReader> {
try {
const {headers} = importNextHeaders();

return headers() as unknown as UnsafeUnwrappedHeaders;
return headers();
} catch (error) {
if (route === undefined) {
throw error;
}
}

return {
return Promise.resolve({
get: (name: string): string|null => {
const requestHeaders = route.req.headers;

Expand All @@ -62,21 +62,21 @@ export function getHeaders(route?: RouteContext): HeaderReader {

return null;
},
};
});
}

export function getCookies(route?: RouteContext): CookieAccessor {
export function getCookies(route?: RouteContext): Promise<CookieAccessor> {
try {
const {cookies} = importNextHeaders();

return cookies() as unknown as UnsafeUnwrappedCookies;
return cookies();
} catch (error) {
if (route === undefined) {
throw error;
}
}

return {
return Promise.resolve({
get: (name: string): {value: string}|undefined => {
const response = route.res;

Expand Down Expand Up @@ -148,7 +148,7 @@ export function getCookies(route?: RouteContext): CookieAccessor {

response.setHeader('Set-Cookie', newValue);
},
};
});
}

function isNextRequestHeaders(headers: PartialRequest['headers']): headers is NextRequest['headers'] {
Expand Down
2 changes: 1 addition & 1 deletion src/server/anonymize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export async function anonymize(context?: RouteContext): Promise<void> {
let cookies: CookieAccessor;

try {
cookies = getCookies(context);
cookies = await getCookies(context);
} catch {
throw new Error(
'anonymize() requires specifying the `route` parameter outside app routes. '
Expand Down
18 changes: 9 additions & 9 deletions src/server/evaluate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ describe('evaluation', () => {
const query = 'true';
const result = true;

jest.mocked(resolveRequestContext).mockReturnValue(scenario.request);
jest.mocked(resolveRequestContext).mockResolvedValue(scenario.request);
jest.mocked(executeQuery).mockResolvedValue(result);

await expect(evaluate(query, scenario.options)).resolves.toBe(result);
Expand All @@ -178,7 +178,7 @@ describe('evaluation', () => {
res: {} as NextResponse,
};

jest.mocked(resolveRequestContext).mockReturnValue(request);
jest.mocked(resolveRequestContext).mockResolvedValue(request);
jest.mocked(executeQuery).mockResolvedValue(true);

await expect(evaluate('true', {route: route})).resolves.toBe(true);
Expand Down Expand Up @@ -240,7 +240,7 @@ describe('evaluation', () => {
jest.spyOn(console, 'info').mockImplementation();

jest.mocked(executeQuery).mockResolvedValue(true);
jest.mocked(resolveRequestContext).mockReturnValue(request);
jest.mocked(resolveRequestContext).mockResolvedValue(request);

await evaluate('true');

Expand Down Expand Up @@ -271,7 +271,7 @@ describe('evaluation', () => {
jest.spyOn(console, 'info').mockImplementation();

jest.mocked(executeQuery).mockResolvedValue(true);
jest.mocked(resolveRequestContext).mockReturnValue(request);
jest.mocked(resolveRequestContext).mockResolvedValue(request);

await evaluate('true');

Expand All @@ -295,7 +295,7 @@ describe('evaluation', () => {
it('should use the base endpoint URL from the environment', async () => {
process.env.NEXT_PUBLIC_CROCT_BASE_ENDPOINT_URL = 'https://example.com';

jest.mocked(resolveRequestContext).mockReturnValue(request);
jest.mocked(resolveRequestContext).mockResolvedValue(request);
jest.mocked(executeQuery).mockResolvedValue(true);

await evaluate('true');
Expand All @@ -310,7 +310,7 @@ describe('evaluation', () => {
const result = true;
const defaultTimeout = 1000;

jest.mocked(resolveRequestContext).mockReturnValue(request);
jest.mocked(resolveRequestContext).mockResolvedValue(request);
jest.mocked(getDefaultFetchTimeout).mockReturnValue(defaultTimeout);
jest.mocked(executeQuery).mockResolvedValue(result);

Expand All @@ -327,7 +327,7 @@ describe('evaluation', () => {
const defaultTimeout = 1000;
const timeout = 2000;

jest.mocked(resolveRequestContext).mockReturnValue(request);
jest.mocked(resolveRequestContext).mockResolvedValue(request);
jest.mocked(getDefaultFetchTimeout).mockReturnValue(defaultTimeout);
jest.mocked(executeQuery).mockResolvedValue(result);

Expand Down Expand Up @@ -372,7 +372,7 @@ describe('evaluation', () => {
it('should evaluate a query with no arguments', async () => {
const result = true;

jest.mocked(resolveRequestContext).mockReturnValue(request);
jest.mocked(resolveRequestContext).mockResolvedValue(request);
jest.mocked(executeQuery).mockResolvedValue(result);

await expect(cql`true`).resolves.toBe(result);
Expand All @@ -398,7 +398,7 @@ describe('evaluation', () => {
it('should evaluate a query with arguments', async () => {
const result = true;

jest.mocked(resolveRequestContext).mockReturnValue(request);
jest.mocked(resolveRequestContext).mockResolvedValue(request);
jest.mocked(executeQuery).mockResolvedValue(result);

const variable = 'variable';
Expand Down
Loading

0 comments on commit 203220a

Please sign in to comment.