Skip to content

Commit

Permalink
Fix compatibility with page and app router
Browse files Browse the repository at this point in the history
  • Loading branch information
marcospassos committed Nov 21, 2024
1 parent 95794b3 commit ed22786
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 7 deletions.
47 changes: 47 additions & 0 deletions src/errors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {isDynamicServerError} from '@/errors';

describe('errors', () => {
type TypeGuardScenario = {
error: any,
expected: boolean,
};

it.each<TypeGuardScenario>([
{
error: null,
expected: false,
},
{
error: undefined,
expected: false,
},
{
error: {},
expected: false,
},
{
error: new Error(),
expected: false,
},
{
error: new class {
public readonly digest = 'DYNAMIC_SERVER_USAGE';
}(),
expected: false,
},
{
error: new class DynamicServerError {
public readonly digest = 'foo';
}(),
expected: false,
},
{
error: new class DynamicServerError {
public readonly digest = 'DYNAMIC_SERVER_USAGE';
}(),
expected: true,
},
])('should return $expected for $error identifying a DynamicServerError', ({error, expected}) => {
expect(isDynamicServerError(error)).toBe(expected);
});
});
11 changes: 11 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export declare class DynamicServerError extends Error {
public readonly digest = 'DYNAMIC_SERVER_USAGE';
}

export function isDynamicServerError(error: unknown): error is DynamicServerError {
return typeof error === 'object'
&& error !== null
&& error.constructor.name === 'DynamicServerError'
&& 'digest' in error
&& error.digest === 'DYNAMIC_SERVER_USAGE';
}
12 changes: 12 additions & 0 deletions src/hooks/useContent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ describe('useContent', () => {
expect(useContentMock).toHaveBeenCalledWith('id', undefined);
});

it('should ignore router errors', () => {
jest.mocked(useContentMock).mockReturnValue({});

jest.mocked(useRouter).mockImplementation(() => {
throw new Error();
});

useContent('id');

expect(useContentMock).toHaveBeenCalledWith('id', undefined);
});

it('should not override the specified locale', () => {
jest.mocked(useContentMock).mockReturnValue({});

Expand Down
10 changes: 9 additions & 1 deletion src/hooks/useContent.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {useContent as useContentReact, UseContentOptions, SlotContent, VersionedSlotId} from '@croct/plug-react';
import {useRouter} from 'next/router';
import {NextRouter, useRouter as usePageRouter} from 'next/router';

export type {UseContentOptions} from '@croct/plug-react';

Expand All @@ -14,4 +14,12 @@ function useContentNext(id: VersionedSlotId, options?: UseContentOptions<any, an
);
}

export function useRouter(): Pick<NextRouter, 'locale'> {
try {
return usePageRouter();
} catch {
return {};
}
}

export const useContent: typeof useContentReact = useContentNext;
11 changes: 9 additions & 2 deletions src/server/evaluate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {ApiKey, ApiKey as MockApiKey} from '@croct/sdk/apiKey';
import {FilteredLogger} from '@croct/sdk/logging/filteredLogger';
import {headers} from 'next/headers';
import {NextRequest, NextResponse} from 'next/server';
import {DynamicServerError} from 'next/dist/client/components/hooks-server-context';
import {cql, evaluate, EvaluationOptions} from './evaluate';
import {resolveRequestContext, RequestContext} from '@/config/context';
import {getDefaultFetchTimeout} from '@/config/timeout';
Expand Down Expand Up @@ -188,7 +187,15 @@ describe('evaluation', () => {
});

it('should rethrow dynamic server errors', async () => {
const error = new DynamicServerError('cause');
const error = new class DynamicServerError extends Error {
public readonly digest = 'DYNAMIC_SERVER_USAGE';

public constructor() {
super('cause');

Object.setPrototypeOf(this, new.target.prototype);
}
}();

jest.mocked(resolveRequestContext).mockImplementation(() => {
throw error;
Expand Down
2 changes: 1 addition & 1 deletion src/server/evaluate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import type {JsonValue} from '@croct/plug-react';
import {FilteredLogger} from '@croct/sdk/logging/filteredLogger';
import {ConsoleLogger} from '@croct/sdk/logging/consoleLogger';
import {formatCause} from '@croct/sdk/error';
import {isDynamicServerError} from 'next/dist/client/components/hooks-server-context';
import {getApiKey} from '@/config/security';
import {RequestContext, resolveRequestContext} from '@/config/context';
import {getDefaultFetchTimeout} from '@/config/timeout';
import {isAppRouter, RouteContext} from '@/headers';
import {getEnvEntry, getEnvFlag} from '@/config/env';
import {isDynamicServerError} from '@/errors';

export type EvaluationOptions<T extends JsonValue = JsonValue> = Omit<BaseOptions<T>, 'apiKey' | 'appId'> & {
route?: RouteContext,
Expand Down
11 changes: 9 additions & 2 deletions src/server/fetchContent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {FetchResponse} from '@croct/plug/plug';
import {ApiKey, ApiKey as MockApiKey} from '@croct/sdk/apiKey';
import {FilteredLogger} from '@croct/sdk/logging/filteredLogger';
import type {NextRequest, NextResponse} from 'next/server';
import {DynamicServerError} from 'next/dist/client/components/hooks-server-context';
import {fetchContent, FetchOptions} from './fetchContent';
import {RequestContext, resolvePreferredLocale, resolveRequestContext} from '@/config/context';
import {getDefaultFetchTimeout} from '@/config/timeout';
Expand Down Expand Up @@ -291,7 +290,15 @@ describe('fetchContent', () => {
});

it('should rethrow dynamic server errors', async () => {
const error = new DynamicServerError('cause');
const error = new class DynamicServerError extends Error {
public readonly digest = 'DYNAMIC_SERVER_USAGE';

public constructor() {
super('cause');

Object.setPrototypeOf(this, new.target.prototype);
}
}();

jest.mocked(resolveRequestContext).mockImplementation(() => {
throw error;
Expand Down
2 changes: 1 addition & 1 deletion src/server/fetchContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import type {SlotContent, VersionedSlotId, JsonObject} from '@croct/plug-react';
import {FilteredLogger} from '@croct/sdk/logging/filteredLogger';
import {ConsoleLogger} from '@croct/sdk/logging/consoleLogger';
import {formatCause} from '@croct/sdk/error';
import {isDynamicServerError} from 'next/dist/client/components/hooks-server-context';
import {getApiKey} from '@/config/security';
import {RequestContext, resolvePreferredLocale, resolveRequestContext} from '@/config/context';
import {getDefaultFetchTimeout} from '@/config/timeout';
import {RouteContext} from '@/headers';
import {getEnvEntry, getEnvFlag} from '@/config/env';
import {isDynamicServerError} from '@/errors';

export type DynamicContentOptions<T extends JsonObject = JsonObject> = Omit<DynamicOptions<T>, 'apiKey' | 'appId'>;

Expand Down

0 comments on commit ed22786

Please sign in to comment.