Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(create-pages): dynamic handlers and static handler #1147

Merged
merged 2 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions e2e/fixtures/create-pages/src/entries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,19 +114,14 @@ const pages: ReturnType<typeof createPages> = createPages(
createApi({
path: '/api/hi',
mode: 'dynamic',
method: 'GET',
handler: async () => {
return new Response('hello world!');
},
}),

createApi({
path: '/api/hi',
mode: 'dynamic',
method: 'POST',
handler: async (req) => {
const body = await req.text();
return new Response(`POST to hello world! ${body}`);
handlers: {
GET: async () => {
return new Response('hello world!');
},
POST: async (req) => {
const body = await req.text();
return new Response(`POST to hello world! ${body}`);
},
},
}),

Expand Down
21 changes: 8 additions & 13 deletions examples/21_create-pages/src/entries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,19 +134,14 @@ const pages = createPages(
createApi({
path: '/api/hi',
mode: 'dynamic',
method: 'GET',
handler: async () => {
return new Response('hello world!');
},
}),

createApi({
path: '/api/hi',
method: 'POST',
mode: 'dynamic',
handler: async (req) => {
const name = await req.text();
return new Response(`hello ${name}!`);
handlers: {
GET: async () => {
return new Response('hello world!');
},
POST: async (req) => {
const name = await req.text();
return new Response(`hello ${name}!`);
},
},
}),

Expand Down
52 changes: 32 additions & 20 deletions packages/waku/src/router/create-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,19 +150,20 @@ export type CreateLayout = <Path extends string>(

type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';

type ApiHandler = (req: Request) => Promise<Response>;

export type CreateApi = <Path extends string>(
params:
| {
path: Path;
mode: 'static';
path: Path;
method: 'GET';
handler: (req: Request) => Promise<Response>;
handler: ApiHandler;
}
| {
path: Path;
mode: 'dynamic';
method: Method;
handler: (req: Request) => Promise<Response>;
path: Path;
handlers: Partial<Record<Method, ApiHandler>>;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you want Partial?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this example explains it better than I can with words

https://tsplay.dev/wOgPpw

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah. I miss that...

},
) => void;

Expand Down Expand Up @@ -240,10 +241,9 @@ export const createPages = <
{
mode: 'static' | 'dynamic';
pathSpec: PathSpec;
handler: Parameters<CreateApi>[0]['handler'];
handlers: Partial<Record<Method, ApiHandler>>;
}
>();
const staticApiPaths = new Set<string>();
const staticComponentMap = new Map<string, FunctionComponent<any>>();
let rootItem: RootItem | undefined = undefined;
const noSsrSet = new WeakSet<PathSpec>();
Expand All @@ -268,9 +268,8 @@ export const createPages = <
path: string,
method: string,
) => string | undefined = (path, method) => {
for (const pathKey of apiPathMap.keys()) {
const [m, p] = pathKey.split(' ');
if (m === method && getPathMapping(parsePathWithSlug(p!), path)) {
for (const [p, v] of apiPathMap.entries()) {
if (method in v.handlers && getPathMapping(parsePathWithSlug(p!), path)) {
return p;
}
}
Expand Down Expand Up @@ -409,24 +408,31 @@ export const createPages = <
}
};

const createApi: CreateApi = ({ path, mode, method, handler }) => {
const createApi: CreateApi = (options) => {
if (!import.meta.env.VITE_EXPERIMENTAL_WAKU_ROUTER) {
console.warn('createApi is still experimental');
return;
}
if (configured) {
throw new Error('createApi no longer available');
}
if (apiPathMap.has(`${method} ${path}`)) {
throw new Error(`Duplicated api path+method: ${path} ${method}`);
} else if (mode === 'static' && staticApiPaths.has(path)) {
throw new Error('Static API Routes cannot share paths: ' + path);
if (apiPathMap.has(options.path)) {
throw new Error(`Duplicated api path: ${options.path}`);
}
if (mode === 'static') {
staticApiPaths.add(path);
const pathSpec = parsePathWithSlug(options.path);
if (options.mode === 'static') {
apiPathMap.set(options.path, {
mode: 'static',
pathSpec,
handlers: { GET: options.handler },
});
} else {
apiPathMap.set(options.path, {
mode: 'dynamic',
pathSpec,
handlers: options.handlers,
});
}
const pathSpec = parsePathWithSlug(path);
apiPathMap.set(`${method} ${path}`, { mode, pathSpec, handler });
};

const createRoot: CreateRoot = (root) => {
Expand Down Expand Up @@ -644,7 +650,7 @@ export const createPages = <
if (!routePath) {
throw new Error('API Route not found: ' + path);
}
const { handler } = apiPathMap.get(`${options.method} ${routePath}`)!;
const { handlers } = apiPathMap.get(routePath)!;

const req = new Request(
new URL(
Expand All @@ -654,6 +660,12 @@ export const createPages = <
),
options,
);
const handler = handlers[options.method as Method];
if (!handler) {
throw new Error(
'API method not found: ' + options.method + 'for path: ' + path,
);
}
const res = await handler(req);

return {
Expand Down
29 changes: 16 additions & 13 deletions packages/waku/tests/create-pages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,10 @@ describe('type tests', () => {
createApi({
path: '/foo',
mode: 'dynamic',
// @ts-expect-error: method is not valid
method: 'foo',
// @ts-expect-error: null is not valid
handler: () => null,
handlers: {
// @ts-expect-error: null is not valid
GET: () => null,
},
});
// @ts-expect-error: handler is not valid
createApi({ path: '/', mode: 'dynamic', method: 'GET', handler: 123 });
Expand All @@ -310,9 +310,10 @@ describe('type tests', () => {
createApi({
path: '/foo/[slug]',
mode: 'dynamic',
method: 'GET',
handler: async () => {
return new Response('Hello World');
handlers: {
POST: async (req) => {
return new Response('Hello World ' + new URL(req.url).pathname);
},
},
});
});
Expand Down Expand Up @@ -578,9 +579,10 @@ describe('createPages pages and layouts', () => {
createApi({
path: '/test/[slug]',
mode: 'dynamic',
method: 'GET',
handler: async () => {
return new Response('Hello World');
handlers: {
GET: async () => {
return new Response('Hello World');
},
},
}),
]);
Expand Down Expand Up @@ -1340,9 +1342,10 @@ describe('createPages api', () => {
createApi({
path: '/test/[slug]',
mode: 'dynamic',
method: 'GET',
handler: async (req) => {
return new Response('Hello World ' + req.url.split('/').at(-1)!);
handlers: {
GET: async (req) => {
return new Response('Hello World ' + req.url.split('/').at(-1)!);
},
},
}),
]);
Expand Down
Loading