diff --git a/e2e/fixtures/create-pages/src/entries.tsx b/e2e/fixtures/create-pages/src/entries.tsx index 37b156585..4082e4391 100644 --- a/e2e/fixtures/create-pages/src/entries.tsx +++ b/e2e/fixtures/create-pages/src/entries.tsx @@ -114,19 +114,14 @@ const pages: ReturnType = 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}`); + }, }, }), diff --git a/examples/21_create-pages/src/entries.tsx b/examples/21_create-pages/src/entries.tsx index b04b905d9..ee3994e80 100644 --- a/examples/21_create-pages/src/entries.tsx +++ b/examples/21_create-pages/src/entries.tsx @@ -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}!`); + }, }, }), diff --git a/packages/waku/src/router/create-pages.ts b/packages/waku/src/router/create-pages.ts index b92be4373..c39fccb31 100644 --- a/packages/waku/src/router/create-pages.ts +++ b/packages/waku/src/router/create-pages.ts @@ -150,19 +150,20 @@ export type CreateLayout = ( type Method = 'GET' | 'POST' | 'PUT' | 'DELETE'; +type ApiHandler = (req: Request) => Promise; + export type CreateApi = ( params: | { - path: Path; mode: 'static'; + path: Path; method: 'GET'; - handler: (req: Request) => Promise; + handler: ApiHandler; } | { - path: Path; mode: 'dynamic'; - method: Method; - handler: (req: Request) => Promise; + path: Path; + handlers: Partial>; }, ) => void; @@ -240,10 +241,9 @@ export const createPages = < { mode: 'static' | 'dynamic'; pathSpec: PathSpec; - handler: Parameters[0]['handler']; + handlers: Partial>; } >(); - const staticApiPaths = new Set(); const staticComponentMap = new Map>(); let rootItem: RootItem | undefined = undefined; const noSsrSet = new WeakSet(); @@ -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; } } @@ -409,7 +408,7 @@ 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; @@ -417,16 +416,23 @@ export const createPages = < 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) => { @@ -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( @@ -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 { diff --git a/packages/waku/tests/create-pages.test.ts b/packages/waku/tests/create-pages.test.ts index bcfc43c70..529d458ca 100644 --- a/packages/waku/tests/create-pages.test.ts +++ b/packages/waku/tests/create-pages.test.ts @@ -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 }); @@ -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); + }, }, }); }); @@ -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'); + }, }, }), ]); @@ -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)!); + }, }, }), ]);